Está en la página 1de 1160

Escuela Superior de Informtica

Universidad de Castilla-La Mancha

D. Vallejo C. Gonzlez D. Villa F. Jurado


F. Moya J. A. Albusac C. Martn S. Prez
F. J. Villanueva C. Mora J. J. Castro
M. A. Redondo L. Jimnez J. Lpez M. Garca
M. Palomo G. Simmross J. L. Gonzlez
Ttulo: Desarrollo de Videojuegos: Un Enfoque Prctico
Edicin: Julio 2014
Autores: David Vallejo Fernndez, Carlos Gonzlez Morcillo, David
Villa Alises, Francisco Jurado Monroy, Francisco Moya
Fernndez, Javier Alonso Albusac Jimnez, Cleto Martn
Angelina, Sergio Prez Camacho, Flix Jess Villanueva
Molina, Csar Mora Castro, Jos Jess Castro Snchez,
Miguel ngel Redondo Duque, Luis Jimnez Linares,
Jorge Lpez Gonzlez, Miguel Garca Corchero, Manuel
Palomo Duarte, Guillermo Simmross Wattenberg, Jos Luis
Gonzlez Snchez. Colaboradores: David Frutos Talavera,
Sergio Fernndez Durn
ISBN: 978-84-942382-9-1 Depsito Legal: VG 415-2014
Publica: EdLibrix
Edita: David Vallejo, Carlos Gonzlez y David Villa
Portada: (Ilustracin de portada) Vctor Barba Pizarro
Diseo: Carlos Gonzlez Morcillo y Vctor Barba Pizarro
Este libro fue compuesto con LaTeX a partir de una plantilla de David Villa Alises
y Carlos Gonzlez Morcillo.

Creative Commons License: Usted es libre de copiar, distribuir y comunicar pblicamente la obra, bajo
las condiciones siguientes: 1. Reconocimiento. Debe reconocer los crditos de la obra de la manera
especicada por el autor o el licenciador. 2. No comercial. No puede utilizar esta obra para nes
comerciales. 3. Sin obras derivadas. No se puede alterar, transformar o generar una obra derivada a
partir de esta obra. Ms informacin en: http://creativecommons.org/licenses/by-nc-nd/3.0/
Prefacio

La industria del videojuego ocupa el primer lugar en el ocio audio-visual e interac-


tivo a nivel mundial, por encima de industrias tan potentes como el cine o la msica.
Como consecuencia directa, existe una gran demanda de profesionales cualicados
para disear y desarrollar videojuegos no slo para consolas y ordenadores, sino tam-
bin para el ms que creciente mercado de los telfonos mviles.
Este libro, dividido en cuatro bloques, tiene como objetivo principal proporcionar
los conocimientos necesarios para llevar a cabo dicha tarea desde una perspectiva
esencialmente tcnica:

1. Arquitectura del Motor, donde se estudian los aspectos esenciales del diseo
de un motor de videojuegos, as como las tcnicas bsicas de programacin
y patrones de diseo. En este bloque tambin se estudian los conceptos ms
relevantes del lenguaje de programacin C++.
2. Programacin Grca, donde se presta especial atencin a los algoritmos y
tcnicas de representacin grca, junto con las optimizaciones en sistemas de
despliegue interactivo.
3. Tcnicas Avanzadas, donde se recogen ciertos aspectos avanzados, como es-
tructuras de datos especcas, tcnicas de validacin y pruebas o simulacin
fsica. As mismo, en este bloque se profundiza en el lenguaje C++.
4. Desarrollo de Componentes, donde, nalmente, se detallan ciertos componen-
tes especcos del motor, como la Inteligencia Articial, Networking, Sonido y
Multimedia o tcnicas avanzadas de Interaccin.
Sobre este libro
Este libro que tienes en tus manos es una ampliacin y revisin de los apuntes del
Curso de Experto en Desarrollo de Videojuegos, impartido en la Escuela Superior de
Informtica de Ciudad Real de la Universidad de Castilla-La Mancha. Puedes obtener
ms informacin sobre el curso, as como los resultados de los trabajos creados por
los alumnos, en la web del mismo: http://www.cedv.es. La versin electrnica de este
libro puede descargarse desde la web anterior. El libro fsico puede adquirirse desde
la pgina web de la editorial online Edlibrix en http://www.shoplibrix.com.

Requisitos previos
Este libro tiene un pblico objetivo con un perl principalmente tcnico. Al igual
que el curso del que surgi, est orientado a la capacitacin de profesionales de la
programacin de videojuegos. De esta forma, este libro no est orientado para un
pblico de perl artstico (modeladores, animadores, msicos, etc.) en el mbito de
los videojuegos.
Se asume que el lector es capaz de desarrollar programas de nivel medio en C
y C++. Aunque se describen algunos aspectos clave de C++ a modo de resumen, es
recomendable refrescar los conceptos bsicos con alguno de los libros recogidos en
la bibliografa. De igual modo, se asume que el lector tiene conocimientos de estruc-
turas de datos y algoritmia. El libro est orientado principalmente para titulados o
estudiantes de ltimos cursos de Ingeniera en Informtica.

Programas y cdigo fuente


El cdigo de los ejemplos del libro pueden descargarse en la siguiente pgina
web: http://www.cedv.es. Salvo que se especique explcitamente otra licencia, todos
los ejemplos del libro se distribuyen bajo GPLv3.

Agradecimientos
Los autores del libro quieren agradecer, en primer lugar, a los alumnos de las tres
primeras ediciones del Curso de Experto en Desarrollo de Videojuegos por su partici-
pacin en el mismo y el excelente ambiente en las clases, las cuestiones planteadas y
la pasin demostrada en el desarrollo de todos los trabajos.
De igual modo, se quiere reejar el agradecimiento especial al personal de admi-
nistracin y servicios de la Escuela Superior de Informtica, por su soporte, predis-
posicin y ayuda en todos los caprichosos requisitos que plantebamos a lo largo del
curso.
Por otra parte, este agradecimiento tambin se hace extensivo a la Escuela de In-
formatica de Ciudad Real y al Departamento de Tecnologas y Sistema de Informacin
de la Universidad de Castilla-La Mancha.
Finalmente, los autores desean agradecer su participacin a los colaboradores de
las tres primeras ediciones: Indra Software Labs, la asociacin de desarrolladores de
videojuegos Stratos, Libro Virtual, Devilish Games, Dolores Entertainment, From the
Bench, Iberlynx, KitMaker, Playspace, Totemcat/Materia Works y ZuinqStudio.
Autores

David Vallejo (2009, Doctor Europeo en Informtica,


Universidad de Castilla-La Mancha) es Profesor
Ayudante Doctor e imparte docencia en la Escuela de
Informtica de Ciudad Real (UCLM) en asignaturas
relacionadas con Informtica Grfica, Programacin y
Sistemas Operativos desde 2007. Actualmente, su
actividad investigadora gira en torno a la Vigilancia
Inteligente, los Sistemas Multi-Agente y el Rendering
Distribuido.

Carlos Gonzlez (2007, Doctor Europeo en


Informtica, Universidad de Castilla-La Mancha) es
Profesor Titular de Universidad e imparte docencia en
la Escuela de Informtica de Ciudad Real (UCLM) en
asignaturas relacionadas con Informtica Grfica,
Sntesis de Imagen Realista y Sistemas Operativos
desde 2002. Actualmente, su actividad investigadora
gira en torno a los Sistemas Multi-Agente, el
Rendering Distribuido y la Realidad Aumentada.

David Villa (2009, Doctor Ingeniero Informtico,


Universidad de Castilla-La Mancha) es Profesor
Ayudante Doctor e imparte docencia en la Escuela de
Informtica de Ciudad Real (UCLM) en materias
relacionadas con las redes de computadores y
sistemas distribuidos desde el 2002. Sus intereses
profesionales se centran en los sistemas empotrados
en red, los sistemas ubicuos y las redes heterogneas
y virtuales. Es experto en mtodos de desarrollo giles
y en los lenguajes C++ y Python. Colabora con el
proyecto Debian como maintainer de paquetes
oficiales.
Francisco Jurado (2010, Doctor Europeo en
Informtica, Universidad de Castilla-La Mancha) es
Profesor Ayudante Doctor en la Universidad Autnoma
de Madrid. Su actividad investigadora actual gira en
torno a la aplicacin de tcnicas de Ingeniera del
Software e Inteligencia Artificial al mbito del
eLearning, los Sistemas Tutores, los Sistemas
Adaptativos y los Entornos Colaborativos.

Francisco Moya (2003, Doctor Ingeniero en


Telecomunicacin, Universidad Politcnica de Madrid).
Desde 1999 trabaja como profesor de la Escuela
Superior de Informtica de la Universidad de Castilla
la Mancha, desde 2008 como Profesor Contratado
Doctor. Sus actuales lneas de investigacin incluyen
los sistemas distribuidos heterogneos, la automa-
tizacin del diseo electrnico y sus aplicaciones en la
construccin de servicios a gran escala y en el diseo
de sistemas en chip. Desde 2007 es tambin Debian
Developer.

Javier Albusac (2009, Doctor Europeo en Informtica,


Universidad de Castilla-La Mancha) es Profesor
Ayudante Doctor e imparte docencia en la Escuela de
Ingeniera Minera e Industrial de Almadn (EIMIA) en
las asignaturas de Informtica, Ofimtica Aplicada a la
Ingeniera y Sistemas de Comunicacin en Edificios
desde 2007. Actualmente, su actividad investigadora
gira en torno a la Vigilancia Inteligente, Robtica Mvil
y Aprendizaje Automtico.

Cleto Martn (2011, Ingeniero Informtica y Mster de


Investigacin en Tecnologas Informticas Avanzadas,
Universidad de Castilla-La Mancha) trabaja como
Software Developer en Digital TV Labs (Bristol, UK) y
como mantenedor de paquetes de aplicaciones para
Canonical Ltd. y el proyecto Debian. Es un gran
entusiasta de los sistemas basados en GNU/Linux, as
como el desarrollo de aplicaciones basadas en redes
de computadores y sistemas distribuidos.
Sergio Prez (2011, Ingeniero en Informtica,
Universidad de Castilla-La Mancha) trabaja como
ingeniero consultor diseando software de redes para
Ericsson R&D. Sus intereses principales son
GNU/Linux, las redes, los videojuegos y la realidad
aumentada.

Flix J. Villanueva (2009, Doctor en Ingeniera


Informtica, Universidad de Castilla-La Mancha) es
contratado doctor e imparte docencia en el rea de
tecnologa y arquitectura de computadores. Las
asignaturas que imparte se centran en el campo de las
redes de computadores con una experiencia docente
de ms de diez aos. Sus principales campos de
investigacin en la actualidad son redes inalmbricas
de sensores, entornos inteligentes y sistemas
empotrados.

Csar Mora (2013, Master en Computer Science por la


Universidad de Minnesota, 2011 Ingeniero en
Informtica, Universidad de Casilla-La Mancha). Sus
temas de inters estn relacionados con la Informtica
Grfica, la Visin Artificial y la Realidad Aumentada.

Jos Jess Castro (2001, Doctor en Informtica,


Universidad de Granada) es Profesor Titular de
Universidad en el rea de Lenguajes y Sistemas
Informticos, desde 1999 imparte docencia en la
Escuela Superior de Informtica de la UCLM. Sus
temas de investigacin estn relacionados con el uso
y desarrollo de mtodos de IA para la resolucin de
problemas reales, donde cuenta con una amplia
experiencia en proyectos de investigacin, siendo
autor de numerosas publicaciones.
Miguel ngel Redondo (2002, Doctor en Ingeniera
Informtica, Universidad de Castilla La Mancha) es
Profesor Titular de Universidad en la Escuela Superior
de Informtica de la UCLM en Ciudad Real,
impartiendo docencia en asignaturas relacionadas con
Interaccin Persona-Computador y Sistemas Opera-
tivos. Su actividad investigadora se centra en la
innovacin y aplicacin de tcnicas de Ingeniera del
Software al desarrollo de sistemas avanzados de
Interaccin Persona-Computador y al desarrollo de
sistemas de e-Learning.

Luis Jimnez (1997, Doctor en Informtica,


Universidad de Granada) es Titular de Universidad e
imparte docencia en la Escuela de Informtica de
Ciudad Real (UCLM) en asignaturas relacionadas la
Inteligencia Artificial y Softcomputing desde 1995.
Actualmente, su actividad investigadora gira en torno
a los Sistemas Inteligentes aplicados mediante
Sistemas Multi-Agente, tcnicas de softcomputing e
inteligencia artificial distribuida.

Jorge Lpez (2011, Ingeniero en Informtica por la


UCLM y Mster en Diseo y Desarrollo de videojuegos
por la UCM). Especializado en desarrollo 3D con C++
y OpenGL, y en el engine Unity 3D. Actualmente
trabaja como programador en Totemcat Materia
Works.

Miguel Garca es desarrollador independiente de


Videojuegos en plataformas iOS, Android, Mac OS X,
GNU/Linux y MS Windows y socio fundador de Atomic
Flavor. Actualmente dirige el estudio de desarrollo de
videojuegos independientes Quaternion Studio.
Manuel Palomo (2011, Doctor por la Universidad de
Cdiz) es Profesor Contratado Doctor e imparte
docencia en la Escuela Superior de Ingeniera de la
Universidad de Cdiz en asignaturas relacionadas con
el Diseo de Videojuegos, Recuperacin de la
Informacin y Sistemas Informticos Abiertos.
Actualmente su actividad investigadora se centra en
las tecnologas del aprendizaje, principalmente
videojuegos educativos y los sistemas colaborativos
de desarrollo y documentacin.

Guillermo Simmross (2003, Ingeniero Tcnico de


Telecomunicacin, 2005 Ingeniero en Electrnica y
2008, Mster Direccin de Proyectos, Universidad de
Valladolid) es Compositor y diseador de sonido
freelance e imparte docencia en colaboracin con la
Universidad Camilo Jos Cela sobre Composicin de
Msica para Videojuegos. Actualmente trabaja como
responsable de producto en Optimyth Software.

Jos Luis Gonzlez (2010, Doctor en Informtica,


Universidad de Granada). Especialista en calidad y
experiencia de usuario en sistemas interactivos y
videojuegos, temas donde imparte su docencia e
investiga. Ha colaborado con distintas compaas del
sector, como Nintendo o MercurySteam. Es autor de
distintos libros sobre la jugabilidad y el diseo y
evaluacin de la experiencia del jugador.
ndice general

I Arquitectura del Motor 1

1. Introduccin 3
1.1. El desarrollo de videojuegos . . . . . . . . . . . . . . . . . . . . . . 3
1.1.1. La industria del videojuego. Presente y futuro . . . . . . . . . 3
1.1.2. Estructura tpica de un equipo de desarrollo . . . . . . . . . . 5
1.1.3. El concepto de juego . . . . . . . . . . . . . . . . . . . . . . 7
1.1.4. Motor de juego . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.1.5. Gneros de juegos . . . . . . . . . . . . . . . . . . . . . . . 10
1.2. Arquitectura del motor. Visin general . . . . . . . . . . . . . . . . . 15
1.2.1. Hardware, drivers y sistema operativo . . . . . . . . . . . . . 15
1.2.2. SDKs y middlewares . . . . . . . . . . . . . . . . . . . . . . 16
1.2.3. Capa independiente de la plataforma . . . . . . . . . . . . . . 17
1.2.4. Subsistemas principales . . . . . . . . . . . . . . . . . . . . 18
1.2.5. Gestor de recursos . . . . . . . . . . . . . . . . . . . . . . . 18
1.2.6. Motor de rendering . . . . . . . . . . . . . . . . . . . . . . . 19
1.2.7. Herramientas de depuracin . . . . . . . . . . . . . . . . . . 22
1.2.8. Motor de fsica . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.2.9. Interfaces de usuario . . . . . . . . . . . . . . . . . . . . . . 23
1.2.10. Networking y multijugador . . . . . . . . . . . . . . . . . . . 23
1.2.11. Subsistema de juego . . . . . . . . . . . . . . . . . . . . . . 24
1.2.12. Audio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
1.2.13. Subsistemas especcos de juego . . . . . . . . . . . . . . . . 26

2. Herramientas de Desarrollo 27

I
[II] NDICE GENERAL

2.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.2. Compilacin, enlazado y depuracin . . . . . . . . . . . . . . . . . . 28
2.2.1. Conceptos bsicos . . . . . . . . . . . . . . . . . . . . . . . 28
2.2.2. Compilando con GCC . . . . . . . . . . . . . . . . . . . . . 31
2.2.3. Cmo funciona GCC? . . . . . . . . . . . . . . . . . . . . . 31
2.2.4. Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.2.5. Otras herramientas . . . . . . . . . . . . . . . . . . . . . . . 39
2.2.6. Depurando con GDB . . . . . . . . . . . . . . . . . . . . . . 39
2.2.7. Construccin automtica con GNU Make . . . . . . . . . . . 45
2.3. Gestin de proyectos y documentacin . . . . . . . . . . . . . . . . . 50
2.3.1. Sistemas de control de versiones . . . . . . . . . . . . . . . . 50
2.3.2. Documentacin . . . . . . . . . . . . . . . . . . . . . . . . . 59
2.3.3. Forjas de desarrollo . . . . . . . . . . . . . . . . . . . . . . . 62

3. C++. Aspectos Esenciales 65


3.1. Utilidades bsicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
3.1.1. Introduccin a C++ . . . . . . . . . . . . . . . . . . . . . . . 65
3.1.2. Hola Mundo! en C++ . . . . . . . . . . . . . . . . . . . . . 66
3.1.3. Tipos, declaraciones y modicadores . . . . . . . . . . . . . 67
3.1.4. Punteros, arrays y estructuras . . . . . . . . . . . . . . . . . 68
3.1.5. Referencias y funciones . . . . . . . . . . . . . . . . . . . . 71
3.2. Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
3.2.1. Fundamentos bsicos . . . . . . . . . . . . . . . . . . . . . . 74
3.2.2. Aspectos especcos de las clases . . . . . . . . . . . . . . . 77
3.2.3. Sobrecarga de operadores . . . . . . . . . . . . . . . . . . . 81
3.3. Herencia y polimorsmo . . . . . . . . . . . . . . . . . . . . . . . . 84
3.3.1. Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
3.3.2. Herencia mltiple . . . . . . . . . . . . . . . . . . . . . . . . 86
3.3.3. Funciones virtuales y polimorsmo . . . . . . . . . . . . . . 90
3.4. Plantillas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
3.4.1. Caso de estudio. Listas . . . . . . . . . . . . . . . . . . . . . 94
3.4.2. Utilizando plantillas en C++ . . . . . . . . . . . . . . . . . . 96
3.4.3. Cundo utilizar plantillas? . . . . . . . . . . . . . . . . . . . 98
3.5. Manejo de excepciones . . . . . . . . . . . . . . . . . . . . . . . . . 99
3.5.1. Alternativas existentes . . . . . . . . . . . . . . . . . . . . . 99
3.5.2. Excepciones en C++ . . . . . . . . . . . . . . . . . . . . . . 100
3.5.3. Cmo manejar excepciones adecuadamente? . . . . . . . . . 103
3.5.4. Cundo utilizar excepciones? . . . . . . . . . . . . . . . . . 105
[III]

4. Patrones de Diseo 107


4.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
4.1.1. Estructura de un patrn de diseo . . . . . . . . . . . . . . . 109
4.1.2. Tipos de patrones . . . . . . . . . . . . . . . . . . . . . . . . 110
4.2. Patrones de creacin . . . . . . . . . . . . . . . . . . . . . . . . . . 110
4.2.1. Singleton . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
4.2.2. Abstract Factory . . . . . . . . . . . . . . . . . . . . . . . . 112
4.2.3. Factory Method . . . . . . . . . . . . . . . . . . . . . . . . . 115
4.2.4. Prototype . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
4.3. Patrones estructurales . . . . . . . . . . . . . . . . . . . . . . . . . . 117
4.3.1. Composite . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
4.3.2. Decorator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
4.3.3. Facade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
4.3.4. MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
4.3.5. Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
4.3.6. Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
4.4. Patrones de comportamiento . . . . . . . . . . . . . . . . . . . . . . 127
4.4.1. Observer . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
4.4.2. State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
4.4.3. Iterator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
4.4.4. Template Method . . . . . . . . . . . . . . . . . . . . . . . . 133
4.4.5. Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
4.4.6. Reactor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
4.4.7. Visitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
4.5. Programming Idioms . . . . . . . . . . . . . . . . . . . . . . . . . . 141
4.5.1. Orthodox Canonical Form . . . . . . . . . . . . . . . . . . . 141
4.5.2. Interface Class . . . . . . . . . . . . . . . . . . . . . . . . . 142
4.5.3. Final Class . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
4.5.4. pImpl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144

5. La Biblioteca STL 147


5.1. Visin general de STL . . . . . . . . . . . . . . . . . . . . . . . . . 147
5.2. STL y el desarrollo de videojuegos . . . . . . . . . . . . . . . . . . . 151
5.2.1. Reutilizacin de cdigo . . . . . . . . . . . . . . . . . . . . . 151
5.2.2. Rendimiento . . . . . . . . . . . . . . . . . . . . . . . . . . 152
5.2.3. Inconvenientes . . . . . . . . . . . . . . . . . . . . . . . . . 152
5.3. Secuencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
5.3.1. Vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
5.3.2. Deque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
[IV] NDICE GENERAL

5.3.3. List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158


5.4. Contenedores asociativos . . . . . . . . . . . . . . . . . . . . . . . . 161
5.4.1. Set y multiset . . . . . . . . . . . . . . . . . . . . . . . . . . 161
5.4.2. Map y multimap . . . . . . . . . . . . . . . . . . . . . . . . 164
5.5. Adaptadores de secuencia . . . . . . . . . . . . . . . . . . . . . . . . 167
5.5.1. Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
5.5.2. Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
5.5.3. Cola de prioridad . . . . . . . . . . . . . . . . . . . . . . . . 168

6. Gestin de Recursos 171


6.1. El bucle de juego . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
6.1.1. El bucle de renderizado . . . . . . . . . . . . . . . . . . . . . 172
6.1.2. Visin general del bucle de juego . . . . . . . . . . . . . . . 172
6.1.3. Arquitecturas tpicas del bucle de juego . . . . . . . . . . . . 174
6.1.4. Gestin de estados de juego con Ogre3D . . . . . . . . . . . 177
6.1.5. Denicin de estados concretos . . . . . . . . . . . . . . . . 182
6.2. Gestin bsica de recursos . . . . . . . . . . . . . . . . . . . . . . . 184
6.2.1. Gestin de recursos con Ogre3D . . . . . . . . . . . . . . . . 184
6.2.2. Gestin bsica del sonido . . . . . . . . . . . . . . . . . . . . 186
6.3. El sistema de archivos . . . . . . . . . . . . . . . . . . . . . . . . . . 195
6.3.1. Gestin y tratamiento de archivos . . . . . . . . . . . . . . . 196
6.3.2. E/S bsica . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
6.3.3. E/S asncrona . . . . . . . . . . . . . . . . . . . . . . . . . . 200
6.3.4. Caso de estudio. La biblioteca Boost.Asio C++ . . . . . . . . 203
6.3.5. Consideraciones nales . . . . . . . . . . . . . . . . . . . . . 207
6.4. Importador de datos de intercambio . . . . . . . . . . . . . . . . . . 207
6.4.1. Formatos de intercambio . . . . . . . . . . . . . . . . . . . . 208
6.4.2. Creacin de un importador . . . . . . . . . . . . . . . . . . . 210

7. Bajo Nivel y Concurrencia 219


7.1. Subsistema de arranque y parada . . . . . . . . . . . . . . . . . . . . 220
7.1.1. Aspectos fundamentales . . . . . . . . . . . . . . . . . . . . 220
7.1.2. Esquema tpico de arranque y parada . . . . . . . . . . . . . 223
7.1.3. Caso de estudio. Ogre 3D . . . . . . . . . . . . . . . . . . . 224
7.1.4. Caso de estudio. Quake III . . . . . . . . . . . . . . . . . . . 227
7.2. Contenedores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
7.2.1. Iteradores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
7.2.2. Ms all de STL . . . . . . . . . . . . . . . . . . . . . . . . 231
7.3. Subsistema de gestin de cadenas . . . . . . . . . . . . . . . . . . . 235
[V]

7.3.1. Cuestiones especcas . . . . . . . . . . . . . . . . . . . . . 235


7.3.2. Optimizando el tratamiento de cadenas . . . . . . . . . . . . 236
7.3.3. Hashing de cadenas . . . . . . . . . . . . . . . . . . . . . . . 238
7.4. Conguracin del motor . . . . . . . . . . . . . . . . . . . . . . . . 239
7.4.1. Esquemas tpicos de conguracin . . . . . . . . . . . . . . . 239
7.4.2. Caso de estudio. Esquemas de denicin. . . . . . . . . . . . 240
7.5. Fundamentos bsicos de concurrencia . . . . . . . . . . . . . . . . . 241
7.5.1. El concepto de hilo . . . . . . . . . . . . . . . . . . . . . . . 241
7.5.2. El problema de la seccin crtica . . . . . . . . . . . . . . . . 242
7.6. La biblioteca de hilos de I CE . . . . . . . . . . . . . . . . . . . . . . 244
7.6.1. Internet Communication Engine . . . . . . . . . . . . . . . . 244
7.6.2. Manejo de hilos . . . . . . . . . . . . . . . . . . . . . . . . . 245
7.6.3. Exclusin mutua bsica . . . . . . . . . . . . . . . . . . . . . 247
7.6.4. Flexibilizando el concepto de mutex . . . . . . . . . . . . . . 251
7.6.5. Introduciendo monitores . . . . . . . . . . . . . . . . . . . . 251
7.7. Concurrencia en C++11 . . . . . . . . . . . . . . . . . . . . . . . . . 256
7.7.1. Filsofos comensales en C++11 . . . . . . . . . . . . . . . . 258
7.8. Multi-threading en Ogre3D . . . . . . . . . . . . . . . . . . . . . . . 260
7.9. Caso de estudio. Procesamiento en segundo plano mediante hilos . . . 263

II Programacin Grca 267

8. Fundamentos de Grcos Tridimensionales 269


8.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
8.2. El Pipeline Grco . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
8.2.1. Etapa de Aplicacin . . . . . . . . . . . . . . . . . . . . . . 272
8.2.2. Etapa de Geometra . . . . . . . . . . . . . . . . . . . . . . . 272
8.2.3. Etapa Rasterizacin . . . . . . . . . . . . . . . . . . . . . . . 275
8.2.4. Proyeccin en Perspectiva . . . . . . . . . . . . . . . . . . . 276
8.3. Implementacin del Pipeline en GPU . . . . . . . . . . . . . . . . . . 277
8.3.1. Vertex Shader . . . . . . . . . . . . . . . . . . . . . . . . . . 278
8.3.2. Geometry Shader . . . . . . . . . . . . . . . . . . . . . . . . 279
8.3.3. Pixel Shader . . . . . . . . . . . . . . . . . . . . . . . . . . 279
8.4. Arquitectura del motor grco . . . . . . . . . . . . . . . . . . . . . 279
8.5. Casos de Estudio . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
8.6. Introduccin a OGRE . . . . . . . . . . . . . . . . . . . . . . . . . . 281
8.6.1. Arquitectura General . . . . . . . . . . . . . . . . . . . . . . 284
8.6.2. Instalacin . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
[VI] NDICE GENERAL

8.7. Hola Mundo en OGRE . . . . . . . . . . . . . . . . . . . . . . . . . 289

9. Matemticas para Videojuegos 295


9.1. Puntos, Vectores y Coordenadas . . . . . . . . . . . . . . . . . . . . 295
9.1.1. Sistemas de Referencia . . . . . . . . . . . . . . . . . . . . . 295
9.1.2. Puntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
9.1.3. Vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
9.2. Transformaciones Geomtricas . . . . . . . . . . . . . . . . . . . . . 300
9.2.1. Representacin Matricial . . . . . . . . . . . . . . . . . . . . 301
9.2.2. Transformaciones Inversas . . . . . . . . . . . . . . . . . . . 303
9.2.3. Composicin . . . . . . . . . . . . . . . . . . . . . . . . . . 303
9.3. Perspectiva: Representacin Matricial . . . . . . . . . . . . . . . . . 304
9.4. Cuaternios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
9.4.1. Suma y Multiplicacin . . . . . . . . . . . . . . . . . . . . . 308
9.4.2. Inversa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308
9.4.3. Rotacin empleando Cuaternios . . . . . . . . . . . . . . . . 308
9.5. Interpolacin Lineal y Esfrica . . . . . . . . . . . . . . . . . . . . . 309
9.6. El Mdulo Math en OGRE . . . . . . . . . . . . . . . . . . . . . . . 310
9.7. Ejercicios Propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . 311

10. Grafos de Escena 313


10.1. Justicacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
10.1.1. Operaciones a Nivel de Nodo . . . . . . . . . . . . . . . . . 314
10.2. El Gestor de Escenas de OGRE . . . . . . . . . . . . . . . . . . . . . 315
10.2.1. Creacin de Objetos . . . . . . . . . . . . . . . . . . . . . . 316
10.2.2. Transformaciones 3D . . . . . . . . . . . . . . . . . . . . . . 317
10.2.3. Espacios de transformacin . . . . . . . . . . . . . . . . . . 319

11. Recursos Grcos y Sistema de Archivos 321


11.1. Formatos de Especicacin . . . . . . . . . . . . . . . . . . . . . . . 321
11.1.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 321
11.1.2. Recursos de grcos 3D: formatos y requerimientos de alma-
cenamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
11.1.3. Casos de Estudio . . . . . . . . . . . . . . . . . . . . . . . . 327
11.2. Exportacin y Adaptacin de Contenidos . . . . . . . . . . . . . . . 338
11.2.1. Instalacin del exportador de Ogre en Blender . . . . . . . . . 338
11.2.2. Creacin de un modelo en Blender . . . . . . . . . . . . . . . 340
11.2.3. Aplicacin de texturas mediante UV Mapping . . . . . . . . . 340
11.2.4. Exportacin del objeto en formato Ogre XML . . . . . . . . . 343
11.2.5. Carga del objeto en una aplicacin Ogre . . . . . . . . . . . . 345
[VII]

11.3. Procesamiento de Recursos Grcos . . . . . . . . . . . . . . . . . . 346


11.3.1. Ejemplo de uso . . . . . . . . . . . . . . . . . . . . . . . . . 348
11.4. Gestin de Recursos y Escena . . . . . . . . . . . . . . . . . . . . . 351
11.4.1. Recursos empaquetados . . . . . . . . . . . . . . . . . . . . 352
11.4.2. Gestin del ratn . . . . . . . . . . . . . . . . . . . . . . . . 352
11.4.3. Geometra Esttica . . . . . . . . . . . . . . . . . . . . . . . 353
11.4.4. Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354
11.4.5. Mscaras . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357

12. APIS de Grcos 3D 359


12.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
12.2. Modelo Conceptual . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
12.2.1. Cambio de Estado . . . . . . . . . . . . . . . . . . . . . . . 361
12.2.2. Dibujar Primitivas . . . . . . . . . . . . . . . . . . . . . . . 362
12.3. Pipeline de OpenGL . . . . . . . . . . . . . . . . . . . . . . . . . . 362
12.3.1. Transformacin de Visualizacin . . . . . . . . . . . . . . . . 363
12.3.2. Transformacin de Modelado . . . . . . . . . . . . . . . . . 363
12.3.3. Transformacin de Proyeccin . . . . . . . . . . . . . . . . . 364
12.3.4. Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
12.3.5. Dos ejemplos de transformaciones jerrquicas . . . . . . . . . 366
12.4. Ejercicios Propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . 368

13. Gestin Manual OGRE 3D 369


13.1. Inicializacin Manual . . . . . . . . . . . . . . . . . . . . . . . . . . 369
13.1.1. Inicializacin . . . . . . . . . . . . . . . . . . . . . . . . . . 370
13.1.2. Carga de Recursos . . . . . . . . . . . . . . . . . . . . . . . 373
13.1.3. FrameListener . . . . . . . . . . . . . . . . . . . . . . . . . 373
13.2. Uso de OIS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374
13.2.1. Uso de Teclado y Ratn . . . . . . . . . . . . . . . . . . . . 376
13.3. Creacin manual de Entidades . . . . . . . . . . . . . . . . . . . . . 377
13.4. Uso de Overlays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379

14. Interaccin y Widgets 383


14.1. Interfaces de usuario en videojuegos . . . . . . . . . . . . . . . . . . 383
14.1.1. Men . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
14.1.2. HUD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
14.2. Introduccin CEGUI . . . . . . . . . . . . . . . . . . . . . . . . . . 387
14.2.1. Instalacin . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
14.2.2. Inicializacin . . . . . . . . . . . . . . . . . . . . . . . . . . 388
[VIII] NDICE GENERAL

14.2.3. El Sistema de Dimensin Unicado . . . . . . . . . . . . . . 390


14.2.4. Deteccin de eventos de entrada . . . . . . . . . . . . . . . . 391
14.3. Primera aplicacin . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
14.4. Tipos de Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
14.5. Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
14.6. Ejemplo de interfaz . . . . . . . . . . . . . . . . . . . . . . . . . . . 397
14.7. Editores de layouts grcos . . . . . . . . . . . . . . . . . . . . . . . 400
14.8. Scripts en detalle . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400
14.8.1. Scheme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400
14.8.2. Font . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
14.8.3. Imageset . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
14.8.4. LookNFeel . . . . . . . . . . . . . . . . . . . . . . . . . . . 402
14.9. Cmara de Ogre en un Window . . . . . . . . . . . . . . . . . . . . . 402
14.10.Formateo de texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . 404
14.10.1.Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 404
14.10.2.Color . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405
14.10.3.Formato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405
14.10.4.Insertar imgenes . . . . . . . . . . . . . . . . . . . . . . . . 405
14.10.5.Alineamiento vertical . . . . . . . . . . . . . . . . . . . . . . 406
14.10.6.Padding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
14.10.7.Ejemplo de texto formateado . . . . . . . . . . . . . . . . . . 407
14.11.Caractersticas avanzadas . . . . . . . . . . . . . . . . . . . . . . . . 408

15. Materiales y Texturas 409


15.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
15.2. Modelos de Sombreado . . . . . . . . . . . . . . . . . . . . . . . . . 410
15.3. Mapeado de Texturas . . . . . . . . . . . . . . . . . . . . . . . . . . 412
15.4. Materiales en Ogre . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
15.4.1. Composicin . . . . . . . . . . . . . . . . . . . . . . . . . . 414
15.4.2. Ejemplo de Materiales . . . . . . . . . . . . . . . . . . . . . 415
15.5. Mapeado UV en Blender . . . . . . . . . . . . . . . . . . . . . . . . 417
15.5.1. Costuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
15.6. Ejemplos de Materiales en Ogre . . . . . . . . . . . . . . . . . . . . 421
15.7. Render a Textura . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424
15.7.1. Texture Listener . . . . . . . . . . . . . . . . . . . . . . . . 426
15.7.2. Espejo (Mirror) . . . . . . . . . . . . . . . . . . . . . . . . . 427

16. Iluminacin 429


16.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
[IX]

16.2. Tipos de Fuentes de Luz . . . . . . . . . . . . . . . . . . . . . . . . 430


16.3. Sombras Estticas Vs Dinmicas . . . . . . . . . . . . . . . . . . . . 431
16.3.1. Sombras basadas en Stencil Buffer . . . . . . . . . . . . . . . 432
16.3.2. Sombras basadas en Texturas . . . . . . . . . . . . . . . . . . 434
16.4. Ejemplo de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435
16.5. Mapas de Iluminacin . . . . . . . . . . . . . . . . . . . . . . . . . . 437
16.6. Ambient Occlusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 439
16.7. Radiosidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440

17. Exportacin y Uso de Datos de Intercambio 445


17.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445

18. Animacin 451


18.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451
18.1.1. Animacin Bsica . . . . . . . . . . . . . . . . . . . . . . . 452
18.1.2. Animacin de Alto Nivel . . . . . . . . . . . . . . . . . . . . 453
18.2. Animacin en Ogre . . . . . . . . . . . . . . . . . . . . . . . . . . . 454
18.2.1. Animacin Keyframe . . . . . . . . . . . . . . . . . . . . . . 454
18.2.2. Controladores . . . . . . . . . . . . . . . . . . . . . . . . . . 456
18.3. Exportacin desde Blender . . . . . . . . . . . . . . . . . . . . . . . 456
18.4. Mezclado de animaciones . . . . . . . . . . . . . . . . . . . . . . . . 459

19. Simulacin Fsica 465


19.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465
19.1.1. Algunos Motores de Simulacin . . . . . . . . . . . . . . . . 466
19.1.2. Aspectos destacables . . . . . . . . . . . . . . . . . . . . . . 467
19.1.3. Conceptos Bsicos . . . . . . . . . . . . . . . . . . . . . . . 468
19.2. Sistema de Deteccin de Colisiones . . . . . . . . . . . . . . . . . . 469
19.2.1. Formas de Colisin . . . . . . . . . . . . . . . . . . . . . . . 470
19.2.2. Optimizaciones . . . . . . . . . . . . . . . . . . . . . . . . . 472
19.2.3. Preguntando al sistema... . . . . . . . . . . . . . . . . . . . . 472
19.3. Dinmica del Cuerpo Rgido . . . . . . . . . . . . . . . . . . . . . . 473
19.4. Restricciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474
19.5. Introduccin a Bullet . . . . . . . . . . . . . . . . . . . . . . . . . . 475
19.5.1. Pipeline de Fsicas de Cuerpo Rgido . . . . . . . . . . . . . 476
19.5.2. Hola Mundo en Bullet . . . . . . . . . . . . . . . . . . . . . 477
19.6. Integracin manual en Ogre . . . . . . . . . . . . . . . . . . . . . . . 484
19.7. Hola Mundo en OgreBullet . . . . . . . . . . . . . . . . . . . . . . . 487
19.8. RayQueries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491
[X] NDICE GENERAL

19.9. TriangleMeshCollisionShape . . . . . . . . . . . . . . . . . . . . . . 493


19.10.Deteccin de colisiones . . . . . . . . . . . . . . . . . . . . . . . . . 494
19.11.Restriccin de Vehculo . . . . . . . . . . . . . . . . . . . . . . . . . 496
19.12.Determinismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500
19.13.Escala de los Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . 502
19.14.Serializacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502

III Tcnicas Avanzadas de Desarrollo 505

20. Aspectos de Jugabilidad y Metodologas de Desarrollo 507


20.1. Jugabilidad y Experiencia del Jugador . . . . . . . . . . . . . . . . . 507
20.1.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 507
20.1.2. Caracterizacin de la Jugabilidad . . . . . . . . . . . . . . . 508
20.1.3. Facetas de la Jugabilidad . . . . . . . . . . . . . . . . . . . . 510
20.1.4. Calidad de un juego en base a la Jugabilidad . . . . . . . . . 512
20.2. Metodologas de Produccin y Desarrollo . . . . . . . . . . . . . . . 518
20.2.1. Pre-Produccin . . . . . . . . . . . . . . . . . . . . . . . . . 519
20.2.2. Produccin . . . . . . . . . . . . . . . . . . . . . . . . . . . 521
20.2.3. Post-Produccin . . . . . . . . . . . . . . . . . . . . . . . . 523
20.3. Metodologas Alternativas . . . . . . . . . . . . . . . . . . . . . . . 524
20.3.1. Proceso Unicado del Juego . . . . . . . . . . . . . . . . . . 524
20.3.2. Desarrollo Incremental . . . . . . . . . . . . . . . . . . . . . 524
20.3.3. Desarrollo gil y Scrum . . . . . . . . . . . . . . . . . . . . 524
20.3.4. Desarrollo Centrado en el Jugador . . . . . . . . . . . . . . . 525

21. C++ Avanzado 527


21.1. Estructuras de datos no lineales . . . . . . . . . . . . . . . . . . . . . 527
21.1.1. rboles binarios . . . . . . . . . . . . . . . . . . . . . . . . 528
21.1.2. Recorrido de rboles . . . . . . . . . . . . . . . . . . . . . . 542
21.1.3. Quadtree y octree . . . . . . . . . . . . . . . . . . . . . . . . 545
21.2. Patrones de diseo avanzados . . . . . . . . . . . . . . . . . . . . . . 548
21.2.1. Smart pointers . . . . . . . . . . . . . . . . . . . . . . . . . 548
21.2.2. Command . . . . . . . . . . . . . . . . . . . . . . . . . . . . 554
21.2.3. Curiously recurring template pattern . . . . . . . . . . . . . . 557
21.2.4. Reactor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 561
21.2.5. Acceptor/Connector . . . . . . . . . . . . . . . . . . . . . . 564
21.3. Programacin genrica . . . . . . . . . . . . . . . . . . . . . . . . . 568
21.3.1. Algoritmos . . . . . . . . . . . . . . . . . . . . . . . . . . . 568
[XI]

21.3.2. Predicados . . . . . . . . . . . . . . . . . . . . . . . . . . . 571


21.3.3. Functors . . . . . . . . . . . . . . . . . . . . . . . . . . . . 572
21.3.4. Adaptadores . . . . . . . . . . . . . . . . . . . . . . . . . . 574
21.3.5. Algoritmos idempotentes . . . . . . . . . . . . . . . . . . . . 576
21.3.6. Algoritmos de transformacin . . . . . . . . . . . . . . . . . 578
21.3.7. Algoritmos de ordenacin . . . . . . . . . . . . . . . . . . . 582
21.3.8. Algoritmos numricos . . . . . . . . . . . . . . . . . . . . . 584
21.3.9. Ejemplo: inventario de armas . . . . . . . . . . . . . . . . . . 585
21.4. Aspectos avanzados de la STL . . . . . . . . . . . . . . . . . . . . . 588
21.4.1. Eciencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . 588
21.4.2. Semntica de copia . . . . . . . . . . . . . . . . . . . . . . . 591
21.4.3. Extendiendo la STL . . . . . . . . . . . . . . . . . . . . . . 593
21.4.4. Allocators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 596
21.4.5. Novedades de la STL en C++11 . . . . . . . . . . . . . . . . 599
21.5. C++11: Novedades del nuevo estndar . . . . . . . . . . . . . . . . . 603
21.5.1. Compilando con g++ y clang . . . . . . . . . . . . . . . . 603
21.5.2. Cambios en el ncleo del lenguaje . . . . . . . . . . . . . . . 603
21.5.3. Cambios en la biblioteca de C++ . . . . . . . . . . . . . . . . 616
21.6. Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 619
21.6.1. Entendiendo las bibliotecas dinmicas . . . . . . . . . . . . . 620
21.6.2. Plugins con libdl . . . . . . . . . . . . . . . . . . . . . . . 622
21.6.3. Plugins con Glib gmodule . . . . . . . . . . . . . . . . . . 627
21.6.4. Carga dinmica desde Python . . . . . . . . . . . . . . . . . 629
21.6.5. Plugins como objetos mediante el patrn Factory Method . . . 629
21.6.6. Plugins multi-plataforma . . . . . . . . . . . . . . . . . . . . 632

22. Tcnicas especcas 635


22.1. Serializacin de objetos . . . . . . . . . . . . . . . . . . . . . . . . . 635
22.1.1. Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 636
22.1.2. Serializacin y Dependencias entre objetos . . . . . . . . . . 639
22.1.3. Serializacin con Boost . . . . . . . . . . . . . . . . . . . . . 649
22.2. C++ y scripting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653
22.2.1. Consideraciones de diseo . . . . . . . . . . . . . . . . . . . 653
22.2.2. Invocando Python desde C++ de forma nativa . . . . . . . . . 655
22.2.3. Librera boost . . . . . . . . . . . . . . . . . . . . . . . . . . 656
22.2.4. Herramienta SWIG . . . . . . . . . . . . . . . . . . . . . . . 660
22.2.5. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . 662

23. Optimizacin 663


[XII] NDICE GENERAL

23.1. Perlado de programas . . . . . . . . . . . . . . . . . . . . . . . . . 664


23.1.1. El perlador de Linux perf . . . . . . . . . . . . . . . . . . . 666
23.1.2. Obteniendo ayuda . . . . . . . . . . . . . . . . . . . . . . . 668
23.1.3. Estadsticas y registro de eventos . . . . . . . . . . . . . . . . 668
23.1.4. Multiplexacin y escalado . . . . . . . . . . . . . . . . . . . 669
23.1.5. Mtricas por hilo, por proceso o por CPU . . . . . . . . . . . 670
23.1.6. Muestreo de eventos . . . . . . . . . . . . . . . . . . . . . . 671
23.1.7. Otras opciones de perf . . . . . . . . . . . . . . . . . . . . 675
23.1.8. Otros perladores . . . . . . . . . . . . . . . . . . . . . . . . 675
23.2. Optimizaciones del compilador . . . . . . . . . . . . . . . . . . . . . 677
23.2.1. Variables registro . . . . . . . . . . . . . . . . . . . . . . . . 677
23.2.2. Cdigo esttico y funciones inline . . . . . . . . . . . . . . . 678
23.2.3. Eliminacin de copias . . . . . . . . . . . . . . . . . . . . . 683
23.2.4. Volatile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 684
23.3. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 685

24. Validacin y Pruebas 687


24.1. Programacin defensiva . . . . . . . . . . . . . . . . . . . . . . . . . 687
24.1.1. Sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . . . 689
24.2. Desarrollo gil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 690
24.3. TDD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 690
24.3.1. Las pruebas primero . . . . . . . . . . . . . . . . . . . . . . 691
24.3.2. rojo, verde, refactorizar . . . . . . . . . . . . . . . . . . . . . 691
24.4. Tipos de pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 692
24.5. Pruebas unitarias con google-tests . . . . . . . . . . . . . . . . . . . 693
24.6. Dobles de prueba . . . . . . . . . . . . . . . . . . . . . . . . . . . . 695
24.7. Dobles de prueba con google-mock . . . . . . . . . . . . . . . . . . 696
24.8. Limitaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701

25. Empaquetado y distribucin 703


25.1. Empaquetado y distribucin en Windows . . . . . . . . . . . . . . . 704
25.1.1. Creacin de un paquete bsico . . . . . . . . . . . . . . . . . 704
25.1.2. Interaccin con el usuario . . . . . . . . . . . . . . . . . . . 710
25.1.3. Otras caractersticas . . . . . . . . . . . . . . . . . . . . . . 711
25.2. Empaquetado y distribucin en GNU/Linux . . . . . . . . . . . . . . 712
25.2.1. Pidiendo un paquete . . . . . . . . . . . . . . . . . . . . . . 713
25.2.2. Obteniendo el fuente original . . . . . . . . . . . . . . . . . . 714
25.2.3. Estructura bsica . . . . . . . . . . . . . . . . . . . . . . . . 714
25.2.4. Construccin del paquete . . . . . . . . . . . . . . . . . . . . 719
[XIII]

25.2.5. Parches: adaptacin a Debian . . . . . . . . . . . . . . . . . 721


25.2.6. Actualizacin del paquete . . . . . . . . . . . . . . . . . . . 724
25.2.7. Subir un paquete a Debian . . . . . . . . . . . . . . . . . . . 725
25.3. Otros formatos de paquete . . . . . . . . . . . . . . . . . . . . . . . 725

26. Representacin Avanzada 727


26.1. Fundamentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 727
26.1.1. Billboards . . . . . . . . . . . . . . . . . . . . . . . . . . . . 727
26.1.2. Sistemas de partculas . . . . . . . . . . . . . . . . . . . . . 730
26.2. Uso de Billboards . . . . . . . . . . . . . . . . . . . . . . . . . . . . 731
26.2.1. Tipos de Billboard . . . . . . . . . . . . . . . . . . . . . . . 733
26.2.2. Aplicando texturas . . . . . . . . . . . . . . . . . . . . . . . 733
26.3. Uso de Sistemas de Partculas . . . . . . . . . . . . . . . . . . . . . 734
26.3.1. Emisores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 735
26.3.2. Efectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 736
26.3.3. Ejemplos de Sistemas de Partculas . . . . . . . . . . . . . . 737
26.4. Introduccin a los Shaders . . . . . . . . . . . . . . . . . . . . . . . 739
26.4.1. Un poco de historia . . . . . . . . . . . . . . . . . . . . . . . 739
26.4.2. Y qu es un Shader? . . . . . . . . . . . . . . . . . . . . . . 741
26.4.3. Pipelines Grcos . . . . . . . . . . . . . . . . . . . . . . . 741
26.4.4. Fixed-Function Pipeline . . . . . . . . . . . . . . . . . . . . 742
26.4.5. Programmable-Function Pipeline . . . . . . . . . . . . . . . 745
26.4.6. Aplicaciones de los Shader . . . . . . . . . . . . . . . . . . . 748
26.4.7. Lenguajes de Shader . . . . . . . . . . . . . . . . . . . . . . 751
26.5. Desarrollo de shaders en Ogre . . . . . . . . . . . . . . . . . . . . . 751
26.5.1. Primer Shader . . . . . . . . . . . . . . . . . . . . . . . . . . 752
26.5.2. Comprobando la interpolacin del color . . . . . . . . . . . . 756
26.5.3. Usando una textura . . . . . . . . . . . . . . . . . . . . . . . 757
26.5.4. Jugando con la textura . . . . . . . . . . . . . . . . . . . . . 759
26.5.5. Jugando con los vrtices . . . . . . . . . . . . . . . . . . . . 762
26.5.6. Iluminacin mediante shaders . . . . . . . . . . . . . . . . . 764
26.6. Optimizacin de interiores . . . . . . . . . . . . . . . . . . . . . . . 765
26.6.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 765
26.6.2. Tcnicas y Algoritmos . . . . . . . . . . . . . . . . . . . . . 766
26.6.3. Manejo de escenas en OGRE . . . . . . . . . . . . . . . . . . 778
26.7. Optimizacin de Exteriores . . . . . . . . . . . . . . . . . . . . . . . 780
26.7.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 780
26.7.2. Estructuras de datos . . . . . . . . . . . . . . . . . . . . . . 780
26.7.3. Determinacin de la resolucin . . . . . . . . . . . . . . . . 782
[XIV] NDICE GENERAL

26.7.4. Tcnicas y Algoritmos . . . . . . . . . . . . . . . . . . . . . 784


26.7.5. Exteriores y LOD en OGRE . . . . . . . . . . . . . . . . . . 789
26.7.6. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . 798

27. Plataformas Mviles 799


27.1. Mtodo de trabajo con un motor de videojuegos . . . . . . . . . . . . 799
27.1.1. Generacin de contenido externo al motor . . . . . . . . . . . 799
27.1.2. Generacin de contenido interno al motor . . . . . . . . . . . 800
27.2. Creacin de escenas . . . . . . . . . . . . . . . . . . . . . . . . . . . 800
27.3. Creacin de prefabs . . . . . . . . . . . . . . . . . . . . . . . . . . . 803
27.4. Programacin de scripts . . . . . . . . . . . . . . . . . . . . . . . . . 805
27.4.1. Algunos scripts bsicos . . . . . . . . . . . . . . . . . . . . . 805
27.4.2. Triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806
27.4.3. Invocacin de mtodos retardada . . . . . . . . . . . . . . . . 806
27.4.4. Comunicacin entre diferentes scripts . . . . . . . . . . . . . 807
27.4.5. Control del ujo general de la partida . . . . . . . . . . . . . 808
27.4.6. Programacin de enemigos . . . . . . . . . . . . . . . . . . . 810
27.4.7. Programacin del control del jugador . . . . . . . . . . . . . 812
27.4.8. Programacin del interface . . . . . . . . . . . . . . . . . . . 814
27.5. Optimizacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 816
27.5.1. Light mapping . . . . . . . . . . . . . . . . . . . . . . . . . 816
27.5.2. Occlusion culling . . . . . . . . . . . . . . . . . . . . . . . . 816
27.6. Resultado nal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 818

IV Desarrollo de Componentes 821

28. Inteligencia Articial 823


28.1. Introduccin a la IA para videojuegos . . . . . . . . . . . . . . . . . 824
28.1.1. Aplicando el Test de Turing . . . . . . . . . . . . . . . . . . 824
28.1.2. Ilusin de inteligencia . . . . . . . . . . . . . . . . . . . . . 825
28.1.3. NPCs o Agentes? . . . . . . . . . . . . . . . . . . . . . . . 827
28.1.4. Diseo de agentes basado en estados . . . . . . . . . . . . . . 829
28.1.5. Los lenguajes de script . . . . . . . . . . . . . . . . . . . . . 831
28.1.6. Caso de estudio. Un Tetris inteligente . . . . . . . . . . . . . 832
28.2. Tcnicas Fundamentales . . . . . . . . . . . . . . . . . . . . . . . . 834
28.2.1. Lgica Difusa . . . . . . . . . . . . . . . . . . . . . . . . . . 835
28.2.2. Algoritmos genticos . . . . . . . . . . . . . . . . . . . . . . 846
28.2.3. Redes neuronales . . . . . . . . . . . . . . . . . . . . . . . . 855
[XV]

28.3. Algoritmos de bsqueda . . . . . . . . . . . . . . . . . . . . . . . . 864


28.3.1. Problemas y soluciones. . . . . . . . . . . . . . . . . . . . . 864
28.3.2. Organizar la bsqueda de soluciones . . . . . . . . . . . . . . 867
28.3.3. Bsqueda con informacin . . . . . . . . . . . . . . . . . . . 871
28.4. Planicacin de caminos . . . . . . . . . . . . . . . . . . . . . . . . 876
28.4.1. Puntos visibles . . . . . . . . . . . . . . . . . . . . . . . . . 876
28.4.2. Problema de bsqueda en grafos . . . . . . . . . . . . . . . . 877
28.4.3. Obtencin del grafo de puntos visibles . . . . . . . . . . . . . 879
28.4.4. Aumentando la resolucin . . . . . . . . . . . . . . . . . . . 880
28.5. Diseo basado en agentes . . . . . . . . . . . . . . . . . . . . . . . . 883
28.5.1. Qu es un agente? . . . . . . . . . . . . . . . . . . . . . . . 883
28.5.2. Cmo se construye un agente? . . . . . . . . . . . . . . . . 885
28.5.3. Los comportamientos como gua de diseo . . . . . . . . . . 888
28.5.4. Implementacin de agentes basados en autmatas . . . . . . . 897
28.5.5. Usando los objetivos como gua de diseo . . . . . . . . . . . 909
28.5.6. Reexiones sobre el futuro . . . . . . . . . . . . . . . . . . . 912
28.6. Caso de estudio. Juego deportivo . . . . . . . . . . . . . . . . . . . . 913
28.6.1. Introduccin a Pygame . . . . . . . . . . . . . . . . . . . . . 914
28.6.2. Arquitectura propuesta . . . . . . . . . . . . . . . . . . . . . 917
28.6.3. Integracin de comportamientos bsicos . . . . . . . . . . . . 924
28.6.4. Diseo de la Inteligencia Articial . . . . . . . . . . . . . . . 928
28.6.5. Consideraciones nales . . . . . . . . . . . . . . . . . . . . . 929
28.7. Sistemas expertos basados en reglas . . . . . . . . . . . . . . . . . . 929
28.7.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 929
28.7.2. Caso de estudio: Stratego . . . . . . . . . . . . . . . . . . . . 931

29. Networking 937


29.1. Conceptos bsicos de redes . . . . . . . . . . . . . . . . . . . . . . . 937
29.2. Consideraciones de diseo . . . . . . . . . . . . . . . . . . . . . . . 939
29.3. Eciencia y limitaciones de la red . . . . . . . . . . . . . . . . . . . 942
29.3.1. Peer to peer . . . . . . . . . . . . . . . . . . . . . . . . . . . 942
29.3.2. Cliente-servidor . . . . . . . . . . . . . . . . . . . . . . . . . 943
29.3.3. Pool de servidores . . . . . . . . . . . . . . . . . . . . . . . 943
29.4. Restricciones especicas de los juegos en red . . . . . . . . . . . . . 944
29.4.1. Capacidad de cmputo . . . . . . . . . . . . . . . . . . . . . 944
29.4.2. Ancho de banda . . . . . . . . . . . . . . . . . . . . . . . . . 944
29.4.3. Latencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 944
29.5. Distribucin de informacin . . . . . . . . . . . . . . . . . . . . . . 945
29.6. Modelo de informacin . . . . . . . . . . . . . . . . . . . . . . . . . 946
[XVI] NDICE GENERAL

29.7. Uso de recursos de red . . . . . . . . . . . . . . . . . . . . . . . . . 946


29.8. Consistencia e inmediatez . . . . . . . . . . . . . . . . . . . . . . . . 947
29.9. Predicciones y extrapolacin . . . . . . . . . . . . . . . . . . . . . . 948
29.9.1. Prediccin del jugador . . . . . . . . . . . . . . . . . . . . . 948
29.9.2. Prediccin del oponente . . . . . . . . . . . . . . . . . . . . 949
29.10.Sockets TCP/IP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 951
29.10.1.Creacin de Sockets . . . . . . . . . . . . . . . . . . . . . . 951
29.10.2.Comunicaciones conables . . . . . . . . . . . . . . . . . . . 954
29.10.3.Comunicaciones no conables . . . . . . . . . . . . . . . . . 957
29.11.Sockets TCP/IP avanzados . . . . . . . . . . . . . . . . . . . . . . . 959
29.11.1.Operaciones bloqueantes . . . . . . . . . . . . . . . . . . . . 961
29.11.2.Gestin de buffers . . . . . . . . . . . . . . . . . . . . . . . 961
29.11.3.Serializacin de datos . . . . . . . . . . . . . . . . . . . . . 962
29.11.4.Multicast y Broadcast . . . . . . . . . . . . . . . . . . . . . 963
29.11.5.Opciones de los sockets . . . . . . . . . . . . . . . . . . . . . 964
29.12.Middlewares de comunicaciones . . . . . . . . . . . . . . . . . . . . 965
29.12.1.ZeroC Ice . . . . . . . . . . . . . . . . . . . . . . . . . . . . 966
29.12.2.Especicacin de interfaces . . . . . . . . . . . . . . . . . . 966
29.12.3.Terminologa . . . . . . . . . . . . . . . . . . . . . . . . . . 966
29.12.4.Hola mundo distribuido . . . . . . . . . . . . . . . . . . . 968
29.12.5.twoway, oneway y datagram . . . . . . . . . . . . . . . . . . 973
29.12.6.Invocacin asncrona . . . . . . . . . . . . . . . . . . . . . . 974
29.12.7.Propagacin de eventos . . . . . . . . . . . . . . . . . . . . . 976

30. Sonido y Multimedia 981


30.1. Edicin de Audio . . . . . . . . . . . . . . . . . . . . . . . . . . . . 982
30.1.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 982
30.1.2. Herramientas para la creacin de audio . . . . . . . . . . . . 985
30.1.3. El proceso creativo . . . . . . . . . . . . . . . . . . . . . . . 987
30.1.4. Tcnicas de creacin . . . . . . . . . . . . . . . . . . . . . . 990
30.1.5. Programacin de audio . . . . . . . . . . . . . . . . . . . . . 992
30.2. Video digital en los videojuegos . . . . . . . . . . . . . . . . . . . . 994
30.3. Grcos 3D y video digital . . . . . . . . . . . . . . . . . . . . . . . 994
30.4. Estndares en video digital . . . . . . . . . . . . . . . . . . . . . . . 995
30.5. Plugins de vdeo para Ogre . . . . . . . . . . . . . . . . . . . . . . . 996
30.5.1. Instalacin de TheoraVideoPlugin . . . . . . . . . . . . . . . 997
30.5.2. Incluyendo vdeo en texturas . . . . . . . . . . . . . . . . . . 997
30.6. Reproduccin de vdeo con GStreamer . . . . . . . . . . . . . . . . . 998
30.6.1. Instalacin del framework de desarrollo y libreras necesarias 998
[XVII]

30.6.2. Introduccin a GStreamer . . . . . . . . . . . . . . . . . . . 999


30.6.3. Algunos conceptos bsicos . . . . . . . . . . . . . . . . . . . 1000
30.6.4. GStreamer en Ogre . . . . . . . . . . . . . . . . . . . . . . . 1001
30.7. Comentarios nales sobre vdeo digital . . . . . . . . . . . . . . . . . 1005

31. Interfaces de Usuario Avanzadas 1007


31.1. Introduccin a la Visin por Computador . . . . . . . . . . . . . . . 1007
31.2. Introduccin a OpenCV . . . . . . . . . . . . . . . . . . . . . . . . . 1008
31.2.1. Instalacin de OpenCV . . . . . . . . . . . . . . . . . . . . . 1008
31.3. Conceptos previos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1009
31.3.1. Nomenclatura . . . . . . . . . . . . . . . . . . . . . . . . . . 1009
31.3.2. Interfaces ligeras con HighGUI . . . . . . . . . . . . . . . . 1010
31.3.3. Estructura de la imagen . . . . . . . . . . . . . . . . . . . . . 1012
31.3.4. Almacenes de memoria y secuencias . . . . . . . . . . . . . . 1013
31.4. Primera toma de contacto con OpenCV: mostrando vdeo . . . . . . . 1014
31.5. Introduccin a los ltros . . . . . . . . . . . . . . . . . . . . . . . . 1015
31.6. Deteccin de objetos mediante reconocimiento . . . . . . . . . . . . 1018
31.6.1. Trabajando con clasicadores . . . . . . . . . . . . . . . . . 1018
31.6.2. Interaccin con el usuario . . . . . . . . . . . . . . . . . . . 1020
31.7. Interaccin mediante deteccin del color de los objetos . . . . . . . . 1023
31.8. Identicacin del movimiento . . . . . . . . . . . . . . . . . . . . . 1025
31.9. Comentarios nales sobre Visin por Computador . . . . . . . . . . . 1029
31.10.Caso de estudio: Wiimote . . . . . . . . . . . . . . . . . . . . . . . . 1029
31.11.Descripcin del mando de Wii . . . . . . . . . . . . . . . . . . . . . 1029
31.12.Libreras para la manipulacin del Wiimote . . . . . . . . . . . . . . 1031
31.13.Caso de estudio: Kinect . . . . . . . . . . . . . . . . . . . . . . . . . 1038
31.14.Comunidad OpenKinect . . . . . . . . . . . . . . . . . . . . . . . . 1039
31.15.OpenNI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1040
31.15.1.Instalacin . . . . . . . . . . . . . . . . . . . . . . . . . . . 1042
31.15.2.Conceptos iniciales . . . . . . . . . . . . . . . . . . . . . . . 1042
31.15.3.Manipulando Kinect con OpenNI . . . . . . . . . . . . . . . 1043
31.16.Realidad Aumentada . . . . . . . . . . . . . . . . . . . . . . . . . . 1047
31.16.1.Un poco de historia . . . . . . . . . . . . . . . . . . . . . . . 1048
31.16.2.Caractersticas Generales . . . . . . . . . . . . . . . . . . . . 1050
31.16.3.Alternativas tecnolgicas . . . . . . . . . . . . . . . . . . . . 1051
31.17.ARToolKit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1052
31.17.1.Instalacin y conguracin . . . . . . . . . . . . . . . . . . . 1052
31.18.El esperado Hola Mundo! . . . . . . . . . . . . . . . . . . . . . . 1053
31.18.1.Inicializacin . . . . . . . . . . . . . . . . . . . . . . . . . . 1056
[XVIII] NDICE GENERAL

31.18.2.Bucle Principal . . . . . . . . . . . . . . . . . . . . . . . . . 1056


31.18.3.Finalizacin y funcin Main . . . . . . . . . . . . . . . . . . 1057
31.19.Las Entraas de ARToolKit . . . . . . . . . . . . . . . . . . . . . . . 1057
31.19.1.Principios Bsicos . . . . . . . . . . . . . . . . . . . . . . . 1058
31.19.2.Calibracin de la Cmara . . . . . . . . . . . . . . . . . . . . 1060
31.19.3.Deteccin de Marcas . . . . . . . . . . . . . . . . . . . . . . 1062
31.20.Histrico de Percepciones . . . . . . . . . . . . . . . . . . . . . . . . 1064
31.21.Utilizacin de varios patrones . . . . . . . . . . . . . . . . . . . . . . 1067
31.22.Relacin entre Coordenadas . . . . . . . . . . . . . . . . . . . . . . 1070
31.23.Integracin con OpenCV y Ogre . . . . . . . . . . . . . . . . . . . . 1073
31.24.Consideraciones Finales . . . . . . . . . . . . . . . . . . . . . . . . 1078

Anexos 1079

A. Introduccin a OgreFramework 1081


A.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1081
A.2. Basic OgreFramework . . . . . . . . . . . . . . . . . . . . . . . . . 1082
A.2.1. Arquitectura . . . . . . . . . . . . . . . . . . . . . . . . . . 1082
A.3. Advanced OgreFramework . . . . . . . . . . . . . . . . . . . . . . . 1084
A.3.1. Sistema de Estados . . . . . . . . . . . . . . . . . . . . . . . 1084
A.3.2. Arquitectura . . . . . . . . . . . . . . . . . . . . . . . . . . 1085
A.4. SdkTrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1088
A.4.1. Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 1089
A.4.2. Requisitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1089
A.4.3. SdkTrayManager . . . . . . . . . . . . . . . . . . . . . . . . 1089
A.4.4. Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1089
A.4.5. SdkTrayListener . . . . . . . . . . . . . . . . . . . . . . . . 1089
A.4.6. Skins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1090
A.4.7. OgreBites . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1090
A.5. btOgre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1090
A.6. Referencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1092

B. Vault Defense al detalle 1093


B.1. Conguracin en Visual 2010 Express . . . . . . . . . . . . . . . . . 1093
B.1.1. Compilando CEGUI en Windows . . . . . . . . . . . . . . . 1094
B.1.2. Congurando OGRE en Windows . . . . . . . . . . . . . . . 1095
B.2. GUI en Vault Defense . . . . . . . . . . . . . . . . . . . . . . . . . . 1100
B.2.1. Botones propios . . . . . . . . . . . . . . . . . . . . . . . . 1100
B.2.2. Pantalla de Carga . . . . . . . . . . . . . . . . . . . . . . . . 1103
[XIX]

B.2.3. Manchas en la cmara . . . . . . . . . . . . . . . . . . . . . 1104


B.3. Luces en Vault Defense: efecto fuego . . . . . . . . . . . . . . . . . 1106
B.4. Enemigos en Vault Defense . . . . . . . . . . . . . . . . . . . . . . . 1107
Listado de acrnimos

ACE Adaptive Communications Environment


AMI Asynchronous Method Invocation
API Application Program Interface
APT Advanced Packaging Tool
ARM Advanced RISC Machine
AVI Audio Video Interleave
AVL Adelson-Velskii and Landis
B-R EP Boundary Representation
BBT Balanced Binary Tree
BDI Belief, Desire, Intention
BIK BINK Video
BMP BitMaP
BRDF Bidirectional Reactance Distribution Function
BSD Berkeley Software Distribution
BSP Binary Space Partitioning
BTT Binary Triangle Tree
CAD Computer Aided Design
CCD Charge-Coupled Device
CD Compact Disc
CEGUI Crazy Eddies GUI
CMOS Complementary Metal-Oxide-Semiconductor
CMYK Cyan Magenta Yellow Key
CORBA Common Object Request Broker Architecture
CPU Central Processing Unit
CRTP Curiously Recurring Template Pattern
CSG Constructive Solid Geometry
CUDA Compute Unied Device Architecture
CVS Concurrent Versions System
DBTS Debian Bug Tracking System
DCU Diseo Centrado en el Usuario

XXI
[XXII] ACRNIMOS

DD Debian Developer
DEB Deep Estimation Buffer
DEHS Debian External Health Status
DFSG Debian Free Software Guidelines
DIP Dependency Inversion Principle
DLL Dynamic Link Library
DOD Diamond Of Death
DOM Document Object Model
DTD Documento Tcnico de Diseo
DVB Digital Video Broadcasting
DVD Digital Video Disc
E/S Entrada/Salida
EASTL Electronic Arts Standard Template Library
EA Electronic Arts
ELF Executable and Linkable Format
FIFO First In, First Out
FLAC Free Lossless Audio Codec
FPS First Person Shooter
FSM Finite State Machine
GCC GNU Compiler Collection
GDB GNU Debugger
GDD Game Design Document
GIMP GNU Image Manipulation Program
GLUT OpenGL Utility Toolkit
GLU OpenGL Utility
GNOME GNU Object Model Environment
GNU GNU is Not Unix
GPL General Public License
GPS Global Positioning System
GPU Graphic Processing Unit
GPV grafos de puntos visibles
GTK GIMP ToolKit
GUI Graphical User Interface
GUP Game Unied Process
HDTV High Denition Television
HFSM Hierarchical Finite State Machine
HOM Hierarchical Occlusion Maps
HSV Hue, Saturation, Value
HTML HyperText Markup Language
I/O Input/Output
IANA Internet Assigned Numbers Authority
IA Inteligencia Articial
IBM International Business Machines
IDL Interface Denition Language
[XXIII]

IEC International Electrotechnical Commission


IEEE Institute of Electrical and Electronics Engineers
IGC In-Game Cinematics
IP Internet Protocol
IPC InterProcess Communication
IPO InterPOlation curve
IPP Integraed Performance Primitives
IPX Internetwork Packet Exchange
ITP Intent to Package
ISO International Organization for Standardization
ISP Interface Segregation Principle
I CE Internet Communication Engine
JPG Joint Photographic Experts Group
KISS Keep it simple, Stupid!
LAN Local Area Network
LED Light Emitter Diode
LGPL Lesser General Public License
LIFO Last In, First Out
LOD Level-Of-Detail
LSP Liskov Substitution Principle
MAC Media Access Control
MAS Multi-Agent Systems
MHI Motion History Image
MMOG Massively Multiplayer Online Game
MMORPG Massively Multiplayer Online Role-Playing Game
MP3 MPEG-2 Audio Layer III
MPEG Moving Picture Experts Group
MTU Maximum Transfer Unit
MVC Model View Controller
NPC Non-Player Character
NRVO Named Return Value Optimization
OCP Open/Closed Principle
ODE Open Dynamics Engine
ODT OpenDocument Text
OGI Oregon Graduate Institute
OGRE Object-Oriented Graphics Rendering Engine
OIS Object Oriented Input System
ONC Open Network Computing
P2P Peer To Peer
PDA Personal Digital Assistant
PDF Portable Document Format
PHP Personal Home Page
PMU Performance Monitoring Units
PNG Portable Network Graphics
[XXIV] ACRNIMOS

POO Programacin Orientada a Objetos


POSIX Portable Operating System Interface X
PPC PowerPC
PUD Proceso Unicado de Desarrollo
PVS Potential Visibility Set
RAII Resource Acquisition Is Initialization
RFA Request For Adoption
RFH Request For Help
RFP Request For Package
RGBA Red Green Blue Alpha
RGB Red Green Blue
RMI Remote Method Invocation
ROAM Real-time Optimally Adapting Meshes
ROI Region Of Interest
RPC Remote Procedure Call
RPG Role-Playing Games
RPM RPM Package Manager
RST reStructuredText
RTP Real Time Protocol
RTS Real-Time Strategy
RTTI Run Time Type Information
RTT Render To Texture
RVO Return Value Optimization
SAX Simple API for XML
SDK Software Development Kit
SDL Simple Directmedia Layer
SGBD Sistema de Gestin de Base de Datos
SGML Standard Generalized Markup Language
SGI Silicon Graphics Incorporated
SIP Session Initiation Protocol
SLERP spherical linear interpolation
SOLID SRP, OCP, LSP, ISP, DIP
SRP Single responsibility principle
SRU Sistema de Referencia Universal
STL Standard Template Library
SUT Subject Under Test
SVCD Super VCD
SVG Scalable Vector Graphics
S LICE Specication Language for Ice
TCP/IP Pila de protocolos de Internet
TCP Transport Control Protocol
TDD Test Driven Development
TDT Televisin Digital Terrestre
TGA Truevision Graphics Adapter
[XXV]

TIFF Tagged Image File Format


TIF TIFF
TLB Translation Lookaside Buffer
TTL Time To Live
UCLM Universidad de Castilla-La Mancha
UDP User Datagram Protocol
UML Unied Modeling Language
URL Uniform Resource Locator
USB Universal Serial Bus
UTF Unicode Transformation Format
UUID Universally Unique Identier
VCD Video CD
VCS Version Control System
VGA Video Graphics Adapter
VRML Virtual Reality Modeling Language
WAV WAVeform
WNPP Work-Needing and Prospective Packages
XDR eXternal Data Representation
XML eXtensible Markup Language
YAGNI You Aint Gonna Need It
YAML YAML Aint Markup Language
Arquitectura del Motor
El objetivo de este mdulo, titulado Arquitectura del Motor dentro
del Curso de Experto en Desarrollo de Videojuegos, es introducir los
conceptos bsicos relativos a las estructuras y principios de diseo y
desarrollo comnmente empleados en la creacin de videojuegos.
Para ello, uno de los principales objetivos es proporcionar una visin
general de la arquitectura general de un motor de juegos. Dentro del
contexto de esta arquitectura general se hace especial hincapi en
aspectos como los subsistemas de bajo nivel, el bucle de juego, la
gestin bsica de recursos, como el sonido, y la gestin de la
concurrencia. Para llevar a cabo una discusin prctica de todos estos
elementos se hace uso del motor de renderizado Ogre3D. Por otra
parte, en este primer mdulo tambin se estudian los fundamentos
del lenguaje de programacin C++ como herramienta esencial para el
desarrollo de videojuegos profesionales. Este estudio se
complementa con una discusin en profundidad de una gran variedad
de patrones de diseo y de la biblioteca STL. Adems, tambin se
realiza un recorrido por herramientas que son esenciales en el
desarrollo de proyectos software complejos, como por ejemplo los
sistemas de control de versiones, o procesos como la compilacin o
la depuracin.
Captulo

Introduccin
1
David Vallejo Fernndez

A
ctualmente, la industria del videojuego goza de una muy buena salud a nivel
mundial, rivalizando en presupuesto con las industrias cinematogrca y mu-
sical. En este captulo se discute, desde una perspectiva general, el desarrollo
de videojuegos, haciendo especial hincapi en su evolucin y en los distintos elemen-
tos involucrados en este complejo proceso de desarrollo. En la segunda parte del cap-
tulo se introduce el concepto de arquitectura del motor, como eje fundamental para
el diseo y desarrollo de videojuegos comerciales.

1.1. El desarrollo de videojuegos

1.1.1. La industria del videojuego. Presente y futuro

El primer videojuego Lejos han quedado los das desde el desarrollo de los primeros videojuegos, ca-
racterizados principalmente por su simplicidad y por el hecho de estar desarrollados
El videojuego Pong se considera completamente sobre hardware. Debido a los distintos avances en el campo de la in-
como unos de los primeros video- formtica, no slo a nivel de desarrollo software y capacidad hardware sino tambin
juegos de la historia. Desarrollado
por Atari en 1975, el juego iba in- en la aplicacin de mtodos, tcnicas y algoritmos, la industria del videojuego ha evo-
cluido en la consola Atari Pong. lucionado hasta llegar a cotas inimaginables, tanto a nivel de jugabilidad como de
Se calcula que se vendieron unas calidad grca, tan slo hace unos aos.
50.000 unidades.

3
[4] CAPTULO 1. INTRODUCCIN

La evolucin de la industria de los videojuegos ha estado ligada a una serie de


hitos, determinados particularmente por juegos que han marcado un antes y un des-
pus, o por fenmenos sociales que han afectado de manera directa a dicha industria.
Juegos como Doom, Quake, Final Fantasy, Zelda, Tekken, Gran Turismo, Metal Gear,
The Sims o World of Warcraft, entre otros, han marcado tendencia y han contribuido
de manera signicativa al desarrollo de videojuegos en distintos gneros.
Por otra parte, y de manera complementaria a la aparicin de estas obras de ar-
te, la propia evolucin de la informtica ha posibilitado la vertiginosa evolucin del
desarrollo de videojuegos. Algunos hitos clave son por ejemplo el uso de la tecno-
loga poligonal en 3D [6] en las consolas de sobremesa, el boom de los ordenadores
personales como plataforma multi-propsito, la expansin de Internet, los avances en
el desarrollo de microprocesadores, el uso de shaders programables [76], el desarrollo
de motores de juegos o, ms recientemente, la eclosin de las redes sociales y el uso
masivo de dispositivos mviles.
Por todo ello, los videojuegos se pueden encontrar en ordenadores personales,
consolas de juego de sobremesa, consolas porttiles, dispositivos mviles como por
ejemplo los smartphones, o incluso en las redes sociales como medio de soporte para
el entretenimiento de cualquier tipo de usuario. Esta diversidad tambin est espe-
cialmente ligada a distintos tipos o gneros de videojuegos, como se introducir ms
adelante en esta misma seccin.
La expansin del videojuego es tan relevante que actualmente se trata de una
industria multimillonaria capaz de rivalizar con las industrias cinematogrca y musi-
cal. Un ejemplo representativo es el valor total del mercado del videojuego en Europa,
tanto a nivel hardware como software, el cual alcanz la nada desdeable cifra de ca-
si 11.000 millones de euros, con pases como Reino Unido, Francia o Alemania a la
cabeza. En este contexto, Espaa representa el cuarto consumidor a nivel europeo y
tambin ocupa una posicin destacada dentro del ranking mundial.
A pesar de la vertiginosa evolucin de la industria del videojuego, hoy en da existe
un gran nmero de retos que el desarrollador de videojuegos ha de afrontar a la hora
de producir un videojuego. En realidad, existen retos que perdurarn eternamente y
que no estn ligados a la propia evolucin del hardware que permite la ejecucin de
los videojuegos. El ms evidente de ellos es la necesidad imperiosa de ofrecer una
experiencia de entretenimiento al usuario basada en la diversin, ya sea a travs de
nuevas formas de interaccin, como por ejemplo la realidad aumentada o la tecnologa
de visualizacin 3D, a travs de una mejora evidente en la calidad de los ttulos, o
mediante innovacin en aspectos vinculados a la jugabilidad.
No obstante, actualmente la evolucin de los videojuegos est estrechamente li-
gada a la evolucin del hardware que permite la ejecucin de los mismos. Esta evo- Figura 1.1: El desarrollo y la inno-
lucin atiende, principalmente, a dos factores: i) la potencia de dicho hardware y ii) vacin en hardware tambin supone
las capacidades interactivas del mismo. En el primer caso, una mayor potencia hard- un pilar fundamental en la industria
ware implica que el desarrollador disfrute de mayores posibilidades a la hora de, por del videojuego.
ejemplo, mejorar la calidad grca de un ttulo o de incrementar la IA (Inteligencia
Articial) de los enemigos. Este factor est vinculado al multiprocesamiento. En el
segundo caso, una mayor riqueza en trminos de interactividad puede contribuir a que
el usuario de videojuegos viva una experiencia ms inmersiva (por ejemplo, mediante
realidad aumentada) o, simplemente, ms natural (por ejemplo, mediante la pantalla
tctil de un smartphone).
C1
1.1. El desarrollo de videojuegos [5]

Finalmente, resulta especialmente importante destacar la existencia de motores de


juego (game engines), como por ejemplo Quake1 o Unreal2 , middlewares para el tra-
tamiento de aspectos especcos de un juego, como por ejemplo la biblioteca Havok3
para el tratamiento de la fsica, o motores de renderizado, como por ejemplo Ogre 3D
[52]. Este tipo de herramientas, junto con tcnicas especcas de desarrollo y optimi-
zacin, metodologas de desarrollo, o patrones de diseo, entre otros, conforman un
aspecto esencial a la hora de desarrollar un videojuego. Al igual que ocurre en otros
aspectos relacionados con la Ingeniera del Software, desde un punto de vista general
resulta aconsejable el uso de todos estos elementos para agilizar el proceso de desa-
rrollo y reducir errores potenciales. En otras palabras, no es necesario, ni productivo,
reinventar la rueda cada vez que se afronta un nuevo proyecto.

1.1.2. Estructura tpica de un equipo de desarrollo

Tiempo real El desarrollo de videojuegos comerciales es un proceso complejo debido a los dis-
tintos requisitos que ha de satisfacer y a la integracin de distintas disciplinas que
En el mbito del desarrollo de vi- intervienen en dicho proceso. Desde un punto de vista general, un videojuego es una
deojuegos, el concepto de tiempo aplicacin grca en tiempo real en la que existe una interaccin explcita mediante
real es muy importante para dotar
de realismo a los juegos, pero no el usuario y el propio videojuego. En este contexto, el concepto de tiempo real se ree-
es tan estricto como el concepto de re a la necesidad de generar una determinada tasa de frames o imgenes por segundo,
tiempo real manejado en los siste- tpicamente 30 60, para que el usuario tenga una sensacin continua de realidad.
mas crticos. Por otra parte, la interaccin se reere a la forma de comunicacin existente entre el
usuario y el videojuego. Normalmente, esta interaccin se realiza mediante joysticks o
mandos, pero tambin es posible llevarla a cabo con otros dispositivos como por ejem-
plo teclados, ratones, cascos o incluso mediante el propio cuerpo a travs de tcnicas
de visin por computador o de interaccin tctil.
A continuacin se describe la estructura tpica de un equipo de desarrollo aten-
diendo a los distintos roles que juegan los componentes de dicho equipo [42]. En
muchos casos, y en funcin del nmero de componentes del equipo, hay personas
especializadas en diversas disciplinas de manera simultnea.
Los ingenieros son los responsables de disear e implementar el software que
permite la ejecucin del juego, as como las herramientas que dan soporte a dicha
ejecucin. Normalmente, los ingenieros se suelen clasicar en dos grandes grupos:

Los programadores del ncleo del juego, es decir, las personas responsables
de desarrollar tanto el motor de juego como el juego propiamente dicho.
Los programadores de herramientas, es decir, las personas responsables de
desarrollar las herramientas que permiten que el resto del equipo de desarrollo
pueda trabajar de manera eciente.

De manera independiente a los dos grupos mencionados, los ingenieros se pueden


especializar en una o en varias disciplinas. Por ejemplo, resulta bastante comn encon-
trar perles de ingenieros especializados en programacin grca o en scripting e IA.
Sin embargo, tal y como se sugiri anteriormente, el concepto de ingeniero transver-
sal es bastante comn, particularmente en equipos de desarrollo que tienen un nmero
reducido de componentes o con un presupuesto que no les permite la contratacin de
personas especializadas en una nica disciplina.
1 http://www.idsoftware.com/games/quake/quake/
2 http://www.unrealengine.com/
3 http://www.havok.com
[6] CAPTULO 1. INTRODUCCIN

Productor ejecutivo

Equipo creativo e innovacin Productor Equipo de marketing

Director artstico Director tcnico Diseador jefe Gestor de pruebas

Artista jefe Programador jefe Equipo de diseo Equipo de pruebas

Programador Networking
Equipo artstico
Herramientas Fsica
Conceptual Modelado Artista
tcnico Inteligencia Art. Motor
Animacin Texturas
Interfaces Audio

Figura 1.2: Visin conceptual de un equipo de desarrollo de videojuegos, considerando especialmente la


parte de programacin.

En el mundo del desarrollo de videojuegos, es bastante probable encontrar inge-


nieros senior responsables de supervisar el desarrollo desde un punto de vista tcnico,
de manera independiente al diseo y generacin de cdigo. No obstante, este tipo de
roles suelen estar asociados a la supervisin tcnica, la gestin del proyecto e incluso
a la toma de decisiones vinculadas a la direccin del proyecto. As mismo, algunas
compaas tambin pueden tener directores tcnicos, responsables de la supervisin
de uno o varios proyectos, e incluso un director ejecutivo, encargado de ser el direc-
tor tcnico del estudio completo y de mantener, normalmente, un rol ejecutivo en la
compaa o empresa.
Los artistas son los responsables de la creacin de todo el contenido audio-visual General VS Especco
del videojuego, como por ejemplo los escenarios, los personajes, las animaciones de
dichos personajes, etc. Al igual que ocurre en el caso de los ingenieros, los artistas En funcin del tamao de una em-
presa de desarrollo de videojuegos,
tambin se pueden especializar en diversas cuestiones, destacando las siguientes: el nivel de especializacin de sus
empleados es mayor o menor. Sin
Artistas de concepto, responsables de crear bocetos que permitan al resto del embargo, las ofertas de trabajo sue-
len incluir diversas disciplinas de
equipo hacerse una idea inicial del aspecto nal del videojuego. Su trabajo re- trabajo para facilitar su integracin.
sulta especialmente importante en las primeras fases de un proyecto.
Modeladores, responsables de generar el contenido 3D del videojuego, como
por ejemplo los escenarios o los propios personajes que forman parte del mismo.
Artistas de texturizado, responsables de crear las texturas o imgenes bidimen-
sionales que formarn parte del contenido visual del juego. Las texturas se apli-
can sobre la geometra de los modelos con el objetivo de dotarlos de mayor
realismo.
C1
1.1. El desarrollo de videojuegos [7]

Artistas de iluminacin, responsables de gestionar las fuentes de luz del video-


juego, as como sus principales propiedades, tanto estticas como dinmicas.
Animadores, responsables de dotar de movimientos a los personajes y objetos
dinmicos del videojuego. Un ejemplo tpico de animacin podra ser el movi-
miento de brazos de un determinado carcter.
Actores de captura de movimiento, responsables de obtener datos de movimien-
to reales para que los animadores puedan integrarlos a la hora de animar los
personajes.
Diseadores de sonido, responsables de integrar los efectos de sonido del vi-
deojuego.
Otros actores, responsables de diversas tareas como por ejemplo los encargados
de dotar de voz a los personajes.

Al igual que suele ocurrir con los ingenieros, existe el rol de artista senior cuyas
responsabilidades tambin incluyen la supervisin de los numerosos aspectos vincu-
lados al componente artstico.
Los diseadores de juego son los responsables de disear el contenido del juego,
Scripting e IA destacando la evolucin del mismo desde el principio hasta el nal, la secuencia de
El uso de lenguajes de alto nivel captulos, las reglas del juego, los objetivos principales y secundarios, etc. Evidente-
es bastante comn en el desarrollo mente, todos los aspectos de diseo estn estrechamente ligados al propio gnero del
de videojuegos y permite diferen- mismo. Por ejemplo, en un juego de conduccin es tarea de los diseadores denir el
ciar claramente la lgica de la apli- comportamiento de los coches adversarios ante, por ejemplo, el adelantamiento de un
cacin y la propia implementacin.
Una parte signicativa de las desa- rival.
rrolladoras utiliza su propio lengua- Los diseadores suelen trabajar directamente con los ingenieros para afrontar di-
je de scripting, aunque existen len-
guajes ampliamente utilizados, co- versos retos, como por ejemplo el comportamiento de los enemigos en una aventura.
mo son Lua o Python. De hecho, es bastante comn que los propios diseadores programen, junto con los in-
genieros, dichos aspectos haciendo uso de lenguajes de scripting de alto nivel, como
por ejemplo Lua4 o Python5 .
Como ocurre con las otras disciplinas previamente comentadas, en algunos estu-
dios los diseadores de juego tambin juegan roles de gestin y supervisin tcnica.
Finalmente, en el desarrollo de videojuegos tambin estn presentes roles vincu-
lados a la produccin, especialmente en estudios de mayor capacidad, asociados a la
planicacin del proyecto y a la gestin de recursos humanos. En algunas ocasiones,
los productores tambin asumen roles relacionados con el diseo del juego. As mis-
mo, los responsables de marketing, de administracin y de soporte juegan un papel
relevante. Tambin resulta importante resaltar la gura de publicador como entidad
responsable del marketing y distribucin del videojuego desarrollado por un determi-
nado estudio. Mientras algunos estudios tienen contratos permanentes con un deter-
minado publicador, otros preeren mantener una relacin temporal y asociarse con el
publicador que le ofrezca mejores condiciones para gestionar el lanzamiento de un
ttulo.

1.1.3. El concepto de juego


Dentro del mundo del entretenimiento electrnico, un juego normalmente se sue-
le asociar a la evolucin, entendida desde un punto de vista general, de uno o varios
personajes principales o entidades que pretenden alcanzar una serie de objetivos en
un mundo acotado, los cuales estn controlados por el propio usuario. As, entre estos
4 http://www.lua.org
5 http://www.python.org
[8] CAPTULO 1. INTRODUCCIN

elementos podemos encontrar desde superhroes hasta coches de competicin pasan-


do por equipos completos de ftbol. El mundo en el que conviven dichos personajes
suele estar compuesto, normalmente, por una serie de escenarios virtuales recreados
en tres dimensiones y tiene asociado una serie de reglas que determinan la interaccin
con el mismo.
De este modo, existe una interaccin explcita entre el jugador o usuario de vi-
deojuegos y el propio videojuego, el cual plantea una serie de retos al usuario con el
objetivo nal de garantizar la diversin y el entretenimiento. Adems de ofrecer este
componente emocional, los videojuegos tambin suelen tener un componente cogni-
tivo asociado, obligando a los jugadores a aprender tcnicas y a dominar el comporta-
miento del personaje que manejan para resolver los retos o puzzles que los videojuegos
plantean.
Desde una perspectiva ms formal, la mayora de videojuegos suponen un ejem-
plo representativo de lo que se dene como aplicaciones grcas o renderizado en
tiempo real [6], las cuales se denen a su vez como la rama ms interactiva de la In-
formtica Grca. Desde un punto de vista abstracto, una aplicacin grca en tiempo
real se basa en un bucle donde en cada iteracin se realizan los siguientes pasos:
Cada de frames
El usuario visualiza una imagen renderizada por la aplicacin en la pantalla o
dispositivo de visualizacin. Si el ncleo de ejecucin de un jue-
go no es capaz de mantener los fps
El usuario acta en funcin de lo que haya visualizado, interactuando directa- a un nivel constante, el juego sufrir
una cada de frames en un momen-
mente con la aplicacin, por ejemplo mediante un teclado. to determinado. Este hecho se deno-
mina comnmente como ralentiza-
En funcin de la accin realizada por el usuario, la aplicacin grca genera cin.
una salida u otra, es decir, existe una retroalimentacin que afecta a la propia
aplicacin.

En el caso de los videojuegos, este ciclo de visualizacin, actuacin y renderizado


ha de ejecutarse con una frecuencia lo sucientemente elevada como para que el usua-
rio se sienta inmerso en el videojuego, y no lo perciba simplemente como una sucesin
de imgenes estticas. En este contexto, el frame rate se dene como el nmero de
imgenes por segundo, comnmente fps, que la aplicacin grca es capaz de generar.
A mayor frame rate, mayor sensacin de realismo en el videojuego. Actualmente, una
tasa de 30 fps se considera ms que aceptable para la mayora de juegos. No obstante,
algunos juegos ofrecen tasas que doblan dicha medida.

Generalmente, el desarrollador de videojuegos ha de buscar un compromiso


entre los fps y el grado de realismo del videojuego. Por ejemplo, el uso de
modelos con una alta complejidad computacional, es decir, con un mayor
nmero de polgonos, o la integracin de comportamientos inteligentes por
parte de los enemigos en un juego, o NPC (Non-Player Character), disminuir
los fps.

En otras palabras, los juegos son aplicaciones interactivas que estn marcadas por
el tiempo, es decir, cada uno de los ciclos de ejecucin tiene un deadline que ha de
cumplirse para no perder realismo.
Aunque el componente grco representa gran parte de la complejidad compu-
tacional de los videojuegos, no es el nico. En cada ciclo de ejecucin, el videojuego
ha de tener en cuenta la evolucin del mundo en el que se desarrolla el mismo. Dicha
evolucin depender del estado de dicho mundo en un momento determinado y de c-
mo las distintas entidades dinmicas interactan con l. Obviamente, recrear el mundo
C1
1.1. El desarrollo de videojuegos [9]

real con un nivel de exactitud elevado no resulta manejable ni prctico, por lo que nor-
malmente dicho mundo se aproxima y se simplica, utilizando modelos matemticos
para tratar con su complejidad. En este contexto, destaca por ejemplo la simulacin
fsica de los propios elementos que forman parte del mundo.
Por otra parte, un juego tambin est ligado al comportamiento del personaje prin-
cipal y del resto de entidades que existen dentro del mundo virtual. En el mbito
acadmico, estas entidades se suelen denir como agentes (agents) y se encuadran
dentro de la denominada simulacin basada en agentes [64]. Bsicamente, este tipo
de aproximaciones tiene como objetivo dotar a los NPC con cierta inteligencia pa-
ra incrementar el grado de realismo de un juego estableciendo, incluso, mecanismos
de cooperacin y coordinacin entre los mismos. Respecto al personaje principal, un
videojuego ha de contemplar las distintas acciones realizadas por el mismo, consi-
derando la posibilidad de decisiones impredecibles a priori y las consecuencias que
podran desencadenar.
Figura 1.3: El motor de juego re- En resumen, y desde un punto de vista general, el desarrollo de un juego implica
presenta el ncleo de un videojuego considerar un gran nmero de factores que, inevitablemente, incrementan la comple-
y determina el comportamiento de jidad del mismo y, al mismo tiempo, garantizar una tasa de fps adecuada para que la
los distintos mdulos que lo com- inmersin del usuario no se vea afectada.
ponen.

1.1.4. Motor de juego


Al igual que ocurre en otras disciplinas en el campo de la informtica, el desarrollo
de videojuegos se ha beneciado de la aparicin de herramientas que facilitan dicho
desarrollo, automatizando determinadas tareas y ocultando la complejidad inherente a
muchos procesos de bajo nivel. Si, por ejemplo, los SGBD han facilitado enormemente
la gestin de persistencia de innumerables aplicaciones informticas, los motores de
juegos hacen la vida ms sencilla a los desarrolladores de videojuegos.
Segn [42], el trmino motor de juego surgi a mediados de los aos 90 con la
aparicin del famossimo juego de accin en primera persona Doom, desarrollado por
la compaa id Software bajo la direccin de John Carmack6 . Esta armacin se sus-
tenta sobre el hecho de que Doom fue diseado con una arquitectura orientada a la
reutilizacin mediante una separacin adecuada en distintos mdulos de los compo-
nentes fundamentales, como por ejemplo el sistema de renderizado grco, el sistema
de deteccin de colisiones o el sistema de audio, y los elementos ms artsticos, como
por ejemplo los escenarios virtuales o las reglas que gobernaban al propio juego.
Este planteamiento facilitaba enormemente la reutilizacin de software y el con-
cepto de motor de juego se hizo ms popular a medida que otros desarrolladores co-
menzaron a utilizar diversos mdulos o juegos previamente licenciados para generar
los suyos propios. En otras palabras, era posible disear un juego del mismo tipo sin
apenas modicar el ncleo o motor del juego, sino que el esfuerzo se poda dirigir
directamente a la parte artstica y a las reglas del mismo.
Este enfoque ha ido evolucionando y se ha expandido, desde la generacin de
mods por desarrolladores independientes o amateurs hasta la creacin de una gran
variedad de herramientas, bibliotecas e incluso lenguajes que facilitan el desarrollo de
videojuegos. A da de hoy, una gran parte de compaas de desarrollo de videojue-
go utilizan motores o herramientas pertenecientes a terceras partes, debido a que les
Figura 1.4: John Carmack, uno de
los desarrolladores de juegos ms
resulta ms rentable econmicamente y obtienen, generalmente, resultados espectacu-
importantes, en el Game Developer lares. Por otra parte, esta evolucin tambin ha permitido que los desarrolladores de
Conference del ao 2010. un juego se planteen licenciar parte de su propio motor de juego, decisin que tambin
forma parte de su poltica de trabajo.
6 http://en.wikipedia.org/wiki/John_D._Carmack
[10] CAPTULO 1. INTRODUCCIN

Obviamente, la separacin entre motor de juego y juego nunca es total y, por una
circunstancia u otra, siempre existen dependencias directas que no permiten la reusa-
bilidad completa del motor para crear otro juego. La dependencia ms evidente es el
genero al que est vinculado el motor de juego. Por ejemplo, un motor de juegos dise-
ado para construir juegos de accin en primera persona, conocidos tradicionalmente
como shooters o shootem all, ser difcilmente reutilizable para desarrollar un juego
de conduccin.
Una forma posible para diferenciar un motor de juego y el software que representa
a un juego est asociada al concepto de arquitectura dirigida por datos (data-driven
architecture). Bsicamente, cuando un juego contiene parte de su lgica o funciona-
miento en el propio cdigo (hard-coded logic), entonces no resulta prctico reutilizar-
la para otro juego, ya que implicara modicar el cdigo fuente sustancialmente. Sin
embargo, si dicha lgica o comportamiento no est denido a nivel de cdigo, sino
por ejemplo mediante una serie de reglas denidas a travs de un lenguaje de script,
entonces la reutilizacin s es posible y, por lo tanto, beneciosa, ya que optimiza el
tiempo de desarrollo.
Como conclusin nal, resulta relevante destacar la evolucin relativa a la genera- Game engine tuning
lidad de los motores de juego, ya que poco a poco estn haciendo posible su utilizacin
para diversos tipos de juegos. Sin embargo, el compromiso entre generalidad y optima- Los motores de juegos se suelen
adaptar para cubrir las necesidades
lidad an est presente. En otras palabras, a la hora de desarrollar un juego utilizando especcas de un ttulo y para obte-
un determinado motor es bastante comn personalizar dicho motor para adaptarlo a ner un mejor rendimiento.
las necesidades concretas del juego a desarrollar.

1.1.5. Gneros de juegos


Los motores de juegos suelen estar, generalmente, ligados a un tipo o gnero par-
ticular de juegos. Por ejemplo, un motor de juegos diseado con la idea de desarrollar
juegos de conduccin diferir en gran parte con respecto a un motor orientado a juegos
de accin en tercera persona. No obstante, y tal y como se discutir en la seccin 1.2,
existen ciertos mdulos, sobre todo relativos al procesamiento de ms bajo nivel, que
son transversales a cualquier tipo de juego, es decir, que se pueden reutilizar en gran
medida de manera independiente al gnero al que pertenezca el motor. Un ejemplo
representativo podra ser el mdulo de tratamiento de eventos de usuario, es decir, el
mdulo responsable de recoger y gestionar la interaccin del usuario a travs de dis-
positivos como el teclado, el ratn, el joystick o la pantalla tctil. Otros ejemplo podra
ser el mdulo de tratamiento del audio o el mdulo de renderizado de texto.
A continuacin, se realizar una descripcin de los distintos gneros de juegos
ms populares atendiendo a las caractersticas que diferencian unos de otros en base
al motor que les da soporte. Esta descripcin resulta til para que el desarrollador iden-
tique los aspectos crticos de cada juego y utilice las tcnicas de desarrollo adecuadas
para obtener un buen resultado.
Probablemente, el gnero de juegos ms popular ha sido y es el de los los denomi- Mercado de shooters
nados FPS, abreviado tradicionalmente como shooters, representado por juegos como
Quake, Half-Life, Call of Duty o Gears of War, entre muchos otros. En este gnero, Los FPS (First Person Shooter) go-
zan actualmente de un buen mo-
el usuario normalmente controla a un personaje con una vista en primera persona a lo mento y, como consecuencia de
largo de escenarios que tradicionalmente han sido interiores, como los tpicos pasillos, ello, el nmero de ttulos disponi-
pero que han ido evolucionando a escenarios exteriores de gran complejidad. bles es muy elevado, ofreciando una
gran variedad al usuario nal.
Los FPS representan juegos con un desarrollo complejo, ya que uno de los retos
principales que han de afrontar es la inmersin del usuario en un mundo hiperrealista
que ofrezca un alto nivel de detalle, al mismo tiempo que se garantice una alta reaccin
de respuesta a las acciones del usuario. Este gnero de juegos se centra en la aplicacin
de las siguientes tecnologas [42]:
C1
1.1. El desarrollo de videojuegos [11]

Figura 1.5: Captura de pantalla del juego Tremulous R , licenciado bajo GPL y desarrollado sobre el motor
de Quake III.

Renderizado eciente de grandes escenarios virtuales 3D.


Mecanismo de respuesta eciente para controlar y apuntar con el personaje.
Detalle de animacin elevado en relacin a las armas y los brazos del personaje
virtual.
Uso de una gran variedad de arsenal.
Sensacin de que el personaje ota sobre el escenario, debido al movimiento
del mismo y al modelo de colisiones.
NPC con un nivel de IA considerable y dotados de buenas animaciones.
Inclusin de opciones multijugador a baja escala, tpicamente entre 32 y 64
jugadores.

Normalmente, la tecnologa de renderizado de los FPS est especialmente optimi-


zada atendiendo, entre otros factores, al tipo de escenario en el que se desarrolla el
juego. Por ejemplo, es muy comn utilizar estructuras de datos auxiliares para dis-
poner de ms informacin del entorno y, consecuentemente, optimizar el clculo de
diversas tareas. Un ejemplo muy representativo en los escenarios interiores son los
rboles BSP (Binary Space Partitioning) (rboles de particin binaria del espacio) [6],
que se utilizan para realizar una divisin del espacio fsico en dos partes, de manera
recursiva, para optimizar, por ejemplo, aspectos como el clculo de la posicin de un
jugador. Otro ejemplo representativo en el caso de los escenarios exteriores es el deno-
minado occlusion culling [6], que se utiliza para optimizar el proceso de renderizado
descartando aquellos objetos 3D que no se ven desde el punto de vista de la cmara,
reduciendo as la carga computacional de dicho proceso.
[12] CAPTULO 1. INTRODUCCIN

En el mbito comercial, la familia de motores Quake, creados por Id Software,


se ha utilizado para desarrollar un gran nmero de juegos, como la saga Medal of
Honor, e incluso motores de juegos. Hoy es posible descargar el cdigo fuente de
Quake, Quake II y Quake III 7 y estudiar su arquitectura para hacerse una idea bastante
aproximada de cmo se construyen los motores de juegos actuales.
Otra familia de motores ampliamente conocida es la de Unreal, juego desarrolla-
do en 1998 por Epic Games. Actualmente, la tecnologa Unreal Engine se utiliza en
multitud de juegos, algunos de ellos tan famosos como Gears of War.
Ms recientemente, la compaa Crytek ha permitido la descarga del CryENGINE
3 SDK (Software Development Kit)8 para propsitos no comerciales, sino principal-
mente acadmicos y con el objetivo de crear una comunidad de desarrollo. Este kit
de desarrollo para aplicaciones grcas en tiempo real es exactamente el mismo que
el utilizado por la propia compaa para desarrollar juegos comerciales, como por
ejemplo Crysis 2.
Otro de los gneros ms relevantes son los denominados juegos en tercera per-
sona, donde el usuario tiene el control de un personaje cuyas acciones se pueden
apreciar por completo desde el punto de vista de la cmara virtual. Aunque existe
un gran parecido entre este gnero y el de los FPS, los juegos en tercera persona ha-
cen especial hincapi en la animacin del personaje, destacando sus movimientos y
habilidades, adems de prestar mucha atencin al detalle grco de la totalidad de su
cuerpo. Ejemplos representativos de este gnero son Resident Evil, Metal Gear, Gears
of War o Uncharted, entre otros.

Super Mario Bros


Figura 1.6: Captura de pantalla del juego Turtlearena R , licenciado bajo GPL y desarrollado sobre el motor
El popular juego de Mario, disea-
de Quake III. do en 1985 por Shigeru Miyamo-
to, ha vendido aproximadamente 40
millones de juegos a nivel mundial.
7 http://www.idsoftware.com/business/techdownloads
Segn el libro de los Record Guin-
ness, es una de los juegos ms ven-
8 http://mycryengine.com/
didos junto a Tetris y a la saga de
Pokemon.
C1
1.1. El desarrollo de videojuegos [13]

Dentro de este gnero resulta importante destacar los juegos de plataformas, en


los que el personaje principal ha de ir avanzado de un lugar a otro del escenario hasta
alcanzar un objetivo. Ejemplos representativos son las sagas de Super Mario, Sonic o
Donkey Kong. En el caso particular de los juegos de plataformas, el avatar del perso-
naje tiene normalmente un efecto de dibujo animado, es decir, no suele necesitar un
renderizado altamente realista y, por lo tanto, complejo. En cualquier caso, la parte
dedicada a la animacin del personaje ha de estar especialmente cuidada para incre-
mentar la sensacin de realismo a la hora de controlarlo.
En los juegos en tercera persona, los desarrolladores han de prestar especial aten-
cin a la aplicacin de las siguientes tecnologas [42]:
Grcos 3D
Uso de plataformas mviles, equipos de escalado, cuerdas y otros modos de
Virtua Fighter, lanzado en 1993 por movimiento avanzados.
Sega y desarrollado por Yu Suzuki,
se considera como el primer juego Inclusin de puzzles en el desarrollo del juego.
de lucha arcade en soportar grcos
tridimensionales. Uso de cmaras de seguimiento en tercera persona centradas en el personaje
y que posibiliten que el propio usuario las maneje a su antojo para facilitar el
control del personaje virtual.
Uso de un complejo sistema de colisiones asociado a la cmara para garantizar
que la visin no se vea dicultada por la geometra del entorno o los distintos
objetos dinmicos que se mueven por el mismo.

Otro gnero importante est representado por los juegos de lucha, en los que,
normalmente, dos jugadores compiten para ganar un determinado nmero de com-
bates minando la vida o stamina del jugador contrario. Ejemplos representativos de
juegos de lucha son Virtua Fighter, Street Fighter, Tekken, o Soul Calibur, entre otros.
Actualmente, los juegos de lucha se desarrollan normalmente en escenarios tridimen-
sionales donde los luchadores tienen una gran libertad de movimiento. Sin embargo,
ltimamente se han desarrollado diversos juegos en los que tanto el escenario como
los personajes son en 3D, pero donde el movimiento de los mismos est limitado a dos
dimensiones, enfoque comnmente conocido como juegos de lucha de scroll lateral.
Debido a que en los juegos de lucha la accin se centra generalmente en dos per-
sonajes, stos han de tener una gran calidad grca y han de contar con una gran
variedad de movimientos y animaciones para dotar al juego del mayor realismo posi-
ble. As mismo, el escenario de lucha suele estar bastante acotado y, por lo tanto, es
posible simplicar su tratamiento y, en general, no es necesario utilizar tcnicas de op-
timizacin como las comentadas en el gnero de los FPS. Por otra parte, el tratamiento
de sonido no resulta tan complejo como lo puede ser en otros gneros de accin.
Los juegos del gnero de la lucha han de prestar atencin a la deteccin y gestin
de colisiones entre los propios luchadores, o entre las armas que utilicen, para dar una
sensacin de mayor realismo. Adems, el mdulo responsable del tratamiento de la
entrada al usuario ha de ser lo sucientemente sosticado para gestionar de manera
adecuada las distintas combinaciones de botones necesarias para realizar complejos
movimientos. Por ejemplo, juegos como Street Fighter IV incorporan un sistema de
timing entre los distintos movimientos de un combo. El objetivo perseguido consiste
Simuladores F1 en que dominar completamente a un personaje no sea una tarea sencilla y requiera que
Los simuladores de juegos de con- el usuario de videojuegos dedique tiempo al entrenaiento del mismo.
duccin no slo se utilizan para Los juegos de lucha, en general, han estado ligados a la evolucin de tcnicas
el entretenimiento domstico sino
tambin para que, por ejemplo, los complejas de sntesis de imagen aplicadas sobre los propios personajes con el objetivo
pilotos de Frmula-1 conozcan to- de mejorar al mximo su calidad y, de este modo, incrementar su realismo. Un ejemplo
dos los entresijos de los circuitos y representativo es el uso de shaders [76] sobre la armadura o la propia piel de los
puedan conocerlos al detalle antes
de embarcarse en los entrenamien-
personajes que permitan implementar tcnicas como el bump mapping [6], planteada
tos reales. para dotar a estos elementos de un aspecto ms rugoso.
[14] CAPTULO 1. INTRODUCCIN

Otro gnero representativo en el mundo de los videojuegos es la conduccin, en


el que el usuario controla a un vehculo que normalmente rivaliza con ms adversarios
virtuales o reales para llegar a la meta en primera posicin. En este gnero se suele
distinguir entre simuladores, como por ejemplo Gran Turismo, y arcade, como por
ejemplo Ridge Racer o Wipe Out.
Mientras los simuladores tienen como objetivo principal representar con delidad
el comportamiento del vehculo y su interaccin con el escenario, los juegos arcade se
centran ms en la jugabilidad para que cualquier tipo de usuario no tenga problemas
de conduccin.
Los juegos de conduccin se caracterizan por la necesidad de dedicar un esfuerzo
considerable en alcanzar una calidad grca elevada en aquellos elementos cercanos
a la cmara, especialmente el propio vehculo. Adems, este tipo de juegos, aunque
suelen ser muy lineales, mantienen una velocidad de desplazamiento muy elevada,
directamente ligada a la del propio vehculo. Figura 1.7: Captura de pantalla
del juego de conduccin Tux Ra-
Al igual que ocurre en el resto de gneros previamente comentados, existen diver- cing, licenciado bajo GPL por Jas-
sas tcnicas que pueden contribuir a mejorar la eciencia de este tipo de juegos. Por min Patry.
ejemplo, suele ser bastante comn utilizar estructuras de datos auxiliares para dividir
el escenario en distintos tramos, con el objetivo de optimizar el proceso de renderizado
o incluso facilitar el clculo de rutas ptimas utilizando tcnicas de IA [79]. Tambin
se suelen usar imgenes para renderizar elementos lejanos, como por ejemplo rboles,
vallas publicitarias u otro tipo de elementos.
Del mismo modo, y al igual que ocurre con los juegos en tercera persona, la cmara
tiene un papel relevante en el seguimiento del juego. En este contexto, el usuario
normalmente tiene la posibilidad de elegir el tipo de cmara ms adecuado, como por
ejemplo una cmara en primera persona, una en la que se visualicen los controles del
propio vehculo o una en tercera persona.
Otro gnero tradicional son los juegos de estrategia, normalmente clasicados en
tiempo real o RTS (Real-Time Strategy)) y por turnos (turn-based strategy). Ejemplos
representativos de este gnero son Warcraft, Command & Conquer, Comandos, Age of
Figura 1.8: Captura de pantalla del
Empires o Starcraft, entre otros. Este tipo de juegos se caracterizan por mantener una juego de estrategia en tiempo real 0
cmara con una perspectiva isomtrica, normalmente ja, de manera que el jugador A.D., licenciado bajo GPL por Wild-
tiene una visin ms o menos completa del escenario, ya sea 2D o 3D. As mismo, regames.
es bastante comn encontrar un gran nmero de unidades virtuales desplegadas en el
mapa, siendo responsabilidad del jugador su control, desplazamiento y accin.
Teniendo en cuenta las caractersticas generales de este gnero, es posible plantear
diversas optimizaciones. Por ejemplo, una de las aproximaciones ms comunes en es-
te tipo de juegos consiste en dividir el escenario en una rejilla o grid, con el objetivo
de facilitar no slo el emplazamiento de unidades o edicios, sino tambin la plani-
cacin de movimiento de un lugar del mapa a otro. Por otra parte, las unidades se
suelen renderizar con una resolucin baja, es decir, con un bajo nmero de polgonos,
con el objetivo de posibilitar el despliegue de un gran nmero de unidades de manera
simultnea.
Finalmente, en los ltimos aos ha aparecido un gnero de juegos cuya principal
caracterstica es la posibilidad de jugar con un gran nmero de jugadores reales al
mismo tiempo, del orden de cientos o incluso miles de jugadores. Los juegos que se
encuadran bajo este gnero se denominan comnmente MMOG (Massively Multipla-
yer Online Game). El ejemplo ms representativo de este gnero es el juego World of
Warcarft. Debido a la necesidad de soportar un gran nmero de jugadores en lnea,
los desarrolladores de este tipo de juegos han de realizar un gran esfuerzo en la parte
relativa al networking, ya que han de proporcionar un servicio de calidad sobre el que
construir su modelo de negocio, el cual suele estar basado en suscripciones mensuales
o anuales por parte de los usuarios.
C1
1.2. Arquitectura del motor. Visin general [15]

Al igual que ocurre en los juegos de estrategia, los MMOG suelen utilizar persona-
jes virtuales en baja resolucin para permitir la aparicin de un gran nmero de ellos
en pantalla de manera simultnea.
Adems de los distintos gneros mencionados en esta seccin, existen algunos
ms como por ejemplo los juegos deportivos, los juegos de rol o RPG (Role-Playing
Games) o los juegos de puzzles.
Antes de pasar a la siguiente seccin en la que se discutir la arquitectura general
de un motor de juego, resulta interesante destacar la existencia de algunas herramien-
tas libres que se pueden utilizar para la construccin de un motor de juegos. Una de
las ms populares, y que se utilizar en el presente curso, es OGRE 3D9 . Bsicamente,
OGRE es un motor de renderizado 3D bien estructurado y con una curva de aprendi-
zaje adecuada. Aunque OGRE no se puede denir como un motor de juegos completo,
s que proporciona un gran nmero de mdulos que permiten integrar funcionalidades
no triviales, como iluminacin avanzada o sistemas de animacin de caracteres.

1.2. Arquitectura del motor. Visin general


En esta seccin se plantea una visin general de la arquitectura de un motor de
juegos [42], de manera independiente al gnero de los mismos, prestando especial
importancia a los mdulos ms relevantes desde el punto de vista del desarrollo de
videojuegos.
Como ocurre con la gran mayora de sistemas software que tienen una complejidad
elevada, los motores de juegos se basan en una arquitectura estructurada en capas.
De este modo, las capas de nivel superior dependen de las capas de nivel inferior,
pero no de manera inversa. Este planteamiento permite ir aadiendo capas de manera
progresiva y, lo que es ms importante, permite modicar determinados aspectos de
una capa en concreto sin que el resto de capas inferiores se vean afectadas por dicho
cambio.
A continuacin, se describen los principales mdulos que forman parte de la ar-
quitectura que se expone en la gura 1.9.

1.2.1. Hardware, drivers y sistema operativo

La arquitectura Cell La capa relativa al hardware est vinculada a la plataforma en la que se ejecutar
el motor de juego. Por ejemplo, un tipo de plataforma especca podra ser una consola
En arquitecturas ms novedosas, de juegos de sobremesa. Muchos de los principios de diseo y desarrollo son comu-
como por ejemplo la arquitectura nes a cualquier videojuego, de manera independiente a la plataforma de despliegue
Cell usada en Playstation 3 y desa-
rrollada por Sony, Toshiba e IBM, nal. Sin embargo, en la prctica los desarrolladores de videojuegos siempre llevan
las optimizaciones aplicadas suelen a cabo optimizaciones en el motor de juegos para mejorar la eciencia del mismo,
ser ms dependientes de la platafor- considerando aquellas cuestiones que son especcas de una determinada plataforma.
ma nal.
La capa de drivers soporta aquellos componentes software de bajo nivel que per-
miten la correcta gestin de determinados dispositivos, como por ejemplo las tarjetas
de aceleracin grca o las tarjetas de sonido.
La capa del sistema operativo representa la capa de comunicacin entre los pro-
cesos que se ejecutan en el mismo y los recursos hardware asociados a la plataforma
en cuestin. Tradicionalmente, en el mundo de los videojuegos los sistemas opera-
tivos se compilan con el propio juego para producir un ejecutable. Sin embargo, las
9 http://www.ogre3d.org/
[16] CAPTULO 1. INTRODUCCIN

Subsistemas especficos de juego

Subsistema
Networking Audio
de juego

Motor de Herramientas Motor Interfaces


rendering de desarrollo de fsica de usuario

Gestor de recursos

Subsistemas principales

Capa independiente de la plataforma

Software development kits (SDKs) y middlewares

Sistema operativo

Drivers

Hardware

Figura 1.9: Visin conceptual de la arquitectura general de un motor de juegos. Esquema adaptado de la
arquitectura propuesta en [42].

consolas de ltima generacin, como por ejemplo Sony Playstation 3 R o Microsoft


XBox 360 R , incluyen un sistema operativo capaz de controlar ciertos recursos e in-
cluso interrumpir a un juego en ejecucin, reduciendo la separacin entre consolas de
sobremesa y ordenadores personales.

1.2.2. SDKs y middlewares


APIs grcas
Al igual que ocurre en otros proyectos software, el desarrollo de un motor de OpenGL y Direct3D son los dos
juegos se suele apoyar en bibliotecas existentes y SDK para proporcionar una deter- ejemplos ms representativos de
minada funcionalidad. No obstante, y aunque generalmente este software est bastante API (Application Program Interfa-
optimizado, algunos desarrolladores preeren personalizarlo para adaptarlo a sus ne- ce)s grcas que se utilizan en el
cesidades particulares, especialmente en consolas de sobremesa y porttiles. mbito comercial. La principal di-
ferencia entre ambas es la estanda-
rizacin, factor que tiene sus venta-
jas y desventajas.
C1
1.2. Arquitectura del motor. Visin general [17]

Un ejemplo representativo de biblioteca para el manejo de estructuras de datos


es STL (Standard Template Library) 10 . STL es una biblioteca de plantillas estndar
para C++, el cual representa a su vez el lenguaje ms extendido actualmente para el
desarrollo de videojuegos, debido principalmente a su portabilidad y eciencia.
En el mbito de los grcos 3D, existe un gran nmero de bibliotecas de desarro-
llo que solventan determinados aspectos que son comunes a la mayora de los juegos,
como el renderizado de modelos tridimensionales. Los ejemplos ms representativos
en este contexto son las APIs grcas OpenGL11 y Direct3D, mantenidas por el grupo
Khronos y Microsoft, respectivamente. Este tipo de bibliotecas tienen como principal
objetivo ocultar los diferentes aspectos de las tarjetas grcas, presentando una inter-
faz comn. Mientras OpenGL es multiplataforma, Direct3D est totalmente ligado a
sistemas Windows.
Otro ejemplo representativo de SDKs vinculados al desarrollo de videojuegos son
aquellos que dan soporte a la deteccin y tratamiento de colisiones y a la gestin de
la fsica de las distintas entidades que forman parte de un videojuego. Por ejemplo,
en el mbito comercial la compaa Havok12 proporciona diversas herramientas, entre
las que destaca Havok Physics. Dicha herramienta representa la alternativa comercial
ms utilizada en el mbito de la deteccin de colisiones en tiempo real y en las simu-
laciones fsicas. Segn sus autores, Havok Physics se ha utilizado en el desarrollo de
ms de 200 ttulos comerciales.
Por otra parte, en el campo del Open Source, ODE (Open Dynamics Engine) 3D13
representa una de las alternativas ms populares para simular dinmicas de cuerpo
rgido [6].
Recientemente, la rama de la Inteligencia Articial en los videojuegos tambin se
ha visto beneciada con herramientas que posibilitan la integracin directa de bloques
de bajo nivel para tratar con problemas clsicos como la bsqueda ptima de caminos
entre dos puntos o la accin de evitar obstculos.

1.2.3. Capa independiente de la plataforma

Abstraccin funcional Gran parte de los juegos se desarrollan teniendo en cuenta su potencial lanzamien-
to en diversas plataformas. Por ejemplo, un ttulo se puede desarrollar para diversas
Aunque en teora las herramien- consolas de sobremesa y para PC al mismo tiempo. En este contexto, es bastante co-
tas multiplataforma deberan abs-
traer de los aspectos subyacentes
mn encontrar una capa software que aisle al resto de capas superiores de cualquier
a las mismas, como por ejemplo aspecto que sea dependiente de la plataforma. Dicha capa se suele denominar capa
el sistema operativo, en la prctica independiente de la plataforma.
suele ser necesario realizar algunos
ajustos en funcin de la plataforma Aunque sera bastante lgico suponer que la capa inmediatamente inferior, es de-
existente en capas de nivel inferior. cir, la capa de SDKs y middleware, ya posibilita la independencia respecto a las pla-
taformas subyacentes debido al uso de mdulos estandarizados, como por ejemplo
bibliotecas asociadas a C/C++, la realidad es que existen diferencias incluso en bi-
bliotecas estandarizadas para distintas plataformas.
Algunos ejemplos representativos de mdulos incluidos en esta capa son las bi-
bliotecas de manejo de hijos o los wrappers o envolturas sobre alguno de los mdulos
de la capa superior, como el mdulo de deteccin de colisiones o el responsable de la
parte grca.
10 http://www.sgi.com/tech/stl/
11 http://http://www.opengl.org/
12 http://www.havok.com
13 http://www.ode.org
[18] CAPTULO 1. INTRODUCCIN

1.2.4. Subsistemas principales


La capa de subsistemas principales est vinculada a todas aquellas utilidades o
bibliotecas de utilidades que dan soporte al motor de juegos. Algunas de ellas son
especcas del mbito de los videojuegos pero otras son comunes a cualquier tipo de
proyecto software que tenga una complejidad signicativa.
A continuacin se enumeran algunos de los subsistemas ms relevantes:

Biblioteca matemtica, responsable de proporcionar al desarrollador diversas


utilidades que faciliten el tratamiento de operaciones relativas a vectores, matri-
ces, cuaterniones u operaciones vinculadas a lneas, rayos, esferas y otras guras
geomtricas. Las bibliotecas matemticas son esenciales en el desarrollo de un
motor de juegos, ya que stos tienen una naturaleza inherentemente matemtica.
Estructuras de datos y algoritmos, responsable de proporcionar una imple-
mentacin ms personalizada y optimizada de diversas estructuras de datos,
como por ejemplo listas enlazadas o rboles binarios, y algoritmos, como por
ejemplo bsqueda u ordenacin, que la encontrada en bibliotecas como STL.
Este subsistema resulta especialmente importante cuando la memoria de la pla-
taforma o plataformas sobre las que se ejecutar el motor est limitada (como
suele ocurrir en consolas de sobremesa).
Gestin de memoria, responsable de garantizar la asignacin y liberacin de
memoria de una manera eciente.
Depuracin y logging, responsable de proporcionar herramientas para facilitar
la depuracin y el volcado de logs para su posterior anlisis.

1.2.5. Gestor de recursos


Esta capa es la responsable de proporcionar una interfaz unicada para acceder a
las distintas entidades software que conforman el motor de juegos, como por ejem-
plo la escena o los propios objetos 3D. En este contexto, existen dos aproximaciones
principales respecto a dicho acceso: i) plantear el gestor de recursos mediante un enfo-
que centralizado y consistente o ii) dejar en manos del programador dicha interaccin
mediante el uso de archivos en disco.
La gura 1.10 muestra una visin general de un gestor de recursos, representando Ogre 3D
una interfaz comn para la gestin de diversas entidades como por ejemplo el mundo
en el que se desarrolla el juego, los objetos 3D, las texturas o los materiales. El motor de rendering Ogre 3D es-
t escrito en C++ y permite que
En el caso particular de Ogre 3D [52], el gestor de recursos est representa- el desarrollador se abstraiga de un
gran nmero de aspectos relativos
do por la clase Ogre::ResourceManager, tal y como se puede apreciar en la gu- al desarrollo de aplicaciones gr-
ra 1.11. Dicha clase mantiene diversas especializaciones, las cuales estn ligadas a cas. Sin embargo, es necesario estu-
las distintas entidades que a su vez gestionan distintos aspectos en un juego, co- diar su funcionamiento y cmo uti-
mo por ejemplo las texturas (clase Ogre::TextureManager), los modelos 3D (clase lizarlo de manera adecuada.
Ogre::MeshManager) o las fuentes de texto (clase Ogre::FontManager). En el caso
particular de Ogre 3D, la clase Ogre::ResourceManager hereda de dos clases, Resour- Shaders
ceAlloc y Ogre::ScriptLoader, con el objetivo de unicar completamente las diversas
gestiones. Por ejemplo, la clase Ogre::ScriptLoader posibilita la carga de algunos re- Un shader se puede denir como
cursos, como los materiales, mediante scripts y, por ello, Ogre::ResourceManager un conjunto de instrucciones soft-
ware que permiten aplicar efectos
hereda de dicha clase. de renderizado a primitivas geom-
tricas. Al ejecutarse en las unidades
de procesamiento grco (Graphic
Processing Units - GPUs), el rendi-
miento de la aplicacin grca me-
jora considerablemente.
C1
1.2. Arquitectura del motor. Visin general [19]

Recurso Recurso
Mundo Etc
esqueleto colisin

Recurso Recurso Recurso Recurso


modelo 3D textura material fuente

Gestor de recursos

Figura 1.10: Visin conceptual del gestor de recursos y sus entidades asociadas. Esquema adaptado de la
arquitectura propuesta en [42].

Ogre::ScriptLoader

ResourceAlloc Ogre::TextureManager

Ogre::SkeletonManager

Ogre::MeshManager

Ogre::ResourceManager Ogre::MaterialManager

Ogre::GPUProgramManager

Ogre::FontManager

Ogre::CompositeManager

Figura 1.11: Diagrama de clases asociado al gestor de recursos de Ogre 3D, representado por la clase
Ogre::ResourceManager.

1.2.6. Motor de rendering


Debido a que el componente grco es una parte fundamental de cualquier juego,
junto con la necesidad de mejorarlo continuamente, el motor de renderizado es una de
las partes ms complejas de cualquier motor de juego.
[20] CAPTULO 1. INTRODUCCIN

Al igual que ocurre con la propia arquitectura de un motor de juegos, el enfoque


ms utilizado para disear el motor de renderizado consiste en utilizar una arquitectura
multi-capa, como se puede apreciar en la gura 1.12.
A continuacin se describen los principales mdulos que forman parte de cada una
de las capas de este componente.

Front end

Efectos visuales

Scene graph/culling y optimizaciones

Renderizado de bajo nivel

Interfaz con el
dispositivo grfico
Motor de rendering

Figura 1.12: Visin conceptual de la arquitectura general de un motor de rendering. Esquema simplicado
de la arquitectura discutida en [42].

La capa de renderizado de bajo nivel aglutina las distintas utilidades de renderi-


zado del motor, es decir, la funcionalidad asociada a la representacin grca de las
distintas entidades que participan en un determinado entorno, como por ejemplo c-
maras, primitivas de rendering, materiales, texturas, etc. El objetivo principal de esta
capa reside precisamente en renderizar las distintas primitivas geomtricas tan rpi-
do como sea posible, sin tener en cuenta posibles optimizaciones ni considerar, por
ejemplo, qu partes de las escenas son visibles desde el punto de vista de la cmara.
Esta capa tambin es responsable de gestionar la interaccin con las APIs de pro- Optimizacin
gramacin grcas, como OpenGL o Direct3D, simplemente para poder acceder a
los distintos dispositivos grcos que estn disponibles. Tpicamente, este mdulo se Las optimizaciones son esenciales
en el desarrollo de aplicaciones gr-
denomina interfaz de dispositivo grco (graphics device interface). cas, en general, y de videojuegos,
As mismo, en la capa de renderizado de bajo nivel existen otros componentes en particular, para mejorar el ren-
dimiento. Los desarrolladores sue-
encargados de procesar el dibujado de distintas primitivas geomtricas, as como de la len hacer uso de estructuras de da-
gestin de la cmara y los diferentes modos de proyeccin. En otras palabras, esta capa tos auxiliares para aprovecharse del
proporciona una serie de abstracciones para manejar tanto las primitivas geomtricas mayor conocimiento disponible so-
como las cmaras virtuales y las propiedades vinculadas a las mismas. bre la propia aplicacin.
C1
1.2. Arquitectura del motor. Visin general [21]

Por otra parte, dicha capa tambin gestiona el estado del hardware grco y los
shaders asociados. Bsicamente, cada primitiva recibida por esta capa tiene asociado
un material y se ve afectada por diversas fuentes de luz. As mismo, el material descri-
be la textura o texturas utilizadas por la primitiva y otras cuestiones como por ejemplo
qu pixel y vertex shaders se utilizarn para renderizarla.
La capa superior a la de renderizado de bajo nivel se denomina scene graph/-
culling y optimizaciones y, desde un punto de vista general, es la responsable de
seleccionar qu parte o partes de la escena se enviarn a la capa de rendering. Esta se-
leccin, u optimizacin, permite incrementar el rendimiento del motor de rendering,
debido a que se limita el nmero de primitivas geomtricas enviadas a la capa de nivel
inferior.
Aunque en la capa de rendering slo se dibujan las primitivas que estn dentro del
campo de visin de la cmara, es decir, dentro del viewport, es posible aplicar ms
optimizaciones que simpliquen la complejidad de la escena a renderizar, obviando
aquellas partes de la misma que no son visibles desde la cmara. Este tipo de optimi-
zaciones son crticas en juegos que tenga una complejidad signicativa con el objetivo
de obtener tasas de frames por segundo aceptables.
Una de las optimizaciones tpicas consiste en hacer uso de estructuras de datos de
subdivisin espacial para hacer ms eciente el renderizado, gracias a que es posible
determinar de una manera rpida el conjunto de objetos potencialmente visibles. Di-
chas estructuras de datos suelen ser rboles, aunque tambin es posible utilizar otras
alternativas. Tradicionalmente, las subdivisiones espaciales se conocen como scene
graph (grafo de escena), aunque en realidad representan un caso particular de estruc-
tura de datos.
Por otra parte, en esta capa tambin es comn integrar mtodos de culling, como
por ejemplo aquellos basados en utilizar informacin relevante de las oclusiones para
determinar qu objetos estn siendo solapados por otros, evitando que los primeros se
tengan que enviar a la capa de rendering y optimizando as este proceso.
Idealmente, esta capa debera ser independiente de la capa de renderizado, permi-
tiendo as aplicar distintas optimizaciones y abstrayndose de la funcionalidad rela-
tiva al dibujado de primitivas. Un ejemplo representativo de esta independencia est
representado por OGRE (Object-Oriented Graphics Rendering Engine) y el uso de la
losofa plug & play, de manera que el desarrollador puede elegir distintos diseos de
grafos de escenas ya implementados y utilizarlos en su desarrollo.
Filosofa Plug & Play Sobre la capa relativa a las optimizaciones se sita la capa de efectos visuales, la
cual proporciona soporte a distintos efectos que, posteriormente, se puedan integrar en
Esta losofa se basa en hacer uso los juegos desarrollados haciendo uso del motor. Ejemplos representativos de mdulos
de un componente funcional, hard-
ware o software, sin necesidad de
que se incluyen en esta capa son aqullos responsables de gestionar los sistemas de
congurar ni de modicar el fun- partculos (humo, agua, etc), los mapeados de entorno o las sombras dinmicas.
cionamiento de otros componentes
asociados al primero. Finalmente, la capa de front-end suele estar vinculada a funcionalidad relativa
a la superposicin de contenido 2D sobre el escenario 3D. Por ejemplo, es bastante
comn utilizar algn tipo de mdulo que permita visualizar el men de un juego o la
interfaz grca que permite conocer el estado del personaje principal del videojuego
(inventario, armas, herramientas, etc). En esta capa tambin se incluyen componentes
para reproducir vdeos previamente grabados y para integrar secuencias cinemticas,
a veces interactivas, en el propio videojuego. Este ltimo componente se conoce como
IGC (In-Game Cinematics) system.
[22] CAPTULO 1. INTRODUCCIN

1.2.7. Herramientas de depuracin


Debido a la naturaleza intrnseca de un videojuego, vinculada a las aplicaciones
grcas en tiempo real, resulta esencial contar con buenas herramientas que permi-
tan depurar y optimizar el propio motor de juegos para obtener el mejor rendimiento
posible. En este contexto, existe un gran nmero de herramientas de este tipo. Algu-
nas de ellas son herramientas de propsito general que se pueden utilizar de manera
externa al motor de juegos. Sin embargo, la prctica ms habitual consiste en cons-
truir herramientas de proling, vinculadas al anlisis del rendimiento, o depuracin
que estn asociadas al propio motor. Algunas de las ms relevantes se enumeran a
continuacin [42]:
Versiones beta
Mecanismos para determinar el tiempo empleado en ejecutar un fragmento es-
pecco de cdigo. Adems del uso extensivo de herra-
mientas de depuracin, las desarro-
lladoras de videojuegos suelen libe-
Utilidades para mostrar de manera grca el rendimiento del motor mientras se rar versiones betas de los mismos
ejecuta el juego. para que los propios usuarios con-
tribuyan en la deteccin de bugs.
Utilidades para volcar logs en cheros de texto o similares.
Herramientas para determinar la cantidad de memoria utilizada por el motor en
general y cada subsistema en particular. Este tipo de herramientas suelen tener
distintas vistas grcas para visualizar la informacin obtenida.
Herramientas de depuracin que gestin el nivel de informacin generada.
Utilidades para grabar eventos particulares del juego, permitiendo reproducirlos
posteriormente para depurar bugs.

1.2.8. Motor de fsica


La deteccin de colisiones en un videojuego y su posterior tratamiento resultan ED auxiliares
esenciales para dotar de realismo al mismo. Sin un mecanismo de deteccin de co-
lisiones, los objetos se traspasaran unos a otros y no sera posible interactuar con Al igual que ocurre en procesos co-
mo la obtencin de la posicin de
ellos. Un ejemplo tpico de colisin est representado en los juegos de conduccin por un enemigo en el mapa, el uso ex-
el choque entre dos o ms vehculos. Desde un punto de vista general, el sistema de tensivo de estructuras de datos au-
deteccin de colisiones es responsable de llevar a cabo las siguientes tareas [6]: xiliares permite obtener soluciones
a problemas computacionalmente
complejos. La gestin de colisiones
1. La deteccin de colisiones, cuya salida es un valor lgico indicando si hay o no es otro proceso que se benecia de
colisin. este tipo de tcnicas.

2. La determinacin de la colisin, cuya tarea consiste en calcular el punto de


interseccin de la colisin.
3. La respuesta a la colisin, que tiene como objetivo determinar las acciones que
se generarn como consecuencia de la misma.

Debido a las restricciones impuestas por la naturaleza de tiempo real de un video-


juego, los mecanismos de gestin de colisiones se suelen aproximar para simplicar
la complejidad de los mismos y no reducir el rendimiento del motor. Por ejemplo,
en algunas ocasiones los objetos 3D se aproximan con una serie de lneas, utilizando
tcnicas de interseccin de lneas para determinar la existancia o no de una colisin.
Tambin es bastante comn hacer uso de rboles BSP para representar el entorno y
optimizar la deteccin de colisiones con respecto a los propios objetos.
C1
1.2. Arquitectura del motor. Visin general [23]

Por otra parte, algunos juegos incluyen sistemas realistas o semi-realistas de simu-
lacin dinmica. En el mbito de la industria del videojuego, estos sistemas se suelen
denominar sistema de fsica y estn directamente ligados al sistema de gestin de
colisiones.
Actualmente, la mayora de compaas utilizan motores de colisin/fsica desarro-
llados por terceras partes, integrando estos kits de desarrollo en el propio motor. Los
ms conocidos en el mbito comercial son Havok, el cual representa el estndar de
facto en la industria debido a su potencia y rendimiento, y PhysX, desarrollado por
NVIDIA e integrado en motores como por ejemplo el Unreal Engine 3.
En el mbito del open source, uno de los ms utilizados es ODE. Sin embargo,
en este curso se har uso del motor de simulacin fsica Bullet14 , el cual se utiliza
actualmente en proyectos tan ambiciosos como la suite 3D Blender.

1.2.9. Interfaces de usuario


En cualquier tipo de juego es necesario desarrollar un mdulo que ofrezca una abs-
traccin respecto a la interaccin del usuario, es decir, un mdulo que principalmente
sea responsable de procesar los eventos de entrada del usuario. Tpicamente, dichos
eventos estarn asociados a la pulsacin de una tecla, al movimiento del ratn o al uso
de un joystick, entre otros.
Desde un punto de vista ms general, el mdulo de interfaces de usuario tambin
es responsable del tratamiento de los eventos de salida, es decir, aquellos eventos
que proporcionan una retroalimentacin al usuario. Dicha interaccin puede estar re-
presentada, por ejemplo, por el sistema de vibracin del mando de una consola o por
la fuerza ejercida por un volante que est siendo utilizado en un juego de conduccin.
Debido a que este mdulo gestiona los eventos de entrada y de salida, se suele denomi-
nar comnmente componente de entrada/salida del jugador (player I/O component).
El mdulo de interfaces de usuario acta como un puente entre los detalles de bajo
nivel del hardware utilizado para interactuar con el juego y el resto de controles de
ms alto nivel. Este mdulo tambin es responsable de otras tareas importantes, como
la asocacin de acciones o funciones lgicas al sistema de control del juego, es decir,
permite asociar eventos de entrada a acciones lgicas de alto nivel.
En la gestin de eventos se suelen utilizar patrones de diseo como el patrn de-
legate [37], de manera que cuando se detecta un evento, ste se traslada a la entidad
adecuada para llevar a cabo su tratamiento.

1.2.10. Networking y multijugador

Lag La mayora de juegos comerciales desarrollados en la actualidad incluyen modos


de juegos multijugador, con el objetivo de incrementar la jugabilidad y duracin de los
El retraso que se produce desde que ttulos lanzados al mercado. De hecho, algunas compaas basan el modelo de negocio
se enva un paquete de datos por de algunos de sus juegos en el modo online, como por ejemplo World of Warcraft de
una entidad hasta que otra lo recibe
se conoce como lag. En el mbito Blizzard Entertainment, mientras algunos ttulos son ampliamente conocidos por su
de los videojuegos, el lag se suele exitoso modo multijugador online, como por ejemplo la saga Call of Duty de Activi-
medir en milsimas de segundo. sion.
Aunque el modo multijugador de un juego puede resultar muy parecido a su ver-
sin single-player, en la prctica incluir el soporte de varios jugadores, ya sea online o
no, tiene un profundo impacto en diseo de ciertos componentes del motor de juego,
como por ejemplo el modelo de objetos del juego, el motor de renderizado, el mdulo
14 http://www.bulletphysics.com
[24] CAPTULO 1. INTRODUCCIN

de entrada/salida o el sistema de animacin de personajes, entre otros. De hecho, una


de las losofas ms utilizadas en el diseo y desarrollo de motores de juegos actua-
les consiste en tratar el modo de un nico jugador como un caso particular del modo
multijugador.
Por otra parte, el mdulo de networking es el responsable de informar de la evo-
lucin del juego a los distintos actores o usuarios involucrados en el mismo mediante
el envo de paquetes de informacin. Tpicamente, dicha informacin se transmite
utilizando sockets. Con el objetivo de reducir la latencia del modo multijugador, es-
pecialmente a travs de Internet, slo se enva/recibe informacin relevante para el
correcto funcionamiento de un juego. Por ejemplo, en el caso de los FPS, dicha infor-
macin incluye tpicamente la posicin de los jugadores en cada momento, entre otros
elementos.

1.2.11. Subsistema de juego


El subsistema de juego, conocido por su trmino en ingls gameplay, integra todos
aquellos mdulos relativos al funcionamiento interno del juego, es decir, aglutina tanto
las propiedades del mundo virtual como las de los distintos personajes. Por una parte,
este subsistema permite la denicin de las reglas que gobiernan el mundo virtual en
el que se desarrolla el juego, como por ejemplo la necesidad de derrotar a un enemigo
antes de enfrentarse a otro de mayor nivel. Por otra parte, este subsistema tambin
permite la denicin de la mecnica del personaje, as como sus objetivos durante el
juego.

Sistema de alto nivel del juego

Sistema de scripting

Objetos Objetos Simulacin Sistema de


estticos dinmicos basada en agentes eventos

Subsistema de juego

Figura 1.13: Visin conceptual de la arquitectura general del subsistema de juego. Esquema simplicado
de la arquitectura discutida en [42].

Este subsistema sirve tambin como capa de aislamiento entre las capas de ms
bajo nivel, como por ejemplo la de rendering, y el propio funcionamiento del juego. Diseando juegos
Es decir, uno de los principales objetivos de diseo que se persiguen consiste en inde-
pendizar la lgica del juego de la implementacin subyacente. Por ello, en esta capa es Los diseadores de los niveles de un
juego, e incluso del comportamien-
bastante comn encontrar algn tipo de sistema de scripting o lenguaje de alto nivel to de los personajes y los NPCs,
para denir, por ejemplo, el comportamiento de los personajes que participan en el suelen dominar perfectamente los
juego. lenguajes de script, ya que son su
principal herramienta para llevar a
cabo su tarea.
C1
1.2. Arquitectura del motor. Visin general [25]

La capa relativa al subsistema de juego maneja conceptos como el mundo del


juego, el cual se reere a los distintos elementos que forman parte del mismo, ya sean
estticos o dinmicos. Los tipos de objetos que forman parte de ese mundo se suelen
denominar modelo de objetos del juego [42]. Este modelo proporciona una simulacin
en tiempo real de esta coleccin heterognea, incluyendo

Elementos geomtricos relativos a fondos estticos, como por ejemplo edicios


o carreteras.
Cuerpos rgidos dinmicos, como por ejemplo rocas o sillas.
El propio personaje principal.
Los personajes no controlados por el usuario (NPCs).
Cmaras y luces virtuales.
Armas, proyectiles, vehculos, etc.

El modelo de objetos del juego est intimamente ligado al modelo de objetos soft-
ware y se puede entender como el conjunto de propiedades del lenguaje, polticas y
convenciones utilizadas para implementar cdigo utilizando una losofa de orienta-
cin a objetos. As mismo, este modelo est vinculado a cuestiones como el lenguaje
de programacin empleado o a la adopcin de una poltica basada en el uso de patrones
de diseo, entre otras.
En la capa de subsistema de juego se integra el sistema de eventos, cuya principal
responsabilidad es la de dar soporte a la comunicacin entre objetos, independiente-
mente de su naturaleza y tipo. Un enfoque tpico en el mundo de los videojuegos con-
siste en utilizar una arquitectura dirigida por eventos, en la que la principal entidad es
el evento. Dicho evento consiste en una estructura de datos que contiene informacin
relevante de manera que la comunicacin est precisamente guiada por el contenido
del evento, y no por el emisor o el receptor del mismo. Los objetos suelen implementar
manejadores de eventos (event handlers) para tratarlos y actuar en consecuencia.
Por otra parte, el sistema de scripting permite modelar fcilmente la lgica del
juego, como por ejemplo el comportamiento de los enemigos o NPCs, sin necesidad
de volver a compilar para comprobar si dicho comportamiento es correcto o no. En
algunos casos, los motores de juego pueden seguir en funcionamiento al mismo tiempo
que se carga un nuevo script.
Finalmente, en la capa del subsistema de juego es posible encontrar algn mdulo
que proporcione funcionalidad aadida respecto al tratamiento de la IA, normalmente
de los NPCs. Este tipo de mdulos, cuya funcionalidad se suele incluir en la propia
capa de software especca del juego en lugar de integrarla en el propio motor, son
cada vez ms populares y permiten asignar comportamientos preestablecidos sin nece-
sidad de programarlos. En este contexto, la simulacin basada en agentes [102] cobra
especial relevancia.
Este tipo de mdulos pueden incluir aspectos relativos a problemas clsicos de la
IA, como por ejemplo la bsqueda de caminos ptimos entre dos puntos, conocida
como pathnding, y tpicamente vinculada al uso de algoritmos A* [79]. As mismo,
tambin es posible hacer uso de informacin privilegiada para optimizar ciertas tareas,
Im all ears!
como por ejemplo la localizacin de entidades de inters para agilizar el clculo de
El apartado sonoro de un juego es aspectos como la deteccin de colisiones.
especialmente importante para que
el usuario se sienta inmerso en el
mismo y es crtico para acompaar
de manera adecuada el desarrollo de
dicho juego.
[26] CAPTULO 1. INTRODUCCIN

1.2.12. Audio
Tradicionalmente, el mundo del desarrollo de videojuegos siempre ha prestado
ms atencin al componente grco. Sin embargo, el apartado sonoro tambin tiene
una gran importancia para conseguir una inmersin total del usuario en el juego. Por
ello, el motor de audio ha ido cobrando ms y ms relevancia.
Asimismo, la aparicin de nuevos formatos de audio de alta denicin y la popu-
laridad de los sistemas de cine en casa han contribuido a esta evolucin en el cada vez
ms relevante apartado sonoro.
Actualmente, al igual que ocurre con otros componentes de la arquitectura del mo-
tor de juego, es bastante comn encontrar desarrollos listos para utilizarse e integrarse
en el motor de juego, los cuales han sido realizados por compaas externas a la del
propio motor. No obstante, el apartado sonoro tambin requiere modicaciones que
son especcas para el juego en cuestin, con el objetivo de obtener un alto de grado
de delidad y garantizar una buena experiencia desde el punto de visto auditivo.

1.2.13. Subsistemas especcos de juego


Por encima de la capa de subsistema de juego y otros componentes de ms bajo
nivel se sita la capa de subsistemas especcos de juego, en la que se integran aquellos
mdulos responsables de ofrecer las caractersticas propias del juego. En funcin del
tipo de juego a desarrollar, en esta capa se situarn un mayor o menor nmero de
mdulos, como por ejemplo los relativos al sistema de cmaras virtuales, mecanismos
de IA especcos de los personajes no controlados por el usuario (NPCs), aspectos de
renderizados especcos del juego, sistemas de armas, puzzles, etc.
Idealmente, la lnea que separa el motor de juego y el propio juego en cuestin
estara entre la capa de subsistema de juego y la capa de subsistemas especcos de
juego.
Herramientas de Desarrollo
Captulo 2
Cleto Martn Angelina

A
ctualmente, existen un gran nmero de aplicaciones y herramientas que permi-
ten a los desarrolladores de videojuegos, y de aplicaciones en general, aumen-
tar su productividad a la hora de construir software, gestionar los proyectos y
recursos, as como automatizar procesos de construccin.
En este captulo, se pone de maniesto la importancia de la gestin en un proyec-
to software y se muestran algunas de las herramientas de desarrollo ms conocidas
en sistemas GNU/Linux. La eleccin de este tipo de sistema no es casual. Por un la-
do, se trata de Software Libre, lo que permite a desarrolladores estudiar, aprender y
entender lo que hace el cdigo que se ejecuta. Por otro lado, probablemente sea el
mejor sistema operativo para construir y desarrollar aplicaciones debido al gran n-
mero de herramientas que proporciona. Es un sistema hecho por programadores para
programadores.

2.1. Introduccin
En la construccin de software no trivial, las herramientas de gestin de proyec-
tos y de desarrollo facilitan la labor de las personas que lo construyen. Conforme el
software se va haciendo ms complejo y se espera ms funcionalidad de l, se hace
necesario el uso de herramientas que permitan automatizar los procesos del desarrollo,
as como la gestin del proyecto y su documentacin.
Adems, dependiendo del contexto, es posible que existan otros integrantes del
proyecto que no tengan formacin tcnica y que necesiten realizar labores sobre el
producto como traducciones, pruebas, diseo grco, etc.

27
[28] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Los videojuegos son proyectos software que, normalmente, requieren la participa-


cin de varias personas con diferentes perles profesionales (programadores, disea-
dores grcos, compositores de sonidos, etc.). Cada uno de ellos, trabaja con diferen-
tes tipos de datos en diferentes tipos de formatos. Todas las herramientas que permitan
la construccin automtica del proyecto, la integracin de sus diferentes componentes
y la coordinacin de sus miembros sern de gran ayuda en un entorno tan heterogneo.
Desde el punto de vista de la gestin del proyecto, una tarea esencial es la automa-
tizacin del proceso de compilacin y de construccin de los programas. Una de las
tareas que ms frecuentemente se realizan mientras se desarrolla y depura un programa
es la de compilacin y construccin. Cuanto ms grande y complejo sea un programa,
mayor es el tiempo que se pierde en esta fase. Por tanto, un proceso automtico de
construccin de software ahorrar muchas prdidas de tiempo en el futuro.
En los sistemas GNU/Linux es habitual el uso de herramientas como el compilador
GCC, el sistema de construccin GNU Make y el depurador GDB. Todas ellas crea-
das en el proyecto GNU y orientadas a la creacin de programas en C/C++, aunque
tambin pueden ser utilizadas con otras tecnologas. Tambin existen editores de texto
como GNU Emacs o vi, y modernos (pero no por ello mejores) entornos de desarrollo Figura 2.1: El proyecto GNU pro-
como Eclipse que no slo facilitan las labores de escritura de cdigo, sino que pro- porciona una gran abanico de herra-
porcionan numerosas herramientas auxiliares dependiendo del tipo de proyecto. Por mientas de desarrollo y son utiliza-
ejemplo, Eclipse puede generar los archivos Makeles necesarios para automatizar el dos en proyectos software de todo
tipo.
proceso de construccin con GNU Make.

2.2. Compilacin, enlazado y depuracin


La compilacin y la depuracin y, en general, el proceso de construccin es una
de las tareas ms importantes desde el punto de vista del desarrollador de aplicaciones
escritas en C/C++. En muchas ocasiones, parte de los problemas en el desarrollo de un
programa vienen originados directa o indirectamente por el propio proceso de cons-
truccin del mismo. Hacer un uso indebido de las opciones del compilador, no depurar
utilizando los programas adecuados o realizar un incorrecto proceso de construccin
del proyecto son ejemplos tpicos que, en muchas ocasiones, consumen demasiado
tiempo en el desarrollo. Por todo ello, tener un conocimiento slido e invertir tiempo
en estas cuestiones ahorra ms de un quebradero de cabeza a lo largo del ciclo de vida
de la aplicacin.
En esta seccin se estudia una terminologa y conceptos bsicos en el mbito de
los procesos de construccin de aplicaciones.
Concretamente, se muestra el uso del compilador de C/C++ GCC, el depurador Figura 2.2: GCC es una coleccin
GDB y el sistema de construccin automtico GNU Make. de compiladores para lenguajes co-
mo C/C++ y Java.

2.2.1. Conceptos bsicos


A la hora de buscar y compartir informacin con el resto de compaeros de profe-
sin es necesario el uso de una terminologa comn. En las tareas de construccin del
software existen algunos trminos que son importantes conocer.
2.2. Compilacin, enlazado y depuracin [29]

C2
Cdigo fuente, cdigo objeto y cdigo ejecutable

Como es de suponer, la programacin consiste en escribir programas. Los pro-


gramas son procedimientos que, al ejecutarse de forma secuencial, se obtienen unos
resultados. En muchos sentidos, un programa es como una receta de cocina: una espe-
cicacin secuencial de las acciones que hay que realizar para conseguir un objetivo.
Cmo de abstractas sean estas especicaciones es lo que dene el nivel de abstrac-
cin de un lenguaje.
Los programas se pueden escribir directamente en cdigo ejecutable, tambin lla-
mado cdigo binario o cdigo mquina. Sin embargo, el nivel de abstraccin tan bajo
que ofrecen estos lenguajes hara imposible que muchos proyectos actuales pudieran
llevarse a cabo. Este cdigo es el que entiende la mquina donde se va a ejecutar el
programa y es especco de la plataforma. Por ejemplo, mquinas basadas en la ar-
quitectura PC no ofrecen el mismo repertorio de instrucciones que otras basadas en
la arquitectura PPC o ARM. A la dicultad de escribir cdigo de bajo nivel se le suma
la caracterstica de no ser portable.
Por este motivo se han creado los compiladores. Estos programas traducen cdigo
fuente, programado en un lenguaje de alto nivel, en el cdigo ejecutable para una
plataforma determinada. Un paso intermedio en este proceso de compilacin es la
generacin de cdigo objeto, que no es sino cdigo en lenguaje mquina al que le
falta realizar el proceso de enlazado.
Aunque en los sistemas como GNU/Linux la extensin en el nombre de los archi-
vos es puramente informativa, los archivos fuente en C++ suelen tener las extensiones
.cpp, .cc, y .h, .hh o .hpp para las cabeceras. Por su parte, los archivos de cdigo
objeto tienen extensin .o y lo ejecutables no suelen tener extensin.

Compilador

Como ya se ha dicho, se trata de un programa que, a partir del cdigo fuente,


genera el cdigo ejecutable para la mquina destino. Este proceso de traduccin auto-
matizado permite al programador, entre otras muchas ventajas:

No escribir cdigo de muy bajo nivel.


Abstraerse de la caractersticas propias de la mquina tales como registros es-
peciales, modos de acceso a memoria, etc.
Escribir cdigo portable. Basta con que exista un compilador en una plataforma
que soporte C++ para que un programa pueda ser portado.

Aunque la funcin principal del compilador es la de actuar como traductor entre


dos tipos de lenguaje, este trmino se reserva a los programas que transforman de un
lenguaje de alto nivel a otro; por ejemplo, el programa que transforma cdigo C++ en
Java.
Existen muchos compiladores comerciales de C++ como Borland C++, Microsoft
Visual C++. Sin embargo, GCC es un compilador libre y gratuito que soporta C/C++
(entre otros lenguajes) y es ampliamente utilizado en muchos sectores de la inform-
tica: desde la programacin grca a los sistemas empotrados.
Obviamente, las diferencias entre las implementaciones vienen determinadas por
el contexto de aplicacin para los que son concebidos. Sin embargo, es posible extraer
una estructura funcional comn como muestra la gura 2.3, que representa las fases
de compilacin en las que, normalmente, est dividido el proceso de compilacin.
[30] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Figura 2.3: Fases de compilacin

Las fases estn divididas en dos grandes bloques:

Frontend: o frontal del compilador. Es el encargado de realizar el anlisis lxi-


co, sintctico y semntico de los cheros de entrada. El resultado de esta fase
es un cdigo intermedio que es independiente de la plataforma destino.
Backend: el cdigo intermedio pasa por el optimizador y es mejorado utilizan-
do diferentes estrategias como la eliminacin de cdigo muerto o de situaciones
redundantes. Finalmente, el cdigo intermedio optimizado lo toma un gene-
rador de cdigo mquina especco de la plataforma destino. Adems de la
generacin, en esta fase tambin se realizan algunas optimizaciones propias de
la plataforma destino.

En denitiva, el proceso de compilacin de un compilador est dividido en etapas


bien diferenciadas, que proporcionan diferente funcionalidad para las etapas siguien-
tes hasta la generacin nal.

Enlazador

Un programa puede estar compuesto por varios mdulos, lo cual permite que un
proyecto pueda ser ms mantenible y manejable. Los mdulos pueden tener indepen-
dencias entre s y la comprobacin y resolucin de estas dependencias corren a cargo
del enlazador. El enlazador toma como entrada el cdigo objeto.

Bibliotecas

Una de las principales ventajas del software es la reutilizacin del cdigo. Normal-
mente, los problemas pueden resolverse utilizando cdigo ya escrito anteriormente y
la reutilizacin del mismo se vuelve un aspecto clave para el tiempo de desarrollo del
producto. Las bibliotecas ofrecen una determinada funcionalidad ya implementada
para que sea utilizada por programas. Las bibliotecas se incorporan a los programas
durante el proceso de enlazado.
2.2. Compilacin, enlazado y depuracin [31]

C2
Las bibliotecas pueden enlazarse contra el programa de dos formas:

Estticamente: en tiempo de enlazado, se resuelven todas las dependencias y


smbolos que queden por denir y se incorpora al ejecutable nal.
La principal ventaja de utilizar enlazado esttico es que el ejecutable puede con-
siderarse standalone y es completamente independiente. El sistema donde vaya
a ser ejecutado no necesita tener instaladas bibliotecas externas de antemano.
Sin embargo, el cdigo ejecutable generado tiene mayor tamao.
Dinmicamente: en tiempo de enlazado slo se comprueba que ciertas depen-
dencias y smbolos estn denidos, pero no se incorpora al ejecutable. Ser en
tiempo de ejecucin cuando se realizar la carga de la biblioteca en memoria.
El cdigo ejecutable generado es mucho menor, pero el sistema debe tener la
biblioteca previamente instalada.

En sistemas GNU/Linux, las bibliotecas ya compiladas suelen encontrarse en /usr/lib


y siguen un convenio para el nombre: libnombre. Las bibliotecas dinmicas tienen
extensin .so y las estticas .a.

2.2.2. Compilando con GCC


Desde un punto de vista estricto, GCC no es un compilador. GNU Compiler Co-
llection (GCC) es un conjunto de compiladores que proporciona el proyecto GNU para
diferentes lenguajes de programacin tales como C, C++, Java, FORTRAN, etc. Den-
tro de este conjunto de compiladores, G++ es el compilador para C++. Teniendo en
cuenta esta precisin, es comn llamar simplemente GCC al compilador de C y C++,
por lo que se usarn de forma indistinta en este texto.
En esta seccin se introducen la estructura bsica del compilador GCC, as como
algunos de sus componentes y su papel dentro del proceso de compilacin. Finalmen-
te, se muestran ejemplos de cmo utilizar GCC (y otras herramientas auxiliares) para
construir un ejecutable, una biblioteca esttica y una dinmica.

2.2.3. Cmo funciona GCC?


GCC es un compilador cuya estructura es muy similar a la presentada en la sec-
cin 2.2.1. Sin embargo, cada una de las fases de compilacin la realiza un componen-
te bien denido e independiente. Concretamente, al principio de la fase de compila-
cin, se realiza un procesamiento inicial del cdigo fuente utilizando el preprocesador
GNU CPP, posteriormente se utiliza GNU Assembler para obtener el cdigo objeto y,
con la ayuda del enlazador GNU ld, se crea el binario nal.
En la gura 2.4 se muestra un esquema general de los componentes de GCC que
participan en el proceso.
El hecho de que est dividido en estas etapas permite una compilacin modular,
es decir, cada chero de entrada se transforma a cdigo objeto y con la ayuda del en-
lazador se resuelven las dependencias que puedan existir entre ellos. A continuacin,
se comenta brevemente cada uno de los componentes principales.
distcc
La compilacin modular y por fa- Preprocesador
ses permite que herramientas como
distcc puedan realizar compila-
ciones distribuidas en red y en pa- El preprocesamiento es la primera transformacin que sufre un programa en C/C++.
ralelo. Se lleva a cabo por el GNU CPP y, entre otras, realiza las siguientes tareas:
[32] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Figura 2.4: Proceso de compilacin en GCC

Inclusin efectiva del cdigo incluido por la directiva #include.


Resuelve de las directivas #ifdef/#ifndef para la compilacin condicional.
Sustitucin efectiva de todas las directivas de tipo #define.

El preprocesador se puede invocar directamente utilizando la orden cpp. Como


ejercicio se reserva al lector observar qu ocurre al invocar al preprocesador con
el siguiente fragmento de cdigo. Se realizan comprobaciones lexcas, sintticas
o semnticas?. Utilice los parmetros que ofrece el programa para denir la macro
DEFINED_IT.

Listado 2.1: Cdigo de ejemplo preprocesable


1 #include <iostream>
2 #define SAY_HELLO "Hi, world!"
3
4 #ifdef DEFINED_IT
5 #warning "If you see this message, you DEFINED_IT"
6 #endif
7
8 using namespace std;
9
10 Code here??
11
12 int main() {
13 cout << SAY_HELLO << endl;
14 return 0;
15 }

Compilacin

El cdigo fuente, una vez preprocesado, se compila a lenguaje ensamblador, es


decir, a una representacin de bajo nivel del cdigo fuente. Originalmente, la sintaxis
de este lenguaje es la de AT&T pero desde algunas versiones recientes tambin se
soporta la sintaxis de Intel.
Entre otras muchas operaciones, en la compilacin se realizan las siguientes ope-
raciones:

Anlisis sintctico y semntico del programa. Pueden ser congurados para


obtener diferentes mensajes de advertencia (warnings) a diferentes niveles.
2.2. Compilacin, enlazado y depuracin [33]

C2
Comprobacin y resolucin de smbolos y dependencias a nivel de declaracin.
Realizar optimizaciones.

Utilizando GCC con la opcin -S puede detenerse el proceso de compilacin hasta


la generacin del cdigo ensamblador. Como ejercicio, se propone cambiar el cdigo
fuente anterior de forma que se pueda construir el correspondiente en ensamblador.

GCC proporciona diferentes niveles de optimizaciones (opcin -O). Cuanto


mayor es el nivel de optimizacin del cdigo resultante, mayor es el tiempo
de compilacin pero suele hacer ms eciente el cdigo de salida. Por ello,
se recomienda no optimizar el cdigo durante las fases de desarrollo y slo
hacerlo en la fase de distribucin/instalacin del software.

Ensamblador

Una vez se ha obtenido el cdigo ensamblador, GNU Assembler es el encargado


de realizar la traduccin a cdigo objeto de cada uno de los mdulos del programa.
Por defecto, el cdigo objeto se genera en archivos con extensin .o y la opcin -c
de GCC permite detener el proceso de compilacin en este punto.
GNU Assembler Como ejercicio, se propone al lector modicar el cdigo ensamblador obtenido
en la fase anterior sustituyendo el mensaje original "Hi, world" por "Hola,
GNU Assembler forma parte de la
distribucin GNU Binutils y se co-
mundo". Generar el cdigo objeto asociado utilizando directamente el ensamblador
rresponde con el programa as. (no GCC).

Enlazador

GNU Linker Con todos los archivos objetos el enlazador (linker) es capaz de generar el eje-
cutable o cdigo binario nal. Algunas de las tareas que se realizan en el proceso de
GNU Linker tambin forma parte de enlazado son las siguientes:
la distribucin GNU Binutils y se
corresponde con el programa ld.
Seleccin y ltrado de los objetos necesarios para la generacin del binario.
Comprobacin y resolucin de smbolos y dependencias a nivel de denicin.
Realizacin del enlazado (esttico y dinmico) de las bibliotecas.

Como ejercicio se propone utilizar el linker directamente con el cdigo objeto


generado en el apartado anterior. Ntese que las opciones -l y -L sirven para aadir
rutas personalizadas a las que por defecto ld utiliza para buscar bibliotecas.

2.2.4. Ejemplos
Como se ha mostrado, el proceso de compilacin est compuesto por varias fases
bien diferenciadas. Sin embargo, con GCC se integra todo este proceso de forma que,
a partir del cdigo fuente se genere el binario nal.
En esta seccin se mostrarn ejemplos en los que se crea un ejecutable al que,
posteriormente, se enlaza con una biblioteca esttica y otra dinmica.
[34] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Compilacin de un ejecutable

Como ejemplo de ejecutable se toma el siguiente programa.

Listado 2.2: Programa bsico de ejemplo

1 #include <iostream>
2
3 using namespace std;
4
5 class Square {
6 private:
7 int side_;
8
9 public:
10 Square(int side_length) : side_(side_length) { };
11 int getArea() const { return side_*side_; };
12 };
13
14 int main () {
15 Square square(5);
16 cout << "Area: " << square.getArea() << endl;
17 return 0;
18 }

En C++, los programas que generan un ejecutable deben tener denida la fun-
cin main, que ser el punto de entrada de la ejecucin. El programa es trivial: se
dene una clase Square que representa a un cuadrado. sta implementa un mtodo
getArea() que devuelve el rea del cuadrado.
Suponiendo que el archivo que contiene el cdigo fuente se llama main.cpp,
para construir el binario utilizaremos g++, el compilador de C++ que se incluye en
GCC. Se podra utilizar gcc y que se seleccionara automticamente el compilador.
Sin embargo, es una buena prctica utilizar el compilador correcto:

$ g++ -o main main.cpp

Ntese que todo el proceso de compilacin se ha realizado automticamente y


cada una de las herramientas auxiliares se han ejecutado internamente en su momento
oportuno (preprocesamiento, compilacin, ensamblado y enlazado).

La opcin -o indica a GCC el nombre del archivo de salida de la compilacin.


2.2. Compilacin, enlazado y depuracin [35]

C2
Compilacin de un ejecutable (modular)

En un proyecto, lo natural es dividir el cdigo fuente en mdulos que realizan


operaciones concretas y bien denidas. En el ejemplo, podemos considerar un mdulo
la declaracin y denicin de la clase Square. Esta extraccin se puede realizar de
muchas maneras. Lo habitual es crear un chero de cabecera .h con la declaracin de
la clase y un chero .cpp con la denicin:

Listado 2.3: Archivo de cabecera Square.h


1 class Square {
2 private:
3 int side_;
4
5 public:
6 Square(int side_length);
7 int getArea() const;
8 };

Listado 2.4: Implementacin (Square.cpp)


1 #include "Square.h"
2
3 Square::Square (int side_length) : side_(side_length)
4 { }
5
6 int
7 Square::getArea() const
8 {
9 return side_*side_;
10 }

De esta forma, el archivo main.cpp quedara como sigue:

Listado 2.5: Programa principal


1 #include <iostream>
2 #include "Square.h"
3
4 using namespace std;
5
6 int main () {
7 Square square(5);
8 cout << "Area: " << square.getArea() << endl;
9 return 0;
10 }

Para construir el programa, se debe primero construir el cdigo objeto del mdulo
y aadirlo a la compilacin de la funcin principal main. Suponiendo que el archivo
de cabecera se encuentra en un directorio llamado headers, la compilacin puede
realizarse de la siguiente manera:

$ g++ -Iheaders -c Square.cpp


$ g++ -Iheaders -c main.cpp
$ g++ Square.o main.o -o main

Tambin se puede realizar todos los pasos al mismo tiempo:

$ g++ -Iheaders Square.cpp main.cpp -o main


[36] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Con la opcin -I, que puede aparecer tantas veces como sea necesario, se puede
aadir rutas donde se buscarn las cabeceras. Ntese que, por ejemplo, en main.cpp
se incluyen las cabeceras usando los smbolos <> y . Se recomienda utilizar los pri-
meros para el caso en que las cabeceras forman parte de una API pblica (si existe) y
deban ser utilizadas por otros programas. Por su parte, las comillas se suelen utilizar
para cabeceras internas al proyecto. Las rutas por defecto son el directorio actual . pa-
ra las cabeceras incluidas con y para el resto el directorio del sistema (normalmente,
/usr/include).

Como norma general, una buena costumbre es generar todos los archivos de
cdigo objeto de un mdulo y aadirlos a la compilacin con el programa
principal.

Compilacin de una biblioteca esttica

Para este ejemplo se supone que se pretende construir una biblioteca con la que se
pueda enlazar estticamente y que contiene una jerarqua de clases correspondientes
a 3 tipos de guras (Figure): Square, Triangle y Circle. Cada gura est
implementada como un mdulo (cabecera + implementacin):

Listado 2.6: Figure.h


1 #ifndef FIGURE_H
2 #define FIGURE_H
3
4 class Figure {
5 public:
6 virtual float getArea() const = 0;
7 };
8
9 #endif

Listado 2.7: Square.h


1 #include <Figure.h>
2
3 class Square : public Figure {
4 private:
5 float side_;
6
7 public:
8 Square(float side_length);
9 float getArea() const;
10 };

Listado 2.8: Square.cpp


1 #include "Square.h"
2
3 Square::Square (float side) : side_(side) { }
4 float Square::getArea() const { return side_*side_; }
2.2. Compilacin, enlazado y depuracin [37]

C2
Listado 2.9: Triangle.h
1 #include <Figure.h>
2
3 class Triangle : public Figure {
4 private:
5 float base_;
6 float height_;
7
8 public:
9 Triangle(float base_, float height_);
10 float getArea() const;
11 };

Listado 2.10: Triangle.cpp


1 #include "Triangle.h"
2
3 Triangle::Triangle (float base, float height) : base_(base),
height_(height) { }
4 float Triangle::getArea() const { return (base_*height_)/2; }

Listado 2.11: Circle.h


1 #include <Figure.h>
2
3 class Square : public Figure {
4 private:
5 float side_;
6
7 public:
8 Square(float side_length);
9 float getArea() const;
10 };

Listado 2.12: Circle.cpp


1 #include <cmath>
2 #include "Circle.h"
3
4 Circle::Circle(float radious) : radious_(radious) { }
5 float Circle::getArea() const { return radious_*(M_PI*M_PI); }

Para generar la biblioteca esttica llamada figures, es necesario el uso de la


herramienta GNU ar:

$ g++ -Iheaders -c Square.cpp


$ g++ -Iheaders -c Triangle.cpp
$ g++ -Iheaders -c Circle.cpp
$ ar rs libfigures.a Square.o Triangle.o Circle.o

ar es un programa que permite, entre otra mucha funcionalidad, empaquetar los


archivos de cdigo objeto y generar un ndice para crear un biblioteca. Este ndice se
incluye en el mismo archivo generado y mejora el proceso de enlazado y carga de la
biblioteca.
A continuacin, se muestra el programa principal que hace uso de la biblioteca:
[38] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Listado 2.13: main.cpp


1 #include <iostream>
2
3 #include <Square.h>
4 #include <Triangle.h>
5 #include <Circle.h>
6
7 using namespace std;
8
9 int main () {
10 Square square(5);
11 Triangle triangle(5,10);
12 Circle circle(10);
13 cout << "Square area: " << square.getArea() << endl;
14 cout << "Triangle area: " << triangle.getArea() << endl;
15 cout << "Circle area: " << circle.getArea() << endl;
16 return 0;
17 }

La generacin del ejecutable se realizara de la siguiente manera:

$ g++ main.cpp -L. -lfigures -Iheaders -o main

Las opciones de enlazado se especican con -L y -l. La primera permite aadir


un directorio donde se buscarn las bibliotecas. La segunda especica el nombre de la
biblioteca con la que debe enlazarse.

La ruta por defecto en la que se busca las bibliotecas instaladas en el siste-


ma depende de la distribucin GNU/Linux que se utilice. Normalmente, se
encuentran en /lib y /usr/lib.

Compilacin de una biblioteca dinmica

La generacin de una biblioteca dinmica con GCC es muy similar a la de una


esttica. Sin embargo, el cdigo objeto debe generarse de forma que pueda ser car-
gado en tiempo de ejecucin. Para ello, se debe utilizar la opcin -fPIC durante la
generacin de los archivos .o. Utilizando el mismo cdigo fuente de la biblioteca
figures, la compilacin se realizara como sigue:

$ g++ -Iheaders -fPIC -c Square.cpp


$ g++ -Iheaders -fPIC -c Triangle.cpp
$ g++ -Iheaders -fPIC -c Circle.cpp
$ g++ -o libfigures.so -shared Square.o Triangle.o Circle.o

Como se puede ver, se utiliza GCC directamente para generar la biblioteca dinmi-
ca. La compilacin y enlazado con el programa principal se realiza de la misma forma
que en el caso del enlazado esttico. Sin embargo, la ejecucin del programa principal
es diferente. Al tratarse de cdigo objeto que se cargar en tiempo de ejecucin, exis-
ten una serie de rutas predenadas donde se buscarn las bibliotecas. Por defecto, son
las mismas que para el proceso de enlazado.
Tambin es posible aadir rutas modicando la variable LD_LIBRARY_PATH:

$ LD_LIBRARY_PATH=. ./main
2.2. Compilacin, enlazado y depuracin [39]

C2
2.2.5. Otras herramientas
La gran mayora de las herramientas utilizadas hasta el momento forman parte
de la distribucin GNU Binutils1 que se proporcionan en la mayora de los sistemas
GNU/Linux. Existen otras herramientas que se ofrecen en este misma distribucin y
que pueden ser de utilidad a lo largo del proceso de desarrollo:

c++filt: el proceso de mangling es el que se realiza cuando se traduce el


nombre de las funciones y mtodos a bajo nivel. Este mecanismo es til para
realizar la sobreescritura de mtodos en C++. c++filt es un programa que
realiza la operacin inversa demangling. Es til para depurar problemas en el
proceso de enlazado.
objdump: proporciona informacin avanzada sobre los archivos objetos: sm-
bolos denidos, tamaos, bibliotecas enlazadas dinmicamente, etc. Proporcio-
na una visin detallada y bien organizada por secciones.
readelf: similar a objdump pero especco para los archivos objeto para
plataformas compatibles con el formato binario Executable and Linkable For-
mat (ELF).
nm: herramienta para obtener los smbolos denidos y utilizados en los archivos
objetos. Muy til ya que permite listar smbolos denidos en diferentes partes
y de distinto tipo (seccin de datos, seccin de cdigo, smbolos de depuracin,
etc.)
ldd: utilidad que permite mostrar las dependencias de un binario con bibliote-
cas externas.

2.2.6. Depurando con GDB


Los programas tienen fallos y los programadores cometen errores. Los compilado-
res ayudan a la hora de detectar errores lxicos, sintcticos y semnticos del lenguaje
de entrada. Sin embargo, el compilador no puede deducir (por lo menos hasta hoy) la
lgica que encierra el programa, su signicado nal o su propsito. Estos errores se
conocen como errores lgicos.
Los depuradores son programas que facilitan la labor de deteccin de errores, so-
bre todo los lgicos. Con un depurador, el programador puede probar una ejecucin
paso por paso, examinar/modicar el contenido de las variables en un cierto momen-
to, etc. En general, se pueden realizar las tareas necesarias para conseguir reproducir
y localizar un error difcil de detectar a simple vista. Muchos entornos de desarrollo
como Eclipse, .NET o Java Beans incorporan un depurador para los lenguajes sopor-
tados. Sin duda alguna, se trata de una herramienta esencial en cualquier proceso de
desarrollo software.
En esta seccin se muestra el uso bsico de GNU Debugger (GDB) , un depurador
libre para sistemas GNU/Linux que soporta diferentes lenguajes de programacin,
entre ellos C++.
1 Ms informacin en http://www.gnu.org/software/binutils/

Figura 2.5: GDB es uno de los de-


puradores ms utilizados.
[40] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Compilar para depurar

GDB necesita informacin extra que, por defecto, GCC no proporciona para po-
der realizar las tareas de depuracin. Para ello, el cdigo fuente debe ser compilado
con la opcin -ggdb. Todo el cdigo objeto debe ser compilado con esta opcin de
compilacin, por ejemplo:

$ g++ -Iheaders -ggdb -c module.cpp


$ g++ -Iheaders -ggdb module.o main.cpp -o main

Para depurar no se debe hacer uso de las optimizaciones. stas pueden gene-
rar cdigo que nada tenga que ver con el original.

Arrancando una sesin GDB

Como ejemplo, se ver el siguiente fragmento de cdigo:

Listado 2.14: main.cpp


1 #include <iostream>
2
3 using namespace std;
4
5 class Test {
6 int _value;
7 public:
8 void setValue(int a) { _value = a; }
9 int getValue() { return _value; }
10 };
11
12 float functionB(string str1, Test* t) {
13 cout << "Function B: " << str1 << ", " << t->getValue() << endl;
14 return 3.14;
15 }
16
17 int functionA(int a) {
18 cout << "Function A: " << a << endl;
19 Test* test = NULL; /** ouch! **/
20 test->setValue(15);
21 cout << "Return B: " << functionB("Hi", test) << endl;
22 return 5;
23 }
24
25 int main() {
26 cout << "Main start" << endl;
27 cout << "Return A: " << functionA(24) << endl;
28 return 0;
29 }

La orden para generar el binario con smbolos de depuracin sera:

$ g++ -ggdb main.cpp -o main

Si se ejecuta el cdigo se obtienes la siguiente salida:


2.2. Compilacin, enlazado y depuracin [41]

C2
$ ./main
Main start
Function A: 24
Segmentation fault

Una violacin de segmento (segmentation fault) es uno de los errores lgicos tpi-
cos de los lenguajes como C++. El problema es que se est accediendo a una zona de
la memoria que no ha sido reservada para el programa, por lo que el sistema operativo
interviene denegando ese acceso indebido.
A continuacin, se muestra cmo iniciar una sesin de depuracin con GDB para
encontrar el origen del problema:

$ gdb main
...
Reading symbols from ./main done.
(gdb)

Como se puede ver, GDB ha cargado el programa, junto con los smbolos de depu-
racin necesarios, y ahora se ha abierto una lnea de rdenes donde el usuario puede
especicar sus acciones.

Con los programas que fallan en tiempo de ejecucin se puede generar un


archivo de core, es decir, un chero que contiene un volcado de la memoria
en el momento en que ocurri el fallo. Este archivo puede ser cargado en una
sesin de GDB para ser examinado usando la opcin -c.

Examinando el contenido

Abreviatura Para comenzar la ejecucin del programa se puede utilizar la orden start:
Todas las rdenes de GDB pueden (gdb) start
escribirse utilizando su abreviatura. Temporary breakpoint 1 at 0x400d31: file main.cpp, line 26.
Ej: run = r. Starting program: main

Temporary breakpoint 1, main () at main.cpp:26


26 cout << "Main start" << endl;
(gdb)

De esta forma, se ha comenzado la ejecucin del programa y se ha detenido en


la primera instruccin de la funcin main(). Para reiniciar la ejecucin basta con
volver a ejecutar start.
Para ver ms en detalle sobre el cdigo fuente se puede utilizar la orden list o
smplemente l:

(gdb) list
21 cout << "Return B: " << functionB("Hi", test) << endl;
22 return 5;
23 }
24
25 int main() {
26 cout << "Main start" << endl;
27 cout << "Return A: " << functionA(24) << endl;
28 return 0;
29 }
(gdb)
[42] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Como el resto de rdenes, list acepta parmetros que permiten ajustar su com-
portamiento.
Las rdenes que permiten realizar una ejecucin controlada son las siguientes:

step (s): ejecuta la instruccin actual y salta a la inmediatamente siguiente sin


mantener el nivel de la ejecucin (stack frame), es decir, entra en la denicin
de la funcin (si la hubiere).
stepi se comporta igual que step pero a nivel de instrucciones mquina.
next (n): ejecuta la instruccin actual y salta a la siguiente manteniendo el
stack frame, es decir, la denicin de la funcin se toma como una instruccin
atmica.
nexti se comporta igual que next pero si la instruccin es una llamada a
funcin se espera a que termine.

A continuacin se va a utilizar step para avanzar en la ejecucin del programa.


Ntese que para repetir la ejecucin de step basta con introducir una orden vaca.
En este caso, GDB vuelve a ejecutar la ltima orden.

(gdb) s
Main start
27 cout << "Return A: " << functionA(24) << endl;
(gdb)
functionA (a=24) at main.cpp:18
18 cout << "Function A: " << a << endl;
(gdb)

En este punto se puede hacer uso de las rdenes para mostrar el contenido del
parmetro a de la funcin functionA():

(gdb) print a
$1 = 24
(gdb) print &a
$2 = (int *) 0x7fffffffe1bc

Con print y el modicador & se puede obtener el contenido y la direccin de


memoria de la variable, respectivamente. Con display se puede congurar GDB
para que muestre su contenido en cada paso de ejecucin.
Tambin es posible cambiar el valor de la variable a:

gdb) set variable a=8080


gdb) print a
3 = 8080
gdb) step
unction A: 8080
9 Test* test = NULL; /** ouch! **/
gdb)

La ejecucin est detenida en la lnea 19 donde un comentario nos avisa del error.
Se est creando un puntero con el valor NULL. Posteriormente, se invoca un mtodo
sobre un objeto que no est convenientemente inicializado, lo que provoca la violacin
de segmento:

(gdb) next
20 test->setValue(15);
(gdb)
2.2. Compilacin, enlazado y depuracin [43]

C2
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400df2 in Test::setValue (this=0x0, a=15) at main.cpp:8
8 void setValue(int a) { _value = a; }

Para arreglar este fallo el basta con construir convenientemente el objeto:

Listado 2.15: functionA arreglada


1 int functionA(int a) {
2 cout << "Function A: " << a << endl;
3 Test* test = new Test();
4 test->setValue(15);
5 cout << "Return B: " << functionB("Hi", test) << endl;
6 return 5;
7 }

Breakpoints

La ejecucin paso a paso es una herramienta til para una depuracin de grano
no. Sin embargo, si el programa realiza grandes iteraciones en bucles o es demasiado
grande, puede ser un poco incmodo (o inviable). Si se tiene la sospecha sobre el lugar
donde est el problema se pueden utilizar puntos de ruptura o breakpoints que permite
detener el ujo del programa en un punto determinado por el usuario.
Con el ejemplo ya arreglado, se congura un breakpoint en la funcin functionB()
y otro en la lnea 28 con la orden break. A continuacin, se ejecuta el programa hasta
que se alcance el breakpoint con la orden run (r):

(gdb) break functionB


Breakpoint 1 at 0x400c15: file main.cpp, line 13.
(gdb) break main.cpp:28
Breakpoint 2 at 0x400ddb: file main.cpp, line 28.
(gdb) run
Starting program: main
Main start
Function A: 24

Breakpoint 1, functionB (str1=..., t=0x602010) at gdb-fix.cpp:13


13 cout << "Function B: " << str1 << ", " << t->getValue() << endl;
(gdb)

No hace falta escribir todo!. Utiliza TAB para completar los argumentos de
una orden.

Con la orden continue (c) la ejecucin avanza hasta el siguiente punto de rup-
tura (o n del programa):

(gdb) continue
Continuing.
Function B: Hi, 15
Return B: 3.14
Return A: 5

Breakpoint 2, main () at gdb-fix.cpp:28


28 return 0;
[44] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

(gdb)

Los breakpoint pueden habilitarse, inhabilitarse y/o eliminarse en tiempo de ejecu-


cin. Adems, GDB ofrece un par de estructuras similares tiles para otras situaciones:

Watchpoints: la ejecucin se detiene cuando una determinada expresin cam-


bia.
Catchpoints: la ejecucin se detiene cuando se produce un evento, como una
excepcin o la carga de una librera dinmica.

Stack y frames

En muchas ocasiones, los errores vienen debidos a que las llamadas a funciones no
se realizan con los parmetros adecuados. Es comn pasar punteros no inicializados o
valores incorrectos a una funcin/mtodo y, por tanto, obtener un error lgico.
Para gestionar las llamadas a funciones y procedimientos, en C/C++ se utiliza la
pila (stack ). En la pila se almacenan frames, estructuras de datos que registran las
variables creadas dentro de una funcin as como otra informacin de contexto. GDB
permite manipular la pila y los frames de forma que sea posible identicar un uso
indebido de las funciones.
Con la ejecucin parada en functionB(), se puede mostrar el contenido de la
pila con la orden backtrace (bt):

(gdb) backtrace
#0 functionB (str1=..., t=0x602010) at main.cpp:13
#1 0x0000000000400d07 in functionA (a=24) at main.cpp:21
#2 0x0000000000400db3 in main () at main.cpp:27
(gdb)

Con up y down se puede navegar por los frames de la pila, y con frame se puede
seleccionar uno en concreto:

(gdb) up
#1 0x0000000000400d07 in functionA (a=24) at gdb-fix.cpp:21
21 cout << "Return B: " << functionB("Hi", test) << endl;
(gdb)
#2 0x0000000000400db3 in main () at gdb-fix.cpp:27
27 cout << "Return A: " << functionA(24) << endl;
(gdb) frame 0
#0 functionB (str1=..., t=0x602010) at gdb-fix.cpp:13
13 cout << "Function B: " << str1 << ", " << t->getValue() << endl;
(gdb)

Una vez seleccionado un frame, se puede obtener toda la informacin del mismo, Invocar funciones
adems de modicar las variables y argumentos:
La orden call se puede utilizar pa-
ra invocar funciones y mtodos .
(gdb) print *t
$1 = {_value = 15}
(gdb) call t->setValue(1000)
(gdb) print *t
$2 = {_value = 1000}
(gdb)
2.2. Compilacin, enlazado y depuracin [45]

C2
Entornos grcos para GDB

El aprendizaje de GDB no es sencillo. La interfaz de lnea de rdenes es muy po-


tente pero puede ser difcil de asimilar, sobre todo en los primeros pasos del aprendi-
zaje de la herramienta. Por ello, existen diferentes versiones grcas que, en denitiva,
hacen ms accesible el uso de GDB:


GDB TUI: normalmente, la distribucin GDB
de

incorpora una interfaz basada
en modo texto accesible pulsando Ctrl + x y, a continuacin, a .
ddd y xxgdb: las libreras grcas utilizadas son algo anticuadas, pero facilitan
el uso de GDB.
gdb-mode: modo de Emacs para GDB. Dentro del modo se puede activar la
opcin M-x many-windows para obtener buffers con toda la informacin
disponible.
kdbg: ms atractivo grcamente (para escritorios KDE).

2.2.7. Construccin automtica con GNU Make


En los ejemplos propuestos en la seccin 2.2.2 se puede apreciar que el proceso de
compilacin no es trivial y que necesita de varios pasos para llevarse a cabo. A medida
que el proyecto crece es deseable que el proceso de construccin de la aplicacin sea
lo ms automtico y able posible. Esto evitar muchos errores de compilacin a lo
largo del proceso de desarrollo.
GNU Make es una herramienta para la construccin de archivos, especicando las
dependencias con sus archivos fuente. Make es una herramienta genrica, por lo que
puede ser utilizada para generar cualquier tipo de archivo desde sus dependencias. Por
ejemplo, generar una imagen PNG a partir de una imagen vectorial SVG, obtener la
grca en JPG de una hoja de clculo de LibreOfce, etc.
Sin duda, el uso ms extendido es la automatizacin del proceso de construccin
de las aplicaciones. Adems, Make ofrece la caracterstica de que slo reconstruye
los archivos que han sido modicados, por lo que no es necesario recompilar todo el
proyecto cada vez que se realiza algn cambio.

Estructura

Los archivos de Make se suelen almacenar en archivos llamados Makefile. La


estructura de estos archivos puede verse en el siguiente listado de cdigo:

Listado 2.16: Estructura tpica de un archivo Makele


1 # Variable definitions
2 VAR1=/home/user
3 export VAR2=yes
4
5 # Rules
6 target1: dependency1 dependency2 ...
7 action1
8 action2
9 dependency1: dependency3
10 action3
11 action4
[46] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

GNU Make tomar como entrada automticamente el archivo cuyo nombre


sea GNUmakele, makele o Makele, en ese orden de prioridad. Puede mo-
dicarse el chero de entrada con -f.

Normalmente, el principio del Makele se reserva para deniciones de variables


que van a ser utilizadas a lo largo del mismo o por otros Makeles (para ello puede
utilizar export). A continuacin, se denen el conjunto de reglas de construccin
para cada uno de los archivos que se pretende generar a partir de sus dependencias. Por
ejemplo, el archivo target1 necesita que existan dependency1, dependency2,
etc. action1 y action2 indican cmo se construye. La siguiente regla tiene co-
mo objetivo dependency1 e igualmente se especica cmo obtenerlo a partir de
dependency3.

Las acciones de una regla van siempre van precedidas de un tabulado.

Existen algunos objetivos especiales como all, install y clean que sirven
como regla de partida inicial, para instalar el software construido y para limpiar del
proyecto los archivos generados, respectivamente.
Tomando como ejemplo la aplicacin que hace uso de la biblioteca dinmica, el
siguiente listado muestra el Makele que generara tanto el programa ejecutable como
la biblioteca esttica:

Listado 2.17: Makele bsico


1
2 all: main
3
4 main: main.o libfigures.a
5 g++ main.o -L. -lfigures -o main
6
7 main.o: main.cpp
8 g++ -Iheaders -c main.cpp
9
10 libfigures.a: Square.o Triangle.o Circle.o
11 ar rs libfigures.a Square.o Triangle.o Circle.o
12
13 Square.o: Square.cpp
14 g++ -Iheaders -fPIC -c Square.cpp
15
16 Triangle.o: Triangle.cpp
17 g++ -Iheaders -fPIC -c Triangle.cpp
18
19 Circle.o: Circle.cpp
20 g++ -Iheaders -fPIC -c Circle.cpp
21
22 clean:
23 rm -f *.o *.a main

Con ello, se puede construir el proyecto utilizando make [objetivo]. Si no


se proporciona objetivo se toma el primero en el archivo. De esta forma, se puede
construir todo el proyecto, un archivo en concreto o limpiarlo completamente. Por
ejemplo, para construir y limpiar todo el proyecto se ejecutaran las siguientes rdenes:
2.2. Compilacin, enlazado y depuracin [47]

C2
$ make
$ make clean

Como ejercicio, se plantean las siguientes preguntas: qu opcin permite ejecutar


make sobre otro archivo que no se llame Makefile? Se puede ejecutar make sobre
un directorio que no sea el directorio actual? Cmo?.
GNU Coding Standars
Variables automticas y reglas con patrones
En el proyecto GNU se denen los
objetivos que se esperan en un soft-
ware que siga estas directrices. Make se caracteriza por ofrecer gran versatilidad en su lenguaje. Las variables
automticas contienen valores que dependen del contexto de la regla donde se aplican
y permiten denir reglas genricas. Por su parte, los patrones permiten generalizar
las reglas utilizando el nombre los archivos generados y los fuentes.
A continuacin se presenta una versin mejorada del anterior Makele haciendo
uso de variables, variables automticas y patrones:

Listado 2.18: Makele con variables automticas y patrones


1
2 LIB_OBJECTS=Square.o Triangle.o Circle.o
3
4 all: main
5
6 main: main.o libfigures.a
7 g++ $< -L. -lfigures -o $@
8
9 libfigures.a: $(LIB_OBJECTS)
10 ar rs $@ $^
11
12 %.o: %.cpp
13 g++ -Iheaders -c $<
14
15 clean:
16 $(RM) *.o *.a main

A continuacin se explica cada elemento con mas detalle:

Variable de usuario LIB_OBJECTS: se trata de la lista de archivos objetos que


forman la biblioteca. Al contenido se puede acceder utilizando el operador $().
Variable predenada RM: con el valor rm -f. Se utiliza en el objetivo clean.
Variables automticas $@, $<, $: se utiliza para hacer referencia al nombre
del objetivo de la regla, al nombre de la primera dependencia y a la lista de
todas las dependencias de la regla, respectivamente.
Regla con patrn: en lnea 12 se dene una regla genrica a travs de un patrn
en el que se dene cmo generar cualquier archivo objeto .o, a partir de un
archivo fuente .cpp.

Como ejercicio se plantean las siguientes cuestiones: qu ocurre si una vez cons-
truido el proyecto se modica algn chero .cpp? Y si se modica una cabecera
.h? Se podra construir una regla con patrn genrica para construir la biblioteca
esttica? Cmo lo haras?.
[48] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Reglas implcitas

Como se ha dicho, GNU Make es ampliamente utilizado para la construccin de


proyectos software donde estn implicados diferentes procesos de compilacin y ge-
neracin de cdigo. Por ello, proporciona las llamadas reglas implcitas de forma que
si se denen ciertas variables de usuario, Make puede deducir cmo construir los ob-
jetivos.
A continuacin, se transforma el ejemplo anterior utilizando las reglas implcitas:

Listado 2.19: Makele con reglas implcitas


1 CC=g++
2 CXXFLAGS=-Iheaders
3 LDFLAGS=-L.
4 LDLIBS=-lfigures
5
6 LIB_OBJECTS=Square.o Triangle.o Circle.o
7
8 all: libfigures.a main
9
10 libfigures.a: $(LIB_OBJECTS)
11 $(AR) r $@ $^
12
13 clean:
14 $(RM) *.o *.a main

Como se puede ver, Make puede generar automticamente los archivos objeto .o
a partir de la coincidencia con el nombre del chero fuente (que es lo habitual). Por
ello, no es necesario especicar cmo construir los archivos .o de la biblioteca, ni
siquiera la regla para generar main ya que asume de que se trata del ejecutable (al
existir un chero llamado main.cpp).
Las variables de usuario que se han denido permiten congurar los ags de com-
pilacin que se van a utilizar en las reglas explcitas. As:

CC: la orden que se utilizar como compilador. En este caso, el de C++.


CXXFLAGS: los ags para el preprocesador y compilador de C++ (ver sec-
cin 2.2.3). Aqu slo se dene la opcin -I, pero tambin es posible aadir
optimizaciones y la opcin de depuracin -ggdb o -fPIC para las bibliotecas
dinmicas.
LDFLAGS: ags para el enlazador (ver seccin 2.2.3). Aqu se denen las rutas
de bsqueda de las bibliotecas estticas y dinmicas.
LDLIBS: en esta variable se especican las opciones de enlazado. Normalmen-
te, basta con utilizar la opcin -l con las bibliotecas necesarias para generar el
ejecutable.

En GNU/Linux, el programa pkg-config permite conocer los ags de


compilacin y enlazado de una biblioteca determinada.
2.2. Compilacin, enlazado y depuracin [49]

C2
Funciones

GNU Make proporciona un conjunto de funciones que pueden ser de gran ayuda
a la hora de construir los Makeles. Muchas de las funciones estn diseadas para
el tratamiento de cadenas, ya que se suelen utilizar para transformar los nombres de
archivos. Sin embargo, existen muchas otras como para realizar ejecucin condicional,
bucles y ejecutar rdenes de consola. En general, las funciones tienen el siguiente
formato:

$(nombre arg1,arg2,arg3,...)

Las funciones se pueden utilizar en cualquier punto del Makele, desde las ac-
ciones de una regla hasta en la denicin de un variable. En el siguiente listado se
muestra el uso de algunas de estas funciones:

Listado 2.20: Makele con reglas implcitas


1 CC=g++
2
3 ifeq ($(DEBUG),yes)
4 CXXFLAGS=-Iheader -Wall -ggdb
5 else
6 CXXFLAGS=-Iheader -O2
7 endif
8
9 LDFLAGS=-L.
10 LDLIBS=-lfigures
11
12 LIB_OBJECTS=Square.o Triangle.o Circle.o
13
14 all: libfigures.a main
15 $(info All done!)
16
17 libfigures.a: $(LIB_OBJECTS)
18 $(AR) r $@ $^
19 $(warning Compiled objects from $(foreach OBJ,
20 $(LIB_OBJECTS),
21 $(patsubst %.o, %.cpp,$(OBJ))))
22
23 clean:
24 $(RM) *.o *.a main
25 $(shell find -name *~ -delete)

Las funciones que se han utilizado se denen a continuacin:

Funciones condicionales: funciones como ifeq o ifneq permiten realizar


una ejecucin condicional. En este caso, si existe una variable de entorno lla-
mada DEBUG con el valor yes, los ags de compilacin se conguran en con-
secuencia.
Para denir la variable DEBUG, es necesario ejecutar make como sigue:

$ DEBUG=yes make

Funciones de bucles: foreach permite aplicar una funcin a cada valor de


una lista. Este tipo de funciones devuelven una lista con el resultado.
Funciones de tratamiento de texto: por ejemplo, patsubst toma como pri-
mer parmetro un patrn que se comprobar por cada OBJ. Si hay matching,
ser sustituido por el patrn denido como segundo parmetro. En denitiva,
cambia la extensin .o por .cpp.
[50] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Funciones de log: info, warning, error, etc. permiten mostrar texto a


diferente nivel de severidad.
Funciones de consola: shell es la funcin que permite ejecutar rdenes en
un terminal. La salida es til utilizarla como entrada a una variable.

Ms informacin

GNU Make es una herramienta que est en continuo crecimiento y esta seccin s-
lo ha sido una pequea presentacin de sus posibilidades. Para obtener ms informa-
cin sobre las funciones disponibles, otras variables automticas y objetivos predeni-
dos se recomiendo utilizar el manual en lnea de Make2 , el cual siempre se encuentra
actualizado.

2.3. Gestin de proyectos y documentacin


Los proyectos software pueden ser realizados por varios equipos de personas, con
formacin y conocimientos diferentes, que deben colaborar entre s. Adems, una
componente importante del proyecto es la documentacin que se genera durante el
transcurso del mismo, es decir, cualquier documento escrito o grco que permita
entender mejor los componentes del mismo y, por ello, asegure una mayor mantenibi-
lidad para el futuro (manuales, diagramas, especicaciones, etc.).
La gestin del proyecto es un proceso transversal al resto de procesos y tareas y se
ocupa de la planicacin y asignacin de los recursos de los que se disponen. Se trata
de un proceso dinmico, ya que debe adaptarse a los diferentes cambios e imprevistos
que puedan surgir durante el desarrollo. Para detectar estos cambios a tiempo, dentro
de la gestin del proyecto se realizan tareas de seguimiento que consisten en registrar
y noticar errores y retrasos en las diferentes fases y entregas.
Existen muchas herramientas que permiten crear entornos colaborativos que fa-
cilitan el trabajo tanto a desarrolladores como a los jefes de proyecto, los cuales estn
ms centrados en las tareas de gestin. En esta seccin se presentan algunos entornos
colaborativos actuales, as como soluciones especcas para un proceso concreto.

2.3.1. Sistemas de control de versiones


El resultado ms importante de un proyecto software son los archivos de distinto
tipo que se generan; desde el cdigo fuente, hasta los diseos, bocetos y documen-
tacin del mismo. Desde el punto de vista tcnico, se trata de gestionar una cantidad
importante de archivos que son modicados a lo largo del tiempo por diferentes per-
sonas. Adems, es posible que para un conjunto de archivos sea necesario volver a una
versin anterior.
Por ejemplo, un fallo de diseo puede tener como consecuencia que se genere un
cdigo que no es escalable y difcil de mantener. Si es posible volver a un estado
original conocido (por ejemplo, antes de tomarse la decisin de diseo), el tiempo
invertido en revertir los cambios es menor.
2 http://www.gnu.org/software/make/manual/make.html
2.3. Gestin de proyectos y documentacin [51]

C2
Por otro lado, sera interesante tener la posibilidad de realizar desarrollos en para-
lelo de forma que exista una versin estable de todo el proyecto y otra ms experi-
mental del mismo donde se probaran diferentes algoritmos y diseos. De esta forma,
probar el impacto que tendra nuevas implementaciones sobre el proyecto no afec-
tara a una versin ms ocial. Tambin es comn que se desee aadir una nueva
funcionalidad y sta se realiza en paralelo junto con otros desarrollos.
Los sistemas de control de versiones o Version Control System (VCS) permiten
gestionar los archivos de un proyecto (y sus versiones) y que sus integrantes puedan
acceder remotamente a ellos para descargarlos, modicarlos y publicar los cambios.
Tambin se encargan de detectar posibles conictos cuando varios usuarios modican
los mismos archivos y de proporcionar un sistema bsico de registro de cambios.

Como norma general, al VCS debe subirse el archivo fuente y nunca el archivo
generado. No se deben subir binarios ya que no es fcil seguir la pista a sus
modicaciones.

Sistemas centralizados vs. distribuidos

Existen diferentes criterios para clasicar los diferentes VCS existentes. Uno de
los que ms inuye tanto en la organizacin y uso del repositorio es si se trata de VCS
centralizado o distribuido. En la gura 2.6 se muestra un esquema de ambas losofas.

Figura 2.6: Esquema centralizado vs. distribuido de VCS.

Los VCS centralizados como CVS o Subversion se basan en que existe un nodo
servidor con el que todos los clientes conectan para obtener los archivos, subir modi-
caciones, etc. La principal ventaja de este esquema reside en su sencillez: las diferentes
versiones del proyecto estn nicamente en el servidor central, por lo que los posibles
conictos entre las modicaciones de los clientes pueden detectarse y gestionarse ms
fcilmente. Sin embargo, el servidor es un nico punto de fallo y en caso de cada, los
clientes quedan aislados.
Por su parte, en los VCS distribuidos como Mercurial o Git, cada cliente tiene un
repositorio local al nodo en el que se suben los diferentes cambios. Los cambios pue-
den agruparse en changesets, lo que permite una gestin ms ordenada. Los clientes
actan de servidores para el resto de los componentes del sistema, es decir, un cliente
puede descargarse una versin concreta de otro cliente.
Esta arquitectura es tolerante a fallos y permite a los clientes realizar cambios sin
necesidad de conexin. Posteriormente, pueden sincronizarse con el resto. An as,
un VCS distribuido puede utilizarse como uno centralizado si se ja un nodo como
servidor, pero se perderan algunas posibilidades que este esquema ofrece.
[52] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Subversion

Subversion (SVN) es uno de los VCS centralizado ms utilizado, probablemen-


te, debido a su sencillez en el uso. Bsicamente, los clientes tienen accesible en un
servidor todo el repositorio y es ah donde se envan los cambios.
Para crear un repositorio, en el servidor se puede ejecutar la siguiente orden:

$ svnadmin create /var/repo/myproject

Esto crear un rbol de directorios en /var/repo/myproject que contiene


toda la informacin necesaria. Una vez hecho esto es necesario hacer accesible este
directorio a los clientes. En lo que sigue, se supone que los usuarios tienen acceso a Figura 2.7: Logotipo del proyecto
Apache Subversion.
la mquina a travs de una cuenta SSH, aunque se pueden utilizar otros mtodos de
acceso.

Es recomendable que el acceso al repositorio est controlado. HTTPS o SSH


son buenas opciones de mtodos de acceso.

Inicialmente, los clientes pueden descargarse el repositorio por primera vez utili-
zando la orden checkout:

$ svn checkout svn+ssh://user1@myserver:/var/repo/myproject


Checked out revision X.

Esto ha creado un directorio myproject con el contenido del repositorio. Una


vez descargado, se pueden realizar todos los cambios que se deseen y, posteriormente,
subirlos al repositorio. Por ejemplo, aadir archivos y/o directorios:

$ mkdir doc
$ echo "This is a new file" > doc/new_file
$ echo "Other file" > other_file
$ svn add doc other_file
A doc
A doc/new_file
A other_file

La operacin add indica qu archivos y directorios han sido seleccionados para


ser aadidos al repositorio (marcados con A). Esta operacin no sube efectivamente
los archivos al servidor. Para subir cualquier cambio se debe hacer un commit:

$ svn commit

A coninuacin, se lanza un editor de texto3 para que se especique un mensaje


que describa los cambios realizados. Adems, tambin incluye un resumen de todas
las operaciones que se van a llevar a cabo en el commit (en este caso, slo se aaden
elementos). Una vez terminada la edicin, se salva y se sale del editor y la carga
comenzar.
Cada commit aumenta en 1 el nmero de revisin. Ese nmero ser el que podre-
mos utilizar para volver a versiones anteriores del proyecto utilizando:
3 El congurado en la variable de entorno EDITOR.
2.3. Gestin de proyectos y documentacin [53]

C2
$ svn update -r REVISION

Si no se especica la opcin -r, la operacin update trae la ltima revisin


(head).
En caso de que otro usuario haya modicado los mismos archivos y lo haya subido
antes al repositorio central, al hacerse el commit se detectar un conicto. Como ejem-
plo, supngase que el cliente user2 ejecuta lo siguiente:

$ svn checkout svn+ssh://user2@myserver:/var/repo/myproject


$ echo "I change this file" > other_file
$ svn commit
Committed revision X+1.

Y que el cliente user1, que est en la versin X, ejecuta lo siguiente:

$ echo "I change the content" > doc/new_file


$ svn remove other_file
D other_file
$ svn commit
svn: Commit failed (details follow):
svn: File other_file is out of date

Para resolver el conicto, el cliente user1 debe actualizar su versin:

$ svn update
C other_file
At revision X+1.

La marca C indica que other_file queda en conicto y que debe resolverse


manualmente. Para resolverlo, se debe editar el archivo donde Subversion marca las
diferencias con los smbolos < y >.
Tambin es posible tomar como solucin el revertir los cambios realizados por
user1 y, de este modo, aceptar los de user2:

$ svn revert other_file


$ svn commit
Committed revision X+2.

Ntese que este commit slo aade los cambios hechos en new_file, aceptando
los cambios en other_file que hizo user2.

Mercurial

Como se ha visto, en los VCS centralizados como Subversion no se permite, por


ejemplo, que los clientes hagan commits si no estn conectados con el servidor cen-
tral. Los VCS como Mercurial (HG), permiten que los clientes tengan un repositorio
local, con su versin modicada del proyecto y la sincronizacin del mismo con otros
servidores (que pueden ser tambin clientes).
Para crear un repositorio Mercurial, se debe ejecutar lo siguiente:

$ hg init /home/user1/myproyect

Al igual que ocurre con Subversion, este directorio debe ser accesible median-
te algn mecanismo (preferiblemente, que sea seguro) para que el resto de usuarios
Figura 2.8: Logotipo del proyecto pueda acceder. Sin embargo, el usuario user1 puede trabajar directamente sobre ese
Mercurial. directorio.
[54] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Para obtener una versin inicial, otro usuario (user2) debe clonar el repositorio.
Basta con ejecutar lo siguiente:

$ hg clone ssh://user2@host//home/user1/myproyect

A partir de este instante, user2 tiene una versin inicial del proyecto extrada a
partir de la del usuario user1. De forma muy similar a Subversion, con la orden add
se pueden aadir archivos y directorios.
Mientras que en el modelo de Subversion, los clientes hacen commit y update
para subir cambios y obtener la ltima versin, respectivamente; en Mercurial es algo
ms complejo, ya que existe un repositorio local. Como se muestra en la gura 2.9, la
operacin commit (3) sube los cambios a un repositorio local que cada cliente tiene.
Cada commit se considera un changeset, es decir, un conjunto de cambios agrupa-
dos por un mismo ID de revisin. Como en el caso de Subversion, en cada commit se
pedir una breve descripcin de lo que se ha modicado.

Figura 2.9: Esquema del ujo de trabajo bsico en Mercurial

Una vez hecho todos commits, para llevar estos cambios a un servidor remoto se
debe ejecutar la orden de push (4). Siguiendo con el ejemplo, el cliente user2 lo
enviar por defecto al repositorio del que hizo la operacin clone.
El sentido inverso, es decir, traerse los cambios del servidor remoto a la copia
local, se realiza tambin en 2 pasos: pull (1) que trae los cambios del repositorio
remoto al repositorio local; y update (2), que aplica dichos cambios del repositorio
local al directorio de trabajo. Para hacer los dos pasos al mismo tiempo, se puede hacer
lo siguiente:

$ hg pull -u

Para evitar conictos con otros usuarios, una buena costumbre antes de reali-
zar un push es conveniente obtener los posibles cambios en el servidor con
pull y update.
2.3. Gestin de proyectos y documentacin [55]

C2
Para ver cmo se gestionan los conictos en Mercurial, supngase que user1
realiza lo siguiente:

$ echo "A file" > a_file


$ hg add a_file
$ hg commit

Al mismo tiempo, user2 ejecuta lo siguiente:

$ echo "This is one file" > a_file


$ hg add a_file
$ hg commit
$ hg push
abort: push creates new remote head xxxxxx!
(you should pull and merge or use push -f to force)

Al intentar realizar el push y entrar en conicto, Mercurial avisa de ello detenien-


do la carga. En este punto se puede utilizar la opcin -f para forzar la operacin de
push, lo cual creara un nuevo head. Como resultado, se creara una nueva rama a
partir de ese conicto de forma que se podra seguir desarrollando omitiendo el con-
icto. Si en el futuro se pretende unir los dos heads se utilizan las operaciones merge
y resolve.
La otra solucin, y normalmente la ms comn, es obtener los cambios con pull,
unicar heads (merge), resolver los posibles conictos manualmente si es necesario
(resolve), hacer commit de la solucin dada (commit) y volver a intentar la subida
(push):
hgview
$ hg pull
adding changesets
hgview es una herramienta grca
adding manifests
que permite visualizar la evolucin
adding file changes
de las ramas y heads de un proyecto
added 2 changesets with 1 changes to 1 files (+1 heads)
que usa Mercurial.
(run hg heads to see heads, hg merge to merge)
$ hg merge
merging a_file
warning: conflicts during merge.
merging a_file failed!
0 files updated, 0 files merged, 0 files removed, 1 files unresolved
$ hg resolve -a
$ hg commit
$ hg push

Para realizar cmodamente la tarea de resolver los conictos manualmente


existen herramientas como meld que son invocadas automticamente por
Mercurial cuando se encuentran conictos de este tipo.

Git

Diseado y desarrollado por Linus Torvalds para el proyecto del kernel Linux, Git
es un VCS distribuido que cada vez es ms utilizado por la comunidad de desarrolla-
dores. En trminos generales, tiene una estructura similar a Mercurial: independencia
entre repositorio remotos y locales, gestin local de cambios, etc.
Sin embargo, Git es en ocasiones preferido sobre Mercurial por algunas de sus
caractersticas propias:
[56] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

Eciencia y rapidez a la hora de gestionar grandes cantidades de archivos. Git


no funciona peor conforme la historia del repositorio crece.
Facilita el desarrollo no lineal, es decir, el programador puede crear ramas loca-
les e integrar los cambios entre diferentes ramas (tanto remotas como locales) de
una forma simple y con muy poco coste. Git est diseado para que los cambios
puedan ir de rama a rama y ser revisados por diferentes usuarios.
Diseado como un conjunto de pequeas herramientas, la mayora escritas en
C, que pueden ser compuestas entre ellas para hacer tareas ms complejas.

En general, Git proporciona ms exibilidad al usuario, permitiendo hacer tareas


complejas y de grano no (como la divisin de un cambio en diferentes cambios) y al
mismo tiempo es eciente y escalable para proyectos con gran cantidad de archivos y
usuarios concurrentes.
Un repositorio Git est formado, bsicamente, por un conjunto de objetos commit Figura 2.10: Logotipo del proyecto
y un conjunto de referencias a esos objetos llamadas heads. Un commit es un concepto Git.
similar a un changeset de Mercurial y se compone de las siguientes partes:

El conjunto de cheros que representan al proyecto en un momento concreto.


Referencias a los commits padres.
Un nombre formado a partir del contenido (usando el algoritmo SHA1).

Cada head apunta a un commit y tiene un nombre simblico para poder ser re-
ferenciado. Por defecto, todos los respositorios Git tienen un head llamado master.
HEAD (ntese todas las letras en mayscula) es una referencia al head usado en cada
instante. Por lo tanto, en un repositorio Git, en un estado sin cambios, HEAD apuntar
al master del respositorio.
Para crear un repositorio donde sea posible que otros usuarios puedan subir cam-
bios se utiliza la orden init:

$ git init --bare /home/user1/myproyect

La opcin -bare indica a Git que se trata de un repositorio que no va a almacenar


una copia de trabajo de usuario, sino que va a actuar como sumidero de cambios de
los usuarios del proyecto. Este directorio deber estar accesible para el resto de los
usuarios utilizando algn protocolo de comunicacin soportado por Git (ssh, HTTP,
etc.).
El resto de usuarios pueden obtener una copia del repositorio utilizando la siguien-
te orden:

$ git clone ssh://user2@host/home/user1/myproyect

A modo de ejemplo ilustrativo, se podra realizar la operacin clone de la si-


guiente forma:

$ git init /home/user2/myproyect


$ cd /home/user2/myproyect
$ git remote add -t master origin ssh://user2@host/home/user1/
myproyect
$ git pull
2.3. Gestin de proyectos y documentacin [57]

C2
Figura 2.11: Esquema del ujo de trabajo bsico en Git

De esta manera, una vez creado un repositorio Git, es posible recongurar en la


URL donde se conectarn las rdenes pull y fetch para descargar el contenido.
En la gura 2.11 se muestra un esquema general del ujo de trabajo y las rdenes
asociadas.
En Git se introduce el espacio stage (tambin llamado index o cache) que acta
como paso intermedio entre la copia de trabajo del usuario y el repositorio local. Slo
se pueden enviar cambios (commits) al repositorio local si stos estn previamente en
el stage. Por ello, todos los nuevos archivos y/o modicaciones que se realicen deben
ser aadidas al stage antes.
La siguiente secuencia de comandos aaden un archivo nuevo al repositorio local.
Ntese que add slo aade el archivo al stage. Hasta que no se realice commit no
llegar a estar en el repositorio local:

$ echo "Test example" > example


$ git add example
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: example
#

$ git commit -m "Test example: initial version"


[master 2f81676] Test example: initial version
1 file changed, 1 insertion(+)
create mode 100644 example

$ git status
# On branch master
nothing to commit, working directory clean

Ntese como la ltima llamada a status no muestra ningn cambio por subir al
repositorio local. Esto signica que la copia de trabajo del usuario est sincronizada
con el repositorio local (todava no se han realizado operaciones con el remoto). Se
puede utilizar reflog para ver la historia:
[58] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

$ git reflog
2f81676 HEAD@{0}: commit: Test example: initial version
...

diff se utiliza para ver cambios entre commits, ramas, etc. Por ejemplo, la si-
guiente orden muestra las diferencias entre master del repositorio local y master del
remoto:

$ git diff master origin/master


diff --git a/example b/example
new file mode 100644
index 0000000..ac19bf2
--- /dev/null
+++ b/example
@@ -0,0 +1 @@
+Test example

Para modicar cdigo y subirlo al repositorio local se sigue el mismo procedi- gitk
miento: (1) realizar la modicacin, (2) usar add para aadir el archivo cambiado,
(3) hacer commit para subir el cambio al repositorio local. Sin embargo, como se ha Para entender mejor estos concep-
dicho anteriormente, una buena caracterstica de Git es la creacin y gestin de ramas tos y visualizarlos durante el proce-
(branches) locales que permiten hacer un desarrollo en paralelo. Esto es muy til ya so de desarrollo, existen herramien-
tas grcas como gitk que permi-
que, normalmente, en el ciclo de vida de desarrollo de un programa se debe simulta- ten ver todos los commits y heads
near tanto la creacin de nuevas caractersticas como arreglos de errores cometidos. en cada instante.
En Git, estas ramas no son ms que referencias a commits, por lo que son muy ligeras
y pueden llevarse de un sitio a otro de forma sencilla.
Como ejemplo, la siguiente secuencia de rdenes crea una rama local a partir del
HEAD, modica un archivo en esa rama y nalmente realiza un merge con master:

$ git checkout -b "NEW-BRANCH"


Switched to a new branch NEW-BRANCH

$ git branch
* NEW-BRANCH
master

$ emacs example # se realizan las modificaciones


$ git add example
$ git commit -m "remove example"
[NEW-BRANCH 263e915] remove example
1 file changed, 1 insertion(+), 1 deletion(-)

$ git checkout master


Switched to branch master
$ git branch
NEW-BRANCH
* master
$ git merge NEW-BRANCH
Updating 2f81676..263e915
Fast-forward
example | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

Ntese cmo la orden branch muestra la rama actual marcada con el smbolo
*. Utilizando las rdenes log y show se pueden listar los commits recientes. Estas
rdenes aceptan, adems de identicadores de commits, ramas y rangos temporales
de forma que pueden obtenerse gran cantidad de informacin de ellos.
Finalmente, para desplegar los cambios en el repositorio remoto slo hay que uti-
lizar:

$ git push
2.3. Gestin de proyectos y documentacin [59]

C2
Git utiliza cheros como .gitconfig y .gitignore para cargar con-
guraciones personalizadas e ignorar cheros a la hora de hacer los commits,
respectivamente. Son muy tiles. Revisa la documentacin de git-config
y gitignore para ms informacin.

2.3.2. Documentacin
Uno de los elementos ms importantes que se generan en un proyecto es la do-
cumentacin: cualquier elemento que permita entender mejor tanto el proyecto en su
totalidad como sus partes, de forma que facilite el proceso de mantenimiento en el
futuro. Adems, una buena documentacin har ms sencilla la reutilizacin de com-
ponentes.
Existen muchos formatos de documentacin que pueden servir para un proyecto
software. Sin embargo, muchos de ellos, tales como PDF, ODT, DOC, etc., son for-
matos binarios por lo que no son aconsejables para utilizarlos en un VCS. Adems,
utilizando texto plano es ms sencillo crear programas que automaticen la generacin
de documentacin, de forma que se ahorre tiempo en este proceso.
Por ello, aqu se describen algunas formas de crear documentacin basadas en
texto plano. Obviamente, existen muchas otras y, seguramente, sean tan vlidas como
las que se proponen aqu.

Doxygen

Doxygen es un sistema que permite generar la documentacin utilizando analiza-


dores de cdigo que averiguan la estructura de mdulos y clases, as como las funcio-
nes y los mtodos utilizados. Adems, se pueden realizar anotaciones en los comenta-
rios del cdigo que sirven para aadir informacin ms detallada. La principal ventaja
es que se vale del propio cdigo fuente para generar la documentacin. Adems, si se
aaden comentarios en un formato determinado, es posible ampliar la documentacin
generada con notas y aclaraciones sobre las estructuras y funciones utilizadas.

Algunos piensan que el uso de programas como Doxygen es bueno porque


obliga a comentar el cdigo. Otros piensan que no es as ya que los co-
mentarios deben seguir un determinado formato, dejando de ser comentarios
propiamente dichos.

El siguiente fragmento de cdigo muestra una clase en C++ documentada con el


formato de Doxygen:

Listado 2.21: Clase con comentarios Doxygen


1 /**
2 This is a test class to show Doxygen format documentation.
3 */
4
5 class Test {
6 public:
7 /// The Test constructor.
[60] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

8 /**
9 \param s the name of the Test.
10 */
11 Test(string s);
12
13 /// Start running the test.
14 /**
15 \param max maximum time of test delay.
16 \param silent if true, do not provide output.
17 \sa Test()
18 */
19 int run(int max, bool silent);
20 };

Por defecto, Doxygen genera la documentacin en HTML y basta con ejecutar la


siguiente orden en el raz del cdigo fuente para obtener una primera aproximacin:

$ doxygen .

reStructuredText

reStructuredText (RST) es un formato de texto bsico que permite escribir texto


plano aadiendo pequeas anotaciones de formato de forma que no se pierda legibi-
lidad. Existen muchos traductores de RST a otros formatos como PDF (rst2pdf)
y HTML (rst2html), que adems permiten modicar el estilo de los documentos
generados.
El formato RST es similar a la sintaxis de los sistema tipo wiki. Un ejemplo de
archivo en RST puede ser el siguiente:

Listado 2.22: Archivo en RST


1 ================
2 The main title
3 ================
4
5 This is an example of document in ReStructured Text (RST). You can
get
6 more info about RST format at RST Reference
7 <http://
docutils.sourceforge.net/docs/ref/rst/restructuredtext.html>_.
8
9 Other section
10 =============
11
12 You can use bullet items:
13
14 - Item A
15
16 - Item B
17
18 And a enumerated list:
19
20 1. Number 1
21
22 2. Number 2
23
24 Tables
25 ------
26
27 +--------------+----------+-----------+-----------+
28 | row 1, col 1 | column 2 | column 3 | column 4 |
29 +--------------+----------+-----------+-----------+
2.3. Gestin de proyectos y documentacin [61]

C2
30 | row 2 | | | |
31 +--------------+----------+-----------+-----------+
32
33 Images
34 ------
35
36 .. image:: gnu.png
37 :scale: 80
38 :alt: A title text

Como se puede ver, aunque RST aade una sintaxis especial, el texto es completa-
mente legible. sta es una de las ventajas de RST, el uso de etiquetas de formato que
no ensucian demasiado el texto.

YAML

YAML (YAML Aint Markup Language)4 es un lenguaje diseado para serializar


datos procedentes de aplicaciones en un formato que sea legible para los humanos.
Estrictamente, no se trata de un sistema para documentacin, sin embargo, y debido
a lo cmodo de su sintaxis, puede ser til para exportar datos, cargar los mismos en
el programa, representar conguraciones, etc. Otra de sus ventajas es que hay un gran
nmero de bibliotecas en diferentes lenguajes (C++, Python, Java, etc.) para tratar
informacin YAML. Las libreras permiten automticamente salvar las estructuras de
datos en formato YAML y el proceso inverso: cargar estructuras de datos a partir del
YAML.
En el ejemplo siguiente, extrado de la documentacin ocial, se muestra una fac-
tura. De un primer vistazo, se puede ver qu campos forman parte del tipo de dato
factura tales como invoice, date, etc. Cada campo puede ser de distintos tipo co-
mo numrico, booleano o cadena de caracteres, pero tambin listas (como product)
o referencias a otros objetos ya declarados (como ship-to).

Listado 2.23: Archivo en YAML


1 --- !<tag:clarkevans.com,2002:invoice>
2 invoice: 34843
3 date : 2001-01-23
4 bill-to: &id001
5 given : Chris
6 family : Dumars
7 address:
8 lines: |
9 458 Walkman Dr.
10 Suite #292
11 city : Royal Oak
12 state : MI
13 postal : 48046
14 ship-to: *id001
15 product:
16 - sku : BL394D
17 quantity : 4
18 description : Basketball
19 price : 450.00
20 - sku : BL4438H
21 quantity : 1
22 description : Super Hoop
23 price : 2392.00
24 tax : 251.42
25 total: 4443.52
26 comments:
4 http://www.yaml.org
[62] CAPTULO 2. HERRAMIENTAS DE DESARROLLO

27 Late afternoon is best.


28 Backup contact is Nancy
29 Billsmer @ 338-4338.

2.3.3. Forjas de desarrollo


Hasta ahora, se han mostrado herramientas especcas que permiten crear y ges-
tionar los elementos ms importantes de un proyecto software: los archivos que lo
forman y su documentacin. Sin embargo, existen otras como la gestin de tareas,
los mecanismos de noticacin de errores o los sistemas de comunicacin con los
usuarios que son de utilidad en un proyecto.
Las forjas de desarrollo son sistemas colaborativos que integran no slo herramien-
tas bsicas para la gestin de proyectos, como un VCS, sino que suelen proporcionar
herramientas para:

Planicacin y gestin de tareas: permite anotar qu tareas quedan por hacer


y los plazos de entrega. Tambin suelen permitir asignar prioridades.
Planicacin y gestin de recursos: ayuda a controlar el grado de ocupacin
del personal de desarrollo (y otros recursos).
Seguimiento de fallos: tambin conocido como bug tracker, es esencial para
llevar un control sobre los errores encontrados en el programa. Normalmente,
permiten gestionar el ciclo de vida de un fallo, desde que se descubre hasta que
se da por solucionado.
Foros: normalmente, las forjas de desarrollo permiten administrar varios foros
de comunicacin donde con la comunidad de usuarios del programa pueden
escribir propuestas y noticar errores.

Las forjas de desarrollo suelen ser accesibles via web, de forma que slo sea ne-
cesario un navegador para poder utilizar los diferentes servicios que ofrece. Depen-
diendo de la forja de desarrollo, se ofrecern ms o menos servicios. Sin embargo, los
expuestos hasta ahora son los que se proporcionan habitualmente. Existen forjas gra-
tuitas en Internet que pueden ser utilizadas para la creacin de un proyecto. Algunas
de ellas:

GNA5 : es una forja de desarrollo creada por la Free Software Foundation de


Francia que soporta, actualmente, repositorios CVS, GNU Arch y Subversion.
Los nuevos proyectos son estudiados cuidadosamente antes de ser autorizados.
Launchpad6 : forja gratuita para proyectos de software libre creada por Cano-
nical Ltd. Se caracteriza por tener un potente sistema de bug tracking y propor-
cionar herramientas automticas para despliegue en sistemas Debian/Ubuntu.
BitBucket7 : forja de la empresa Atlassian que ofrece repositorios Mercurial y
Git. Permite crear proyectos privados gratuitos pero con lmite en el nmero de
desarrolladores por proyecto.
GitHub8 : forja proporcionada por GitHub Inc. que utiliza repositorios Git. Es
gratuito siempre que el proyecto sea pblico, es decir, pueda ser descargado y
modicado por cualquier usuario de Github sin restriccin.
5 http://gna.org
6 https://launchpad.net/
7 http://bitbucket.org
8 http://github.com
2.3. Gestin de proyectos y documentacin [63]

C2
SourceForge9 : probablemente, una de las forjas gratuitas ms conocidas. Pro-
piedad de la empresa GeekNet Inc., soporta Subversion, Git, Mercurial, Bazaar
y CVS.
Google Code10 : la forja de desarrollo de Google que soporta Git, Mercurial y
Subversion.

Redmine

Figura 2.12: Aspecto de la herramienta de gestin de tareas de Redmine

Adems de los servicios gratuitos presentados, existe gran variedad de software


que puede ser utilizado para gestionar un proyecto de forma que pueda ser instalado
en un servidor personal y as no depender de un servicio externo. Tal es el caso de
Redmine (vase gura 2.12) que entre las herramientas que proporciona cabe destacar
las siguientes caractersticas:

Permite crear varios proyectos. Tambin es congurable qu servicios se pro-


porcionan en cada proyecto: gestor de tareas, tracking de fallos, sistema de do-
cumentacin wiki, etc.
Integracin con repositorios, es decir, el cdigo es accesible a travs de Red-
mine y se pueden gestionar tareas y errores utilizando los comentarios de los
commits.
Gestin de usuarios que pueden utilizar el sistema y sus polticas de acceso.
Est construido en Ruby y existe una amplia variedad de plugins que aaden
funcionalidad extra.

9 http://sourceforge.net
10 http://code.google.com
C++. Aspectos Esenciales
Captulo 3
David Vallejo Fernndez

E
l lenguaje ms utilizado para el desarrollo de videojuegos comerciales es C++,
debido especialmente a su potencia, eciencia y portabilidad. En este captulo
se hace un recorrido por C++ desde los aspectos ms bsicos hasta las he-
rramientas que soportan la POO (Programacin Orientada a Objetos) y que permiten
disear y desarrollar cdigo que sea reutilizable y mantenible, como por ejemplo las
plantillas y las excepciones.

3.1. Utilidades bsicas

POO En esta seccin se realiza un recorrido por los aspectos bsicos de C++, hacien-
do especial hincapi en aquellos elementos que lo diferencian de otros lenguajes de
La programacin orientada a obje- programacin y que, en ocasiones, pueden resultar ms complicados de dominar por
tos tiene como objetivo la organi-
zacin ecaz de programas. Bsi- aquellos programadores inexpertos en C++.
camente, cada componente es un
objeto autocontenido que tiene una
serie de operaciones y de datos o
estado. Este planteamiento permi-
3.1.1. Introduccin a C++
te reducir la complejidad y gestio-
nar grandes proyectos de programa- C++ se puede considerar como el lenguaje de programacin ms importante en la
cin. actualidad. De hecho, algunas autores relevantes [81] consideran que si un programa-
dor tuviera que aprender un nico lenguaje, ste debera ser C++. Aspectos como su
sintaxis y su losofa de diseo denen elementos clave de programacin, como por
ejemplo la orientacin a objetos. C++ no slo es importante por sus propias caracters-
ticas, sino tambin porque ha sentado las bases para el desarrollo de futuros lenguajes
de programacin. Por ejemplo, Java o C# son descendientes directos de C++. Desde el
punto de vista profesional, C++ es sumamente importante para cualquier programador.

65
[66] CAPTULO 3. C++. ASPECTOS ESENCIALES

El origen de C++ est ligado al origen de C, ya que C++ est construido sobre
C. De hecho, C++ es un superconjunto de C y se puede entender como una versin
extendida y mejorada del mismo que integra la losofa de la POO y otras mejoras,
como por ejemplo la inclusin de un conjunto amplio de bibliotecas. Algunos autores
consideran que C++ surgi debido a la necesidad de tratar con programas de mayor
complejidad, siendo impulsado en gran parte por la POO.
C++ fue diseado por Bjarne Stroustrup1 en 1979. La idea de Stroustrup fue
aadir nuevos aspectos y mejoras a C, especialmente en relacin a la POO, de manera
que un programador de C slo que tuviera que aprender aquellos aspectos relativos a
la OO.
En el caso particular de la industria del videojuego, C++ se puede considerar co-
mo el estndar de facto debido principalmente a su eciencia y portabilidad. C++ es
una de los pocos lenguajes que posibilitan la programacin de alto nivel y, de manera
simultnea, el acceso a los recursos de bajo nivel de la plataforma subyacente. Por lo
tanto, C++ es una mezcla perfecta para la programacin de sistemas y para el desarro-
llo de videojuegos. Una de las principales claves a la hora de manejarlo ecientemente
en la industria del videojuego consiste en encontrar el equilibrio adecuado entre e-
ciencia, abilidad y mantenibilidad [27].
Figura 3.1: Bjarne Stroustrup,
creador del lenguaje de programa-
3.1.2. Hola Mundo! en C++ cin C++ y personaje relevante en
el mbito de la programacin.
A continuacin se muestra el clsico Hola Mundo! implementado en C++. En
este primer ejemplo, se pueden apreciar ciertas diferencias con respecto a un programa Dominar C++
escrito en C. Una de las mayores ventajas de
C++ es que es extremadamente po-
tente. Sin embargo, utilizarlo e-
Listado 3.1: Hola Mundo en C++ cientemente es dcil y su curva de
aprendizaje no es gradual.
1 /* Mi primer programa con C++. */
2
3 #include <iostream>
4 using namespace std;
5
6 int main () {
7
8 string nombre;
9
10 cout << "Por favor, introduzca su nombre... ";
11 cin >> nombre;
12 cout << "Hola " << nombre << "!"<< endl;
13
14 return 0;
15
16 }


La directiva include de la lnea 3 incluye la biblioteca
<iostream>, la cual soporta
el sistema de E/S de C++. A continuacin, en la 4 el programa le indica al compila-
dor que utilice el espacio de nombres std, en el que se declara la biblioteca estndar de
C++. Un espacio de nombres delimita una zona de declaracin en la que incluir dife-
rentes elementos de un programa. Los espacios de nombres siguen la misma losofa
que los paquetes en Java y tienen como objetivo organizar los programas. El hecho de
utilizar un espacio de nombres permite acceder a sus elementos y funcionalidades sin
tener que especicar a qu espacio pertenecen.
1 http://www2.research.att.com/~bs/
3.1. Utilidades bsicas [67]


En la lnea 10 de hace uso de cout (console output), la sentencia de salida por

C3
consola junto con el operador <<, redireccionando lo que queda a su derecha, es
decir, Por favor, introduzca su nombre..., hacia la salida por consola. A continuacin,
en la siguiente lnea se hace uso de cin (console input) junto con el operador >>
para redirigir la entrada proporcionado por teclado a la variable nombre. Note que
dicha variable es de tipo cadena, un tipo de datos de C++ que se dene como
un array
de caracteres nalizado con el carcter null. Finalmente, en la lnea 12 se saluda al
lector, utilizando adems la sentencia endl para aadir un retorno de carro a la salida
y limpiar el buffer.

3.1.3. Tipos, declaraciones y modicadores


En C++, los tipos de datos bsicos y sus declaraciones siguen un esquema muy
similar al de otros lenguajes de programacin, como por ejemplo Java. En este caso,
existen siete tipos de datos bsicos: carcter, carcter amplio, entero, punto otante,
punto otante de doble precisin, lgico o booleano y nulo. Recuerde que en C++ es
necesario declarar una variable antes de utilizarla para que el compilador conozca el
tipo de datos asociado a la variable. Al igual que ocurre en otros lenguajes de pro-
gramacin, las variables pueden tener un alcance global o local (por ejemplo en una
funcin).
En el caso particular de los tipos nulos o void, cabe destacar que stos se utilizan
para las funciones con tipo de retorno nulo o como tipo base para punteros a objetos
de tipo desconocido a priori.
C++ permite modicar los tipos char, int y double con los modicadores signed,
unsigned long, y short, con el objetivo de incrementar la precisin en funcin de las
necesidades de la aplicacin. Por ejemplo, el tipo de datos double se puede usar con
el modicador long, y no as con el resto.
C++. Tipado. Por otra parte, C++ tambin posibilita el concepto de constante mediante la pala-
bra reservada const, con el objetivo de expresar que un valor no cambia de manera
C++ es un lenguaje de programa- directa. Por ejemplo, muchos objetos no cambian sus valores despus de inicializar-
cin con tipado esttico, es decir, la
comprobacin de tipos se realiza en los. Adems, las constantes conducen a un cdigo ms mantenible en comparacin
tipo de compilacin y no en tiempo con los valores literales incluidos directamente en el cdigo. Por otra parte, muchos
de ejecucin. punteros tambin se suelen utilizar solamente para operaciones de lectura y, particu-
larmente, los parmetros de una funcin no se suelen modicar sino que simplemente
se consultan.

Como regla general, se debera delegar en el compilador todo lo posible en


relacin a la deteccin de errores.

El uso de const tambin es ventajoso respecto a la directiva de preprocesado #de-


ne, ya que permite que el compilador aplique la tpica prevencin de errores de tipo.
Esto posibilita detectar errores en tiempo de compilacin y evitar problemas poten-
ciales. Adems, la depuracin es mucho ms sencilla por el hecho de manejar los
nombres simblicos de las constantes.
Desreferencia
El acceso al objeto al que seala un
puntero es una de las operaciones
fundamentales y se denomina indi-
reccin o desreferencia, indistinta-
mente.
[68] CAPTULO 3. C++. ASPECTOS ESENCIALES

3.1.4. Punteros, arrays y estructuras


Un puntero es una variable que almacena una direccin de memoria, tpicamente
la localizacin o direccin de otra variable. Si p contiene la direccin de q, entonces p
apunta a q. Los punteros se declaran igual que el resto de variables y estn asociados
a un tipo especco, el cual ha de ser vlido en C++. Un ejemplo de declaracin de
puntero a entero es

int *ip;

En el caso de los punteros, el operador unario * es el utilizado para desreferenciar-


los y acceder a su contenido, mientras que el operador unario & se utiliza para acceder
a la direccin de memoria de su operando. Por ejemplo,

edadptr = &edad;

asigna la direccin de memoria de la variable edad a la variable edadptr. Recuerde


que con el operador * es posible acceder al contenido de la variable a la que apunta un
puntero. Por ejemplo,

miEdad = *edadptr

permite almacenar el contenido de edad en miEdad.


Indireccin mltiple
Un puntero a puntero es un ca-
so de indireccin mltiple. El pri-
Cuando el compilador de C++ encuentra una cadena literal, la almacena en la mer puntero contiene la direccin
de memoria de otro puntero, mien-
tabla de cadenas del programa y genera un puntero a dicha cadena. tras que ste contiene la direccin
de memoria de una variable.

En C++ existe una relacin muy estrecha entre los punteros y los arrays, siendo
posible intercambiarlos en la mayora de casos. El siguiente listado de cdigo muestra
un sencillo ejemplo de indexacin de un array mediante aritmtica de punteros.

Listado 3.2: Indexacin de un array


1 #include <iostream>
2 using namespace std;
3
4 int main () {
5
6 char s[20] = "hola mundo";
7 char *p;
8 int i;
9
10 for (p = s, i = 0; p[i]; i++)
11 p[i] = toupper(p[i]);
12
13 cout << s << endl;
14
15 return 0;
16
17 }


En la inicializacin del bucle for de la lnea 10 se aprecia cmo se asigna la direc-
cin de inicio del array s al puntero p para, posteriormente, indexar el array mediante
p para resaltar en maysculas el contenido del array una vez nalizada la ejecucin
del bucle. Note que C++ permite la inicializacin mltiple.
3.1. Utilidades bsicas [69]

C3
Los punteros son enormemente tiles y potentes. Sin embargo, cuando un puntero
almacena, de manera accidental, valores incorrectos, el proceso de depuracin puede
resultar un autntico quebradero de cabeza. Esto se debe a la propia naturaleza del
puntero y al hecho de que indirectamente afecte a otros elementos de un programa, lo
cual puede complicar la localizacin de errores.
El caso tpico tiene lugar cuando un puntero apunta a algn lugar de memoria que
no debe, modicando datos a los que no debera apuntar, de manera que el programa
muestra resultados indeseables posteriormente a su ejecucin inicial. En estos casos,
cuando se detecta el problema, encontrar la evidencia del fallo no es una tarea trivial,
ya que inicialmente puede que no exista evidencia del puntero que provoc dicho
error. A continuacin se muestran algunos de los errores tpicos a la hora de manejar
punteros [81].
1. No inicializar punteros. En el listado que se muestra a continuacin, p contiene
una direccin desconocida debido a que nunca fue denida. En otras palabras, no es
posible conocer dnde se ha escrito el valor contenido en edad.

Listado 3.3: Primer error tpico. No inicializar punteros.


1 int main () {
2 int edad, *p;
3
4 edad = 23;
5 *p = edad; // p?
6
7 return 0;
8 }

En un programa que tenga una mayor complejidad, la probabilidad de que p apunte


a otra parte de dicho programa se incrementa, con la ms que probable consecuencia
de alcanzar un resultado desastroso.
Punteros y funciones 2. Comparar punteros de forma no vlida. La comparacin de punteros es, ge-
neralmente, invlida y puede causar errores. En otras palabras, no se deben realizar
Recuerde que es posible utilizar
punteros a funciones, es decir, se suposiciones sobre qu direccin de memoria se utilizar para almacenar los datos,
puede recuperar la direccin de me- si siempre ser dicha direccin o si distintos compiladores tratarn estos aspectos del
moria de una funcin para, pos- mismo modo. No obstante, si dos punteros apuntan a miembros del mismo arreglo, en-
teriormente, llamarla. Este tipo de tonces es posible compararlos. El siguiente fragmento de cdigo muestra un ejemplo
punteros se utilizan para manejar
rutinas que se puedan aplicar a dis- de uso incorrecto de punteros en relacin a este tipo de errores.
tintos tipos de objetos, es decir, para
manejar el polimorsmo.
Listado 3.4: Segundo error tpico. Comparacin incorrecta de punteros.
1 int main () {
2 int s[10];
3 int t[10];
4 int *p, *q;
5
6 p = s;
7 q = t;
8
9 if (p < q) {
10 // ...
11 }
12
13 return 0;
14 }
[70] CAPTULO 3. C++. ASPECTOS ESENCIALES

No olvide que una de las claves para garantizar un uso seguro de los punteros
consiste en conocer en todo momento hacia dnde estn apuntando.

3. Olvidar el reseteo de punteros. El siguiente listado de cdigo muestra otro


error tpico, que se puede resumir en no controlar el comportamiento de un puntero.
Bsicamente, la primera vez que se ejecuta el bucle, p apunta al comienzo de s. Sin
embargo, en la segunda iteracin p continua incrementndose debido a que su valor
no se ha establecido al principio de s. La solucin pasa por mover la lnea 11 al bucle
do-while.

Listado 3.5: Tercer error tpico. Olvidar el reseteo de un puntero.


1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4 using namespace std;
5
6 int main () {
7
8 char s[100];
9 char *p;
10
11 p = s;
12
13 do {
14 cout << "Introduzca una cadena... ";
15 fgets(p, 100, stdin);
16
17 while (*p)
18 cout << *p++ << " ";
19
20 cout << endl;
21 }while (strcmp(s, "fin"));
22
23 return 0;
24
25 }

Finalmente, una estructura es un tipo agregado de datos que se puede denir


como una coleccin de variables que mantienen algn tipo de relacin lgica. Obvia-
mente, antes de poder crear objetos de una determinada estructura, sta ha de denirse.
El listado de cdigo siguiente muestra un ejemplo de declaracin de estructura y Punteros y const
de su posterior manipulacin mediante el uso de punteros. Recuerde que el acceso
a los campos de una estructura se realiza mediante el operador echa -> en caso de El uso de punteros y const puede
dar lugar a confusin, en funcin
acceder a ellos mediante un puntero, mientras que el operador punto . se utiliza cuando del lugar en el que se site dicha
se accede de manera directa. palabra clave. Recuerde que const
siempre se reere a lo que se en-
Ntese el uso del modicador const en el segundo parmetro de la funcin modi- cuentra inmediatamente a la dere-
car_nombre que, en este caso, se utiliza para informar al compilador de que dicha cha. As, int* const p es un puntero
variable no se modicar internamente en dicha funcin. Asimismo, en dicha funcin constante, pero no as los datos a los
que apunta.
se hace uso del operador & en relacin a la variable nuevo_nombre. En la siguien-
te subseccin se dene y explica un nuevo concepto que C++ proporciona para la
especicacin de parmetros y los valores de retorno: las referencias.

Listado 3.6: Denicin y uso de estructuras.


1 #include <iostream>
3.1. Utilidades bsicas [71]

C3
2 using namespace std;
3
4 struct persona {
5 string nombre;
6 int edad;
7 };
8
9 void modificar_nombre (persona *p, const string& nuevo_nombre);
10
11 int main () {
12 persona p;
13 persona *q;
14
15 p.nombre = "Luis";
16 p.edad = 23;
17 q = &p;
18
19 cout << q->nombre << endl;
20 modificar_nombre(q, "Sergio");
21 cout << q->nombre << endl;
22
23 return 0;
24 }
25
26 void modificar_nombre (persona *p, const string& nuevo_nombre) {
27 p->nombre = nuevo_nombre;
28 }

3.1.5. Referencias y funciones


En general, existen dos formas de pasar argumentos a una funcin. La primera
se denomina paso por valor y consiste en pasar una copia del valor del argumento
como parmetro a una funcin. Por lo tanto, los cambios realizados por la funcin no
afectan a dicho argumento. Por otra parte, el paso por referencia permite la copia de
la direccin del argumento (y no su valor) en el propio parmetro. Esto implica que
los cambios efectuados sobre el parmetro afectan al argumento utilizado para realizar
la llamada a la funcin.
Paso de parmetros Por defecto, C++ utiliza el paso por valor. Sin embargo, el paso por referencia se
puede realizar utilizando punteros, pasando la direccin de memoria del argumento
Es muy importante distinguir co- externo a la funcin para que sta lo modique (si as est diseada). Este enfoque
rrectamente entre paso de parme-
tros por valor y por referencia para implica hacer un uso explcito de los operadores asociados a los punteros, lo cual
obtener el comportamiento deseado implica que el programador ha de pasar las direcciones de los argumentos a la hora
en un programa y para garantizar de llamar a la funcin. Sin embargo, en C++ tambin es posible indicar de manera
que la eciencia del mismo no se
vea penalizada.
automtica al compilador que haga uso del paso por referencia: mediante el uso de
referencias.
Una referencia es simplemente un nombre alternativo para un objeto. Este con-
cepto tan sumamente simple es en realidad extremadamente til para gestionar la
complejidad. Cuando se utiliza una referencia, la direccin del argumento se pasa
automticamente a la funcin de manera que, dentro de la funcin, la referencia se
desreferencia automticamente, sin necesidad de utilizar punteros. Las referencias se
declaran precediendo el operador & al nombre del parmetro.

Las referencias son muy parecidas a los punteros. Se reeren a un objeto de


manera que las operaciones afectan al objeto al cual apunta la referencia. La
creacin de referencias, al igual que la creacin de punteros, es una operacin
muy eciente.
[72] CAPTULO 3. C++. ASPECTOS ESENCIALES

El siguiente listado de cdigo muestra la tpica funcin swap implementada me-


diante el uso de referencias. Como se puede apreciar, los parmetros pasados por refe-
rencia no hacen uso en ningn momento del operador *, como ocurre con los punteros.
En realidad, el compilador genera automticamente la direccin de los argumentos con
los que se llama a swap, desreferenciando a ambos.

Listado 3.7: Funcin swap con referencias.


1 #include <iostream>
2 using namespace std;
3
4 void swap (int &a, int &b);
5
6 int main () {
7 int x = 7, y = 13;
8
9 cout << "[" << x << ", " << y << "]" << endl; // Imprime [7, 13].
10 swap(x, y);
11 cout << "[" << x << ", " << y << "]" << endl; // Imprime [13, 7].
12
13 return 0;
14 }
15
16 void swap (int &a, int &b) {
17 int aux;
18
19 aux = a; // Guarda el valor al que referencia a.
20 a = b; // Asigna el valor de b a a.
21 b = aux; // Asigna el valor de aux a b.
22 }

En C++, existen ciertas diferencias relevantes entre referencias y punteros [27]:

A la hora de trabajar con una referencia, la sintaxis utilizada es la misma que


con los objetos. En otras palabras, en lugar de desreferenciar con el operador
echa ->, para acceder a variables y funciones se utiliza el operador punto.
Las referencias slo se pueden inicializar una vez. Por el contrario, un puntero
puede apuntar a un determinado objeto y, posteriormente, apuntar a otro distin-
to. Sin embargo, una vez inicializada una referencia, sta no se puede cambiar,
comportndose como un puntero constante.
Las referencias han de inicializarse tan pronto como sean declaradas. Al con-
trario que ocurre con los punteros, no es posible crear una referencia y esperar
para despus inicializarla.
Las referencias no pueden ser nulas, como consecuencia directa de los dos pun-
tos anteriores. Sin embargo, esto no quiere decir que el elemento al que referen-
cian siempre sea vlido. Por ejemplo, es posible borrar el objeto al que apunta
una referencia e incluso truncarla mediante algn molde para que apunte a null.
Las referencias no se pueden crear o eliminar como los punteros. En ese sentido,
son iguales que los objetos.
3.1. Utilidades bsicas [73]

C3
Las funciones tambin pueden devolver referencias. En C++, una de las mayores
utilidades de esta posibilidad es la sobrecarga de operadores. Sin embargo, en el lis-
tado que se muestra a continuacin se reeja otro uso potencial, debido a que cuando
se devuelve una referencia, en realidad se est devolviendo un puntero implcito al
valor de retorno. Por lo tanto, es posible utilizar la funcin en la parte izquierda de
una asignacin.
Funciones y referencias Como se puede apreciar en el siguiente listado, la funcin f devuelve una referen-
cia a un valor en punto otante de doble precisin, en concreto
a la variable global
En una funcin, las referencias se valor. La parte importante del cdigo est en la lnea 15 , en la que valor se actualiza
pueden utilizar como parmetros de
entrada o como valores de retorno. a 7,5, debido a que la funcin devuelve dicha referencia.

Listado 3.8: Retorno de referencias.


1 #include <iostream>
2 using namespace std;
3
4 double &f ();
5
6 double valor = 10.0;
7
8 int main () {
9 double nuevo_valor;
10
11 cout << f() << endl;
12 nuevo_valor = f();
13 cout << nuevo_valor << endl;
14
15 f() = 7.5;
16 cout << f() << endl;
17
18 return 0;
19 }
20
21 double &f () { return valor; }

Aunque se discutir ms adelante, las referencias tambin se pueden utilizar para


devolver objetos desde una funcin de una manera eciente. Sin embargo, hay que
ser cuidadoso con la referencia a devolver, ya que si se asigna a un objeto, entonces
se crear una copia. El siguiente fragmento de cdigo muestra un ejemplo represen-
tativo vinculado al uso de matrices de 16 elementos, estructuras de datos tpicamente
utilizada en el desarrollo de videojuegos.

Listado 3.9: Retorno de referencias. Copia de objetos


1 const Matrix4x4 &GameScene::getCameraRotation () const
2 {
3 return c_rotation; // Eficiente. Devuelve una referencia.
4 }
5
6 // Cuidado! Se genera una copia del objeto.
7 Matrix4x4 rotation = camera.getCameraRotation;
8
9 // Eficiente.
10 Matrix4x4 &rotation = camera.getCameraRotation;
[74] CAPTULO 3. C++. ASPECTOS ESENCIALES

Las ventajas de las referencias sobre los punteros se pueden resumir en que utili-
zar referencias es una forma de ms alto nivel de manipular objetos, ya que permite al
desarrollador olvidarse de los detalles de gestin de memoria y centrarse en la lgica
del problema a resolver. Aunque pueden darse situaciones en las que los punteros son
ms adecuados, una buena regla consiste en utilizar referencias siempre que sea posi-
ble, ya que su sintaxis es ms limpia que la de los punteros y su uso es menos proclive
a errores.

Siempre que sea posible, es conveniente utilizar referencias, debido a su sen-


cillez, manejabilidad y a la ocultacin de ciertos aspectos como la gestin de
memoria.

Quiz el uso ms evidente de un puntero en detrimento de una referencia consiste


en la creacin o eliminacin de objetos de manera dinmica. En este caso, el mante-
nimiento de dicho puntero es responsable de su creador. Si alguien hace uso de dicho
puntero, debera indicar explcitamente que no es responsable de su liberacin.
Por otra parte, si se necesita cambiar el objeto al que se referencia, entonces tam-
bin se hace uso de punteros, ya que las referencias no se pueden reasignar. En otros
casos, el desarrollador tambin puede asumir el manejo de un puntero nulo, devol-
vindolo cuando una funcin gener algn error o incluso para manejar parmetros
que sean opcionales. En estos casos, las referencias tampoco se pueden utilizar.
Finalmente, otra razn importante para el manejo de punteros en lugar de referen-
cias est vinculada a la aritmtica de punteros, la cual se puede utilizar para iterar sobre
una regin de memoria. Sin embargo, este mecanismo de bajo nivel tiene el riesgo de
generar bugs y su mantenimiento puede ser tedioso. En general, debera evitarse cuan-
do as sea posible. En la prctica, la aritmtica de punteros se puede justicar debido
a su elevada eciencia en lugar de realizar una iteracin que garantice la integridad de
los tipos [27].

3.2. Clases

3.2.1. Fundamentos bsicos


En el mbito de la POO, las clases representan una manera de asociar datos con
funcionalidad. Los objetos son las instancias especcas de una clase, de manera que
cada una tiene sus propios datos pero todas ellas comparten la misma funcionalidad a
nivel de clase.
La parte de datos en una clase de C++ no diere de una estructura en C. Sin em- Destruccin de objetos
bargo, C++ ofrece tres niveles de acceso a los datos: pblicos, privados o protegidos.
Recuerde que los objetos creados
Por defecto, los miembros de una clase son privados, mientras que en una estructura dentro de un bloque se destruyen
son pblicos. cuando dicho bloque se abandona
por el ujo del programa. Por el
Debido a que la mayora de los objetos requieren una inicializacin de su esta- contrario, los objetos globales se
do, C++ permite inicializar los objetos cuando estos son creados mediante el uso de destruyen cuando el programa na-
constructores. Del mismo modo, C++ contempla el concepto de destructor para con- liza su ejecucin.
templar la posibilidad de que un objeto realice una serie de operaciones antes de ser
destruido. El siguiente listado de cdigo muestra la especicacin de una clase en C++
con los miembros de dicha clase, su visibilidad, el constructor, el destructor y otras
funciones.
3.2. Clases [75]

C3
Listado 3.10: Clase Figura
1 class Figura
2 {
3 public:
4 Figura (double i, double j);
5 ~Figura ();
6
7 void setDim (double i, double j);
8 double getX () const;
9 double getY () const;
10
11 protected:
12 double _x, _y;
13 };

Note cmo las variables de clase se denen como protegidas, es decir, con una vi-
sibilidad privada fuera de dicha clase a excepcin de las clases que hereden de Figura,
tal y como se discutir en la seccin 3.3.1. El constructor y el destructor comparten el
nombre con la clase, pero el destructor tiene delante el smbolo ~.
Paso por referencia El resto de funciones sirven para modicar y acceder al estado de los objetos ins-
tanciados a partir de dicha clase. Note el uso del modicador const en las funciones
Recuerde utilizar parmetros por de acceso getX() y getY(), con el objetivo de informar de manera explcita al compila-
referencia const para minimizar el
nmero de copias de los mismos. dor de que dichas funciones no modican el estado de los objetos. A continuacin, se
muestra la implementacin de las funciones denidas en la clase Figura.

Recuerde estructurar adecuademente su cdigo y seguir un convenio de nom-


brado que facilite su mantenibilidad. Si se integra en un proyecto activo, pro-
cure seguir el convenio previamente adoptado.

Uso de inline Antes de continuar discutiendo ms aspectos de las clases, resulta interesante in-
El modicador inline se suele in- troducir brevemente el concepto de funciones en lnea (inlining), una tcnica que
cluir despus de la declaracin de la puede reducir la sobrecarga implcita en las llamadas a funciones. Para ello, slo es
funcin para evitar lneas de cdigo necesario incluir el modicador inline delante de la declaracin de una funcin. Es-
demasiado largas (siempre dentro
del archivo de cabecera). Sin em-
ta tcnica permite obtener exactamente el mismo rendimiento que el acceso directo
bargo, algunos compiladores obli- a una variable sin tener que desperdiciar tiempo en ejecutar la llamada a la funcin,
gan a incluirlo en ambos lugares. interactuar con la pila del programa y volver de dicha funcin.
Las funciones en lnea no se pueden usar indiscriminadamente, ya que pueden
degradar el rendimiento de la aplicacin fcilmente. En primer lugar, el tamao del
ejecutable nal se puede disparar debido a la duplicidad de cdigo. As mismo, la
cach de cdigo tambin puede hacer que dicho rendimiento disminuya debido a las
continuas penalizaciones asociadas a incluir tantas funciones en lnea. Finalmente, los
tiempos de compilacin se pueden incrementar en grandes proyectos.
[76] CAPTULO 3. C++. ASPECTOS ESENCIALES

Listado 3.11: Clase Figura (implementacin)


1 #include "Figura.h"
2
3 Figura::Figura
4 (double i, double j)
5 {
6 _x = i;
7 _y = j;
8 }
9
10 Figura::~Figura ()
11 {
12 }
13
14 void
15 Figura::setDim
16 (double i, double j)
17 {
18 _x = i;
19 _y = j;
20 }
21
22 double
23 Figura::getX () const
24 {
25 return _x;
26 }
27
28 double
29 Figura::getY () const
30 {
31 return _y;
32 }

Una buena regla para usar de manera adecuada el modicador inline consiste
en evitar su uso hasta prcticamente completar el desarrollo de un proyecto. A
continuacin, se puede utilizar alguna herramienta de proling para detectar
si alguna funcin sencilla est entre las ms utilidas. Este tipo de funciones
son candidatas potenciales para modicarlas con inline y, en consecuencia,
elementos para mejorar el rendimiento del programa.

Al igual que ocurre con otros tipos de datos, los objetos tambin se pueden mani-
pular mediante punteros. Simplemente se ha de utilizar la misma notacin y recordar
que la aritmtica de punteros tambin se puede usar con objetos que, por ejemplo,
formen parte de un array.
Para los objetos creados en memoria dinmica, el operador new invoca al cons-
tructor de la clase de manera que dichos objetos existen hasta que explcitamente
se eliminen con el operador delete sobre los punteros asociados. A continuacin se
muestra un listado de cdigo que hace uso de la clase Figura previamente introducida.

Listado 3.12: Manipulacin de objetos con punteros


1 #include <iostream>
3.2. Clases [77]

C3
2 #include "Figura.h"
3 using namespace std;
4
5 int main () {
6 Figura *f1;
7 f1 = new Figura (1.0, 0.5);
8
9 cout << "[" << f1->getX() << ", " << f1->getY() << "]" << endl;
10
11 delete f1;
12 return 0;
13 }

3.2.2. Aspectos especcos de las clases


En esta subseccin se discutirn algunos aspectos de C++ relacionados con el
concepto de clase que resultan muy tiles en una gran variedad de situaciones y pueden
contribuir a mejorar la calidad y la mantenibilidad del cdigo fuente generado por un
desarrollador.
En primer lugar, se discutir el concepto de funcin amiga de una clase. Un fun-
cin amiga de una o varias clases, especicada as con el modicador friend y sin ser
una de sus funciones miembro, puede acceder a los miembros privados de la misma.
Aunque en principio puede parecer que esta posibilidad no ofrece ninguna ventaja so-
bre una funcin miembro, en realidad s que puede aportar benecios desde el punto
de vista del diseo. Por ejemplo, este tipo de funciones pueden ser tiles para sobrecar-
gar ciertos tipos de operadores y pueden simplicar la creacin de algunas funciones
de entrada/salida.
Un uso bastante comn de las funciones amigas se da cuando existen dos o ms
clases con miembros que de algn modo estn relacionados. Por ejemplo, imagine dos
clases distintas que hacen uso de un recurso comn cuando se da algn tipo de evento
externo. Por otra parte, otro elemento del programa necesita conocer si se ha hecho
uso de dicho recurso antes de poder utilizarlo para evitar algn tipo de inconsistencia
futura. En este contexto, es posible crear una funcin en cada una de las dos clases
que compruebe, consultado una variable booleana, si dicho recurso fue utilizado, pro-
vocando dos llamadas independientes. Si esta situacin se da continuamente, entonces
se puede llegar a producir una sobrecarga de llamadas.
Por el contrario, el uso de una funcin amiga permitira comprobar de manera
directa el estado de cada objeto mediante una nica llamada que tenga acceso a las
dos clases. En este tipo de situaciones, las funciones amigas contribuyen a un cdigo
ms limpio y mantenible. El siguiente listado de cdigo muestra un ejemplo de este

tipo de situaciones.
En el ejemplo, lnea 21 , se puede apreciar cmo la funcin recibe dos objetos
como parmetros. Al contrario de lo que ocurre en otros lenguajes como Java, en C++
los objetos, por defecto, se pasan por valor. Esto implica que en realidad la funcin
recibe una copia del objeto, en lugar del propio objeto con el que se realiz la lla-
mada inicial. Por tanto, los cambios realizados dentro de la funcin no afectan a los
argumentos. Aunque el paso de objetos es un procedimiento sencillo, en realidad se
generan ciertos eventos que pueden sorprender inicialmente. El siguiente listado de
cdigo muestra un ejemplo.
La salida del programa es la siguiente:

Construyendo...
7
Destruyendo...
Destruyendo...
[78] CAPTULO 3. C++. ASPECTOS ESENCIALES

Listado 3.13: Ejemplo de uso de funciones amigas


1 const int USADO = 1;
2 const int NO_USADO = 0;
3
4 class B;
5
6 class A {
7 int _estado;
8
9 public:
10 A() {_estado = NO_USADO;}
11 void setEstado (int estado) {_estado = estado;}
12 friend int usado (A a, B b);
13 };
14
15 class B {
16 int _estado;
17
18 public:
19 B() {_estado = NO_USADO;}
20 void setEstado (int estado) {_estado = estado;}
21 friend int usado (A a, B b);
22 };
23
24 int usado (A a, B b) {
25 return (a._estado || b._estado);
26 }


Como se puede apreciar, existe una llamada al constructor al crear a (lnea 24 ) y
dos llamadas al destructor. Como se ha comentado antes, cuando un objeto se pasa a
una funcin, entonces se crea una copia del mismo, la cual se destruye cuando naliza
la ejecucin de la funcin. Ante esta situacin surgen dos preguntas: i) se realiza una
llamada al constructor? y ii) se realiza una llamada al destructor?
En realidad, lo que ocurre cuando se pasa un objeto a una funcin es que se llama
al constructor de copia, cuya responsabilidad consiste en denir cmo se copia un
objeto. Si una clase no tiene un constructor de copia, entonces C++ proporciona uno
por defecto, el cual crea una copia bit a bit del objeto. En realidad, esta decisin es
bastante lgica, ya que el uso del constructor normal para copiar un objeto no generara
el mismo resultado que el estado que mantiene el objeto actual (generara una copia
con el estado inicial).
Sin embargo, cuando una funcin naliza y se ha de eliminar la copia del objeto,
entonces se hace uso del destructor debido a que la copia se encuentra fuera de su
mbito local. Por lo tanto, en el ejemplo anterior se llama al destructor tanto para la
copia como para el argumento inicial.

El paso de objetos no siempre es seguro. Por ejemplo, si un objeto utilizado


como argumento reserva memoria de manera dinmica, liberndola en el des-
tructor, entonces la copia local dentro de la funcin liberar la misma regin
de memoria al llamar a su destructor. Este tipo de situaciones puede causar
errores potenciales en un programa. La solucin ms directa pasa por utili-
zar un puntero o una referencia en lugar del propio objeto. De este modo, el
destructor no se llamar al volver de la funcin.
3.2. Clases [79]

C3
Listado 3.14: Paso de objetos por valor
1 #include <iostream>
2 using namespace std;
3
4 class A {
5 int _valor;
6 public:
7 A(int valor): _valor(valor) {
8 cout << "Construyendo..." << endl;
9 }
10 ~A() {
11 cout << "Destruyendo..." << endl;
12 }
13
14 int getValor () const {
15 return _valor;
16 }
17 };
18
19 void mostrar (A a) {
20 cout << a.getValor() << endl;
21 }
22
23 int main () {
24 A a(7);
25 mostrar(a);
26 return 0;
27 }

En el caso de devolver objetos al nalizar la ejecucin de una funcin se puede


producir un problema similar, ya que el objeto temporal que se crea para almacenar
el valor de retorno tambin realiza una llamada a su destructor. La solucin pasa por
devolver un puntero o una referencia, pero en casos en los que no sea posible el cons-
tructor de copia puede contribuir a solventar este tipo de problemas.

No devuelva punteros o referencias a variables locales.

El constructor de copia representa a un tipo especial de sobrecarga del construc-


tor y se utiliza para gestionar de manera adecuada la copia de objetos. Como se ha
discutido anteriormente, la copia exacta de objetos puede producir efectos no desea-
bles, especialmente cuando se trata con asignacin dinmica de memoria en el propio
constructor.
Recuerde que C++ contempla dos tipos de situaciones distintas en las que el valor
de un objeto se da a otro: la asignacin y la inicializacin. Esta segunda se pueda dar
de tres formas distintas:

Cuando un objeto inicializa explcitamente a otro, por ejemplo en una declara-


cin.
Cuando la copia de un objeto se pasa como parmetro en una funcin.
[80] CAPTULO 3. C++. ASPECTOS ESENCIALES

Cuando se genera un objeto temporal, por ejemplo al devolverlo en una funcin.

Es importante resaltar que el constructor de copia slo se aplica en las inicializa-


ciones y no en las asignaciones. El siguiente listado de cdigo muestra un ejemplo de
implementacin del constructor de copia, en el que se gestiona adecuadamente la asig-
nacin de memoria dinmica en el constructor. Dicho ejemplo se basa en el discutido
anteriormente sobre el uso de paso de objetos por valor.
Como se puede apreciar, el constructor normal hace una reserva dinmica de me-
moria. Por lo tanto, el constructor de copia se utiliza para que, al hacer la copia, se
reserve nueva memoria y se asigne el valor adecuado a la variable de clase.

Listado 3.15: Uso del constructor de copia


1 #include <iostream>
2 using namespace std;
3
4 class A {
5 int *_valor;
6 public:
7 A(int valor); // Constructor.
8 A(const A &obj); // Constructor de copia.
9 ~A(); // Destructor.
10
11 int getValor () const { return *_valor;}
12 };
13
14 A::A (int valor) {
15 cout << "Construyendo..." << endl;
16 _valor = new int;
17 *_valor = valor;
18 }
19
20 A::A (const A &obj) {
21 cout << "Constructor copia..." << endl;
22 _valor = new int;
23 *_valor = obj.getValor();
24 }
25
26 A::~A () {
27 cout << "Destruyendo..." << endl;
28 delete _valor;
29 }
30
31 void mostrar (A a) {
32 cout << a.getValor() << endl;
33 }
34
35 int main () {
36 A a(7);
37 mostrar(a);
38 return 0;
39 }

this y funciones amigas


La salida del programa es la siguiente: Las funciones amigas no manejan
el puntero this, debido a que no
son miembros de una clase. Slo las
Construyendo... funciones miembro tienen acceso a
Constructor copia... this.
3.2. Clases [81]

C3
7
Destruyendo...
Destruyendo...

Finalmente, antes de pasar a la seccin de sobrecarga de operadores, C++ contem-


pla, al igual que en otros lenguajes de programacin, el uso de this como el puntero al
objeto que invoca una funcin miembro. Bsicamente, dicho puntero es un parmetro
implcito a todas las funciones miembro de una clase.

3.2.3. Sobrecarga de operadores


La sobrecarga de operadores permite denir de manera explcita el signicado de
un operador en relacin a una clase. Por ejemplo, una clase que gestione la matricu-
lacin de alumnos podra hacer uso del operador + para incluir a un nuevo alumno.
En C++, los operadores se pueden sobrecargar de acuerdo a los tipos de clase de-
nidos por el usuario. De este modo, es posible integrar nuevos tipos de datos cuando
sea necesario. La sobrecarga de operadores est muy relacionada con la sobrecarga de
funciones, siendo necesario denir el signicado del operador sobre una determinada
clase.
El siguiente ejemplo muestra la sobrecarga del operador + en la clase Point3D.

Listado 3.16: Sobrecarga del operador +


1 class Point3D {
2 public:
3 Point3D ():
4 _x(0), _y(0), _z(0) {}
5 Point3D (int x, int y, int z):
6 _x(x), _y(y), _z(z) {}
7 Point3D operator+ (const Point3D &op2);
8
9 private:
10 int _x, _y, _z;
11 };
12
13 Point3D
14 Point3D::operator+
15 (const Point3D &op2) {
16 Point3D resultado;
17
18 resultado._x = this->_x + op2._x;
19 resultado._y = this->_y + op2._y;
20 resultado._z = this->_z + op2._z;
21
22 return resultado;
23 }

Como se puede apreciar, el operador + de la clase Point3D permite sumar una


a una los distintos componentes vinculados a las variables miembro para, posterior-
mente, devolver el resultado. Es importante resaltar que, aunque la operacin est
compuesta de dos operandos, slo se pasa un operando por parmetro. El segundo
operando es implcito y se pasa mediante this.
[82] CAPTULO 3. C++. ASPECTOS ESENCIALES

Existen ciertas restricciones relativas a la sobrecarga de operadores: no es


posible alterar la precedencia de cualquier operador, no es posible alterar el
nmero de operandos requeridos por un operador y, en general, no es posible
utilizar argumentos por defecto.

En C++, tambin es posible sobrecargar operadores unarios, como por ejemplo


++. En este caso particular, no sera necesario pasar ningn parmetro de manera
explcita, ya que la operacin afectara al parmetro implcito this.
Otro uso importante de la sobrecarga de operadores est relacionado con los pro-
blemas discutidos en la seccin 3.2.2. C++ utiliza un constructor de copia por defecto
que se basa en realizar una copia exacta del objeto cuando ste se pasa como par-
metro a una funcin, cuando se devuelve de la misma o cuando se inicializa. Si el
constructor de una clase realiza una reserva de recursos, entonces el uso implcito del
constructor de copia por defecto puede generar problemas. La solucin, como se ha
comentado anteriormente, es el constructor de copia.
Sin embargo, el constructor de copia slo se utiliza en las inicializaciones y no en
las asignaciones. En el caso de realizar una asignacin, el objeto de la parte izquierda
de la asignacin recibe por defecto una copia exacta del objeto que se encuentra a la
derecha de la misma. Esta situacin puede causar problemas si, por ejemplo, el objeto
realiza una reserva de memoria. Si, despus de una asignacin, un objeto altera o libera
dicha memoria, el segundo objeto se ve afectado debido a que sigue haciendo uso de
dicha memoria. La solucin a este problema consiste en sobrecargar el operador de
asignacin.
El siguiente listado de cdigo muestra la implementacin de una clase en la que
se reserva memoria en el constructor, en concreto, un array de caracteres.

Listado 3.17: Sobrecarga del operador de asignacin


1 #include <iostream>
2 #include <cstring>
3 #include <cstdlib>
4 using namespace std;
5
6 class A {
7 char *_valor;
8 public:
9 A() {_valor = 0;}
10 A(const A &obj); // Constructor de copia.
11 ~A() {if(_valor) delete [] _valor;
12 cout << "Liberando..." << endl;}
13
14 void mostrar () const {cout << _valor << endl;}
15 void set (char *valor);
16 };
17
18 A::A(const A &obj) {
19 _valor = new char[strlen(obj._valor) + 1];
20 strcpy(_valor, obj._valor);
21 }
22
23 void A::set (char *valor) {
24 delete [] _valor;
25 _valor = new char[strlen(valor) + 1];
26 strcpy(_valor, valor);
27 }
3.2. Clases [83]


El constructor de copia se dene entre las lneas 18 y 21 , reservando una nueva

C3
regin de memoria para el contenido
de _valor y copiando el mismo en la variable
miembro. Por otra parte, las lneas 23-26 muestran la implementacin de la funcin
set, que modica el contenido de dicha variable miembro.
El siguiente listado muestra la implementacin de la funcin entrada, que pide
una cadena por teclado y devuelve un objeto que alberga la entrada proporcionada por
el usuario.
En primer lugar, el programa se comporta adecuadamente cuando se llama a entra-
da, particularmente cuando se devuelve la copia del objeto a, utilizando el constructor
de copia previamente denido. Sin embargo, el programar abortar abruptamente
cuando el objeto devuelto por entrada se asigna a obj en la funcin principal. Recuer-
de que en este caso se efectua una copia idntica. El problema reside en que obj.valor
apunta a la misma direccin de memoria que el objeto temporal, y ste ltimo se des-
truye despus de volver desde entrada, por lo que obj.valor apunta a memoria que
acaba de ser liberada. Adems, obj.valor se vuelve a liberar al nalizar el programa.

Listado 3.18: Sobrecarga del operador de asignacin (cont.)


1 A entrada () {
2 char entrada[80];
3 A a;
4
5 cout << "Introduzca texto... ";
6 cin >> entrada;
7
8 a.set(entrada);
9 return a;
10 }
11
12 int main () {
13 A obj;
14 obj = entrada(); // Fallo.
15 obj.mostrar();
16 return 0;
17 }

Para solucionar este problema se sobrecarga el operador de asignacin de copia en


la clase en cuestin, tal y como muestra el siguiente listado de cdigo.

Listado 3.19: Sobrecarga del operador de asignacin (cont.)


1 A& A::operator= (const A &obj) {
2 if (strlen(obj._valor) > strlen(_valor)) {
3 delete [] _valor;
4 _valor = new char[strlen(obj._valor) + 1];
5 }
6
7 strcpy(_valor, obj._valor);
8
9 return *this;
10 }

tiene suciente me-


En la funcin anterior se comprueba si la variable miembro
moria para albergar el objeto pasado como parmetro (lnea 2 ). Si no es as, libera
memoria y reserva la que sea necesaria para, posteriormente, devolver la copia de
manera adecuada.
[84] CAPTULO 3. C++. ASPECTOS ESENCIALES

Finalmente, resulta especialmente relevante destacar que C++ permite la sobre-


carga de cualquier operador, a excepcin de new, delete, ->, ->* y el operador coma,
que requieren otro tipo de tcnicas. El resto de operadores se sobrecargan del mismo
modo que los discutidos en esta seccin.

3.3. Herencia y polimorsmo


La herencia representa uno de los conceptos fundamentales de la POO debido a
que permite la creacin de jerarquas de elementos. De este modo, es posible crear una
clase base que dene los aspectos o caractersticas comunes de una serie de elementos
relacionados. A continuacin, esta clase se puede extender para que cada uno de estos
elementos aada las caractersticas nicas que lo diferencian del resto.
En C++, las clases que son heredadas se denen como clases base, mientras que
las clases que heredan se denen como clases derivadas. Una clase derivada puede
ser a su vez clase base, posibilitando la generacin de jerarquas de clases. Dichas
jerarquas permiten la creacin de cadenas en las que los eslabones estn representados
por clases individuales.
Por otra parte, el polimorsmo es el trmino utilizado para describir el proceso Polimorsmo
mediante el cual distintas implementaciones de una misma funcin se utilizan bajo un El polimorsmo se suele denir co-
mismo nombre. De este modo, se garantiza un acceso uniforme a la funcionalidad, mo una interfaz, mltiples mtodos
aunque las caractersticas propias de cada operacin sean distintas. y representa una de los aspectos cla-
ve de la POO.
En C++, el polimorsmo est soportado tanto en tiempo de compilacin como en
tiempo de ejecucin. Por una parte, la sobrecarga de operadores y de funciones son
ejemplos de polimorsmo en tiempo de compilacin. Por otra parte, el uso de clases
derivadas y de funciones virtuales posibilitan el polimorsmo en tiempo de ejecucin.

3.3.1. Herencia
El siguiente listado de cdigo muestra la clase base Vehculo que, desde un punto
de vista general, dene un medio de transporte por carretera. De hecho, sus variables
miembro son el nmero de ruedas y el nmero de pasajeros.

Listado 3.20: Clase base Vehculo


1 class Vehiculo {
2 int _ruedas; // Privado. No accesible en clases derivadas.
3 int _pasajeros;
4
5 public:
6 void setRuedas (int ruedas) {_ruedas = ruedas;}
7 int getRuedas () const {return _ruedas;}
8 void setPasajeros (int pasajeros) {_pasajeros = pasajeros;}
9 int getPasajeros () const {return _pasajeros;}
10 };

Herencia y acceso
La clase base anterior se puede extender para denir coches con una nueva carac-
terstica propia de los mismos, como se puede apreciar en el siguiente listado. El modicador de acceso cuando se


usa herencia es opcional. Sin em-
En este ejemplo no se han denido los constructores de manera intencionada para
discutir el acceso a los miembros de la clase. Como se puede apreciar en la lnea 5
bargo, si ste se especica ha de ser
public, protected o private. Por
del siguiente listado, la clase Coche hereda de la clase Vehculo, utilizando el operador defecto, su valor es private si la
clase derivada es efectivamente una
:. La palabra reservada public delante de Vehculo determina el tipo de acceso. En clase. Si la clase derivada es una es-
este caso concreto, el uso de public implica que todos los miembros pblicos de la tructura, entonces su valor por de-
fecto es public.
3.3. Herencia y polimorsmo [85]

C3
Listado 3.21: Clase derivada Coche
1 #include <iostream>
2 #include "Vehiculo.cpp"
3 using namespace std;
4
5 class Coche : public Vehiculo {
6 int _PMA;
7
8 public:
9 void setPMA (int PMA) {_PMA = PMA;}
10 int getPMA () const {return _PMA;}
11
12 void mostrar () const {
13 cout << "Ruedas: " << getRuedas() << endl;
14 cout << "Pasajeros: " << getPasajeros() << endl;
15 cout << "PMA: " << _PMA << endl;
16 }
17 };

clase base sern tambin miembros pblicos de la clase derivada. En otras palabras, el
efecto que se produce equivale a que los miembros pblicos de Vehculo se hubieran
declarado dentro de Coche. Sin embargo, desde Coche no es posible acceder a los
miembros privados de Vehculo, como por ejemplo a la variable _ruedas.
El caso contrario a la herencia pblica es la herencia privada. En este caso, cuando
la clase base se hereda con private, entonces todos los miembros pblicos de la clase
base se convierten en privados en la clase derivada.
Adems de ser pblico o privado, un miembro de clase se puede denir como
protegido. Del mismo modo, una clase base se puede heredar como protegida. Si un
miembro se declara como protegido, dicho miembro no es accesible por elementos que
no sean miembros de la clase salvo en una excepcin. Dicha excepcin consiste en he-
redar un miembro protegido, hecho que marca la diferencia entre private y protected.
En esencia, los miembros protegidos de la clase base se convierten en miembros pro-
tegidos de la clase derivada. Desde otro punto de vista, los miembros protegidos son
miembros privados de una clase base pero con la posibilidad de heredarlos y acceder
a ellos por parte de una clase derivada. El siguiente listado de cdigo muestra el uso
de protected.

Listado 3.22: Clase derivada Coche. Acceso protegido


1 #include <iostream>
2 using namespace std;
3
4 class Vehiculo {
5 protected:
6 int _ruedas; // Accesibles en Coche.
7 int _pasajeros;
8 // ...
9 };
10
11 class Coche : protected Vehiculo {
12 int _PMA;
13
14 public:
15 // ...
16 void mostrar () const {
17 cout << "Ruedas: " << _ruedas << endl;
18 cout << "Pasajeros: " << _pasajeros << endl;
[86] CAPTULO 3. C++. ASPECTOS ESENCIALES

19 cout << "PMA: " << _PMA << endl;


20 }
21 };

Otro caso particular que resulta relevante comentar se da cuando una clase ba-
se se hereda como privada. En este caso, los miembros protegidos se heredan como
miembros privados en la clase protegida.
Si una clase base se hereda como protegida mediante el modicador de acceso pro-
tected, entonces todos los miembros pblicos y protegidos de dicha clase se heredan
como miembros protegidos en la clase derivada.

Constructores y destructores

Cuando se hace uso de herencia y se denen constructores y/o destructores de Inicializacin de objetos
clase, es importante conocer el orden en el que se ejecutan en el caso de la clase
base y de la clase derivada, respectivamente. Bsicamente, a la hora de construir un El constructor de una clase debera
inicializar idealmente todo el esta-
objeto de una clase derivada, primero se ejecuta el constructor de la clase base y, a do de los objetos instanciados. Uti-
continuacin, el constructor de la derivada. En el caso de la destruccin de objetos, lice el constructor de la clase base
el orden se invierte, es decir, primero se ejecuta el destructor de la clase derivada y, a cuando as sea necesario.
continuacin, el de la clase base.
Otro aspecto relevante est vinculado al paso de parmetros al constructor de la
clase base desde el constructor de la clase derivada. Para ello, simplemente se realiza
una llamada al constructor de la clase base, pasando los argumentos que sean nece-
sarios. Este planteamiento es similar al utilizado en Java mediante super(). Note que
aunque una clase derivada no tenga variables miembro, en su constructor han de es-
pecicarse aquellos parmetros que se deseen utilizar para llamar al constructor de la
clase base.

Recuerde que, al utilizar herencia, los constructores se ejecutan en el orden


de su derivacin mientras que los destructores se ejecutan en el orden inverso
a la derivacin.

3.3.2. Herencia mltiple


C++ posibilita la herencia mltiple, es decir, permite que una clase derivada herede
de dos o ms clases base. Para ejemplicar la potencia de la herencia mltiple, a
continuacin se plantea un problema que se abordar con distintos enfoques con el
objetivo de obtener una buena solucin de diseo [27].
Suponga que es necesario disear la clase ObjetoJuego, la cual se utilizar como
clase base para distintas entidades en un juego, como los enemigos, las cmaras, los
items, etc. En concreto, es necesario que todos los objetos del juego soporten funcio-
nalidad relativa a la recepcin de mensajes y, de manera simultnea, dichos objetos
han de poder relacionarse como parte de una estructura de rbol.
3.3. Herencia y polimorsmo [87]

C3
El enfoque todo en uno

Una primera opcin de diseo podra consistir en aplicar un enfoque todo en uno,
es decir, implementar los requisitos previamente comentados en la propia clase Ob-
jetoJuego, aadiendo la funcionalidad de recepcin de mensajes y la posibilidad de
enlazar el objeto en cualquier parte del rbol a la propia clase.
Aunque la simplicidad de esta aproximacin es su principal ventaja, en general
aadir todo lo que se necesita en una nica clase no es la mejor decisin de diseo. Si
se utiliza este enfoque para aadir ms funcionalidad, la clase crecer en tamao y en
complejidad cada vez que se integre un nuevo requisito funcional. As, una clase base
que resulta fundamental en el diseo de un juego se convertir en un elemento difcil
de utilizar y de mantener. En otras palabras, la simplicidad a corto plazo se transforma
en complejidad a largo plazo.
Otro problema concreto con este enfoque es la duplicidad de cdigo, ya que la
clase ObjetoJuego puede no ser la nica en recibir mensajes, por ejemplo. La clase
Jugador podra necesitar recibir mensajes sin ser un tipo particular de la primera clase.
En el caso de enlazar con una estructura de rbol se podra dar el mismo problema, ya
que otros elementos del juego, como por ejemplo los nodos de una escena se podra
organizar del mismo modo y haciendo uso del mismo tipo de estructura de rbol.
En este contexto, copiar el cdigo all donde sea necesario no es una solucin viable
debido a que complica enormemente el mantenimiento y afecta de manera directa a la
arquitectura del diseo.

Enfoque basado en agregacin

Contenedores La conclusin directa que se obtiene al reexionar sobre el anterior enfoque es que
resulta necesario disear sendas clases, ReceptorMensajes y NodoArbol, para repre-
La aplicacin de un esquema basa-
do en agregacin, de manera que sentar la funcionalidad previamente discutida. La cuestin reside en cmo relacionar
una clase contiene elementos rele- dichas clases con la clase ObjetoJuego.
vantes vinculados a su funcionali-
dad, es en general un buen diseo. Una opcin inmediata podra ser la agregacin, de manera que un objeto de la cla-
se ObjetoJuego contuviera un objeto de la clase ReceptorMensajes y otro de la clase
NodoArbol, respectivamente. As, la clase ObjetoJuego sera responsable de propor-
cionar la funcionalidad necesaria para manejarlos en su propia interfaz. En trminos
generales, esta solucin proporciona un gran nivel de reutilizacin sin incrementar de
manera signicativa la complejidad de las clases que se extienden de esta forma.
El siguiente listado de cdigo muestra una posible implementacin de este diseo.
La desventaja directa de este enfoque es la generacin de un gran nmero de fun-
ciones que simplemente llaman a la funcin de una variable miembro, las cuales han
de crearse y mantenerse. Si unimos este hecho a un cambio en su interfaz, el man-
tenimiento se complica an ms. As mismo, se puede producir una sobrecarga en el
nmero de llamadas a funcin, hecho que puede reducir el rendimiento de la aplica-
cin.
Una posible solucin a este problema consiste en exponer los propios objetos en
lugar de envolverlos con llamadas a funciones miembro. Este planteamiento simpli-
ca el mantenimiento pero tiene la desventaja de que proporciona ms informacin
de la realmente necesaria en la clase ObjetoJuego. Si adems, posteriormente, es ne-
cesario modicar la implementacin de dicha clase con propsitos de incrementar la
Uso de la herencia eciencia, entonces habra que modicar todo el cdigo que haga uso de la misma.
Recuerde utilizar la herencia con
prudencia. Un buen truco consiste
en preguntarse si la clase derivada
es un tipo particular de la clase ba-
se.
[88] CAPTULO 3. C++. ASPECTOS ESENCIALES

Listado 3.23: Clase ObjetoJuego. Uso agregacin


1 class ObjetoJuego {
2 public:
3 bool recibirMensaje (const Mensaje &m);
4
5 ObjetoJuego* getPadre ();
6 ObjetoJuego* getPrimerHijo ();
7 // ...
8
9 private:
10 ReceptorMensajes *_receptorMensajes;
11 NodoArbol *_nodoArbol;
12 };
13
14 inline bool recibirMensaje (const Mensaje &m) {
15 return _receptorMensajes->recibirMensaje(m);
16 }
17
18 inline ObjetoJuego* getPadre () {
19 return _nodoArbol->getPadre();
20 }
21
22 inline ObjetoJuego* getPrimerHijo () {
23 return _nodoArbol->getPrimerHijo();
24 }

Objeto Receptor Receptor Nodo


Juego Mensajes Mensajes rbol

Nodo
rbol
Receptor Nodo Objeto
Mensajes rbol Juego
Objeto
Juego

(a) (b) (c)

Figura 3.2: Distintas soluciones de diseo para el problema de la clase ObjetoJuego. (a) Uso de agregacin.
(b) Herencia simple. (c) Herencia mltiple.

Enfoque basado en herencia simple

Otra posible solucin de diseo consiste en usar herencia simple, es decir, Ob-
jetoJuego se podra declarar como una clase derivada de ReceptorMensajes, aunque
NodoArbol quedara aislado. Si se utiliza herencia simple, entonces una alternativa se-
ra aplicar una cadena de herencia, de manera que, por ejemplo, ArbolNodo hereda de
ReceptorMensajes y, a su vez, ObjetoJuego hereda de ArbolNodo (ver gura 3.2.b).
3.3. Herencia y polimorsmo [89]

C3
Aunque este planteamiento es perfectamente funcional, el diseo no es adecua-
do ya que resulta bastante lgico pensar que ArbolNodo no es un tipo especial de
ReceptorMensajes. Si no es as, entonces no debera utilizarse herencia. Simple y lla-
namente. Del mismo modo, la relacin inversa tampoco es lgica.

Enfoque basado en herencia mltiple

La herencia mltiple representa la solucin idnea al problema planteado. Real-


mente, la herencia mltiple funciona como la herencia simple, pero posibilita que una
clase derivada herede de dos clases base. En este caso particular, ObjetoJuego podra
heredar de ReceptorMensajes y de ArbolNodo simultneamente. As, la primera clase
tendra de manera automtica la interfaz, las variables miembro y la funcionalidad de
las otras dos clases.

Listado 3.24: Clase ObjetoJuego. Herencia mltiple


1 class ObjetoJuego: public ReceptorMensajes, public NodoArbol {
2 public:
3 // Funcionalidad necesaria.
4 };

Desde un punto de vista general, la herencia mltiple puede introducir una serie
de complicaciones y desventajas, entre las que destacan las siguientes:

Ambigedad, debido a que las clases base de las que hereda una clase derivada
pueden mantener el mismo nombre para una funcin. Para solucionar este pro-
blema, se puede explicitar el nombre de la clase base antes de hacer uso de la
funcin, es decir, ClaseBase::Funcion.
Topografa, debido a que se puede dar la situacin en la que una clase derivada
herede de dos clases base, que a su vez heredan de otra clase, compartiendo
todas ellas la misma clase. Este tipo de rboles de herencia puede generar con-
secuencias inesperadas, como duplicidad de variables y ambigedad. Este tipo
de problemas se puede solventar mediante herencia virtual, concepto distinto al
que se estudiar en el siguiente apartado relativo al uso de funciones virtuales.
Arquitectura del programa, debido a que el uso de la herencia, simple o mlti-
ple, puede contribuir a degradar el diseo del programa y crear un fuerte acopla-
miento entre las distintas clases que la componen. En general, es recomendable
utilizar alternativas como la composicin y relegar el uso de la herencia mltiple
slo cuando sea la mejor alternativa real.

Las jerarquas de herencia que forman un diamante, conocidas comnmente


como DOD (Diamond Of Death) deberan evitarse y, generalmente, es un
signo de un diseo incorrecto.
[90] CAPTULO 3. C++. ASPECTOS ESENCIALES

3.3.3. Funciones virtuales y polimorsmo


Como se introdujo al principio de la seccin 3.3, en C++ el polimorsmo est so-
portado tanto en tiempo de compilacin como en tiempo de ejecucin. La sobrecarga
de operadores y de funciones son ejemplos de polimorsmo en tiempo de compila-
cin, mientras que el uso de clases derivadas y de funciones virtuales posibilitan el
polimorsmo en tiempo de ejecucin. Antes de pasar a discutir las funciones virtua-
les, se introducir el concepto de puntero a la clase base como fundamento bsico del
polimorsmo en tiempo de ejecucin.
Flexibilidad en C++
El puntero a la clase base El polimorsmo en C++ es un arma
muy poderosa y, junto con la heren-
En trminos generales, un puntero de un determinado tipo no puede apuntar a un cia, permite disear e implementar
programas complejos.
objeto de otro tipo distinto. Sin embargo, los punteros a las clases bases y derivadas
representan la excepcin a esta regla. En C++, un puntero de una determinada clase
base se puede utilizar para apuntar a objetos de una clase derivada a partir de dicha
clase base.
En el listado de cdigo 3.25 se retoma el ejemplo de uso de herencia entre las
el uso de un puntero a una clase base (Vehculo).
clases Vehculo y Coche para mostrar
Como se puede apreciar en la lnea 11 , el puntero de tipo base Vehculo se utiliza para
derivado Coche, para, posteriormente, acceder al nmero
apuntar a un objeto de tipo
de pasajeros en la lnea 12 .

Listado 3.25: Manejo de punteros a clase base


1 #include "Coche.cpp"
2
3 int main () {
4 Vehiculo *v; // Puntero a objeto de tipo vehculo.
5 Coche c; // Objeto de tipo coche.
6
7 c.setRuedas(4); // Se establece el estado de c.
8 c.setPasajeros(7);
9 c.setPMA(1885);
10
11 v = &c; // v apunta a un objeto de tipo coche.
12 cout << "Pasajeros: " << v->getPasajeros() << endl;
13
14 return 0;
15 }

Cuando se utilizan punteros a la clase base, es importante recordar que slo es


posible acceder a aquellos elementos que pertenecen a la clase base. En el listado
de cdigo 3.26 se ejemplica cmo no es posible acceder a elementos de una clase

derivada utilizando un puntero a la clase base.
Fjese cmo en la lnea 13 el programa est intentando acceder a un elemento
particular de la clase Coche mediante un puntero de tipo Vehculo. Obviamente, el
compilador generar un error ya que la funcin getPMA() es especca de la clase
derivada. Si se desea acceder a los elementos de una clase derivada a travs de un

puntero a la clase base, entonces es necesario utilizar un molde o cast.
En la lnea 15 se muestra cmo realizar un casting para poder utilizar la funcio-

nalidad anteriormente mencionada. Sin embargo, y aunque la instruccin es perfec-
tamente vlida, es preferible utilizar la nomenclatura de la lnea 17 , la cual es ms
limpia y hace uso de elementos tpicos de C++.
3.3. Herencia y polimorsmo [91]

C3
Listado 3.26: Manejo de punteros a clase base (cont.)
1 #include "Coche.cpp"
2
3 int main () {
4 Vehiculo *v; // Puntero a objeto de tipo vehculo.
5 Coche c; // Objeto de tipo coche.
6
7 c.setRuedas(4); // Se establece el estado de c.
8 c.setPasajeros(7);
9 c.setPMA(1885);
10
11 v = &c; // v apunta a un objeto de tipo coche.
12
13 cout << v->getPMA() << endl; // ERROR en tiempo de compilacin.
14
15 cout << ((Coche*)v)->getPMA() << endl; // NO recomendable.
16
17 cout << static_cast<Coche*>(v)->getPMA() << endl; // Estilo C++.
18
19 return 0;
20 }

Otro punto a destacar est relacionado con la aritmtica de punteros. En esencia,


los punteros se incrementan o decrementan de acuerdo a la clase base. En otras pala-
bras, cuando un puntero a una clase base est apuntando a una clase derivada y dicho
puntero se incrementa, entonces no apuntar al siguiente objeto de la clase derivada.
Por el contrario, apuntar a lo que l cree que es el siguiente objeto de la clase ba-
se. Por lo tanto, no es correcto incrementar un puntero de clase base que apunta a un
objeto de clase derivada.
Finalmente, es importante destacar que, al igual que ocurre con los punteros, una
referencia a una clase base se puede utilizar para referenciar a un objeto de la clase
derivada. La aplicacin directa de este planteamiento se da en los parmetros de una
funcin.

Uso de funciones virtuales

La palabra clave virtual Una funcin virtual es una funcin declarada como virtual en la clase base y rede-
nida en una o ms clases derivadas. De este modo, cada clase derivada puede tener
Una clase que incluya una funcin su propia versin de dicha funcin. El aspecto interesante es lo que ocurre cuando se
virtual se denomina clase polimr-
ca. llama a esta funcin con un puntero o referencia a la clase base. En este contexto,
C++ determina en tiempo de ejecucin qu versin de la funcin se ha de ejecutar en
funcin del tipo de objeto al que apunta el puntero.

Listado 3.27: Uso bsico de funciones virtuales


1 #include <iostream>
2 using namespace std;
3
4 class Base {
5 public:
6 virtual void imprimir () const { cout << "Soy Base!" << endl; }
7 };
8
9 class Derivada1 : public Base {
10 public:
[92] CAPTULO 3. C++. ASPECTOS ESENCIALES

11 void imprimir () const { cout << "Soy Derivada1!" << endl; }


12 };
13
14 class Derivada2 : public Base {
15 public:
16 void imprimir () const { cout << "Soy Derivada2!" << endl; }
17 };
18
19 int main () {
20 Base *pb, base_obj;
21 Derivada1 d1_obj;
22 Derivada2 d2_obj;
23
24 pb = &base_obj;
25 pb->imprimir(); // Acceso a imprimir de Base.
26 pb = &d1_obj;
27 pb->imprimir(); // Acceso a imprimir de Derivada1.
28 pb = &d2_obj;
29 pb->imprimir(); // Acceso a imprimir de Derivada2.
30
31 return 0;
32 }

La ejecucin del programa produce la siguiente salida:

Soy Base!
Soy Derivada1!
Soy Derivada2!

Las funciones virtuales han de ser miembros de la clase en la que se denen, Sobrecarga/sobreescrit.
es decir, no pueden ser funciones amigas. Sin embargo, una funcin virtual puede
ser amiga de otra clase. Adems, los destructores se pueden denir como funciones Cuando una funcin virtual se rede-
ne en una clase derivada, la fun-
virtuales, mientras que en los constructores no es posible. cin se sobreescribe. Para sobrecar-
Las funciones virtuales se heredan de manera independiente del nmero de niveles gar una funcin, recuerde que el n-
mero de parmetros y/o sus tipos
que tenga la jerarqua de clases. Suponga que en el ejemplo anterior Derivada2 hereda han de ser diferentes.
de Derivada1 en lugar de heredar de Base. En este caso, la funcin imprimir seguira
siendo virtual y C++ sera capaz de seleccionar la versin adecuada al llamar a dicha
funcin. Si una clase derivada no sobreescribe una funcin virtual denida en la clase
base, entonces se utiliza la versin de la clase base.
El polimorsmo permite manejar la complejidad de los programas, garantizando
la escalabilidad de los mismos, debido a que se basa en el principio de una interfaz,
mltiples mtodos. Por ejemplo, si un programa est bien diseado, entonces se puede
suponer que todos los objetos que derivan de una clase base se acceden de la misma
forma, incluso si las acciones especcas varan de una clase derivada a la siguiente.
Esto implica que slo es necesario recordar una interfaz. Sin embargo, la clase deri-
vada es libre de aadir uno o todos los aspectos funcionales especicados en la clase
base.

Es importante destacar que un aspecto clave para entender el polimorsmo re-


side en que la clase base y las derivadas forman una jerarqua, la cual plantea
una evolucin desde los aspectos ms generales (clase base) hasta los aspec-
tos ms especcos (clases derivadas). Por lo tanto, disear correctamente la
clase base es esencial, ya que dene tanto los aspectos generales como aque-
llos aspectos que las clases derivadas tendrn que especicar.
3.3. Herencia y polimorsmo [93]

C3
Funciones virtuales puras y clases abstractas

Si una funcin virtual no se redene en la clase derivada, entonces se utiliza la


funcin denida en la clase base. Sin embargo, en determinadas situaciones no tiene
sentido denir una funcin virtual en una clase base debido a que semnticamente no
es correcto. El escenario tpico se produce cuando existe una clase base, asociada a
un concepto abstracto, para la que no pueden existir objetos. Por ejemplo, una clase
Figura slo tiene sentido como base de alguna clase derivada.
En estos casos, es posible implementar las funciones virtuales de la clase base de
manera que generen un error, ya que su ejecucin carece de sentido en este tipo de cla-
ses que manejan conceptos abstractos. Sin embargo, C++ proporciona un mecanismo
para tratar este tipo de situaciones: el uso de funciones virtuales puras. Este tipo de
funciones virtuales permite la denicin de clases abstractas, es decir, clases a partir
de las cuales no es posible instanciar objetos.
El siguiente listado de cdigo muestra un ejemplo en el que se dene la clase
abstracta Figura, como base para la denicin de guras concretos, como el crculo.
Note como la transformacin de una funcin virtual en pura se consigue mediante el
especicador =0.
Fundamentos POO Recuerde que una clase con una o ms funciones virtuales puras es una clase abs-
La encapsulacin, la herencia y el
tracta y, por lo tanto, no se pueden realizar instancias a partir de ella. En realidad, la
polimormos representan los pila- clase abstracta dene una interfaz que sirve como contrato funcional para el resto
res fundamentales de la programa- de clases que hereden a partir de la misma. En el ejemplo anterior, la clase Circulo
cin orientada a objetos. est obligada a implementar la funcin area en caso de denirla. En caso contrario,
el compilador generar un error. De hecho, si una funcin virtual pura no se dene en
una clase derivada, entonces dicha funcin virtual sigue siendo pura y, por lo tanto, la
clase derivada es tambin una clase abstracta.

Listado 3.28: Uso bsico de funciones virtuales puras


1 #include <iostream>
2 using namespace std;
3
4 class Figura { // Clase abstracta Figura.
5 public:
6 virtual float area () const = 0; // Funcin virtual pura.
7 };
8
9 class Circulo : public Figura {
10 public:
11 Circulo (float r): _radio(r) {}
12 void setRadio (float r) { _radio = r; }
13 float getRadio () const { return _radio; }
14
15 // Redefinicin de area () en Crculo.
16 float area () const { return _radio * _radio * 3.14; }
17
18 private:
19 float _radio;
20 };
21
22 int main () {
23 Figura *f;
24 Circulo c(1.0);
25
26 f = &c;
27 cout << "AREA: " << f->area() << endl;
28
29 // Recuerde realizar un casting al acceder a func. especfica.
30 cout << "Radio:" << static_cast<Circulo*>(f)->getRadio() << endl;
31
32 return 0;
[94] CAPTULO 3. C++. ASPECTOS ESENCIALES

33 }

El uso ms relevante de las clases abstractas consiste en proporcionar una interfaz


sin revelar ningn aspecto de la implementacin subyacente. Esta idea est fuertemen-
te relacionada con la encapsulacin, otro de los conceptos fundamentales de la POO
junto con la herencia y el polimorsmo.

3.4. Plantillas
En el desarrollo de software es bastante comn encontrarse con situaciones en las
que los programas implementados se parecen enormemente a otros implementados
con anterioridad, salvo por la necesidad de tratar con distintos tipos de datos o de
clases. Por ejemplo, un mismo algoritmo puede mantener el mismo comportamiento
de manera que ste no se ve afectado por el tipo de datos a manejar.
En esta seccin se discute el uso de las plantillas en C++, un mecanismo que
permite escribir cdigo genrico sin tener dependencias explcitas respecto a tipos de
datos especcos.

3.4.1. Caso de estudio. Listas


En el mbito del desarrollo de videojuegos, una de las principales estructuras de
General y ptimo
datos manejadas es la lista de elementos. Por ejemplo, puede existir una lista que alma-
cene las distintas entidades de nuestro juego, otra lista que contenga la lista de mallas Recuerde que en el desarrollo de vi-
poligonales de un objeto o incluso es bastante comn disponer de listas que almace- deojuegos siempre existe un com-
nen los nombres de los jugadores en el modo multijugador. Debido a que existe una promiso entre plantear una solucin
general y una solucin optimizada
fuerte necesidad de manejar listas con distintos tipos de datos, es importante plantear para la plataforma sobre la que se
una implementacin que sea mantenible y prctica para tratar con esta problemtica. ejecutar el juego en cuestin.
Una posible alternativa consiste en que la propia clase que dene los objetos con-
tenidos en la lista acte como propio nodo de la misma, es decir, que la propia clase
sirva para implementar la lista (ver gura 3.3.a). Para ello, simplemente hay que man-
tener un enlace al siguiente elemento, o dos enlaces si se pretende construir una lista
doblemente enlazada. El siguiente listado de cdigo muestra un ejemplo de imple-
mentacin.

Listado 3.29: Implementacin de listas con nodos enlace


1 class Entidad {
2 public:
3 // Funcionalidad de la lista.
4 Entidad * getSiguiente ();
5 void eliminar ();
6 void insertar (Entidad *pNuevo);
7
8 private:
9 // Puntero a la cabeza de la lista.
10 Entidad *_pSiguiente;
11 };

Aunque este planteamiento es muy sencillo y funciona correctamente, la realidad


es que adolece de varios problemas:

Es propenso a errores de programacin. El desarrollador ha de recordar casos


particulares en la implementacin, como por ejemplo la eliminacin del ltimo
elemento de la lista.
3.4. Plantillas [95]

C3
<MiClase> <MiClase> <MiClase> <MiClase>

(a)

Elemento Elemento Elemento


Lista Lista Lista

<MiClase> <MiClase>
MiClase

(b) (c)

Figura 3.3: Distintos enfoques para la implementacin de una lista con elementos gnericos. (a) Integracin
en la propia clase de dominio. (b) Uso de herencia. (c) Contenedor con elementos de tipo nulo.

Un cambio en la implementacin de la clase previamente expuesta implicara


cambiar un elevado nmero de clases.
No es correcto suponer que todas las listas manejadas en nuestro programa van
a tener la misma interfaz, es decir, la misma funcionalidad. Adems, es bastante
probable que un desarrollador utilice una nomenclatura distinta a la hora de
implementar dicha interfaz.

Otra posible solucin consiste en hacer uso de la herencia para denir una clase
base que represente a cualquier elemento de una lista (ver gura 3.3.b). De este modo,
cualquier clase que desee incluir la funcionalidad asociada a la lista simplemente ha
de extenderla. Este planteamiento permite tratar a los elementos de la lista mediante
polimorsmo. Sin embargo, la mayor desventaja que presenta este enfoque est en el
diseo, ya que no es posible separar la funcionalidad de la lista de la clase propiamente
dicha, de manera que no es posible tener el objeto en mltiples listas o en alguna otra
estructura de datos. Por lo tanto, es importante separar la propia lista de los elementos
que realmente contiene.
Una alternativa para proporcionar esta separacin consiste en hacer uso de una
lista que maneja punteros de tipo nulo para albergar distintos tipos de datos (ver -
gura 3.3.c). De este modo, y mediante los moldes correspondientes, es posible tener
una lista con elementos de distinto tipo y, al mismo tiempo, la funcionalidad de la mis-
ma est separada del contenido. La principal desventaja de esta aproximacin es que
no es type-safe, es decir, depende del programador incluir la funcionalidad necesaria
para convertir tipos, ya que el compilador no los detectar.
Otra desventaja de esta propuesta es que son necesarias dos reservas de memoria
para cada uno de los nodos de la lista: una para el objeto y otra para el siguiente
nodo de la lista. Este tipo de cuestiones han de considerarse de manera especial en
el desarrollo de videojuegos, ya que la plataforma hardware nal puede tener ciertas
restricciones de recursos.
[96] CAPTULO 3. C++. ASPECTOS ESENCIALES

La gura 3.3 muestra de manera grca las distintas opciones discutidas hasta
ahora en lo relativo a la implementacin de una lista que permita el tratamiento de
datos genricos.

3.4.2. Utilizando plantillas en C++


C++ proporciona el concepto de plantilla como solucin a la implementacin de
cdigo genrico, es decir, cdigo que no est vinculado a ningn tipo de datos en
particular. La idea principal reside en instanciar la plantilla para utilizarla con un tipo
de datos en particular. Existen dos tipos principales de plantillas: i) plantillas de clases
y ii) plantillas de funciones.
Las plantillas de clases permiten utilizar tipos de datos genricos asociados a una
clase, posibilitando posteriormente su instanciacin con tipos especcos. El siguiente
listado de cdigo muestra un ejemplo muy sencillo. Como se puede apreciar, se ha
denido una clase Tringulo que se puede utilizar para almacenar cualquier tipo de
datos. En este ejemplo, se ha instanciado un tringulo con elementos del tipo Vec2,
que representan valores en el espacio bidimensional. Para ello, se ha utilizado la pa-
labra clave template para completar la denicin de la clase Tringulo, permitiendo
el manejo de tipos genricos T. Note cmo este tipo genrico se usa para declarar las
variables miembro de dicha clase.
Clase Vec2
Listado 3.30: Implementacin de un tringulo con plantilla La clase Vec2 se podra haber ex-
tendido mediante el uso de planti-
1 #include <iostream> llas para manejar otros tipos de da-
2 using namespace std; tos comunes a la representacin de
3
puntos en el espacio bidimensional,
4 template<class T> // Tipo general T. como por ejemplo valores en punto
5 class Triangle { otante.
6 public:
7 Triangle (const T &v1, const T &v2, const T &v3):
8 _v1(v1), _v2(v2), _v3(v3) {}
9 ~Triangle () {}
10 T getV1 () const { return _v1; }
11 T getV2 () const { return _v2; }
12 T getV3 () const { return _v3; }
13
14 private:
15 T _v1, _v2, _v3;
16 };
17
18 class Vec2 {
19 public:
20 Vec2 (int x, int y): _x(x), _y(y) {}
21 ~Vec2 () {}
22 int getX () const { return _x; }
23 int getY () const { return _y; }
24
25 private:
26 int _x, _y;
27 };
28
29 int main () {
30 Vec2 p1(2, 7), p2(3, 4), p3(7, 10);
31 Triangle<Vec2> t(p1, p2, p3); // Instancia de la plantilla.
32
33 cout << "V1: [" << t.getV1().getX() << ", "
34 << t.getV1().getY() << "]" << endl;
35 return 0;
36 }
3.4. Plantillas [97]

C3
Las plantillas de funciones siguen la misma idea que las plantillas de clases apli-
cndolas a las funciones. Obviamente, la principal diferencia con respecto a las plan-
tillas a clases es que no necesitan instanciarse. El siguiente listado de cdigo muestra
un ejemplo sencillo de la clsica funcin swap para intercambiar el contenido de dos
variables. Dicha funcin puede utilizarse con enteros, valores en punto otante, ca-
denas o cualquier clase con un constructor de copia y un constructor de asignacin.
Adems, la funcin se instancia dependiendo del tipo de datos utilizado. Recuerde que
no es posible utilizar dos tipos de datos distintos, es decir, por ejemplo un entero y un
valor en punto otante, ya que se producir un error en tiempo de compilacin.

Listado 3.31: Ejemplo de uso plantillas de funciones


1 #include <iostream>
2 using namespace std;
3
4 template<class T> // Tipo general T.
5 void swap (T &a, T &b) {
6 T aux(a);
7 a = b;
8 b = aux;
9 }
10
11 int main () {
12 string a = "Hello", b = "Good-bye";
13 cout << "[" << a << ", " << b << "]" << endl;
14
15 swap(a, b); // Se instancia para cadenas.
16
17 cout << "[" << a << ", " << b << "]" << endl;
18 return 0;
19 }

Uso de plantillas El uso de plantillas en C++ solventa todas las necesidades planteadas para manejar
las listas introducidas en la seccin 3.4.1, principalmente las siguientes:
Las plantillas son una herramienta
excelente para escribir cdigo que
no dependa de un tipo de datos es- Flexibilidad, para poder utilizar las listas con distintos tipos de datos.
pecco.
Simplicidad, para evitar la copia de cdigo cada vez que se utilice una estruc-
tura de lista.
Uniformidad, ya que se maneja una nica interfaz para la lista.
Independencia, entre el cdigo asociado a la funcionalidad de la lista y el c-
digo asociado al tipo de datos que contendr la lista.

A continuacin se muestra la implementacin de una posible solucin, la cual est


compuesta por dos clases generales. La primera de ellas se utilizar para los nodos de
la lista y la segunda para especicar la funcionalidad de la propia lista.
Como se puede apreciar en el siguiente listado de cdigo, las dos clases estn
denidas para poder utilizar cualquier tipo de dato y, adems, la funcionalidad de la
lista es totalmente independiente del su contenido.

Listado 3.32: Uso de plantillas para implementar listas


1 template<class T>
2 class NodoLista {
3 public:
4 NodoLista (T datos);
5 T & getDatos ();
6 NodoLista * siguiente ();
[98] CAPTULO 3. C++. ASPECTOS ESENCIALES

7
8 private:
9 T _datos;
10 };
11
12 template<class T>
13 class Lista {
14 public:
15 NodoLista<T> getCabeza ();
16 void insertarFinal (T datos);
17 // Resto funcionalidad...
18
19 private:
20 NodoLista<T> *_cabeza;
21 };

3.4.3. Cundo utilizar plantillas?


Las plantillas de C++ representan un arma muy poderosa para implementar cdigo
gnerico que se pueda utilizar con distintos tipos de datos. Sin embargo, al igual que
ocurre con la herencia, hay que ser especialmente cuidadoso a la hora de utilizarlas,
debido a que un uso inadecuado puede generar problemas y retrasos en el desarrollo
de software. A continuacin se enumeran las principales desventajas que plantea el
uso de plantillas [27]:
Equipo de desarrollo
Complejidad, debido a la integracin de nueva nomenclatura que puede dicul-
tar la legibilidad del cdigo. Adems, el uso de plantillas hace que la depuracin Es importante recordar la experien-
cia de los compaeros, actuales y
de cdigo sea ms difcil. futuros, en un grupo de desarrollo
a la hora de introducir dependencias
Dependencia, ya que el cdigo de la plantilla ha de incluirse en un chero de con aspectos avanzados en el uso de
cabecera para que sea visible por el compilador a la hora de instanciarlo. Este plantillas en C++.
planteamiento incrementa el acoplamiento entre clases. Adems, el tiempo de
compilacin se ve afectado.
Duplicidad de cdigo, debido a que si, por ejemplo, se crea una lista con un
nuevo tipo de datos, el compilador ha de crear una nueva clase de lista. Es
decir, todas las funciones y variables miembro se duplican. En el desarrollo de
videojuegos, este inconveniente es generalmente asumible debido a la magnitud
de los proyectos.
Soporte del compilador, ya que las plantillas no existen como una solucin
plenamente estandarizada, por lo que es posible, aunque poco probable, que
algunos compiladores no las soporten.

Desde una perspectiva general, no debe olvidar que las plantillas representan una
herramienta adecuada para un determinado uso, por lo que su uso indiscriminado es un
error. Recuerde tambin que las plantillas introducen una dependencia de uso respecto
a otras clases y, por lo tanto, su diseo debera ser simple y mantenible.
Una de las situaciones en las que el uso de plantillas resulta adecuado est aso-
ciada al uso de contenedores, es decir, estructuras de datos que contienen objetos de
distintas clases. En este contexto, es importante destacar que la biblioteca STL de C++
ya proporciona una implementacin de listas, adems de otras estructuras de datos y
de algoritmos listos para utilizarse. Por lo tanto, es bastante probable que el desarro-
llador haga un uso directo de las mismas en lugar de tener que desarrollar desde cero
su propia implementacin. En el captulo 5 se estudia el uso de la biblioteca STL y se
discute su uso en el mbito del desarrollo de videojuegos.
3.5. Manejo de excepciones [99]

3.5. Manejo de excepciones

C3
Freezing issues A la hora de afrontar cualquier desarrollo software, un programador siempre tiene
que tratar con los errores que dicho software puede generar. Existen diversas estra-
Aunque el desarrollo de videojue- tegias para afrontar este problema, desde simplemente ignorarlos hasta hacer uso de
gos comerciales madura ao a ao,
an hoy en da es bastante comn tcnicas que los controlen de manera que sea posible recuperarse de los mismos. En
encontrar errores y bugs en los mis- esta seccin se discute el manejo de excepciones en C++ con el objetivo de escribir
mos. Algunos de ellos obligan in- programas robustos que permitan gestionar de manera adecuada el tratamiento de
cluso a resetear la estacin de jue- errores y situaciones inesperadas. Sin embargo, antes de profundizar en este aspecto
gos por completo.
se introducirn brevemente las distintas alternativas ms relevantes a la hora de tratar
con errores.

3.5.1. Alternativas existentes


La estrategia ms simple relativa al tratamiento de errores consiste en ignorarlos.
Aunque parezca una opcin insensata y arriesgada, la realidad es que la mayora de
programas hacen uso de este planteamiento en una parte relevante de su cdigo fuente.
Este hecho se debe, principalmente, a que el programador asume que existen distintos
tipos de errores que no se producirn nunca, o al menos que se producirn con una
probabilidad muy baja. Por ejemplo, es bastante comn encontrar la sentencia fclose
sin ningn tipo de comprobacin de errores, an cuando la misma devuelve un valor
entero indicando si se ha ejecutado correctamente o no.
En el caso particular del desarrollo de videojuegos, hay que ser especialmente cui-
dadoso con determinadas situaciones que en otros dominios de aplicacin pueden no
ser tan crticas. Por ejemplo, no es correcto suponer que un PC tendr memoria su-
ciente para ejecutar un juego, siendo necesario el tratamiento explcito de situaciones
como una posible falta de memoria por parte del sistema. Desde otro punto de vista,
el usuario que compra un videojuego profesional asume que ste nunca va a fallar de
manera independiente a cualquier tipo de situacin que se pueda producir en el jue-
go, por lo que el desarrollador de videojuegos est obligado a considerar de manera
especial el tratamiento de errores.
Tradicionalmente, uno de los enfoques ms utilizados ha sido el retorno de c-
digos de error, tpico en lenguajes de programacin de sistemas como C. Desde un
punto de vista general, este planteamiento se basa en devolver un cdigo numrico de
error, o al menos un valor booleano, indicando si una funcin se ejecut correctamente
o no. El cdigo que realiza la llamada a la funcin es el responsable de recoger y tratar
dicho valor de retorno.
Este enfoque tiene su principal desventaja en el mantenimiento de cdigo, moti-
vado fundamentalmente por la necesidad de incluir bloques if-then-else para capturar
y gestionar los posibles errores que se puedan producir en un fragmento de cdigo.
Adems, la inclusin de este tipo de bloques complica la legibilidad del cdigo, di-
cultando el entendimiento y ocultando el objetivo real del mismo. Finalmente, el
rendimiento del programa tambin se ve afectado debido a que cada llamada que rea-
lizamos ha de estar envuelta en una sentencia if.
Constructores Otra posible alternativa para afrontar el tratamiento de errores consiste en utilizar
El uso de cdigos de error presenta
aserciones (asserts), con el objetivo de parar la ejecucin del programa y evitar as
una dicultad aadida en el uso de una posible terminacin abrupta. Obviamente, en el desarrollo de videojuegos esta
constructores, ya que estos no per- alternativa no es aceptable, pero s se puede utilizar en la fase de depuracin para
miten la devolucin de valores. En obtener la mayor cantidad de informacin posible (por ejemplo, la lnea de cdigo
los destructores se da la misma si-
tuacin.
que produjo el error) ante una situacin inesperada.
[100] CAPTULO 3. C++. ASPECTOS ESENCIALES

Hasta ahora, los enfoques comentados tienen una serie de desventajas importantes.
En este contexto, las excepciones se posicionan como una alternativa ms adecuada
y prctica. En esencia, el uso de excepciones permite que cuando un programa se
tope con una situacin inesperada se arroje una excepcin. Este hecho tiene como
consecuencia que el ujo de ejecucin salte al bloque de captura de excepciones ms
cercano. Si dicho bloque no existe en la funcin en la que se arroj la excepcin,
entonces el programa gestionar de manera adecuada la salida de dicha funcin (des-
truyendo los objetos vinculados) y saltar a la funcin padre con el objetivo de buscar
un bloque de captura de excepciones.
Este proceso se realiza recursivamente hasta encontrar dicho bloque o llegar a
la funcin principal delegando en el cdigo de manejo de errores por defecto, que
tpicamente nalizar la ejecucin del programa y mostrar informacin por la salida
estndar o generar un chero de log.
Los bloques de tratamiento de excepciones ofrecen al desarrollador la exibili-
dad de hacer lo que desee con el error, ya sea ignorarlo, tratar de recuperarse del
mismo o simplemente informar sobre lo que ha ocurrido. Este planteamiento facili-
ta enormemente la distincin entre distintos tipos de errores y, consecuentemente, su
tratamiento.

Recuerde que despus de la ejecucin de un bloque de captura de excepcio-


nes, la ejecucin del programa continuar a partir de este punto y no desde
donde la excepcin fue arrojada.

Adems de la mantenibilidad del cdigo y de la exibilidad del planteamiento, las


excepciones permiten enviar ms informacin sobre la naturaleza del error detectado a
las capas de nivel superior. Por ejemplo, si se ha detectado un error al abrir un chero,
la interfaz grca sera capaz de especicar el chero que gener el problema. Por el
contrario, la utilizacin de cdigos de error no permitira manejar informacin ms
all de la deteccin de un error de entrada/salida.

3.5.2. Excepciones en C++


El uso de excepciones en C++ es realmente sencillo y gira en torno a tres palabras
clave: throw, catch y try. En resumen, cuando un fragmento de cdigo necesita arrojar
una excepcin, entonces hace uso de throw. El control del programa pasa entonces
al bloque de cdigo de captura de excepciones ms cercano, representado por la sen-
tencia catch. Este bloque de captura est vinculado obligatoriamente a un bloque de
cdigo en el cual se podra lanzar la excepcin, el cual est a su vez envuelto por una
sentencia try.
de cdigo muestra un ejemplo muy sencillo de captura de
El siguiente listado Excepciones estndar
9-11
excepciones (lneas ) ante la posibilidad de que el sistema no pueda reservar
memoria (lneas 6-8 ). En este caso particular, el programa captura una excepcin C++ maneja una jerarqua de ex-
cepciones estndar para tipos ge-
denida en la biblioteca estndar de C++ denominada bad_alloc, de manera que se nerales, como por ejemplo lo-
contempla un posible lanzamiento de la misma cuando se utiliza el operador new para gic_error o runtime_error, o aspec-
asignar memoria de manera dinmica. tos ms especcos, como por ejem-
plo out_of_range o bad_alloc. Al-
gunas de las funciones de la biblio-
Listado 3.33: Uso bsico de excepciones teca estndar de C++ lanzan algu-
nas de estas excepciones.
1 #include <iostream>
2 #include <exception>
3 using namespace std;
3.5. Manejo de excepciones [101]

C3
4
5 int main () {
6 try {
7 int *array = new int[1000000];
8 }
9 catch (bad_alloc &e) {
10 cerr << "Error al reservar memoria." << endl;
11 }
12
13 return 0;
14 }

Como se ha comentado anteriormente, la sentencia throw se utiliza para arrojar


excepciones. C++ es estremadamente exible y permite lanzar un objeto de cualquier
tipo de datos como excepcin. Este planteamiento posibilita la creacin de excepcio-
nes denidas por el usuario que modelen las distintas situaciones de error que se
pueden dar en un programa e incluyan la informacin ms relevante vinculadas a las
mismas.
El siguiente listado de cdigo muestra un ejemplo de creacin y tratamiento de

excepciones denidas por el usuario.
En particular, el cdigo dene una excepcin general en las lneas 4-10 mediante
la denicin de la clase MiExcepcion, que tiene como variable miembro una cadena
de texto que se utilizar para indicar la razn de la excepcin. En
la funcin main,
se lanza una instancia de dicha excepcin, denida en la lnea 21 , cuando el usuario
introduce un valor numrico que no est en el rango [1, 10]. Posteriormente, dicha
excepcin se captura en las lneas 24-26 .

Listado 3.34: Excepcin denida por el usuario


1 #include <iostream>
2 using namespace std;
3
4 class MiExcepcion {
5 const string &_razon;
6
7 public:
8 MiExcepcion (const string &razon): _razon(razon) {}
9 const string &getRazon () const {return _razon;}
10 };
11
12 int main () {
13 int valor;
14 const string &r = "Valor introducido incorrecto.";
15
16 try {
17 cout << "Introduzca valor entre 1 y 10...";
18 cin >> valor;
19
20 if ((valor < 1) || (valor > 10)) {
21 throw MiExcepcion(r);
22 }
23 }
24 catch (MiExcepcion &e) {
25 cerr << e.getRazon() << endl;
26 }
27
28 return 0;
Excepciones y clases 29 }

Las excepciones se modelan exac-


tamente igual que cualquier otro ti-
po de objeto y la denicin de clase
puede contener tanto variables co-
mo funciones miembro.
[102] CAPTULO 3. C++. ASPECTOS ESENCIALES

Normalmente, ser necesario tratar con distintos tipos de excepciones de manera


simultnea en un mismo programa. Un enfoque bastante comn consiste en hacer uso
de una jerarqua de excepciones, con el mismo planteamiento usado en una jerarqua
de clases, para modelar distintos tipos de excepciones especcas.
La gura 3.4 muestra un ejemplo representativo vinculado con el desarrollo de
videojuegos, en la que se plantea una jerarqua con una clase base y tres especializa-
ciones asociadas a errores de gestin de memoria, E/S y operaciones matemticas.

MiExcepcin

MiExcepcinMemoria MiExcepcinIO MiExcepcinMatemtica

Figura 3.4: Ejemplo de jerarqua de excepciones.

Como se ha comentado anteriormente, los bloques de sentencias try-catch son


realmente exibles y posibilitan la gestin de diversos tipos de excepciones. El si-
guiente listado de cdigo muestra un ejemplo en el que se carga informacin tridi-
mensional en una estructura de datos.

Listado 3.35: Gestin de mltiples excepciones


1 void Mesh::cargar (const char *archivo) {
2 try {
3 Stream stream(archivo); // Puede generar un error de I/O.
4 cargar(stream);
5 }
6 catch (MiExcepcionIO &e) {
7 // Gestionar error I/O.
8 }
9 catch (MiExcepcionMatematica & e) {
10 // Gestionar error matemtico.
11 }
12 catch (MiExcepcion &e) {
13 // Gestionar otro error...
14 }
15 catch (...) {
16 // Cualquier otro tipo de error...
17 }
18 }

La idea del anterior fragmento de cdigo se puede resumir en que el desarrollador


est preocupado especialmente por la gestin de errores de entrada/salida o mtema-
ticos (primer y segundo bloque catch, respectivamente) pero, al mismo tiempo, no
desea que otro tipo de error se propague a capas superiores, al menos un error que
est denido en la jerarqua de excepciones (tercer bloque catch). Finalmente, si se
3.5. Manejo de excepciones [103]

C3
desea que el programa capture cualquier tipo de excepcin, entonces se puede aadir
una captura genrica (ver cuarto bloque catch). En resumen, si se lanza una excepcin
no contemplada en un bloque catch, entonces el programa seguir buscando el bloque
catch ms cercano.
Es importante resaltar que el orden de las sentencias catch es relevante, ya que
dichas sentencias siempre se procesan de arriba a abajo. Adems, cuando el programa
encuentra un bloque que trata con la excepcin lanzada, el resto de bloques se ignoran
automticamente.
Exception handlers Otro aspecto que permite C++ relativo al manejo de excepciones es la posibilidad
de re-lanzar una excepcin, con el objetivo de delegar en una capa superior el trata-
El tratamiento de excepciones se
puede enfocar con un esquema pa- miento de la misma. El siguiente listado de cdigo muestra un ejemplo en el que se
recido al del tratamiento de eventos, delega el tratamiento del error de entrada/salida.
es decir, mediante un planteamien-
to basado en capas y que delege las
excepciones para su posterior ges- Listado 3.36: Re-lanzando una excepcin
tin.
1 void Mesh::cargar (const char *archivo) {
2
3 try {
4 Stream stream(archivo); // Puede generar un error de I/O.
5 cargar(stream);
6 }
7
8 catch (MiExcepcionIO &e) {
9 if (e.datosCorruptos()) {
10 // Tratar error I/O.
11 }
12 else {
13 throw; // Se re-lanza la excepcin.
14 }
15 }
16
17 }

3.5.3. Cmo manejar excepciones adecuadamente?


Adems de conocer cmo utilizar las sentencias relativas al tratamiento de ex-
cepciones, un desarrollador ha de conocer cmo utilizarlas de manera adecuada para
evitar problemas potenciales, como por ejemplo no liberar memoria que fue reservada
previamente al lanzamiento de una excepcin. En trminos generales, este problema
se puede extrapolar a cmo liberar un recurso que se adquiri previamente a la gene-
racin de un error.
El siguiente listado de cdigo muestra cmo utilizar excepciones para liberar co-
rrectamente los recursos previamente reservados en una funcin relativa a la carga
de texturas a partir de la ruta de una imagen.
Como se puede apreciar, en la funcin cargarTextura se reserva memoria para
el manejador del archivo y para el propio objeto de tipo textura. Si se generase una
excepcin dentro del bloque try, entonces se ejecutara el bloque catch genrico que
se encarga de liberar los dos recursos previamente mencionados. As mismo, todos los
recursos locales de la propia funcin se destruirn tras salir de la misma.
Smart pointers Sin embargo, este planteamiento se puede mejorar considerando especialmente la
En C++ es bastante comn utili-
naturaleza de los recursos manejados en la funcin, es decir, los propios punteros. El
zar herramientas que permitan ma- resto de recursos utilizados en la funcin tendrn sus destructores correctamente im-
nejar los punteros de una forma ms plementados y se puede suponer que nalizarn adecuadamente tras la salida de la
cmodo. Un ejemplo representati- funcin en caso de que se genere una excepcin. La parte problemtica est represen-
vo son los punteros inteligentes o
smart pointers.
tada por los punteros, ya que no tienen asociado destructores.
[104] CAPTULO 3. C++. ASPECTOS ESENCIALES

Listado 3.37: Uso adecuado de excepciones


1 Textura * cargarTextura (const char *ruta) {
2 FILE *entrada = NULL;
3 Textura *pTextura = NULL;
4
5 try {
6 entrada = fopen(ruta, "rb");
7 // Instanciar recursos locales...
8 pTextura = new Textura(/*..., ...*/);
9 leerTextura(entrada, pTextura);
10 }
11 catch (...) { // Liberar memoria ante un error.
12 delete pTextura;
13 pTexture = NULL;
14 }
15
16 fclose(entrada);
17 return pTextura;
18 }

En el caso del manejador de archivos, una solucin elegante consiste en construir


un wrapper, es decir, una clase que envuelva la funcionalidad de dicho manejador y
que su constructor haga uso de fopen mientras que su destructor haga uso de fclose.
As, si se crea un objeto de ese tipo en la pila, el manejador del archivo se liberar
cuando dicho objeto quede fuera de alcance (de la funcin).
En el caso del puntero a la textura, es posible aplicar una solucin ms sencilla
para todos los punteros: el uso de la plantilla unique_ptr. Dicha clase forma parte del
estndar de 2011 de C++ y permite plantear un enfoque similar al del anterior mane-
jador pero con punteros en lugar de con cheros. Bsicamente, cuando un objeto de
dicha clase se destruye, entonces el puntero asociado tambin se libera correctamente.
A continuacin se muestra un listado de cdigo con las dos soluciones discutidas.

Listado 3.38: Uso adecuado de excepciones (simple)


1 unique_ptr<Textura> cargarTextura (const char *ruta) {
2 FilePtr entrada(ruta, "rb");
3 // Instanciar recursos locales...
4 unique_ptr<Textura> pTextura(new Textura(/*..., ...*/));
5
6 leerTextura(entrada, pTextura);
7 return pTextura;
8 }

Como se puede apreciar, no es necesario incluir ningn tipo de cdigo de mane-


jo de excepciones, ya que la propia funcin se encarga de manera implcita gracias
al enfoque planteado. Recuerde que una vez que el ujo del programa abandone la
funcin, ya sea de manera normal o provocado por una excepcin, todo los recursos
habrn sido liberados de manera adecuada.
Finalmente, es importante destacar que este tipo de punteros inteligentes se pueden
utilizar en otro tipo de situaciones especcas, como por ejemplo los constructores.
Recuerde que, si se genera una excepcin en un constructor, no es posible devolver
ningn cdigo de error. Adems, si ya se reserv memoria en el constructor, entonces
3.5. Manejo de excepciones [105]

C3
se producir el clsico memory leak, es decir, la situacin en la que no se libera una
porcin de memoria que fue previamente reservada. En estos casos, el destructor no se
ejecuta ya que, despus de todo, el constructor no naliz correctamente su ejecucin.
La solucin pasa por hacer uso de este tipo de punteros.
El caso de los destructores es menos problemtico, ya que es lgico suponer
que nadie har uso de un objeto que se va a destruir, incluso cuando se genere una
excepcin dentro del propio destructor. Sin embargo, es importante recordar que el
destructor no puede lanzar una excepcin si el mismo fue llamado como consecuencia
de otra excepcin.

3.5.4. Cundo utilizar excepciones?

Plataformas HW En el mbito particular del tratamiento de excepciones en el desarrollo de video-


juegos, las excepciones se deberan utilizar para modelar situaciones realmente excep-
En el mbito del desarrollo de vi-
deojuegos, el uso de excepciones cionales [27], es decir, situaciones que nunca ocurren o que ocurren con poqusima
est estrechamente con la platafor- frecuencia. Cuando se lanza una excepcin se pone en marcha un proceso complejo
ma HW nal sobre la que se ejecu- compuesto de diversas operaciones, como por ejemplo la bsqueda del bloque de ma-
tar el juego en cuestin, debido al nejo de excepciones ms cercano, la modicacin de la pila, la destruccin de objetos
impacto en el rendimiento que tie-
nen las mismas. y la transferencia del control del programa al bloque catch.
Este proceso es lento y afectara enormemente al rendimiento del programa. Sin
embargo, en el mbito del desarrollo de videojuegos esta situacin no resulta tan cr-
tica, debido a que el uso de excepciones se limita, generalmente, a casos realmente
excepcionales. En otras palabras, las excepciones no se utilizan para detectar que, por
ejemplo, un enemigo ha cado al agua, sino para modelar aspectos crticos como que
el sistema se est quedando sin memoria fsica. Al nal, este tipo de situaciones puede
conducir a una eventual parada del sistema, por lo que el impacto de una excepcin
no resulta tan importante.
En este contexto, las consolas de videojuegos representan el caso ms extremo,
ya que normalmente tienen los recursos acotados y hay que ser especialmente cuida-
doso con el rendimiento de los juegos. El caso ms representativo es el tamao de
la memoria principal, en el que el impacto de utilizar excepciones puede ser desas-
troso. Sin embargo, en un PC este problema no es tan crtico, por lo que el uso de
excepciones puede ser ms recomendable. Algunos autores recomiendan no hacer uso
de excepciones [42], sino de cdigos de error, en el desarrollo para consolas de video-
juegos, debido a su limitada capacidad de memoria.
Tambin es importante reexionar sobre el impacto del uso de las excepciones
cuando no se lanzan, es decir, debido principalmente a la inclusin de sentencias try-
catch. En general, este impacto est vinculado al compilador y a la forma que ste
tiene para tratar con esta situacin. Otro aspecto relevante es dnde se usa este tipo
de sentencias. Si es en fases del juego como la carga inicial de recursos, por ejemplo
mapas o modelos, se puede asumir perfectamente la degradacin del rendimiento.
Por el contrario, si dichas sentencias se van a ejecutar en un mdulo que se ejecuta
continuamente, entonces el rendimiento se ver afectado enormemente.

En general, las excepciones deberan usarse en aquellas partes de cdigo en


las que es posible que ocurra un error de manera inesperada. En el caso par-
ticular de los videojuegos, algunos ejemplos representativos son la deteccin
de un archivo corrupto, un fallo hardware o una desconexin de la red.
[106] CAPTULO 3. C++. ASPECTOS ESENCIALES

El uso de excepciones puede coexistir perfectamente con el uso de valores de re-


torno, aunque es importante tener claro cundo utilizar un planteamiento y cundo
utilizar otro. Por ejemplo, en el mbito del desarrollo de videojuegos, el uso de valo-
res de retorno es una solucin ms prctica y limpia si lo que se desea es simplemente
conocer si una funcin termin su ejecucin de manera adecuada o no, independien-
temente de los tipos de errores que puedan producirse.
Patrones de Diseo
Captulo 4
Cleto Martn Angelina
Francisco Moya Fernndez

C
uando nos enfrentamos al diseo de un programa informtico como un video-
juego, no es posible abordarlo por completo. El proceso de diseo de una
aplicacin suele ser iterativo y en diferentes etapas de forma que se vaya re-
nando con el tiempo. El diseo perfecto y a la primera es muy difcil de conseguir.
La tarea de disear aplicaciones es compleja y, con seguridad, una de las ms
importantes y que ms impacto tiene no slo sobre el producto nal, sino tambin
sobre su vida futura. En el diseo de la aplicacin es donde se denen las estructuras
y entidades que se van a encargar de resolver el problema modelado, as como sus
relaciones y sus dependencias. Cmo de bien denamos estas entidades y relaciones
inuir, en gran medida, en el xito o fracaso del proyecto y en la viabilidad de su
mantenimiento.
El diseo, por tanto, es una tarea capital en el ciclo de vida del software. Sin
embargo, no existe un procedimiento sistemtico y claro sobre cmo crear el mejor
diseo para un problema dado. Podemos utilizar metodologas, tcnicas y herramien-
tas que nos permitan renar nuestro diseo. La experiencia tambin juega un papel
importante. Sin embargo, el contexto de la aplicacin es crucial y los requisitos, que
pueden cambiar durante el proceso de desarrollo, tambin.
Un videojuego es un programa con una componente muy creativa. Adems, el
mercado de videojuegos se mueve muy deprisa y la adaptacin a ese medio cam-
biante es un factor determinante. En general, los diseos de los programas deben ser
escalables, extensibles y que permitan crear componentes reutilizables.
Esta ltima caracterstica es muy importante ya que la experiencia nos hace ver que
al construir una aplicacin se nos presentan situaciones recurrentes y que se asemejan
a situaciones pasadas. Es el deja v en el diseo: cmo solucion esto?. En esencia,
muchos componentes pueden modelarse de formas similares.

107
[108] CAPTULO 4. PATRONES DE DISEO

En este captulo, se describen algunos patrones de diseo que almacenan este co-
nocimiento experimental de diseo procedente del estudio de aplicaciones y de los
xitos y fracasos de casos reales. Bien utilizados, permiten obtener un mejor diseo
ms temprano.

4.1. Introduccin
El diseo de una aplicacin es un proceso iterativo y de continuo renamiento.
Normalmente, una aplicacin es lo sucientemente compleja como para que su di-
seo tenga que ser realizado por etapas, de forma que al principio se identican los
mdulos ms abstractos y, progresivamente, se concreta cada mdulo con un diseo
en particular.
En el camino, es comn encontrar problemas y situaciones que conceptualmente
pueden parecerse entre s, por lo menos a priori. Quizs un estudio ms exhaustivo de
los requisitos permitan determinar si realmente se trata de problemas equivalentes.
Por ejemplo, supongamos que para resolver un determinado problema se llega
a la conclusin de que varios tipos de objetos deben esperar a un evento producido
por otro. Esta situacin puede darse en la creacin de una interfaz grca donde la
pulsacin de un botn dispara la ejecucin de otras acciones. Pero tambin es similar
a la implementacin de un manejador del teclado, cuyas pulsaciones son recogidas por
los procesos interesados, o la de un gestor de colisiones, que notica choques entre
elementos del juego. Incluso se parece a la forma en que muchos programas de chat
envan mensajes a un grupo de usuarios.
Ciertamente, cada uno de los ejemplos anteriores tiene su contexto y no es posible Denicin
(ni a veces deseable) aplicar exactamente la misma solucin a cada uno de ellos. Sin En [38], un patrn de diseo es una
embargo, s que es cierto que existe semejanza en la esencia del problema. En nuestro descripcin de la comunicacin en-
ejemplo, en ambos casos existen entidades que necesitan ser noticadas cuando ocurre tre objetos y clases personalizadas
un cierto evento. para solucionar un problema gen-
rico de diseo bajo un contexto de-
Los patrones de diseo son formas bien conocidas y probadas de resolver proble- terminado.
mas de diseo que son recurrentes en el tiempo. Los patrones de diseo son amplia-
mente utilizados en las disciplinas creativas y tcnicas. As, de la misma forma que
un guionista de cine crea guiones a partir de patrones argumentales como comedia
o ciencia-ccin, un ingeniero se basa en la experiencia de otros proyectos para
identicar patrones comunes que le ayuden a disear nuevos procesos. De esta for-
ma, reutilizando soluciones bien probadas y conocidas se ayuda a reducir el tiempo
necesario para el diseo.
Los patrones sintetizan la tradicin y experiencia profesional de diseadores de
software experimentados que han evaluado y demostrado que la solucin proporciona-
da es una buena solucin bajo un determinado contexto. El diseador o desarrollador
que conozca diferentes patrones de diseo podr reutilizar estas soluciones, pudiendo
alcanzar un mejor diseo ms rpidamente.
4.1. Introduccin [109]

4.1.1. Estructura de un patrn de diseo

C4
Cuando se describe un patrn de diseo se pueden citar ms o menos propiedades
del mismo: el problema que resuelve, sus ventajas, si proporciona escalabilidad en el
diseo o no, etc. Nosotros vamos a seguir las directrices marcadas por los autores del
famoso libro de Design Patterns [38] 1 , por lo que para denir un patrn de diseo es
necesario describir, como mnimo, cuatro componentes fundamentales:

Nombre: el nombre del patrn es fundamental. Es deseable tener un nombre


corto y autodenido, de forma que sea fcil de manejar por diseadores y desa-
rrolladores.
Los buenos nombres pueden ser compartidos por todos de forma que se cree un
vocabulario comn con el que se pueda describir documentacin fcilmente,
adems de construir y detallar soluciones ms complejas basadas en patrones.
Tambin se deben indicar los alias del patrn.
Problema y contexto: obviamente, el problema que resuelve un patrn en con-
creto debe ser descrito detalladamente. Sin embargo, es muy importante que se
d una denicin clara del contexto en el que el patrn tiene sentido aplicar-
lo. El contexto se puede ver como un listado de precondiciones que deben ser
cumplidas para poder aplicar el patrn.
Solucin: la solucin que proporciona un patrn se describe genricamente y
nunca ligada a ninguna implementacin. Normalmente, se utiliza los conceptos
y nomenclatura de la programacin orientada objetos. Por ello, la solucin nor-
malmente describe las clases y las relaciones entre objetos, as como la respon-
sabilidad de cada entidad y cmo colaboran entre ellas para llegar a la solucin.
Gracias a la adopcin de esta nomenclatura, la implementacin de los patrones
en los lenguajes orientados a objetos como C++ es ms directa dada su especi-
cacin abstracta.
Ventajas y desventajas: la aplicacin de un patrn de diseo no es una decisin
que debe tomarse sin tener en cuenta los benecios que aporta y sus posibles
inconvenientes. Junto con los anteriores apartados, se deben especicar las ven-
tajas y desventajas que supone la aplicacin del patrn en diferentes trminos:
complejidad, tiempo de ejecucin, acoplamiento, cohesin, extensibilidad, por-
tabilidad, etc. Si estos trminos estn documentados, ser ms sencillo tomar
una decisin.

El uso de patrones de diseo es recomendable. Sin embargo, hay que tener


en cuenta que un patrn no es bueno ni malo en s mismo ni en su totalidad.
El contexto de aplicacin puede ser determinante para no optar por una so-
lucin basada en un determinado patrn. Los caones pueden ser una buena
arma de guerra, pero no para matar moscas.

1 Tambin conocido como The Gang of Four (GoF) (la Banda de los Cuatro) en referencia a los autores

del mismo. Sin duda se trata de un famoso libro en este rea, al que no le faltan detractores.
[110] CAPTULO 4. PATRONES DE DISEO

4.1.2. Tipos de patrones


De entre los diferentes criterios que se podran adoptar para clasicar los diferentes
patrones de diseo, uno de los ms aceptados es el mbito de diseo donde tienen
aplicacin. De esta forma, se denen tres categoras fundamentales:

Patrones de creacin: se trata de aquellos que proporcionan una solucin rela-


cionada con la construccin de clases, objetos y otras estructuras de datos. Por
ejemplo, patrones como Abstract Factory, Builder y otros ofrecen mecanismos
de creacin de instancias de objetos y estructuras escalables dependiendo de las
necesidades.
Patrones estructurales: este tipo de patrones versan sobre la forma de organizar
las jerarquas de clases, las relaciones y las diferentes composiciones entre ob-
jetos para obtener un buen diseo en base a unos requisitos de entrada. Patrones
como Adapter, Facade o Flyweight son ejemplos de patrones estructurales.
Patrones de comportamiento: las soluciones de diseo que proporcionan los
patrones de comportamiento estn orientadas al envo de mensajes entre obje-
tos y cmo organizar ejecuciones de diferentes mtodos para conseguir realizar
algn tipo de tarea de forma ms conveniente. Algunos ejemplos son Visitor,
Iterator y Observer.

Algunos profesionales, como Jeff Atwood, critican el uso excesivo de pa-


trones. Argumentan que es ms importante identicar bien las responsabili-
dades de cada entidad que el propio uso del patrn. Otros se plantean si el
problema no est realmente en los propios lenguajes de programacin, que
no proporcionan las herramientas semnticas necesarias.

4.2. Patrones de creacin


En esta seccin se describen algunos patrones que ayudan en el diseo de proble-
mas en los que la creacin de instancias de diferentes tipos es el principal problema.

4.2.1. Singleton
El patrn singleton se suele utilizar cuando se requiere tener una nica instancia
de un determinado tipo de objeto.

La idoneidad del patrn Singleton es muy controvertida y est muy cuestio-


nada. Muchos autores y desarrolladores, entre los que destaca Eric Gamma
(uno de los autores de [38]) consideran que es un antipatrn, es decir, una
mala solucin a un problema de diseo.)
4.2. Patrones de creacin [111]

Problema

C4
En C++, utilizando el operador new es posible crear una instancia de un objeto.
Sin embargo, es posible que necesitemos que slo exista una instancia de una clase
determinada por diferentes motivos (prevencin de errores, seguridad, etc.).
El baln en un juego de ftbol o la entidad que representa al mundo 3D son ejem-
plos donde podra ser conveniente mantener una nica instancia de este tipo de objetos.

Solucin

Para garantizar que slo existe una instancia de una clase es necesario que los
clientes no puedan acceder directamente al constructor. Por ello, en un singleton el
constructor es, por lo menos, protected. A cambio se debe proporcionar un nico
punto (controlado) por el cual se pide la instancia nica. El diagrama de clases de este
patrn se muestra en la gura 4.1.

Implementacin

A continuacin, se muestra una implementacin bsica del patrn Singleton:


Figura 4.1: Diagrama de clases del
patrn Singleton.
Listado 4.1: Singleton (ejemplo)
1 /* Header */
2 class Ball {
3 protected:
4
5 float _x, _y;
6 static Ball* theBall_;
7
8 Ball(float x, float y) : _x(x), _y(y) { };
9 Ball(const Ball& ball);
10 void operator=(const Ball& ball ) ;
11
12 public:
13 static Ball& getTheBall();
14
15 void move(float _x, float _y) { /*...*/ };
16 };
17
18 Ball& Ball::getTheBall()
19 {
20 static Ball theBall_;
21 return theBall_;
22 }

Como se puede ver, la caracterstica ms importante es que los mtodos que pue-
den crear una instancia de Ball son todos privados para los clientes externos. Todos
ellos deben utilizar el mtodo esttico getTheBall() para obtener la nica instan-
cia.

Esta implementacin no es vlida para programas multihilo, es decir, no es


thread-safe.
[112] CAPTULO 4. PATRONES DE DISEO

Como ejercicio se plantea la siguiente pregunta: en la implementacin propor-


cionada, se garantiza que no hay memory leak? Qu sera necesario para que la
implementacin fuera thread-safe?

Consideraciones

El patrn Singleton puede ser utilizado para modelar:

Gestores de acceso a base de datos, sistemas de cheros, render de grcos, etc.


Estructuras que representan la conguracin del programa para que sea accesi-
ble por todos los elementos en cualquier instante.

El Singleton es un caso particular de un patrn de diseo ms general llamado


Object Pool, que permite crear n instancias de objetos de forma controlada.

4.2.2. Abstract Factory


El patrn Abstract Factory permite crear diferentes tipos de instancias, aislando al
cliente sobre cmo se debe crear cada una de ellas.

Problema

Conforme un programa crece, el nmero de clases que representan los diferentes


tipos de objetos suele tambin crecer. Muchos de los diseos tienen jerarquas de
objetos tales como la que se muestra en la gura 4.2.

Figura 4.2: Ejemplos de jerarquas de clases

En ella, se muestra jerarquas de clases que modelan los diferentes tipos de per-
sonajes de un juego y algunas de sus armas. Para construir cada tipo de personaje es
necesario saber cmo construirlo y con qu otro tipo de objetos tiene relacin. Por
ejemplo, restricciones del tipo la gente del pueblo no puede llevar armas o los ar-
queros slo pueden puede tener un arco, es conocimiento especco de la clase que
se est construyendo.
Supongamos que en nuestro juego, queremos obtener razas de personajes: hom-
bres y orcos. Cada raza tiene una serie de caractersticas propias que hacen que pueda
moverse ms rpido, trabajar ms o tener ms resistencia a los ataques.
4.2. Patrones de creacin [113]

El patrn Abstract Factory puede ser de ayuda en este tipo de situaciones en las que
es necesario crear diferentes tipos de objetos utilizando una jerarqua de componentes.

C4
Dada la complejidad que puede llegar a tener la creacin de una instancia es deseable
aislar la forma en que se construye cada clase de objeto.

Solucin

En la gura 4.3 se muestra la aplicacin del patrn para crear las diferentes ra-
zas de soldados. Por simplicidad, slo se ha aplicado a esta parte de la jerarqua de
personajes.
En primer lugar se dene una factora abstracta que ser la que utilice el cliente
(Game) para crear los diferentes objetos. CharFactory es una factora que slo
dene mtodos abstractos y que sern implementados por sus clases hijas. stas son
factoras concretas a cada tipo de raza (ManFactory y OrcFactory) y ellas son
las que crean las instancias concretas de objetos Archer y Rider para cada una de
las razas.
En denitiva, el patrn Abstract Factory recomienda crear las siguientes entidades:

Factora abstracta que dena una interfaz para que los clientes puedan crear los
distintos tipos de objetos.
Factoras concretas que realmente crean las instancias nales.

Implementacin

Basndonos en el ejemplo anterior, el objeto Game sera el encargado de crear los


diferentes personajes utilizando una factora abstracta. El siguiente fragmento de cdi-
go muestra cmo la clase Game recibe una factora concreta (utilizando polimorsmo)
y la implementacin del mtodo que crea los soldados.

Figura 4.3: Aplicacin del patrn Abstract Factory


[114] CAPTULO 4. PATRONES DE DISEO

Listado 4.2: Abstract Factory (Game)


1 /* ... */
2 Game game;
3 SoldierFactory* factory;
4
5 if (isSelectedMan) {
6 factory = new ManFactory();
7 } else {
8 factory = new OrcFactory();
9 }
10
11 game->createSoldiers(factory);
12 /* ... */
13
14
15 /* Game implementation */
16 vector<Soldier*> Game::createSoldiers(SoldierFactory* factory)
17 {
18 vector<Solider*> soldiers;
19 for (int i=0; i<5; i++) {
20 soldiers.push_back(factory->makeArcher());
21 soldiers.push_back(factory->makeRider());
22 }
23 return soldiers;
24 }

Como puede observarse, la clase Game simplemente invoca los mtodos de la


factora abstracta. Por ello, createSoldier() funciona exactamente igual para
cualquier tipo de factora concreta (de hombres o de orcos).
Una implementacin del mtodo makeArcher() de cada factora concreta po-
dra ser como sigue:

Listado 4.3: Abstract Factory (factoras concretas)


1 /* OrcFactory */
2 Archer* OrcFactory::makeArcher()
3 {
4 Archer archer = new Archer();
5 archer->setLife(200);
6 archer->setName(Orc);
7 return archer;
8 }
9
10 /* ManFactory */
11 Archer* ManFactory::makeArcher()
12 {
13 Archer archer = new Archer();
14 archer->setLife(100);
15 archer->setName(Man);
16 return archer;
17 }

Ntese como las factoras concretas ocultan las particularidades de cada tipo. Una
implementacin similar tendra el mtodo makeRider().

Consideraciones

El patrn Abstract Factory puede ser aplicable cuando:

el sistema de creacin de instancias debe aislarse.


4.2. Patrones de creacin [115]

es necesaria la creacin de varias instancias de objetos para tener el sistema


congurado.

C4
cuando la creacin de las instancias implican la imposicin de restricciones y
otras particularidades propias de los objetos que se construyen.
los productos que se deben fabricar en las factoras no cambian excesivamente
en el tiempo. Aadir nuevos productos implica aadir mtodos a todas las fac-
toras ya creadas, por lo que es un poco problemtico. En nuestro ejemplo, si
quisiramos aadir un nuevo tipo de soldado deberamos modicar la factora
abstracta y las concretas. Por ello, es recomendable que se aplique este patrn
sobre diseos con un cierto grado de estabilidad.

Un patrn muy similar a ste es el patrn Builder. Con una estructura similar,
el patrn Builder se centra en el proceso de cmo se crean las instancias y no en
la jerarqua de factoras que lo hacen posible. Como ejercicio se plantea estudiar el
patrn Builder y encontrar las diferencias.

4.2.3. Factory Method


El patrn Factory Method se basa en la denicin de una interfaz para crear ins-
tancias de objetos y permite a las subclases decidir cmo se crean dichas instancias
implementando un mtodo determinado.

Problema

Al igual que ocurre con el patrn Abstract Factory, el problema que se pretende
resolver es la creacin de diferentes instancias de objetos abstrayendo la forma en que
realmente se crean.

Figura 4.4: Ejemplo de aplicacin de Factory Mehod


[116] CAPTULO 4. PATRONES DE DISEO

Solucin

La gura 4.4 muestra un diagrama de clases para nuestro ejemplo que emplea el
patrn Factory Method para crear ciudades en las que habitan personajes de diferentes
razas.
Como puede verse, los objetos de tipo Village tienen un mtodo populate()
que es implementado por las subclases. Este mtodo es el que crea las instancias de
Villager correspondientes a cada raza. Este mtodo es el mtodo factora. Adems
de este mtodo, tambin se proporcionan otros como population() que devuelve
la poblacin total, o location() que devuelve la posicin de la cuidad en el mapa.
Todos estos mtodos son comunes y heredados por las ciudades de hombres y orcos.
Finalmente, objetos Game podran crear ciudades y, consecuentemente, crear ciu-
dadanos de distintos tipos de una forma transparente.

Consideraciones

Este patrn presenta las siguientes caractersticas:

No es necesario tener una factora o una jerarqua de factoras para la creacin


de objetos. Permite diseos ms adaptados a la realidad.
El mtodo factora, al estar integrado en una clase, hace posible conectar dos
jerarqua de objetos distintas. Por ejemplo, si los personajes tienen un mtodo
factora de las armas que pueden utilizar, el dominio de las armas y los perso-
najes queda unido a travs el mtodo. Las subclases de los personajes crearan
las instancias de Weapon correspondientes.

Ntese que el patrn Factory Method se utiliza para implementar el patrn Abs-
tract Factory ya que la factora abstracta dene una interfaz con mtodos de construc-
cin de objetos que son implementados por las subclases.

4.2.4. Prototype
El patrn Prototype proporciona abstraccin a la hora de crear diferentes objetos
en un contexto donde se desconoce cuntos y cules deben ser creados a priori. La
idea principal es que los objetos deben poder clonarse en tiempo de ejecucin.

Problema

Los patrones Factory Method y Abstract Factory tienen el problema de que se


basan en la herencia e implementacin de mtodos abstractos por subclases para de-
nir cmo se construye cada producto concreto. Para sistemas donde el nmero de
productos concretos puede ser elevado o indeterminado esto puede ser un problema.
Supongamos que en nuestra jerarqua de armas, cuya clase padre es Weapon,
comienza a crecer con nuevos tipos de armas y, adems, pensamos en dejar libertad
para que se carguen en tiempo de ejecucin nuevas armas que se implementarn como
libreras dinmicas. Adems, el nmero de armas variar dependiendo de ciertas con-
diciones del juego y de conguracin. En este contexto, puede ser ms que dudoso el
uso de factoras.
4.3. Patrones estructurales [117]

Solucin

C4
Para atender a las nuevas necesidades dinmicas en la creacin de los distintos
tipo de armas, sin perder la abstraccin sobre la creacin misma, se puede utilizar el
patrn Prototype como se muestra en la gura 4.5.

Figura 4.5: Ejemplo de aplicacin de Prototype

La diferencia fundamental se encuentra en la adicin del mtodo clone() a todas


los productos que pueden ser creados. El cliente del prototipo slo tiene que invocar
clone() sobre su instancia Weapon para que se cree una instancia concreta. Como
se puede ver, no es necesario un agente intermedio (factoras) para crear instancias
de un determinado tipo. La creacin se realiza en la clase concreta que representa a
la instancia, por lo que basta con cambiar la instancia prototype de Client para
que se creen nuevos tipos de objetos en tiempo de ejecucin.

Consideraciones

Algunas notas interesantes sobre Prototype:

Puede parecer que entra en conicto con Abstract Factory debido a que intenta
eliminar, precisamente, factoras intermedias. Sin embargo, es posible utilizar
ambas aproximaciones en una Prototype Abstract Factory de forma que la fac-
tora se congura con los prototipos concretos que puede crear y sta slo invoca
a clone().
Tambin es posible utilizar un gestor de prototipos que permita cargar y descar-
gar los prototipos disponibles en tiempo de ejecucin. Este gestor es interesante
para tener diseos ampliables en tiempo de ejecucin (plugins).
Para que los objetos puedan devolver una copia de s mismo es necesario que en
su implementacin est el constructor de copia (copy constructor) que en C++
viene por defecto implementado.

4.3. Patrones estructurales


Hasta ahora, hemos visto patrones para disear aplicaciones donde el problema
principal es la creacin de diferentes instancias de clases. En esta seccin se mostrarn
los patrones de diseo estructurales que se centran en las relaciones entre clases y
en cmo organizarlas para obtener un diseo eciente para resolver un determinado
problema.
[118] CAPTULO 4. PATRONES DE DISEO

4.3.1. Composite
El patrn Composite se utiliza para crear una organizacin arbrea y homognea
de instancias de objetos.

Problema

Para ilustrar el problema supngase un juego de estrategia en el que los jugadores


pueden recoger objetos o items, los cuales tienen una serie de propiedades como pre-
cio, descripcin, etc. Cada item, a su vez, puede contener otros items. Por ejemplo,
un bolso de cuero puede contener una pequea caja de madera que, a su vez, contiene
un pequeo reloj dorado.
En denitiva, el patrn Composite habla sobre cmo disear este tipo de estructu-
ras recursivas donde la composicin homognea de objetos recuerda a una estructura
arbrea.

Solucin

Para el ejemplo expuesto anteriormente, la aplicacin del patrn Composite que-


dara como se muestran en la gura 4.6. Como se puede ver, todos los elementos
son Items que implementan una serie de mtodos comunes. En la jerarqua, existen
objetos compuestos, como Bag, que mantienen una lista (items) donde residen los
objetos que contiene. Naturalmente, los objetos compuestos suelen ofrecer tambin
operaciones para aadir, eliminar y actualizar.

Figura 4.6: Ejemplo de aplicacin del patrn Composite

Por otro lado, hay objetos hoja que no contienen a ms objetos, como es el caso
de Clock.

Consideraciones

Al utilizar este patrn, se debe tener en cuenta las siguientes consideraciones:

Una buena estrategia para identicar la situacin en la que aplicar este patrn
es cuando tengo un X y tiene varios objetos X.
4.3. Patrones estructurales [119]

La estructura generada es muy exible siempre y cuando no importe el tipo de


objetos que pueden tener los objetos compuestos. Es posible que sea deseable

C4
prohibir la composicin de un tipo de objeto con otro. Por ejemplo, un jarrn
grande dentro de una pequea bolsa. La comprobacin debe hacerse en tiempo
de ejecucin y no es posible utilizar el sistema de tipos del compilador. En este
sentido, usando Composite se relajan las restricciones de composicin entre
objetos.
Los usuarios de la jerarqua se hacen ms sencillos, ya que slo tratan con un
tipo abstracto de objeto, dndole homogeneidad a la forma en que se maneja la
estructura.

4.3.2. Decorator
Tambin conocido como Wrapper, el patrn Decorator sirve para aadir y/o mo-
dicar la responsabilidad, funcionalidad o propiedades de un objeto en tiempo de
ejecucin.

Problema

Supongamos que el personaje de nuestro videojuego porta un arma que utiliza para
eliminar a sus enemigos. Dicha arma, por ser de un tipo determinado, tiene una serie
de propiedades como el radio de accin, nivel de ruido, nmero de balas que puede
almacenar, etc. Sin embargo, es posible que el personaje incorpore elementos al arma
que puedan cambiar estas propiedades como un silenciador o un cargador extra.
El patrn Decorator permite organizar el diseo de forma que la incorporacin
de nueva funcionalidad en tiempo de ejecucin a un objeto sea transparente desde el
punto de vista del usuario de la clase decorada.

Solucin

En la gura 4.7 se muestra la aplicacin del patrn Decorator al supuesto anterior-


mente descrito. Bsicamente, los diferentes tipos de armas de fuego implementan una
clase abstracta llamada Firearm. Una de sus hijas es FirearmDecorator que es
el padre de todos los componentes que decoran a un objeto Firearm. Ntese que
este decorador implementa la interfaz propuesta por Firearm y est compuesta por
un objeto gun, el cual decora.

Implementacin

A continuacin, se expone una implementacin en C++ del ejemplo del patrn


Decorator. En el ejemplo, un arma de tipo Rifle es decorada para tener tanto silen-
ciador como una nueva carga de municin. Ntese cmo se utiliza la instancia gun a
lo largo de los constructores de cada decorador.

Listado 4.4: Decorator


1 class Firearm {
2 public:
3 virtual float noise() const = 0;
4 virtual int bullets() const = 0;
5 };
6
7 class Rifle : public Firearm {
[120] CAPTULO 4. PATRONES DE DISEO

Figura 4.7: Ejemplo de aplicacin del patrn Decorator

8 public:
9 float noise () const { return 150.0; }
10 int bullets () const { return 5; }
11 };
12
13 /* Decorators */
14
15 class FirearmDecorator : public Firearm {
16 protected:
17 Firearm* _gun;
18 public:
19 FirearmDecorator(Firearm* gun): _gun(gun) {};
20 virtual float noise () const { return _gun->noise(); }
21 virtual int bullets () const { return _gun->bullets(); }
22 };
23
24 class Silencer : public FirearmDecorator {
25 public:
26 Silencer(Firearm* gun) : FirearmDecorator(gun) {};
27 float noise () const { return _gun->noise() - 55; }
28 int bullets () const { return _gun->bullets(); }
29 };
30
31 class Magazine : public FirearmDecorator {
32 public:
33 Magazine(Firearm* gun) : FirearmDecorator(gun) {};
34 float noise () const { return _gun->noise(); }
35 int bullets () const { return _gun->bullets() + 5; }
36 };
37
38 /* Using decorators */
39
40 ...
41 Firearm* gun = new Rifle();
42 cout << "Noise: " << gun->noise() << endl;
43 cout << "Bullets: " << gun->bullets() << endl;
44 ...
45 // char gets a silencer
46 gun = new Silencer(gun);
47 cout << "Noise: " << gun->noise() << endl;
48 cout << "Bullets: " << gun->bullets() << endl;
49 ...
50 // char gets a new magazine
51 gun = new Magazine(gun);
52 cout << "Noise: " << gun->noise() << endl;
53 cout << "Bullets: " << gun->bullets() << endl;
4.3. Patrones estructurales [121]

En cada momento, qu valores se imprimen?. Supn que el personaje puede qui-


tar el silenciador. Qu cambios habra que hacer en el cdigo para quitar el deco-

C4
rador a la instancia?

Consideraciones

A la hora de aplicar el patrn Decorator se deben tener en cuenta las siguientes


consideraciones:

Es un patrn similar al Composite. Sin embargo, existen grandes diferencias:

Est ms centrado en la extensin de la funcionalidad que en la compo-


sicin de objetos para la generacin de una jerarqua como ocurre en el
Composite.
Normalmente, slo existe un objeto decorado y no un vector de objetos
(aunque tambin es posible).

Este patrn permite tener una jerarqua de clases compuestas, formando una es-
tructura ms dinmica y exible que la herencia esttica. El diseo equivalente
utilizando mecanismos de herencia debera considerar todos los posibles casos
en las clases hijas. En nuestro ejemplo, habra 4 clases: rie, rie con silencia-
dor, rie con cargador extra y rie con silenciador y cargador. Sin duda, este
esquema es muy poco exible.

4.3.3. Facade
El patrn Facade eleva el nivel de abstraccin de un determinado sistema para
ocultar ciertos detalles de implementacin y hacer ms sencillo su uso.

Problema

Muchos de los sistemas que proporcionan la capacidad de escribir texto en pantalla


son complejos de utilizar. Su complejidad reside en su naturaleza generalista, es decir,
estn diseados para abarcar un gran nmero de tipos de aplicaciones. Por ello, el
usuario normalmente debe considerar cuestiones de bajo nivel como es congurar
el propio sistema interconectando diferentes objetos entre s que, a priori, parece que
nada tienen que ver con la tarea que se tiene que realizar.
Para ver el problema que supone para un usuario un bajo nivel de abstraccin no
es necesario recurrir a una librera o sistema externo. Nuestro propio proyecto, si est
bien diseado, estar dividido en subsistemas que proporcionan una cierta funciona-
lidad. Basta con que sean genricos y reutilizables para que su complejidad aumente
considerablemente y, por ello, su uso sea cada vez ms tedioso.
Por ejemplo, supongamos que hemos creado diferentes sistemas para realizar dis-
tintas operaciones grcas (manejador de archivos, cargador de imgenes, etc.) el si-
guiente cdigo correspondera con la animacin de una explosin en un punto deter-
minado de la pantalla.
Sin duda alguna, y pese a que ya se tienen objetos que abstraen subsistemas tales
como sistemas de archivos, para los clientes que nicamente quieran mostrar explo-
siones no proporciona un nivel de abstraccin suciente. Si esta operacin se realiza
frecuentemente el problema se agrava.
[122] CAPTULO 4. PATRONES DE DISEO

Listado 4.5: Ejemplo de uso de diferentes subsistemas


1 File* file_exp1 = FileManager::load_file("explosion1.tif");
2 File* file_exp2 = FileManager::load_file("explosion2.tif");
3 Image* explosion1 = ImageManager::get_image_from_file(file_exp1);
4 Image* explosion2 = ImageManager::get_image_from_file(file_exp2);
5 Screen* screen = Screen::get_screen();
6
7 screen->add_element(explosion1, x, y);
8 screen->add_element(explosion2, x+2, y+2);
9 ...
10 /* more configuration */
11 ...
12 screen->draw();

Solucin

Utilizando el patrn Facade, se proporciona un mayor nivel de abstraccin al clien-


te de forma que se construye una clase fachada entre l y los subsistemas con menos
nivel de abstraccin. De esta forma, se proporciona una visin unicada del conjunto
y, adems, se controla el uso de cada componente.
Para el ejemplo anterior, se podra crear una clase que proporcione la una funcio-
nalidad ms abstracta. Por ejemplo, algo parecido a lo siguiente::

Listado 4.6: Simplicacin utilizando Facade


1 AnimationManager* animation = new AnimationManager();
2 animation->explosion_at(3,4);

Como se puede ver, el usuario ya no tiene que conocer las relaciones que exis-
ten entre los diferentes mdulos para crear este tipo de animaciones. Esto aumenta,
levemente, el nivel de abstraccin y hace ms sencillo su uso.
En denitiva, el uso del patrn Facade proporciona una estructura de diseo como
la mostrada en la gura 4.8.

Figura 4.8: Ejemplo de aplicacin del patrn Facade

Consideraciones

El patrn Facade puede ser til cuando:


4.3. Patrones estructurales [123]

Es necesario refactorizar, es decir, extraer funcionalidad comn de los sistemas


y agruparla en funcin de las necesidades.

C4
Los sistemas deben ser independientes y portables.
Controlar el acceso y la forma en que se utiliza un sistema determinado.
Los clientes pueden seguir utilizando los subsistemas directamente, sin pasar
por la fachada, lo que da la exibilidad de elegir entre una implementacin de
bajo nivel o no.

Sin embargo, utilizando el patrn Facade es posible caer en los siguientes errores:

Crear clases con un tamao desproporcionado. Las clases fachada pueden con-
tener demasiada funcionalidad si no se divide bien las responsabilidades y se
tiene claro el objetivo para el cual se creo la fachada. Para evitarlo, es necesario
ser crtico/a con el nivel de abstraccin que se proporciona.
Obtener diseos poco exibles y con mucha contencin. A veces, es posible
crear fachadas que obliguen a los usuarios a un uso demasiado rgido de la fun-
cionalidad que proporciona y que puede hacer que sea ms cmodo, a la larga,
utilizar los subsistemas directamente. Adems, una fachada puede convertirse
en un nico punto de fallo, sobre todo en sistemas distribuidos en red.
Exponer demasiados elementos y, en denitiva, no proporcionar un nivel de
abstraccin adecuado.

4.3.4. MVC

El patrn MVC (Model View Controller) se utiliza para aislar el dominio de apli-
cacin, es decir, la lgica, de la parte de presentacin (interfaz de usuario).

Problema

Programas como los videojuegos requieren la interaccin de un usuario que, nor-


malmente, realiza diferentes acciones sobre una interfaz grca. Las interfaces dispo-
nibles son muy variadas: desde aplicaciones de escritorio con un entorno GTK (GIMP
ToolKit) a aplicaciones web, pasando por una interfaz en 3D creada para un juego
determinado.
Supongamos que una aplicacin debe soportar varios tipos de interfaz a la vez. Por
ejemplo, un juego que puede ser utilizado con una aplicacin de escritorio y, tambin,
a travs de una pgina web. El patrn MVC sirve para aislar la lgica de la aplicacin,
de la forma en que se sta se presenta, su interfaz grca.

Solucin

En el patrn MVC, mostrado en la gura 4.9, existen tres entidades bien denidas:

Vista: se trata de la interfaz de usuario que interacta con el usuario y recibe sus
rdenes (pulsar un botn, introducir texto, etc.). Tambin recibe rdenes desde
el controlador, para mostrar informacin o realizar un cambio en la interfaz.
Figura 4.9: Estructura del patrn
MVC.
[124] CAPTULO 4. PATRONES DE DISEO

Controlador: el controlador recibe rdenes utilizando, habitualmente, maneja-


dores o callbacks y traduce esa accin al dominio del modelo de la aplicacin.
La accin puede ser crear una nueva instancia de un objeto determinado, actua-
lizar estados, pedir operaciones al modelo, etc.
Modelo: el modelo de la aplicacin recibe las acciones a realizar por el usuario,
pero ya independientes del tipo de interfaz utilizado porque se utilizan, nica-
mente, estructuras propias del dominio del modelo y llamadas desde el contro-
lador.
Normalmente, la mayora de las acciones que realiza el controlador sobre el
modelo son operaciones de consulta de su estado para que pueda ser convenien-
temente representado por la vista.
MVC no es patrn con una separacin tan rgida. Es posible encontrar imple-
mentaciones en las que, por ejemplo, el modelo notique directamente a las in-
terfaces de forma asncrona eventos producidos en sus estructuras y que deben
ser representados en la vista (siempre y cuando exista una aceptable indepen-
dencia entre las capas). Para ello, es de gran utilidad el patrn Observer (ver
seccin 4.4.1).

Consideraciones

El patrn MVC es la losofa que se utiliza en un gran nmero de entornos de


ventanas. Sin embargo, muchos sistemas web como Django tambin se basan en este
patrn. Sin duda, la divisin del cdigo en estos roles proporciona exibilidad a la
hora de crear diferentes tipos de presentaciones para un mismo dominio.
De hecho, desde un punto de vista general, la estructura ms utilizada en los vi-
deojuegos se asemeja a un patrn MVC: la interfaz grca utilizando grcos 3D/2D
(vista), bucle de eventos (controlador) y las estructuras de datos internas (modelo).

4.3.5. Adapter
El patrn Adapter se utiliza para proporcionar una interfaz que, por un lado, cum-
pla con las demandas de los clientes y, por otra, haga compatible otra interfaz que, a
priori, no lo es.

Problema

Es muy probable que conforme avanza la construccin de la aplicacin, el diseo


de las interfaces que ofrecen los componentes pueden no ser las adecuadas o, al me-
nos, las esperadas por los usuarios de los mismos. Una solucin rpida y directa es
adaptar dichas interfaces a nuestras necesidades. Sin embargo, esto puede que no sea
tan sencillo.
En primer lugar, es posible que no tengamos la posibilidad de modicar el cdigo
de la clase o sistema que pretendemos cambiar. Por otro lado, puede ser que sea un
requisito no funcional por parte del cliente: determinado sistema o biblioteca debe
utilizarse s o s. Si se trata de una biblioteca externa (third party), puede ocurrir que
la modicacin suponga un coste adicional para el proyecto ya que tendra que ser
mantenida por el propio proyecto y adaptar las mejoras y cambios que se aadan en la
versin no modicada.
Por lo tanto, es posible llegar a la conclusin de que a pesar de que el sistema,
biblioteca o clase no se adapta perfectamente a nuestras necesidades, trae ms a cuenta
utilizarla que hacerse una versin propia.
4.3. Patrones estructurales [125]

Solucin

C4
Usando el patrn Adapter es posible crear una nueva interfaz de acceso a un deter-
minado objeto, por lo que proporciona un mecanismo de adaptacin entre las deman-
das del objeto cliente y el objeto servidor que proporciona la funcionalidad.

Figura 4.10: Diagrama de clases del patrn Adapter

En la gura 4.10 se muestra un diagrama de clases genrico del patrn basado en la


composicin. Como puede verse, el cliente no utiliza el sistema adaptado, sino el adap-
tador. Este es el que transforma la invocacin a method() en otherMethod().
Es posible que el adaptador tambin incluya nueva funcionalidad. Algunas de las ms
comunes son:

La comprobacin de la correccin de los parmetros.


La transformacin de los parmetros para ser compatibles con el sistema adap-
tado.

Consideraciones

Algunas consideraciones sobre el uso del patrn Adapter:

Tener sistemas muy reutilizables puede hacer que sus interfaces no puedan ser
compatibles con una comn. El patrn Adapter es una buena opcin en este
caso.
Un mismo adaptador puede utilizarse con varios sistemas.
Otra versin del patrn es que la clase Adapter sea una subclase del sistema
adaptado. En este caso, la clase Adapter y la adaptada tienen una relacin ms
estrecha que si se realiza por composicin.
Este patrn se parece mucho al Decorator. Sin embargo, dieren en que la nali-
dad de ste es proporcionar una interfaz completa del objeto adaptador, mientras
que el decorador puede centrarse slo en una parte.

4.3.6. Proxy
El patrn Proxy proporciona mecanismos de abstraccin y control para acceder a
un determinado objeto simulando que se trata del objeto real.
[126] CAPTULO 4. PATRONES DE DISEO

Problema

Muchos de los objetos de los que puede constar una aplicacin pueden presentar
diferentes problemas a la hora de ser utilizados por clientes:

Coste computacional: es posible que un objeto, como una imagen, sea costoso
de manipular y cargar.
Acceso remoto: el acceso por red es una componente cada vez ms comn entre
las aplicaciones actuales. Para acceder a servidores remotos, los clientes deben
conocer las interioridades y pormenores de la red (sockets, protocolos, etc.).
Acceso seguro: es posible que muchos objetos necesiten diferentes privilegios
para poder ser utilizados. Por ejemplo, los clientes deben estar autorizados para
poder acceder a ciertos mtodos.
Dobles de prueba: a la hora de disear y probar el cdigo, puede ser til uti-
lizar objetos dobles que reemplacen instancias reales que pueden hacer que las
pruebas sea pesadas y/o lentas.

Solucin

Supongamos el problema de mostrar una imagen cuya carga es costosa en trminos


computacionales. La idea detrs del patrn Proxy (ver gura 4.11) es crear una un
objeto intermedio (ImageProxy) que representa al objeto real (Image) y que se
utiliza de la misma forma desde el punto de vista del cliente. De esta forma, el objeto
proxy puede cargar una nica vez la imagen y mostrarla tantas veces como el cliente
lo solicite.

Figura 4.11: Ejemplo de aplicacin del patrn Proxy

Implementacin

A continuacin, se muestra una implementacin del problema anteriormente des-


crito donde se utiliza el patrn Proxy. En el ejemplo puede verse (en la parte del clien-
te) cmo la imagen slo se carga una vez: la primera vez que se invoca a display().
El resto de invocaciones slo muestran la imagen ya cargada.

Listado 4.7: Ejemplo de implementacin de Proxy


1 class Graphic {
2 public:
3 void display() = 0;
4 };
5
4.4. Patrones de comportamiento [127]

6 class Image : public Graphic {


7 public:
void load() {

C4
8
9 ...
10 /* perform a hard file load */
11 ...
12 }
13
14 void display() {
15 ...
16 /* perform display operation */
17 ...
18 }
19 };
20
21 class ImageProxy : public Graphic {
22 private:
23 Image* _image;
24 public:
25 void display() {
26 if (not _image) {
27 _image = new Image();
28 _image.load();
29 }
30 _image->display();
31 }
32 };
33
34 /* Client */
35 ...
36 Graphic image = new ImageProxy();
37 image->display(); // loading and display
38 image->display(); // just display
39 image->display(); // just display
40 ...

Consideraciones

Existen muchos ejemplos donde se hace un uso intensivo del patrn proxy en
diferentes sistemas:

En los sistemas de autenticacin, dependiendo de las credenciales presentadas


por el cliente, devuelven un proxy u otro que permiten realizar ms o menos
operaciones.
En middlewares orientados a objetos como CORBA o ZeroC I CE (Internet Com-
munication Engine), se utiliza la abstraccin del Proxy para proporcionar invo-
caciones remotas entre objetos distribuidos. Desde el punto de vista del cliente,
la invocacin se produce como si el objeto estuviera accesible localmente. El
proxy es el encargado de proporcionar esta abstraccin.

4.4. Patrones de comportamiento


Los patrones de diseo relacionados con el comportamiento de las aplicaciones
se centran en cmo disear los sistemas para obtener una cierta funcionalidad y, al
mismo tiempo, un diseo escalable.
[128] CAPTULO 4. PATRONES DE DISEO

4.4.1. Observer
El patrn Observer se utiliza para denir relaciones 1 a n de forma que un objeto
pueda noticar y/o actualizar el estado de otros automticamente.

Problema

Tal y como se describi en la seccin 4.3.4, el dominio de la aplicacin en el


patrn MVC puede noticar de cambios a las diferentes vistas. Es importante que el
dominio no conozca los tipos concretos de las vistas, de forma que haya que modicar
el dominio en caso de aadir o quitar una vista.
Otros ejemplos donde se da este tipo de problemas ocurren cuando el estado un
elemento tiene inuencia directa sobre otros. Por ejemplo, si en el juego se lanza un
misil seguramente haya que noticar que se ha producido el lanzamiento a diferentes
sistemas de sonido, partculas, luz, y los objetos que puedan estar alrededor.

Solucin

El patrn Observer proporciona un diseo con poco acoplamiento entre los obser-
vadores y el objeto observado. Siguiendo la losofa de publicacin/suscripcin, los
objetos observadores se deben registrar en el objeto observado, tambin conocido co-
mo subject. As, cuando ocurra el evento oportuno, el subject recibir una invocacin
a travs de notify() y ser el encargado de noticar a todos los elementos suscri-
tos a l a travs del mtodo update(). Los observadores que reciben la invocacin
pueden realizar las acciones pertinentes como consultar el estado del dominio para
obtener nuevos valores. En la gura 4.12 se muestra un esquema general del patrn
Observer.

Figura 4.12: Diagrama de clases del patrn Observer

A modo de ejemplo, en la gura 4.13 se muestra un diagrama de secuencia en


el que se describe el orden de las invocaciones en un sistema que utiliza el patrn
Observer. Ntese que los observadores se suscriben al subject (RocketLauncher)
y, a continuacin, reciben las actualizaciones. Tambin pueden dejar de recibirlas,
utilizando la operacin detach().

Consideraciones

Al emplear el patrn Observer en nuestros diseos, se deben tener en cuenta las


siguientes consideraciones:
4.4. Patrones de comportamiento [129]

C4
Figura 4.13: Diagrama de secuencia de ejemplo utilizando un Observer

El objeto subject puede encapsular funcionalidad compleja semnticamente


que ser noticada asncronamente a los observadores. El objeto observable,
puede denir diferentes estrategias a la hora de noticar un cambio.
Subject y sus observadores forman un modelo push/pull, por lo que evita la
creacin de protocolos de comunicacin concretos entre ellos. Toda comunica-
cin de este tipo pueden realizarse de la misma forma.
No es necesario que el subject sea el que realiza la llamada a notify(). Un
cliente externo puede ser el que fuerce dicha llamada.
Los observadores pueden estar suscritos a ms de un subject.
Los observadores no tienen control sobre las actualizaciones no deseadas. Es
posible que un observador no est interesado en ciertas noticaciones y que sea
necesario consultar al Subject por su estado en demasiadas ocasiones. Esto
puede ser un problema en determinados escenarios.

Los canales de eventos es un patrn ms genrico que el Observer pero que sigue
respetando el modelo push/pull. Consiste en denir estructuras que permiten la comu-
nicacin n a n a travs de un medio de comunicacin (canal) que se puede multiplexar
en diferentes temas (topics). Un objeto puede establecer un rol suscriptor de un tema
dentro de un canal y slo recibir las noticaciones del mismo. Adems, tambin pue-
de congurarse como publicador, por lo que podra enviar actualizaciones al mismo
canal.
[130] CAPTULO 4. PATRONES DE DISEO

4.4.2. State
El patrn State es til para realizar transiciones de estado e implementar autmatas
respetando el principio de encapsulacin.

Problema

Es muy comn que en cualquier aplicacin, includo los videojuegos, existan es-
tructuras que pueden ser modeladas directamente como un autmata, es decir, una
coleccin de estados y unas transiciones dependientes de una entrada. En este caso, la
entrada pueden ser invocaciones y/o eventos recibidos.
Por ejemplo, los estados de un personaje de un videojuego podran ser: de pie,
tumbado, andando y saltando. Dependiendo del estado en el que se encuentre y de la
invocacin recibida, el siguiente estado ser uno u otro. Por ejemplo, si est de pie y
recibe la orden de tumbarse, sta se podr realizar. Sin embargo, si ya est tumbado
no tiene sentido volver a tumbarse, por lo que debe permanecer en ese estado.

Solucin

El patrn State permite encapsular el mecanismo de las transiciones que sufre un


objeto a partir de los estmulos externos. En la gura 4.14 se muestra un ejemplo de
aplicacin del mismo. La idea es crear una clase abstracta que representa al estado
del personaje (CharacterState). En ella se denen las mismas operaciones que
puede recibir el personaje con una implementacin por defecto. En este caso, la im-
plementacin es vaca.

Figura 4.14: Ejemplo de aplicacin del patrn State

Por cada estado en el que puede encontrarse el personaje, se crea una clase que
hereda de la clase abstracta anterior, de forma que en cada una de ellas se implementen
los mtodos que producen cambio de estado.
Por ejemplo, segn el diagrama, en el estado de pie se puede recibir la orden
de caminar, tumbarse y saltar, pero no de levantarse. En caso de recibir esta ltima, se
ejecutar la implementacin por defecto, es decir, no hacer nada.
En denitiva, la idea es que las clases que representan a los estados sean las encar-
gadas de cambiar el estado del personaje, de forma que los cambios de estados quedan
encapsulados y delegados al estado correspondiente.
4.4. Patrones de comportamiento [131]

Consideraciones

C4
Los componentes del diseo que se comporten como autmatas son buenos
candidatos a ser modelados con el patrn State. Una conexin TCP (Transport
Control Protocol) o un carrito en una tienda web son ejemplos de este tipo de
problemas.
Es posible que una entrada provoque una situacin de error estando en un deter-
minado estado. Para ello, es posible utilizar las excepciones para noticar dicho
error.
Las clases que representan a los estados no deben mantener un estado intrn-
seco, es decir, no se debe hacer uso de variables que dependan de un contexto.
De esta forma, el estado puede compartirse entre varias instancias. La idea de
compartir un estado que no depende del contexto es la base fundamental del pa-
trn Flyweight, que sirve para las situaciones en las que crear muchas instancias
puede ser un problema de rendimiento.

4.4.3. Iterator
El patrn Iterator se utiliza para ofrecer una interfaz de acceso secuencial a una
determinada estructura ocultando la representacin interna y la forma en que realmen-
te se accede.

Problema

Manejar colecciones de datos es algo muy habitual en el desarrollo de aplica-


ciones. Listas, pilas y, sobre todo, rboles son ejemplos de estructuras de datos muy
presentes en los juegos y se utilizan de forma intensiva.
Una operacin muy habitual es recorrer las estructuras para analizar y/o buscar
los datos que contienen. Es posible que sea necesario recorrer la estructura de for-
ma secuencial, de dos en dos o, simplemente, de forma aleatoria. Los clientes suelen
implementar el mtodo concreto con el que desean recorrer la estructura por lo que
puede ser un problema si, por ejemplo, se desea recorrer una misma estructura de da-
tos de varias formas distintas. Conforme aumenta las combinaciones entre los tipos de
estructuras y mtodos de acceso, el problema se agrava.

Solucin

Con ayuda del patrn Iterator es posible obtener acceso secuencial, desde el punto
de vista del usuario, a cualquier estructura de datos, independientemente de su imple-
mentacin interna. En la gura 4.15 se muestra un diagrama de clases genrico del
patrn. Como puede verse, la estructura de datos es la encargada de crear el iterador
adecuado para ser accedida a travs del mtodo iterator(). Una vez que el cliente
ha obtenido el iterador, puede utilizar los mtodos de acceso que ofrecen tales como
next() (para obtener el siguiente elemento) o isDone() para comprobar si no
existen ms elementos.
[132] CAPTULO 4. PATRONES DE DISEO

Figura 4.15: Diagrama de clases del patrn Iterator

Implementacin

A continuacin, se muestra una implementacin simplicada y aplicada a una es-


tructura de datos de tipo lista. Ntese cmo utilizando las primitivas que ofrece la
estructura, el iterator proporciona una visin de acceso secuencial a travs del mtodo
next().

Listado 4.8: Iterator (ejemplo)


1 class List : public Struct {
2 public:
3 void add(const Object& ob) { /* add element in a list*/ };
4 void remove(const Object& ob) { /* remove element in a list*/ };
5 Object get_at(const int index) { /* get list[index] element*/ };
6
7 /* more access methods */
8
9 void iterator(const Object& ob) {
10 return new ListIterator(this);
11 };
12 };
13
14 class ListIterator : public Iterator {
15 private:
16 int _currentIndex;
17 List _list;
18
19 public:
20 ListIterator (List* list) : _currentIndex(0), _list(list) { };
21
22 Object next() {
23 if (isDone()) {
24 throw new IteratorOutOfBounds();
25 }
26 Object retval = _list->get_at(_currentIndex);
27 _currentIndex++;
28 return retval;
29 };
30
31 Object first() {
32 return _list->get_at(0);
33 };
34
4.4. Patrones de comportamiento [133]

35 bool isDone() {
36 return _currentIndex > _list->length();
};

C4
37
38 };
39
40 /* client using iterator */
41
42 List list = new List();
43 ListIterator it = list.iterator();
44
45 for (Object ob = it.first(); not it.isDone(); it.next()) {
46 // do the loop using ob
47 };

Consideraciones

La ventaja fundamental de utilizar el patrn Iterator es la simplicacin de los


clientes que acceden a las diferentes estructuras de datos. El control sobre el acceso
lo realiza el propio iterador y, adems, almacena todo el estado del acceso del cliente.
De esta forma se crea un nivel de abstraccin para los clientes que acceden siempre
de la misma forma a cualquier estructura de datos con iteradores.
Obviamente, nuevos tipos de estructuras de datos requieren nuevos iteradores. Sin
embargo, para aadir nuevos tipos de iteradores a estructuras ya existentes puede rea-
lizarse de dos formas:

Aadir un mtodo virtual en la clase padre de todas las estructuras de forma


que los clientes puedan crear el nuevo tipo iterador. Esto puede tener un gran
impacto porque puede haber estructuras que no se pueda o no se desee acceder
con el nuevo iterador.
Aadir un nuevo tipo de estructura que sea dependiente del nuevo tipo del itera-
dor. Por ejemplo, RandomizedList que devolvera un iterador RandomIterator
y que accede de forma aleatoria a todos los elementos.

La STL de C++ implementa el patrn Iterator en todos los contenedores que


ofrece.

4.4.4. Template Method


El patrn Template Method se puede utilizar cuando es necesario redenir algunos
pasos de un determinado algoritmo utilizando herencia.

Problema

En un buen diseo los algoritmos complejos se dividen en funciones ms peque-


as, de forma que si se llama a dichas funciones en un determinado orden se consigue
implementar el algoritmo completo. Conforme se disea cada paso concreto, se suele
ir detectando funcionalidad comn con otros algoritmos.
[134] CAPTULO 4. PATRONES DE DISEO

Por ejemplo, supongamos que tenemos dos tipos de jugadores de juegos de mesa:
ajedrez y damas. En esencia, ambos juegan igual; lo que cambia son las reglas del
juego que, obviamente, condiciona su estrategia y su forma de jugar concreta. Sin
embargo, en ambos juegos, los jugadores mueven en su turno, esperan al rival y esto
se repite hasta que acaba la partida.
El patrn Template Method consiste extraer este comportamiento comn en una
clase padre y denir en las clases hijas la funcionalidad concreta.

Solucin

Siguiendo con el ejemplo de los jugadores de ajedrez y damas, la gura 4.16 mues-
tra una posible aplicacin del patrn Template Method a modo de ejemplo. Ntese que
la clase GamePlayer es la que implementa el mtodo play() que es el que invoca
a los otros mtodos que son implementados por las clases hijas. Este mtodo es el
mtodo plantilla.

Figura 4.16: Aplicacin de ejemplo del patrn Template Method

Cada tipo de jugador dene los mtodos en base a las reglas y heursticas de su
juego. Por ejemplo, el mtodo isOver() indica si el jugador ya no puede seguir
jugando porque se ha terminado el juego. En caso de las damas, el juego se acaba para
el jugador si se ha quedado sin chas; mientras que en el caso ajedrez puede ocurrir
por jaque mate (adems de otros motivos).

Consideraciones

Algunas consideraciones sobre el patrn Template Method:

Utilizando este patrn se suele obtener estructuras altamente reutilizables.


Introduce el concepto de operaciones hook que, en caso de no estar implementa-
das en las clases hijas, tienen una implementacin por defecto. Las clases hijas
pueden sobreescribirlas para aadir su propia funcionalidad.
4.4. Patrones de comportamiento [135]

4.4.5. Strategy

C4
El patrn Strategy se utiliza para encapsular el funcionamiento de una familia de
algoritmos, de forma que se pueda intercambiar su uso sin necesidad de modicar a
los clientes.

Problema

En muchas ocasiones, se suele proporcionar diferentes algoritmos para realizar una


misma tarea. Por ejemplo, el nivel de habilidad de un jugador viene determinado por
diferentes algoritmos y heursticas que determinan el grado de dicultad. Utilizando
diferentes tipos algoritmos podemos obtener desde jugadores que realizan movimien-
tos aleatorios hasta aquellos que pueden tener cierta inteligencia y que se basan en
tcnicas de IA.
Lo deseable sera poder tener jugadores de ambos tipos y que, desde el punto de
vista del cliente, no fueran tipos distintos de jugadores. Simplemente se comportan di-
ferente porque usan distintos algoritmos internamente, pero todos ellos son jugadores.

Solucin

Mediante el uso de la herencia, el patrn Strategy permite encapsular diferentes


algoritmos para que los clientes puedan utilizarlos de forma transparente. En la gu-
ra 4.17 puede verse la aplicacin de este patrn al ejemplo anterior de los jugadores.

Figura 4.17: Aplicacin de ejemplo del patrn Strategy

La idea es extraer los mtodos que conforman el comportamiento que puede ser
intercambiado y encapsularlo en una familia de algoritmos. En este caso, el movi-
miento del jugador se extrae para formar una jerarqua de diferentes movimientos
(Movement). Todos ellos implementan el mtodo move() que recibe un contexto
que incluye toda la informacin necesaria para llevar a cabo el algoritmo.
El siguiente fragmento de cdigo indica cmo se usa este esquema por parte de
un cliente. Ntese que al congurarse cada jugador, ambos son del mismo tipo de
cara al cliente aunque ambos se comportarn de forma diferente al invocar al mtodo
doBestMove().

Listado 4.9: Uso de los jugadores (Strategy)


1 GamePlayer bad_player = new GamePlayer(new RandomMovement());
2 GamePlayer good_player = new GamePlayer(new IAMovement());
3
4 bad_player->doBestMove();
5 good_player->doBestMove();
[136] CAPTULO 4. PATRONES DE DISEO

Consideraciones

El patrn Strategy es una buena alternativa a realizar subclases en las entidades que
deben comportarse de forma diferente en funcin del algoritmo utilizado. Al extraer la
heurstica a una familia de algoritmos externos, obtenemos los siguientes benecios:

Se aumenta la reutilizacin de dichos algoritmos.


Se evitan sentencias condicionales para elegir el comportamiento deseado.
Los clientes pueden elegir diferentes implementaciones para un mismo compor-
tamiento deseado, por lo que es til para depuracin y pruebas donde se pueden
escoger implementaciones ms simples y rpidas.

4.4.6. Reactor
El patrn Reactor es un patrn arquitectural para resolver el problema de cmo
atender peticiones concurrentes a travs de seales y manejadores de seales.

Problema

Existen aplicaciones, como los servidores web, cuyo comportamiento es reactivo,


es decir, a partir de la ocurrencia de un evento externo se realizan todas las operaciones
necesarias para atender a ese evento externo. En el caso del servidor web, una conexin
entrante (evento) disparara la ejecucin del cdigo pertinente que creara un hilo de
ejecucin para atender a dicha conexin. Pero tambin pueden tener comportamiento
proactivo. Por ejemplo, una seal interna puede indicar cundo destruir una conexin
con un cliente que lleva demasiado tiempo sin estar accesible.
En los videojuegos ocurre algo muy similar: diferentes entidades pueden lanzar
eventos que deben ser tratados en el momento en el que se producen. Por ejemplo, la
pulsacin de un botn en el joystick de un jugador es un evento que debe ejecutar el
cdigo pertinente para que la accin tenga efecto en el juego.

Solucin

En el patrn Reactor se denen una serie de actores con las siguientes responsabi-
lidades (vase gura 4.18):

Figura 4.18: Diagrama de clases del patrn Reactor


4.4. Patrones de comportamiento [137]

Eventos: los eventos externos que puedan ocurrir sobre los recursos (Handles).
Normalmente su ocurrencia es asncrona y siempre est relaciona a un recurso

C4
determinado.
Recursos (Handles): se reere a los objetos sobre los que ocurren los eventos.
La pulsacin de una tecla, la expiracin de un temporizador o una conexin en-
trante en un socket son ejemplos de eventos que ocurren sobre ciertos recursos.
La representacin de los recursos en sistemas tipo GNU/Linux es el descriptor
de chero.
Manejadores de Eventos: Asociados a los recursos y a los eventos que se pro-
ducen en ellos, se encuentran los manejadores de eventos (EventHandler)
que reciben una invocacin a travs del mtodo handle() con la informacin
del evento que se ha producido.
Reactor: se trata de la clase que encapsula todo el comportamiento relativo a
la desmultiplexacin de los eventos en manejadores de eventos (dispatching).
Cuando ocurre un cierto evento, se busca los manejadores asociados y se les
invoca el mtodo handle().

En general, el comportamiento sera el siguiente:

1. Los manejadores se registran utilizando el mtodo regHandler() del Reac-


tor. De esta forma, el Reactor puede congurarse para esperar los eventos del
recurso que el manejador espera. El manejador puede dejar de recibir notica-
ciones con unregHandler().
2. A continuacin, el Reactor entra en el bucle innito (loop()), en el que se
espera la ocurrencia de eventos.
3. Utilizando alguna llamada al sistema, como puede ser select(), el Reactor
espera a que se produzca algn evento sobre los recursos monitorizados.
4. Cuando ocurre, busca los manejadores asociados a ese recurso y les invoca el
mtodo handle() con el evento que ha ocurrido como parmetro.
5. El manejador recibe la invocacin y ejecuta todo el cdigo asociado al evento.

Ntese que aunque los eventos ocurran concurrentemente el Reactor serializa las
llamadas a los manejadores. Por lo tanto, la ejecucin de los manejadores de eventos
ocurre de forma secuencial.

Consideraciones

Al utilizar un Reactor, se deben tener las siguientes consideraciones:

1. Los manejadores de eventos no pueden consumir mucho tiempo. Si lo hacen,


pueden provocar un efecto convoy y, dependiendo de la frecuencia de los even-
tos, pueden hacer que el sistema sea inoperable. En general, cuanto mayor sea
la frecuencia en que ocurren los eventos, menos tiempo deben consumir los
manejadores.
2. Existen implementaciones de Reactors que permiten una desmultiplexacin con-
currente.
3. Desde un punto de vista general, el patrn Observer tiene un comportamiento
muy parecido. Sin embargo, el Reactor est pensado para las relaciones 1 a 1 y
no 1 a n como en el caso del Observer.
[138] CAPTULO 4. PATRONES DE DISEO

4.4.7. Visitor
El patrn Visitor proporciona un mecanismo para realizar diferentes operaciones
sobre una jerarqua de objetos de forma que aadir nuevas operaciones no haga nece-
sario cambiar las clases de los objetos sobre los que se realizan las operaciones.

Problema

En el diseo de un programa, normalmente se obtienen jerarquas de objetos a tra-


vs de herencia o utilizando el patrn Composite (vase seccin 4.3.1). Considerando
una jerarqua de objetos que sea ms o menos estable, es muy probable que necesi-
temos realizar operaciones sobre dicha jerarqua. Sin embargo, puede ser que cada
objeto deba ser tratado de una forma diferente en funcin de su tipo. La complejidad
de estas operaciones aumenta muchsimo.
Supongamos el problema de detectar las colisiones entre los objetos de un juego.
Dada una estructura de objetos (con un estado determinado), una primera aproxima-
cin sera recorrer toda la estructura en busca de dichas colisiones y, en cada caso
particular, realizar las operaciones especcas que el objeto concreto necesita. Por
ejemplo, en caso de detectarse una colisin de un misil con un edicio se producirn
una serie de acciones diferentes a si el misil impacta contra un vehculo.
En denitiva, realizar operaciones sobre una estructura de objetos que mantiene un
cierto estado puede complicar la implementacin de las mismas debido a que se deben
de tener en cuenta las particularidades de cada tipo de objeto y operacin realizada.

Solucin

El patrn Visitor se basa en la creacin de dos jerarquas independientes:

Visitables: son los elementos de la estructura de objetos que aceptan a un deter-


minado visitante y que le proporcionan toda la informacin a ste para realizar
una determinada operacin.
Visitantes: jerarqua de objetos que realizan una operacin determinada sobre
dichos elementos.

En la gura 4.19 se puede muestra un diagrama de clases genrico del patrn


Visitor donde se muestran estas dos jerarquas. Cada visitante concreto realiza una
operacin sobre la estructura de objetos. Es posible que al visitante no le interesen to-
dos los objetos y, por lo tanto, la implementacin de alguno de sus mtodos sea vaca.
Sin embargo, lo importante del patrn Visitor es que se pueden aadir nuevos tipos de
visitantes concretos y, por lo tanto, realizar nuevas operaciones sobre la estructura sin
la necesidad de modicar nada en la propia estructura.

Implementacin

Como ejemplo de implementacin supongamos que tenemos una escena (Scene)


en la que existe una coleccin de elementos de tipo ObjectScene. Cada elemento
tiene atributos como su nombre, peso y posicin en la escena, es decir, name, weight
y position, respectivamente. Se denen dos tipos visitantes:

NameVisitor: mostrar los nombres de los elementos de una escena.


4.4. Patrones de comportamiento [139]

C4
Figura 4.19: Diagrama de clases del patrn Visitor

BombVisitor: modicar la posicin nal de todos los elementos de una


escena al estallar una bomba. Para calcularla tendr que tener en cuenta los
valores de los atributos de cada objeto de la escena.

Se ha simplicado la implementacin de Scene y ObjectScene. nicamente


se ha incluido la parte relativa al patrn visitor, es decir, la implementacin de los
mtodos accept(). Ntese que es la escena la que ejecuta accept() sobre todos
sus elementos y cada uno de ellos invoca a visitObject(), con una referencia a s
mismos para que el visitante pueda extraer informacin. Dependiendo del tipo de visi-
tor instanciado, uno simplemente almacenar el nombre del objeto y el otro calcular
si el objeto debe moverse a causa de una determinada explosin. Este mecanismo se
conoce como despachado doble o double dispatching. El objeto que recibe la invoca-
cin del accept() delega la implementacin de lo que se debe realizar a un tercero,
en este caso, al visitante.
Finalmente, la escena tambin invoca al visitante para que realice las operaciones
oportunas una vez nalizado el anlisis de cada objeto. Ntese que, en el ejemplo, en
el caso de BombVisitor no se realiza ninguna accin en este caso.

Listado 4.10: Visitor (ejemplo)


1 class ObjectScene {
2 public:
3 void accept(SceneVisitor* v) { v->visitObject(this); };
4 };
5
6 class Scene {
7 private:
8 vector<ObjectScene> _objects;
9 public:
10 void accept(SceneVisitor* v) {
11 for (vector<ObjectScene>::iterator ob = _objects.begin();
12 ob != _objects.end(); ob++)
13 v->accept(v);
14 v->visitScene(this);
15 };
16 };
17
18 class SceneVisitor {
19 virtual void visitObject(ObjectScene* ob) = 0;
20 virtual void visitScene(Scene* scene) = 0;
21 };
22
[140] CAPTULO 4. PATRONES DE DISEO

23 class NameVisitor : public SceneVisitor {


24 private:
25 vector<string> _names;
26 public:
27 void visitObject(ObjectScene* ob) {
28 _names.push_back(ob->name);
29 };
30 void visitScene(Scene* scene) {
31 cout << "The scene " << scene->name << " has following
objects:"
32 << endl;
33 for (vector<string>::iterator it = _names.begin();
34 it != _names.end(); it++)
35 cout << *it << endl;
36 };
37 };
38
39 class BombVisitor : public SceneVisitor {
40 private:
41 Bomb _bomb;
42 public:
43 BombVisitor(Bomb bomb) : _bomb(bomb);
44 void visitObject(ObjectScene* ob) {
45 Point new_pos = calculateNewPosition(ob->position,
46 ob->weight,
47 _bomb->intensity);
48 ob->position = new_pos;
49 };
50 void visitScene(ObjectScene* scene) {};
51 };
52
53 /* client usage */
54 Scene* scene = createScene();
55 SceneVisitor* name_visitor = new NameVisitor();
56 scene->accept(name_visitor);
57 ...
58 /* bomb explosion occurs */
59 SceneVisitor* bomb_visitor = new BombVisitor(bomb);
60 scene->accept(bomb_visitor);

Consideraciones

Algunas consideraciones sobre el patrn Visitor:

El patrn Visitor es muy conveniente para recorrer estructuras arbreas y reali-


zar operaciones en base a los datos almacenados.
En el ejemplo, la ejecucin se realiza de forma secuencial ya que se utiliza un
iterador de la clase vector. La forma en que se recorra la estructura inuir
notablemente en el rendimiento del anlisis de la estructura. Se puede hacer uso
del patrn Iterator para decidir cmo escoger el siguiente elemento.
Uno de los problemas de este patrn es que no es recomendable si la estructura
de objetos cambia frecuentemente o es necesario aadir nuevos tipos de objetos
de forma habitual. Cada nuevo objeto que sea susceptible de ser visitado puede
provocar grandes cambios en la jerarqua de los visitantes.
4.5. Programming Idioms [141]

4.5. Programming Idioms

C4
Hasta el momento, se han descrito algunos de los patrones de diseo ms impor-
tantes que proporcionan una buena solucin a determinados problemas a nivel de dise-
o. Todos ellos son aplicables a cualquier lenguaje de programacin en mayo o menor
medida: algunos patrones de diseo son ms o menos difciles de implementar en un
lenguaje de programacin determinado. Depender de las estructuras de abstraccin
que ste proporcione.
Sin embargo, a nivel de lenguaje de programacin, existen patrones que hacen
que un programador comprenda mejor dicho lenguaje y aplique soluciones mejores,
e incluso ptimas, a la hora de resolver un problema de codicacin. Los expresiones
idiomticas (o simplemente, idioms) son un conjunto de buenas soluciones de progra-
macin que permiten:

Resolver ciertos problemas de codicacin, normalmente asociados a un len-


guaje especco. Por ejemplo, cmo obtener la direccin de memoria real de
un objeto en C++ aunque est sobreescrito el operador &. Este idiom se llama
AddressOf.
Entender y explotar las interioridades del lenguaje y, por tanto, programar mejor.
Establecer un repertorio de buenas prcticas de programacin para el lenguaje.

Al igual que los patrones, los idioms tienen un nombre asociado (adems de sus
alias), la denicin del problema que resuelven y bajo qu contexto, as como algunas
consideraciones (eciencia, etc.). A continuacin, se muestran algunos de los ms
relevantes. En la seccin de Patrones de Diseo Avanzados se exploran ms de
ellos.

4.5.1. Orthodox Canonical Form


Veamos un ejemplo de mal uso de C++:

Listado 4.11: Ejemplo de uso incorrecto de C++


1 #include <vector>
2
3 struct A {
4 A() : a(new char[3000]) {}
5 ~A() { delete [] a; }
6 char* a;
7 };
8
9 int main() {
10 A var;
11 std::vector<A> v;
12 v.push_back(var);
13 return 0;
14 }

Si compilamos y ejecutamos este ejemplo nos llevaremos una desagradable sor-


presa.

$ g++ bad.cc -o bad


$ ./bad
*** glibc detected *** ./bad: double free or corruption (!prev): 0
x00000000025de010 ***
[142] CAPTULO 4. PATRONES DE DISEO

======= Backtrace: =========


...

Qu es lo que ha pasado? No estamos reservando memoria en el constructor y


liberando en el destructor? Cmo es posible que haya corrupcin de memoria? La
solucin al enigma es lo que no se ve en el cdigo. Si no lo dene el usuario el com-
pilador de C++ aade automticamente un constructor de copia que implementa la
estrategia ms simple, copia de todos los miembros. En particular cuando llamamos
a push_back() creamos una copia de var. Esa copia recibe a su vez una copia
del miembro var.a que es un puntero a memoria ya reservada. Cuando se termina
main() se llama al destructor de var y del vector. Al destruir el vector se destru-
yen todos los elementos. En particular se destruye la copia de var, que a su vez libera
la memoria apuntada por su miembro a, que apunta a la misma memoria que ya haba
liberado el destructor de var.
Antes de avanzar ms en esta seccin conviene formalizar un poco la estructura
que debe tener una clase en C++ para no tener sorpresas. Bsicamente se trata de
especicar todo lo que debe implementar una clase para poder ser usada como un tipo
cualquiera:

Pasarlo como parmetro por valor o como resultado de una funcin.


Crear arrays y contenedores de la STL.
Usar algoritmos de la STL sobre estos contenedores.

Para que no aparezcan sorpresas una clase no trivial debe tener como mnimo:

1. Constructor por defecto. Sin l sera imposible instanciar arrays y no funciona-


ran los contenedores de la STL.
2. Constructor de copia. Sin l no podramos pasar argumentos por valor, ni de-
volverlo como resultado de una funcin.
3. Operador de asignacin. Sin l no funcionara la mayora de los algoritmos
sobre contenedores.
4. Destructor. Es necesario para liberar la memoria dinmica reservada. El des-
tructor por defecto puede valer si no hay reserva explcita.

A este conjunto de reglas se le llama normalmente forma cannica ortodoxa (ort-


hodox canonical form).
Adems, si la clase tiene alguna funcin virtual, el destructor debe ser virtual. Esto
es as porque si alguna funcin virtual es sobrecargada en clases derivadas podran
reservar memoria dinmica que habra que liberar en el destructor. Si el destructor no
es virtual no se podra garantizar que se llama. Por ejemplo, porque la instancia est
siendo usada a travs de un puntero a la clase base.

4.5.2. Interface Class


En C++, a diferencia de lenguajes como Java, no existen clases interfaces, es decir,
tipos abstractos que no proporcionan implementacin y que son muy tiles para denir
el contrato entre un objeto cliente y uno servidor. Por ello, el concepto de interfaz es
muy importante en POO. Permiten obtener un bajo grado de acoplamiento entre las
entidades y facilitan en gran medida las pruebas unitarias, ya que permite utilizar
objetos dobles (mocks, stubs, etc.) en lugar de las implementaciones reales.
4.5. Programming Idioms [143]

Listado 4.12: Ejemplo de Interface Class

C4
1 class Vehicle
2 {
3 public:
4 virtual ~Vehicle();
5 virtual std::string name() = 0;
6 virtual void run() = 0;
7 };
8
9 class Car: public Vehicle
10 {
11 public:
12 virtual ~Car();
13 std::string name() { return "Car"; }
14 void run() { /* ... */ }
15 };
16
17 class Motorbike: public Vehicle
18 {
19 public:
20 virtual ~Motorbike();
21 std::string name() { return "Motorbike"; }
22 void run() { /* ... */ }
23 };

Este idiom muestra cmo crear una clase que se comporta como una interfaz,
utilizando mtodosvirtuales puros.
El compilador fuerza que los mtodos virtuales puros sean implementados por
las clases derivadas, por lo que fallar en tiempo de compilacin si hay alguna que
no lo hace. Como resultado, tenemos que Vehicle acta como una clase interfaz.
Ntese que el destructor se ha declarado como virtual. Como ya se cit en la forma
cannica ortodoxa (seccin 4.5.1), esto es una buena prctica para evitar posibles leaks
de memoria en tiempo de destruccin de un objeto Vehicle usado polimrcamente
(por ejemplo, en un contenedor). Con esto, se consigue que se llame al destructor de
la clase ms derivada.

4.5.3. Final Class


En Java o C# es posible denir una clase como final, es decir, no es posible he-
redar una clase hija de ella. Este mecanismo de proteccin puede tener mucho sentido
en diferentes ocasiones:

Desarrollo de una librera o una API que va ser utilizada por terceros.
Clases externas de mdulos internos de un programa de tamao medio o grande.

En C++, no existe este mecanismo. Sin embargo, es posible simularlo utilizando


el idiom Final Class.
Este idiom se basa en una regla de la herencia virtual: el constructor y destruc-
tor de una clase heredada virtualmente ser invocado por la clase ms derivada de
la jerarqua. Como, en este caso, el destructor es privado, el compilador prohbe la
instanciacin de B y el efecto es que no se puede heredar ms all de A.
[144] CAPTULO 4. PATRONES DE DISEO

Listado 4.13: Ejemplo de Final Class


1 class Final
2 {
3 ~Final() {} // privado
4 friend class A;
5 };
6
7 class A : virtual Final
8 { };
9
10 class B : public B
11 { };
12
13 int main (void)
14 {
15 B b; // fallo de compilacin
16 }

4.5.4. pImpl
Pointer To Implementation (pImpl), tambin conocido como Handle Body u Opa-
que Pointer, es un famoso idiom (utilizado en otros muchos) para ocultar la implemen-
tacin de una clase en C++. Este mecanismo puede ser muy til sobre todo cuando se
tienen componentes reutilizables cuya declaracin o interfaz puede cambiar, lo cual
implicara recompilar a todos sus usuarios. El objetivo es minimizar el impacto de un
cambio en la declaracin de la clase a sus usuarios.
En C++, un cambio en las variables miembro de una clase o en los mtodos
inline puede suponer que los usuarios de dicha clase tengan que recompilar. Para
resolverlo, la idea de pImpl es que la clase ofrezca una interfaz pblica bien denida
y que sta contenga un puntero a su implementacin, descrita de forma privada.
Por ejemplo, la clase Vehicle podra ser de la siguiente forma:

Listado 4.14: Ejemplo bsico de clase Vehicle


1 class Vehicle
2 {
3 public:
4 void run(int distance);
5
6 private:
7 int _wheels;
8 };

Como se puede ver, es una clase muy sencilla ya que ofrece slo un mtodo pbli-
co. Sin embargo, si queremos modicarla aadiendo ms atributos o nuevos mtodos
privados, se obligar a los usuarios de la clase a recompilar por algo que realmente no
utilizan directamente. Usando pImpl, quedara el siguiente esquema:

Listado 4.15: Clase Vehicle usando pImpl (Vehicle.h)


1 /* Interfaz publica Vehicle.h */
2
3 class Vehicle
4 {
5 public:
4.5. Programming Idioms [145]

6 void run(int distance);


7
8 private:

C4
9 class VehicleImpl;
10 VehicleImpl* _pimpl;
11 };

Listado 4.16: Implementacin de Vehicle usando pImpl (Vehicle.cpp


1 /* Vehicle.cpp */
2
3 #include <Vehicle.h>
4
5 Vehicle::Vehicle()
6 {
7 _pimpl = new VehicleImpl();
8 }
9
10 void Vehicle::run()
11 {
12 _pimpl->run();
13 }

Listado 4.17: Declaracin de VehicleImpl (VehicleImpl.h


1 /* VehicleImpl.h */
2
3 class VehicleImpl
4 {
5 public:
6 void run();
7
8 private:
9 int _wheels;
10 std::string name;
11 };

Si la clase Vehicle est bien denida, se puede cambiar su implementacin sin


obligar a sus usuarios a recompilar. Esto proporciona una mayor exibilidad a la hora
de denir, implementar y realizar cambios sobre la clase VehicleImpl.
La Biblioteca STL
Captulo 5
David Vallejo Fernndez

capitalEn este captulo se proporciona una visin general de STL (Standard Tem-
plate Library), la biblioteca estndar proporcionada por C++, en el contexto del desa-
rrollo de videojuegos, discutiendo su utilizacin en dicho mbito de programacin.
Asimismo, tambin se realiza un recorrido exhaustivo por los principales tipos de
contenedores, estudiando aspectos relevantes de su implementacin, rendimiento y
uso en memoria. El objetivo principal que se pretende alcanzar es que el lector sea ca-
paz de utilizar la estructura de datos ms adecuada para solucionar un problema,
justicando el porqu y el impacto que tiene dicha decisin sobre el proyecto en su
conjunto.

5.1. Visin general de STL

Usando STL Desde un punto de vista abstracto, la biblioteca estndar de C++, STL, es un
STL es sin duda una de las bibliote-
conjunto de clases que proporciona la siguiente funcionalidad [94]:
cas ms utilizadas en el desarrollo
de aplicaciones de C++. Adems,
est muy optimizada para el mane-
Soporte a caractersticas del lenguaje, como por ejemplo la gestin de memoria
jo de estructuras de datos y de algo- e informacin relativa a los tipos de datos manejados en tiempo de ejecucin.
ritmos bsicos, aunque su comple-
jidad es elevada y el cdigo fuente Soporte relativo a aspectos del lenguaje denidos por la implementacin, como
es poco legible para desarrolladores por ejemplo el valor en punto otante con mayor precisin.
poco experimentados.
Soporte para funciones que no se pueden implementar de manera ptima con
las herramientas del propio lenguaje, como por ejemplo ciertas funciones mate-
mticas o asignacin de memoria dinmica.

147
[148] CAPTULO 5. LA BIBLIOTECA STL

<type> <type> <type> <type>

it = begin () ++it ... end ()

Figura 5.1: Representacin grca del recorrido de un contenedor mediante iteradores.

Inclusin de contenedores para almacenar datos en distintas estructuras, itera-


dores para acceder a los elementos de los contenedores y algoritmos para llevar
a cabo operaciones sobre los mismos.
Soporte como base comn para otras bibliotecas.

La gura 5.2 muestra una perspectiva global de la organizacin de STL y los di-
ferentes elementos que la denen. Como se puede apreciar, STL proporciona una gran
variedad de elementos que se pueden utilizar como herramientas para la resolucin de
problemas dependientes de un dominio en particular.
Las utilidades de la biblioteca estndar se denen en el espacio de nombres std y
se encuentran a su vez en una serie de bibliotecas, que identican las partes fundamen-
tales de STL. Note que no se permite la modicacin de la biblioteca estndar y no es
aceptable modicar su contenido mediante macros, ya que afectara a la portabilidad
del cdigo desarrollado.
En el mbito del desarrollo de videojuegos, los contenedores juegan un papel fun- Abstraccin STL
damental como herramienta para el almacenamiento de informacin en memoria. En El uso de los iteradores en STL re-
este contexto, la realizacin un estudio de los mismos, en trminos de operaciones, presenta un mecanismo de abstrac-
gestin de memoria y rendimiento, es especialmente importante para utilizarlos ade- cin fundamental para realizar el re-
cuadamente. Los contenedores estn representados por dos tipos principales. Por una corrido, el acceso y la modicacin
sobre los distintos elementos alma-
parte, las secuencias permiten almacenar elementos en un determinado orden. Por otra cenados en un contenedor.
parte, los contenedores asociativos no tienen vinculado ningn tipo de restriccin de
orden.
La herramienta para recorrer los contenedores est representada por el iterador.
Todos los contenedores mantienen dos funciones relevantes que permiten obtener dos
iteradores:

1. begin(), que devuelve un iterador al primer elemento del contenedor,


2. end(), que devuelve un iterador al elemento siguiente al ltimo.

El hecho de que end() devuelva un iterador al siguiente elemento al ltimo alber-


gado en el contenedor es una convencin en STL que simplica la iteracin sobre los
elementos del mismo o la implementacin de algoritmos.
Una vez que se obtiene un iterador que apunta a una parte del contenedor, es posi-
ble utilizarlo como referencia para acceder a los elementos contiguos. En funcin del
tipo de iterador y de la funcionalidad que implemente, ser posible acceder solamente
al elemento contiguo, al contiguo y al anterior o a uno aleatorio.
5.1. Visin general de STL [149]

Iteradores Algoritmos

C5
<iterator> Iteradores y soporte a iteradores
<algorithm> algoritmos generales
<cstdlib> bsearch() y qsort()
Cadenas

<string> cadena Contenedores


<cctype> clasificacin de caracteres
<cwctype> clasificacin caracteres extendidos <vector> array unidimensional
<cstring> funciones de cadena <list> lista doblemente enlazada
<cwchar> funciones caracteres extendidos* <deque> cola de doble extremo
<cstdlib> funciones cadena* <queue> cola
<stack> pila
<map> array asociativo
Entrada/Salida <set> conjunto
<bitset> array de booleanos
<iosfwd> declaraciones utilidades E/S
<iostream> objetos/operaciones iostream estndar
<ios> bases de iostream Diagnsticos
<streambuf> bferes de flujos
<istream> plantilla de flujo de entrada
<exception> clase excepcin
<ostream> plantilla de flujo de salida
<stdexcept> excepciones estndar
<iomanip> manipuladores
<cassert> macro assert
<sstream> flujos hacia/desde cadenas
<cerrno> tratamiento errores*
<cstdlib> funciones de clasificacin de caracteres
<fstream> flujos hacia/desde archivos
<cstdio> familia printf() de E/S
<cwchar> E/S caracteres dobles familia printf()
Utilidades generales

Soporte del lenguaje <utility> operadores y pares


<functional> objetos funcin
<memory> asignadores contenedores
<limits> lmites numricos <ctime> fecha y hora*
<climits> macros lmites numricos escalares*
<cfloats> macros lmites numricos pto flotante*
<new> gestin de memoria dinmica
<typeinfo> soporte a identificacin de tipos Nmeros
<exception> soporte al tratamiento de excepciones
<cstddef> soporte de la biblioteca al lenguaje C
<cstdarg> lista parm. funcin long. variable <complex> nmeros complejos y operaciones
<csetjmp> rebobinado de la pila* <valarray> vectores nmericos y operaciones
<cstdlib> finalizacin del programa <numeric> operaciones numricas generaliz.
<ctime> reloj del sistema <cmath> funciones matemticas estndar
<csignal> tratamiento de seales* <cstdlib> nmeros aleatorios*

Figura 5.2: Visin general de la organizacin de STL [94]. El asterisco referencia a elementos con el estilo
del lenguaje C.
[150] CAPTULO 5. LA BIBLIOTECA STL

El siguiente fragmento de cdigo muestra un ejemplo sencillo en el que se utiliza



STL para instanciar un vector de enteros, aadir elementos y, nalmente, recorrerlo
haciendo uso de un iterador. Como se puede apreciar en la lnea 7 , STL hace uso de
plantillas para manejar los distintos contenedores, permitiendo separar la funcionali-
dad de los mismos respecto a los datos que contienen. En el ejemplo, se instancia un
vector de tipos enteros.
Ms adelante se estudirn los distintos tipos de contenedores y la funcionalidad Manejo de iteradores
que ofrecen. Sin embargo, el lector puede suponer que cada contenedor proporciona
una funcionalidad distinta en funcin de su naturaleza, de manera que cada uno de El concepto de iterador es funda-
mental para recorrer e interactuar
ellos ofrece una API que se utilizar para acceder a la misma. En el ejemplo, se utiliza de manera eciente con las distin-


en concreto la funcin push_back para aadir un elemento al nal del vector. tas estructuras de datos incluidas en
Finalmente, en la lnea 13 se declara un iterador de tipo vector<int> que se utili-
la biblioteca estndar.

zar como base para el recorrido del vector. La inicializacin del mismo se encuentra
en la lnea 15 , es decir, en la parte de inicializacin del bucle for, de manera que
originalmente el iterador apunta al primer elemento del vector. La iteracin sobre el
vector
se estar realizando hasta que no se llegue al iterador devuelto por end() (lnea
16 ), es decir, al elemento posterior al ltimo.

Listado 5.1: Ejemplo sencillo de uso de STL


1 #include <iostream>
2 #include <vector>
3
4 using namespace std;
5
6 int main () {
7 vector<int> v; // Instanciar el vector de int.
8
9 v.push_back(7); // Aadir informacin.
10 v.push_back(4);
11 v.push_back(6);
12
13 vector<int>::iterator it; // Declarar el iterador.
14
15 for (it = v.begin(); // it apunta al principio del vector,
16 it != v.end(); // mientras it no llegue a end(),
17 ++it) // incrementar el iterador.
18 cout << *it << endl; // Acceso al contenido de it.
19
20 return 0;
21 }


(lnea 17 ), al igual que el acceso
Note cmo el incremento del iterador es trivial
del contenido al que apunta el iterador (lnea 18 ), mediante una nomenclatura similar
a la utilizada para manejar punteros.
Por otra parte, STL proporciona un conjunto estndar de algoritmos que se pueden
aplicar a los contenedores e iteradores. Aunque dichos algoritmos son bsicos, stos se
pueden combinar para obtener el resultado deseado por el propio programador. Algu-
nos ejemplos de algoritmos son la bsqueda de elementos, la copia, la ordenacin, etc.
Al igual que ocurre con todo el cdigo de STL, los algoritmos estn muy optimizados
y suelen sacricar la simplicidad para obtener mejores resultados que proporcionen
una mayor eciencia.
5.2. STL y el desarrollo de videojuegos [151]

5.2. STL y el desarrollo de videojuegos

En la seccin 1.2 se discuti una visin general de la arquitectura estructurada en


capas de un motor de juegos. Una de esas capas es la que proporciona bibliotecas de

C5
desarrollo, herramientas transversales y middlewares con el objetivo de dar soporte al
proceso de desarrollo de un videojuego (ver seccin 1.2.2). STL estara incluido en
dicha capa como biblioteca estndar de C++, posibilitando el uso de diversos conte-
nedores como los mencionados anteriormente.
Desde un punto de vista general, a la hora de abordar el desarrollo de un videojue-
go, el programador o ingeniero tendra que decidir si utilizar STL o, por el contrario,
utilizar alguna otra biblioteca que se adapte mejor a los requisitos impuestos por el
juego a implementar.

5.2.1. Reutilizacin de cdigo


Uno de los principales argumentos para utilizar STL en el desarrollo de videojue-
gos es la reutilizacin de cdigo que ya ha sido implementado, depurado y portado a
distintas plataformas. En este contexto, STL proporciona directamente estructuras de
datos y algoritmos que se pueden utilizar y aplicar, respectivamente, para implementar
soluciones dependientes de un dominio, como por ejemplo los videojuegos.
Gran parte del cdigo de STL est vinculado a la construccin de bloques bsicos
en el desarrollo de videojuegos, como las listas, las tablas hash, y a algoritmos funda-
mentales como la ordenacin o la bsqueda de elementos. Adems, el diseo de STL
est basado en el uso extensivo de plantillas. Por este motivo, es posible utilizarlo
para manejar cualquier tipo de estructura de datos sin tener que modicar el diseo
interno de la propia aplicacin.

Recuerde que la reutilizacin de cdigo es uno de los pilares fundamentales


para afrontar el desarrollo de proyectos complejos y de gran envergadura,
como por ejemplo los videojuegos.

Otra ventaja importante de STL es que muchos desarrolladores y programadores


de todo el mundo lo utilizan. Este hecho tiene un impacto enorme, ya que ha posibilita-
do la creacin de una comunidad a nivel mundial y ha afectado al diseo y desarrollo
Figura 5.3: La reutilizacin de c- de bibliotecas utilizadas para programar en C++. Por una parte, si alguien tiene un pro-
digo es esencial para agilizar el
blema utilizando STL, es relativamente fcil buscar ayuda y encontrar la solucin al
desarrollo y mantenimiento de pro-
gramas.
problema, debido a que es muy probable que alguien haya tenido ese mismo problema
con anterioridad. Por otra parte, los desarrolladores de bibliotecas implementadas en
C++ suelen tener en cuenta el planteamiento de STL en sus propios proyectos para fa-
cilitar la interoperabilidad, facilitando as el uso de dichas bibliotecas y su integracin
con STL.
[152] CAPTULO 5. LA BIBLIOTECA STL

5.2.2. Rendimiento
Uno de los aspectos crticos en el mbito del desarrollo de videojuegos es el ren-
dimiento, ya que es fundamental para lograr un sensacin de interactividad y para
dotar de sensacin de realismo el usuario de videojuegos. Recuerde que un videojue-
go es una aplicacin grca de renderizado en tiempo real y, por lo tanto, es necesario
asegurar una tasa de frames por segundo adecuada y en todo momento. Para ello, el
rendimiento de la aplicacin es crtico y ste viene determinado en gran medida por
las herramientas utilizadas.
En general, STL proporciona un muy buen rendimiento debido principalmente a
que ha sido mejorado y optimizado por cientos de desarrolladores en estos ltimos
aos, considerando las propiedades intrnsecas de cada uno de sus elementos y de las
plataformas sobre las que se ejecutar.
STL ser normalmente ms eciente que otra implementacin y, por lo tanto, es La herramienta ideal
el candidato ideal para manejar las estructuras de datos de un videojuego. Mejorar el Benjamin Franklin arm que si
rendimiento de STL implica tener un conocimiento muy profundo de las estructuras dispusiera de 8 horas para derribar
de datos a manejar y puede ser una alternativa en casos extremos y con plataformas un rbol, empleara 6 horas para a-
hardware especcas. Si ste no es el caso, entonces STL es la alternativa directa. lar su hacha. Esta reexin se puede
aplicar perfectamente a la hora de
No obstante, algunas compaas tan relevantes en el mbito del desarrollo de vi- decidir qu estructura de datos uti-
deojuegos, como EA (Electronic Arts), han liberado su propia adaptacin de STL de- lizar para solventar un problema.
nominada EASTL (Electronic Arts Standard Template Library) 1 , justicando esta
decisin en base a la deteccin de ciertas debilidades, como el modelo de asignacin
de memoria, o el hecho de garantizar la consistencia desde el punto de vista de la
portabilidad.
Adems del rendimiento de STL, es importante considerar que su uso permite
que el desarrollador se centre principalmente en el manejo de elementos propios, es
decir, a nivel de nodos en una lista o claves en un diccionario, en lugar de prestar ms
importancia a elementos de ms bajo nivel, como punteros o buffers de memoria.
Uno de los aspectos claves a la hora de manejar de manera eciente STL se basa en
utilizar la herramienta adecuada para un problema concreto. Si no es as, entonces es
fcil reducir enormemente el rendimiento de la aplicacin debido a que no se utiliz,
por ejemplo, la estructura de datos ms adecuada.

5.2.3. Inconvenientes
El uso de STL tambin puede presentar ciertos inconvenientes; algunos de ellos
directamente vinculados a su propia complejidad [27]. En este contexto, uno de los
principales inconvenientes al utilizar STL es la depuracin de programas, debido a
aspectos como el uso extensivo de plantillas por parte de STL. Este planteamiento
complica bastante la inclusin de puntos de ruptura y la depuracin interactiva. Sin
embargo, este problema no es tan relevante si se supone que STL ha sido extensamente
probado y depurado, por lo que en teora no sera necesario llegar a dicho nivel.
Por otra parte, visualizar de manera directa el contenido de los contenedores o
estructuras de datos utilizadas puede resultar complejo. A veces, es muy complicado
conocer exactamente a qu elemento est apuntando un iterador o simplemente ver
todos los elementos de un vector.
La ltima desventaja es la asignacin de memoria, debido a que se pretende que
STL se utilice en cualquier entorno de cmputo de propsito general. En este contexto,
es lgico suponer que dicho entorno tenga suciente memoria y la penalizacin por
asignar memoria no es crtica. Aunque esta situacin es la ms comn a la hora de
1 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html
5.3. Secuencias [153]

plantear un desarrollo, en determinados casos esta suposicin no se puede aplicar,


como por ejemplo en el desarrollo de juegos para consolas de sobremesa. Note que
este tipo de plataformas suelen tener restricciones de memoria, por lo que es crtico
controlar este factor para obtener un buen rendimiento.

C5
En general, hacer uso de STL para desarrollar un videojuego es una de las
mejores alternativas posibles. En el mbito comercial, un gran nmero de
juegos de ordenador y de consola han hecho uso de STL. Recuerde que siem-
pre es posible personalizar algunos aspectos de STL, como la asignacin de
memoria, en funcin de las restricciones existentes.

Una posible solucin a este inconveniente consiste en utilizar asignadores de me-


Figura 5.4: Los contenedores de moria personalizados cuando se utilizan contenedores especcos, de manera que el
secuencia mantienen un orden de- desarrollador puede controlar cmo y cundo se asigna memoria. Obviamente, esta so-
terminado a la hora de almacenar lucin implica que dicho desarrollador tenga un nivel de especializacin considerable,
elementos, posibilitando optimizar ya que esta tarea no es trivial. En trminos general, el uso de asignadores personali-
el acceso y gestionando la consis- zados se lleva a cabo cuando el desarrollo de un videojuego se encuentra en un estado
tencia cach. avanzado.
Cabecera
5.3. Secuencias
#elementos

Tamao elemento La principal caracterstica de los contenedores de secuencia es que los elementos
almacenados mantienen un orden determinado. La insercin y eliminacin de ele-
Inicio mentos se puede realizar en cualquier posicin debido a que los elementos residen
en una secuencia concreta. A continuacin se lleva a cabo una discusin de tres de
los contenedores de secuencia ms utilizados: el vector (vector), la cola de doble n
(deque) y la lista (list).

Elemento 1 5.3.1. Vector


El vector es uno de los contenedores ms simples y ms utilizados de STL, ya que
Elemento 2
posibilita la insercin y eliminacin de elementos en cualquier posicin. Sin embargo,
la complejidad computacional de dichas operaciones depende de la posicin exacta en
Elemento 3 la que se inserta o elimina, respectivamente, el elemento en cuestin. Dicha compleji-
dad determina el rendimiento de la operacin y, por lo tanto, el rendimiento global del
... contenedor.
Un aspecto importante del vector es que proporciona iteradores bidireccionales, es
Elemento i decir, es posible acceder tanto al elemento posterior como al elemento anterior a partir
de un determinado iterador. As mismo, el vector permite el acceso directo sobre un
... determinado elemento, de manera similar al acceso en los arrays de C.
A diferencia de lo que ocurre con los arrays, un vector no tiene lmite en cuanto
Elemento n al nmero de elementos que se pueden aadir. Al menos, mientras el sistema tenga
memoria disponible. En caso de utilizar un array, el programador ha de comprobar
continuamente el tamao del mismo para asegurarse de que la inserccin es factible,
Vaco evitando as potenciales violaciones de segmento. En este contexto, el vector repre-
senta una solucin a este tipo de problemas.

Figura 5.5: Implementacin tpica


del contenedor vector con cabecera.
[154] CAPTULO 5. LA BIBLIOTECA STL

Los vectores proporcionan operaciones para aadir y eliminar elementos al nal,


insertar y eliminar elementos en cualquier posicin y acceder a los mismos en tiempo
constante a partir de un ndice2 .

Implementacin

Tpicamente, el contenido de los vectores se almacena de manera contigua en un


bloque de memoria, el cual suele contemplar el uso de espacio adicional para futuros
elementos. Cada elemento de un vector se almacena utilizando un desplazamiento a la
derecha, sin necesidad de hacer uso de punteros extra. De este modo, y partiendo de
la base de que todos los elementos del vector ocupan el mismo tamao debido a que
son del mismo tipo, la localizacin de los mismos se calcula a partir del ndice en la
secuencia y del propio tamao del elemento.
Los vectores utilizan una pequea cabecera que contiene informacin general del
contenedor, como el puntero al inicio del mismo, el nmero actual de elementos y el
tamao actual del bloque de memoria reservado (ver gura 5.5).

Rendimiento

En general, la insercin o eliminacin de elementos al nal del vector es muy


eciente. En este contexto, STL garantiza una complejidad constante en dichas ope-
raciones, es decir, una complejidad O(1). Desde otro punto de vista, la complejidad
de estas operaciones es independiente del tamao del vector. Adems, la insercin es
realmente eciente y consiste en la simple copia del elemento a insertar.
La insercin o eliminacin en posiciones arbitrarias requiere un mayor coste
computacional debido a que es necesario recolocar los elementos a partir de la posi-
cin de insercin o eliminacin en una posicin ms o una posicin menos, respecti-
vamente. La complejidad en estos casos es lineal, es decir, O(n).
Es importante destacar el caso particular en el que la insercin de un elemento al
nal del vector implique la reasignacin y copia del resto de elementos, debido a que
el bloque de memoria inicialmente vinculado al vector est lleno. Sin embargo, esta
situacin se puede evitar en bucles que requieran un rendimiento muy alto.
A pesar de la penalizacin a la hora de insertar y eliminar elementos en posiciones
aleatorias, el vector podra ser la mejor opcin si el programa a implementar no suele
utilizar esta funcionalidad o si el nmero de elementos a manejar es bastante reducido.
Finalmente, el recorrido transversal de vectores es tan eciente como el reco- Complejidad
rrido de elementos en un array. Para ello, simplemente hay que utilizar un iterador y
alguna estructura de bucle, tal y como se muestra en el siguiente listado de cdigo. La nomenclatura O se suele uti-
lizar para acotar superiormente la
complejidad de una determinada
funcin o algoritmo. Los rde-
Listado 5.2: Recorrido bsico de un vector nes de complejidad ms utiliza-
1 vector<int>::iterator it; dos son O(1) (complejidad cons-
2 tante), O(logn) (complejidad lo-
3 for (it = _vector.begin(); it != _vector.end(); ++it) { gartmica), O(n) (complejidad li-
4 int valor = *it; neal), O(nlogn), O(n2 ) (comple-
5 // Utilizar valor... jidad cuadrtica), O(n3 ) (comple-
6 } jidad cbica), O(nx ) (complejidad
polinomial), O(bn ) (complejidad
exponencial).
2 http://www.cplusplus.com/reference/stl/vector/
5.3. Secuencias [155]

Respecto al uso de memoria, los vectores gestionan sus elementos en un gran


bloque de memoria que permita almacenar los elementos actualmente contenidos y
considere algn espacio extra para elementos futuros. Si el bloque inicial no puede
albergar un nuevo elemento, entonces el vector reserva un bloque de memoria mayor,
comnmente con el doble de tamao, copia el contenido del bloque inicial al nuevo

C5
y libera el bloque de memoria inicial. Este planteamiento evita que el vector tenga
que reasignar memoria con frecuencia. En este contexto, es importante resaltar que un
vector no garantiza la validez de un puntero o un iterador despus de una insercin, ya
que se podra dar esta situacin. Por lo tanto, si es necesario acceder a un elemento en
concreto, la solucin ideal pasa por utilizar el ndice junto con el operador [].
Vectores y reserve() Los vectores permiten la preasignacin de un nmero de entradas con el objetivo
de evitar la reasignacin de memoria y la copia de elementos a un nuevo bloque.
La reserva de memoria explcita Para ello, se puede utilizar la operacin reserve de vector que permite especicar la
puede contribuir a mejorar el rendi-
miento de los vectores y evitar un cantidad de memoria reservada para el vector y sus futuros elementos.
alto nmero de operaciones de re-
serva.

Listado 5.3: Reserva de memoria de vector


1 #include <iostream>
2 #include <vector>
3
4 using namespace std;
5
6 int main () {
7 vector<int> v;
8 v.reserve(4);
9 cout << "Capacidad inicial: " << v.capacity() << endl;
10
11 v.push_back(7); v.push_back(6);
12 v.push_back(4); v.push_back(6);
13 cout << "Capacidad actual: " << v.capacity() << endl;
14
15 v.push_back(4); // Provoca pasar de 4 a 8.
16 // Se puede evitar con v.reserve (8) al principio.
17 cout << "Capacidad actual: " << v.capacity() << endl;
18
19 return 0;
20 }

El caso contrario a esta situacin est representado por vectores que contienen
datos que se usan durante un clculo en concreto pero que despus se descartan. Si
esta situacin se repite de manera continuada, entonces se est asignando y liberando
el vector constantemente. Una posible solucin consiste en mantener el vector como
esttico y limpiar todos los elementos despus de utilizarlos. Este planteamiento ha-
ce que el vector quede vaco y se llame al destructor de cada uno de los elementos
previamente contenidos.
Finalmente, es posible aprovecharse del hecho de que los elementos de un vector
se almacenan de manera contigua en memoria. Por ejemplo, sera posible pasar el
contenido de un vector a una funcin que acepte un array, siempre y cuando dicha
funcin no modique su contenido [27], ya que el contenido del vector no estara
sincronizado.
[156] CAPTULO 5. LA BIBLIOTECA STL

Cundo usar vectores?

Como regla general, los vectores se pueden utilizar extensivamente debido a su


buen rendimiento. En concreto, los vectores deberan utilizarse en lugar de usar
arrays estticos, ya que proporcionan un enfoque muy exible y no es necesario tener
en cuenta el nmero de elementos del mismo para aplicar una operacin de insercin,
por ejemplo.
En el mbito del desarrollo de videojuegos, algunos autores plantean utilizar el
vector como contenedor base y plantear el uso de otros contenedores en su lugar si
as lo requiere la aplicacin a desarrollar. Mientras tanto, los vectores son la solucin
ideal para aspectos como los siguientes [27]:

Lista de jugadores de un juego.


Lista de vrtices con geometra simplicada para la deteccin de colisiones.
Lista de hijos de un nodo en un rbol, siempre que el tamao de la misma no
vare mucho y no haya muchos hijos por nodo.
Lista de animaciones asociada a una determinada entidad del juego.
Lista de componentes de un juego, a nivel global.
Lista de puntos que conforman la trayectoria de una cmara.
Lista de puntos a utilizar por algoritmos de Inteligencia Articial para el clculo
de rutas.

5.3.2. Deque
Deque proviene del trmino ingls double-ended queue y representa una cola de
doble n. Este tipo de contenedor es muy parecido al vector, ya que proporciona
acceso directo a cualquier elemento y permite la insercin y eliminacin en cualquier
posicin, aunque con distinto impacto en el rendimiento. La principal diferencia reside
en que tanto la insercin como eliminacin del primer y ltimo elemento de la cola
son muy rpidas. En concreto, tienen una complejidad constante, es decir, O(1).
La colas de doble n incluyen la funcionalidad bsica de los vectores pero tam- Deque y punteros
bin considera operaciones para insertar y eliminar elementos, de manera explcita, al
principio del contenedor3 . La cola de doble n no garanti-
za que todos los elementos almace-
nados residan en direcciones conti-
guas de memoria. Por lo tanto, no es
Implementacin posible realizar un acceso seguro a
los mismos mediante aritmtica de
punteros.
Este contenedor de secuencia, a diferencia de los vectores, mantiene varios blo-
ques de memoria, en lugar de uno solo, de forma que se reservan nuevos bloques
conforme el contenedor va creciendo en tamao. Al contrario que ocurra con los vec-
tores, no es necesario hacer copias de datos cuando se reserva un nuevo bloque de
memoria, ya que los reservados anteriormente siguen siendo utilizados.
La cabecera de la cola de doble n almacena una serie de punteros a cada uno de
los bloques de memoria, por lo que su tamao aumentar si se aaden nuevos bloques
que contengan nuevos elementos.
3 http://www.cplusplus.com/reference/stl/deque/
5.3. Secuencias [157]

Cabecera Bloque 1
Principio
Vaco

C5
Final
Elemento 1
#elementos
Elemento 2
Tamao elemento

Bloque 1 Bloque 2
Bloque 2 Elemento 3
Bloque n Elemento 4

Elemento 5
Vaco
Bloque n Elemento 6

Elemento j

Elemento k

Vaco

Figura 5.6: Implementacin tpica de deque.

Rendimiento

Al igual que ocurre con los vectores, la insercin y eliminacin de elementos al


nal de la cola implica una complejidad constante, mientras que en una posicin alea-
toria en el centro del contenedor implica una complejidad lineal. Sin embargo, a di-
ferencia del vector, la insercin y eliminacin de elementos al principio es tan rpida
como al nal. La consecuencia directa es que las colas de doble n son el candidato
perfecto para estructuras FIFO (First In, First Out).
FIFO El hecho de manejar distintos bloques de memoria hace que el rendimiento se de-
Las estructuras First In, First Out grade mnimamente cuando se accede a un elemento que est en otro bloque distinto.
se suelen utilizar en el mbito del Note que dentro de un mismo bloque, los elementos se almacenan de manera secuen-
desarrollo de videojuegos para mo- cial al igual que en los vectores. Sin embargo, el recorrido transversal de una cola de
delar colas de mensajes o colas doble n es prcticamente igual de rpido que en el caso de un vector.
con prioridad para atender peticio-
nes asociadas a distintas tareas. Respecto al uso de memoria, es importante resaltar que las asignaciones se produ-
cen de manera peridica durante el uso normal de la cola de doble n. Por una parte,
si se aaden nuevos elementos, entonces se reservan nuevos bloques memoria. Por
otra parte, si se eliminan elementos, entonces se liberan otros bloques. Sin embargo,
y aunque el nmero de elementos permanezca constante, es posible que se sigan asig-
nando nuevos bloques si, por ejemplo, los elementos se aaden al nal y se eliminan
al principio. Desde un punto de vista abstracto, este contenedor se puede interpretar
como una ventana deslizante en memoria, aunque siempre tenga el mismo tamao.
Finalmente, y debido a la naturaleza dinmica de la cola de doble n, no existe
una funcin equivalente a la funcin reserve de vector.
[158] CAPTULO 5. LA BIBLIOTECA STL

Cundo usar deques?

En general, la cola de doble n puede no ser la mejor opcin en entornos con


restricciones de memoria, debido al mecanismo utilizado para reservar bloques de
memoria. Desafortunadamente, este caso suele ser comn en el mbito del desarrollo
de videojuegos. Si se maneja un nmero reducido de elementos, entonces el vector
puede ser una alternativa ms adecuada incluso aunque el rendimiento se degrade
cuando se elimine el primer elemento de la estructura.
El uso de este contenedor no es, normalmente, tan amplio como el del vector.
No obstante, existen situaciones en las que su utilizacin representa una opcin muy
buena, como por ejemplo la cola de mensajes a procesar en orden FIFO.

5.3.3. List
La lista es otro contenedor de secuencia que diere de los dos anteriores. En pri-
mer lugar, la lista proporciona iteradores bidireccionales, es decir, iteradores que
permiten navegar en los dos sentidos posibles: hacia adelante y hacia atrs. En se-
gundo lugar, la lista no proporciona un acceso aleatorio a los elementos que contiene,
como s hacen tanto los vectores como las colas de doble n. Por lo tanto, cualquier
algoritmo que haga uso de este tipo de acceso no se puede aplicar directamente sobre
listas.
La principal ventaja de la lista es el rendimiento y la conveniencia para determina-
das operaciones, suponiendo que se dispone del iterador necesario para apuntar a una
determinada posicin.
La especicacin de listas de STL ofrece una funcionalidad bsica muy similar Algoritmos en listas
a la de cola de doble n, pero tambin incluye funcionalidad relativa a aspectos ms
complejos como la fusin de listas o la bsqueda de elementos4 . La propia naturaleza de la lista per-
mite que se pueda utilizar con un
buen rendimiento para implementar
algoritmos o utilizar los ya existen-
Implementacin tes en la propia biblioteca.

Este contenedor se implementa mediante una lista doblemente enlazada de ele-


mentos. Al contrario del planteamiento de los dos contenedores previamente discu-
tidos, la memoria asociada a los elementos de la lista se reserva individualmente, de
manera que cada nodo contiene un puntero al anterior elemento y otro al siguiente (ver
gura 5.7).
La lista tambin tiene vinculada una cabecera con informacin relevante, desta-
cando especialmente los punteros al primer y ltimo elemento de la lista.

Rendimiento

La principal ventaja de una lista en trminos de rendimiento es que la insercin y


eliminacin de elementos en cualquier posicin se realiza en tiempo constante, es de-
cir, O(1). Sin embargo, para garantizar esta propiedad es necesario obtener un iterador
a la posicin deseada, operacin que puede tener una complejidad no constante.
Una desventaja con respecto a los vectores y a las colas de doble n es que el Entre listas...
recorrido transvesal de las listas es mucho ms lento, debido a que es necesario leer Las operaciones que implican el
un puntero para acceder a cada nodo y, adems, los nodos se encuentran en fragmentos movimiento de bloques de elemen-
de memoria no adyacentes. Desde un punto de vista general, el recorrido de elementos tos en listas se realiza en tiem-
en una lista puede ser de hasta un orden de magnitud ms lento que en vectores. po constante, incluso cuando dichas
operaciones se realizan utilizando
4 http://www.cplusplus.com/reference/stl/list/ ms de un contenedor.
5.3. Secuencias [159]

Cabecera
Principio Siguiente Siguiente Siguiente

C5
Final Anterior Anterior Anterior

#elementos
Elemento 1 Elemento 2 Elemento 3
Tamao elemento

...

Figura 5.7: Implementacin tpica del contenedor lista.

Listado 5.4: Uso bsico de listas con STL


1 #include <iostream>
2 #include <list>
3 #include <stdlib.h>
4 using namespace std;
5
6 class Clase {
7 public:
8 Clase (int id, int num_alumnos):
9 _id(id), _num_alumnos(num_alumnos) {}
10 int getId () const { return _id; }
11 int getNumAlumnos () const { return _num_alumnos; }
12
13 // Sobrecarga del operador para poder comparar clases.
14 bool operator< (const Clase &c) const {
15 return (_num_alumnos < c.getNumAlumnos());
16 }
17
18 private:
19 int _id;
20 int _num_alumnos;
21 };
22
23 void muestra_clases (list<Clase> lista);
24
25 int main () {
26 list<Clase> clases; // Lista de clases.
27 srand(time(NULL));
28
29 for (int i = 0; i < 7; ++i) // Insercin de clases.
30 clases.push_back(Clase(i, int(rand() % 30 + 10)));
31
32 muestra_clases(clases);
33
34 // Se ordena la lista de clases.
35 // Usa la implementacin del operador de sobrecarga.
36 clases.sort();
37 muestra_clases(clases);
38
39 return 0;
40 }
[160] CAPTULO 5. LA BIBLIOTECA STL

El planteamiento basado en manejar punteros hace que las listas sean ms ecien-
tes a la hora de, por ejemplo, reordenar sus elementos, ya que no es necesario realizar
copias de datos. En su lugar, simplemente hay que modicar el valor de los punteros
de acuerdo al resultado esperado o planteado por un determinado algoritmo. En este
contexto, a mayor nmero de elementos en una lista, mayor benecio en comparacin
con otro tipo de contenedores.
El anterior listado de cdigo mostrar el siguiente resultado por la salida estndar:

0: 23 1: 28 2: 10 3: 17 4: 20 5: 22 6: 11
2: 10 6: 11 3: 17 4: 20 5: 22 0: 23 1: 28

es decir, mostrar la informacin relevante de los objetos de tipo Clase ordenados de


acuerdo a la variable miembro _num_alumnos, ya que el valor de dicha variable se
Estecriterio se especica de manera explcita al
utiliza como criterio de ordenacin.
sobrecargar el operador < (lneas 14-16 ). Internamente, la lista de STL hace uso de
esta informacin para ordenar los elementos y, desde un punto de vista general, se
basa en la sobrecarga de dicho operador para ordenar, entre otras funciones, tipos de
datos denidos por el propio usuario.
Respecto al uso en memoria, el planteamiento de la lista reside en reservar pe-
queos bloques de memoria cuando se inserta un nuevo elemento en el contenedor.
La principal ventaja de este enfoque es que no es necesario reasignar datos. Adems,
los punteros e iteradores a los elementos se preservan cuando se realizan inserciones
y eliminaciones, posibilitando la implementacin de distintas clases de algoritmos.
Evidentemente, la principal desventaja es que casi cualquier operacin implica Asignacin de memoria
una nueva asignacin de memoria. Este inconveniente se puede solventar utilizando
asignadores de memoria personalizados, los cuales pueden usarse incluso para mejorar El desarrollo de juegos y la ges-
tin de servidores de juego con al-
la penalizacin que implica tener los datos asociados a los nodos de la lista en distintas tas cargas computacionales implica
partes de la memoria. la implementacin asignadores de
memoria personalizados. Este plan-
En el mbito del desarrollo de videojuegos, especialmente en plataformas con res- teamiento permite mejorar el rendi-
tricciones de memoria, las listas pueden degradar el rendimiento debido a que cada miento de alternativas clsicas ba-
nodo implica una cantidad extra, aunque pequea, de memoria para llevar a cabo su sadas en el uso de stacks y heaps.
gestin.

Cundo usar listas?

Aunque las listas proporcionan un contenedor de uso general y se podran utilizar


como sustitutas de vectores o deques, su bajo rendimiento al iterar sobre elementos
y la constante de reserva de memoria hacen que las listas no sean el candidato ideal
para aplicaciones de alto rendimiento, como los videojuegos. Por lo tanto, las listas
deberan considerarse como una tercera opcin tras valores los otros dos contenedores
previamente comentados.
A continuacin se listan algunos de los posibles usos de listas en el mbito del
desarrollo de videojuegos:

Lista de entidades del juego, suponiendo un alto nmero de inserciones y elimi-


naciones.
Lista de mallas a renderizar en un frame en particular, suponiendo una ordena-
cin en base a algn criterio como el material o el estado. En general, se puede
evaluar el uso de listas cuando sea necesario aplicar algn criterio de ordena-
cin.
Lista dinmica de posibles objetivos a evaluar por un componente de Inteligen-
cia Articial, suponiendo un alto nmero de inserciones y eliminaciones.
5.4. Contenedores asociativos [161]

Propiedad Vector Deque List


I/E al nal O(1) O(1) O(1)
I/E al principio O(n) O(1) O(1)
I/E en el medio O(n) O(n) O(1)

C5
Recorrido Rpido Rpido Menos rpido
Asignacin memoria Raramente Peridicamente Con cada I/E
Acceso memoria sec. S Casi siempre No
Invalidacin iterador Tras I/E Tras I/E Nunca

Cuadro 5.1: Resumen de las principales propiedades de los contenedores de secuencia previamente estu-
diados (I/E = insercin/eliminacin).

5.4. Contenedores asociativos


Mientras las secuencias se basan en la premisa de mantener posiciones relativas
respecto a los elementos que contienen, los contenedores asociativos se desmarcan de
dicho enfoque y estn diseados con el objetivo de optimizar el acceso a elementos
concretos del contenedor de la manera ms rpida posible.
En el caso de las secuencias, la bsqueda de elementos en el peor de los casos es
de complejidad lineal, es decir, O(n), ya que podra ser necesario iterar sobre todos
los elementos del contenedor para dar con el elemento deseado. En algunos casos, es
posible obtener una complejidad logartmica, es decir, O(logn), si los elementos de la
secuencia estn ordenados y se aplica una bsqueda binaria. Sin embargo, en general
no es deseable mantener los elementos ordenados debido a que el rendimiento se ver
degradado a la hora de insertar y eliminar elementos.
Figura 5.8: Los contenedores aso-
ciativos se basan en hacer uso de un Por el contrario, los contenedores asociativos permiten obtener una complejidad
elemento clave para acceder al con- logartmica o incluso constante para encontrar un elemento concreto. Para ello, los ele-
tenido. mentos del contenedor asociativo se suelen indexar utilizando una clave que permite
acceder al propio valor del elemento almacenado.

5.4.1. Set y multiset


En STL, un conjunto o set sigue la misma losofa que un conjunto matemtico,
es decir, sirve como almacn para una serie de elementos. En esencia, un elemento se
encuentra en un conjunto o no se encuentra en el mismo.
La insercin de objetos se realiza con la operacin insert, la eliminacin con erase
y la bsqueda mediante nd5 . Esta operacin tiene una complejidad logartmica.
Un conjunto slo tiene una instancia de cada objeto, de manera que insertar varias
veces el mismo objeto produce el mismo resultado que insertarlo una nica vez, es
decir, no modica el conjunto. De este modo, un conjunto es el candidato ideal para
introducir un nmero de objetos y obviar los que sean redundantes. Este enfoque es
mucho ms eciente que, por ejemplo, mantener una lista y hacer una bsqueda de
elementos duplicados, ya que el algoritmo implicara una complejidad cuadrtica, es
decir, O(n2 ).
5 http://www.cplusplus.com/reference/stl/set/
[162] CAPTULO 5. LA BIBLIOTECA STL

STL tambin soporte el contenedor multiset o multi-conjunto6 , que s permite


mantener varias copias de un mismo objeto siempre y cuando se inserte en ms de
una ocasin. Este contenedor tambin permite acceder al nmero de veces que una
determinada instancia se encuentra almacenada mediante la operacin count.

A continuacin se muestra un ejemplo de uso bsico de los conjuntos. El aspecto Claves en sets
ms relevante del mismo es la declaracin del conjunto en la lnea 17 , de manera que El contenedor set tiene como carac-
posibilite almacenar elementos enteros y, al mismo tiempo, haga uso de la denicin terstica principal que los elemen-
de la clase ValorAbsMenos para denir un criterio de inclusin de elementos en el tos almacenados en el mismo ac-
conjunto. Desde un punto de vista general, los conjuntos posibilitan la inclusin de tan como las propias claves.
criterios para incluir o no elementos en un conjunto. En el ejemplo propuesto, este
criterio se basa en que la distancia entre los enteros del conjunto no ha de superar un
umbral (DISTANCIA) para poder pertenecer al mismo. Note cmo la comparacin
se realiza en las lneas 9-11 con el operador <, es decir, mediante la funcin menor.
Este enfoque es bastante comn en STL, en lugar de utilizar el operador de igualdad.

Listado 5.5: Uso bsico de conjuntos con STL


1 #include <iostream>
2 #include <set>
3 #include <algorithm>
4 using namespace std;
5
6 #define DISTANCIA 5
7
8 struct ValorAbsMenos {
9 bool operator() (const int& v1, const int& v2) const {
10 return (abs(v1 - v2) < DISTANCIA);
11 }
12 };
13
14 void recorrer (set<int, ValorAbsMenos> valores);
15
16 int main () {
17 set<int, ValorAbsMenos> valores;
18
19 valores.insert(5); valores.insert(9);
20 valores.insert(3); valores.insert(7);
21
22 recorrer(valores);
23
24 return 0;
25 }

La salida al ejecutar el listado de cdigo anterior se expone a continuacin:

7 9 5

es decir, el elemento 3 no se encuentra en el conjunto debido a que su distancia con el


elemento 9 es mayor a la denida en el umbral (5).
Finalmente, set y multiset son contenedores asociativos ordenados, debido a que
los elementos se almacenan utilizando un cierto orden, el cual se basa en una funcin
de comparacin como la discutida anteriormente. Este enfoque permite encontrar ele-
mentos rpidamente e iterar sobre los mismos en un determinado orden.
6 http://www.cplusplus.com/reference/stl/multiset/
5.4. Contenedores asociativos [163]

Cabecera
Principio

C5
Final

#elementos Hijoi Elemento Hijod

Tamao elemento

...
Hijoi Elemento Hijodi Hijodi Elemento Hijod

Hijoi Elemento Hijodi Hijoi Elemento Hijodi

Figura 5.9: Implementacin tpica del contenedor set.

Implementacin

En trminos generales, los conjuntos se implementan utilizando un rbol binario


balanceado (ver gura 5.9) para garantizar que la complejidad de las bsquedas en
el peor caso posible sea logartmica, es decir, O(logn). Cada elemento, al igual que
ocurre con la lista, est vinculado a un nodo que enlaza con otros manteniendo una
estructura de rbol. Dicho rbol se ordena con la funcin de comparacin especicada
en la plantilla. Aunque este enfoque es el ms comn, es posible encontrar alguna
variacin en funcin de la implementacin de STL utilizada.

Rendimiento

La principal baza de los conjuntos es su gran rendimiento en operaciones relativas


a la bsqueda de elementos, garantizando una complejidad logartmica en el peor
de los casos. Sin embargo, es importante tener en cuenta el tiempo invertido en la
comparacin de elementos, ya que sta no es trivial en caso de manejar estructuras de
datos ms complejas, como por ejemplo las matrices. As mismo, la potencia de los
conjuntos se demuestra especialmente cuando el nmero de elementos del contenedor
comienza a crecer.
Comparando elementos Una alternativa posible para buscar elementos de manera eciente puede ser un
vector, planteando para ello una ordenacin de elementos seguida de una bsqueda
Aunque los conjuntos permiten rea-
lizar bsquedas ecientes, es im- binaria. Este planteamiento puede resultar eciente si el nmero de bsquedas es muy
portante tener en cuenta la comple- reducido y el nmero de accesos es elevado.
jidad asociada a la propia compara-
cin de elementos, especialmente si La insercin de elementos en un rbol balanceado tiene una complejidad logart-
se utilizan estructuras de datos com- mica, pero puede implicar la reordenacin de los mismos para mantener balanceada la
plejas. estructura. Por otra parte, el recorrido de los elementos de un conjunto es muy similar
al de listas, es decir, se basa en el uso de manejo de enlaces a los siguientes nodos de
la estructura.
[164] CAPTULO 5. LA BIBLIOTECA STL

Respecto al uso de memoria, la implementacin basada en rboles binarios ba-


lanceados puede no ser la mejor opcin ya que la insercin de elementos implica la
asignacin de un nuevo nodo, mientras que la eliminacin implica la liberacin del
mismo.

Cundo usar sets y multisets?

En general, este tipo de contenedores asociativos estn ideados para situaciones


especcas, como por ejemplo el hecho de manejar conjuntos con elementos no re-
dundantes o cuando el nmero de bsquedas es relevante para obtener un rendimiento
aceptable. En el mbito del desarrollo de videojuegos, este tipo de contenedores se
pueden utilizar para mantener un conjunto de objetos de manera que sea posible cargar
de manera automtica actualizaciones de los mismos, permitiendo su sobreescritura en
funcin de determinados criterios.

5.4.2. Map y multimap


En STL, el contenedor map se puede interpretar como una extensin de los con-
juntos con el principal objetivo de optimizar an ms la bsqueda de elementos. Para
ello, este contenedor distingue entre clave y valor para cada elemento almacenado en
el mismo. Este planteamiento diere de los conjuntos, ya que estos hacen uso del pro-
pio elemento como clave para ordenar y buscar. En esencia, un map es una secuencia
de pares <clave, valor>, proporcionando una recuperacin rpida del valor a partir de
dicha clave. As mismo, un map se puede entender como un tipo especial de array
que permite la indexacin de elementos mediante cualquier tipo de datos. De hecho,
el contenedor permite el uso del operador [].
El multimap permite manejar distintas instancias de la misma clave, mientras que
el map no. Es importante resaltar que no existe ninguna restriccin respecto al valor,
es decir, es posible tener un mismo objeto como valor asociado a distintas claves.
La principal aplicacin de un map es manejar estructuras de tipo diccionario que
Clave
posibiliten el acceso inmediato a un determinado valor dada una clave. Por ejemplo,
sera posible asociar un identicador nico a cada una de las entidades de un juego
y recuperar los punteros asociados a las propias entidades cuando as sea necesario.
De este modo, es posible utilizar slo la memoria necesaria cuando se aaden nuevas
entidades.
Tanto el map7 como el multimap8 son contenedores asociativos ordenados, por
lo que es posible recorrer su contenido en el orden en el que estn almacenados. Sin
embargo, este orden no es el orden de insercin inicial, como ocurre con los conjuntos, Mapping
sino que viene determinado por aplicar una funcin de comparacin.

Implementacin

La implementacin tpica de este tipo de contenedores es idntica a la de los con-


juntos, es decir, mediante un rbol binario balanceado. Sin embargo, la diferencia
reside en que las claves para acceder y ordenar los elementos son objetos distintos a
los propios elementos.
7 http://www.cplusplus.com/reference/stl/map/
Valor
8 http://www.cplusplus.com/reference/stl/multimap/
Figura 5.10: El proceso de utilizar
una clave en un map para obtener
un valor se suele denominar comn-
mente mapping.
5.4. Contenedores asociativos [165]

Rendimiento

El rendimiento de estos contenedores es prcticamente idntico al de los conjun-


tos, salvo por el hecho de que las comparaciones se realizan a nivel de clave. De este

C5
modo, es posible obtener cierta ventaja de manejar claves simples sobre una gran
cantidad de elementos complejos, mejorando el rendimiento de la aplicacin. Sin em-
bargo, nunca hay que olvidar que si se manejan claves ms complejas como cadenas
o tipos de datos denidos por el usuario, el rendimiento puede verse afectado negati-
vamente.
El operador [] Por otra parte, el uso del operador [] no tiene el mismo rendimiento que en vecto-
res, ya que implica realizar la bsqueda del elemento a partir de la clave y, por lo tanto,
El operador [] se puede utilizar pa-
ra acceder, escribir o actualizar in- no sera de un orden de complejidad constante sino logartmico. Al usar este operador,
formacin sobre algunos contene- tambin hay que tener en cuenta que si se escribe sobre un elemento que no existe,
dores. Sin embargo, es importante entonces ste se aade al contenedor. Sin embargo, si se intenta leer un elemento que
considerar el impacto en el rendi- no existe, entonces el resultado es que el elemento por defecto se aade para la clave
miento al utilizar dicho operador, el
cual vendr determinado por el con- en concreto y, al mismo tiempo, se devuelve dicho valor. El siguiente listado de cdigo
tenedor usado. muestra un ejemplo.
Como se puede apreciar, el listado de cdigo muestra caractersticas propias de
STL para manejar los elementos del contenedor:


En la lnea 7 se hace uso de pair paradeclarar
una pareja de valores: i) un itera-
dor al contenedor denido en la lnea 6 y ii) un valor booleano. Esta
estructura
se utilizar para recoger el valor de retorno de una insercin (lnea 14 ).

Las lneas 10-11 muestran inserciones de elementos.

En la lnea 15 se recoge el valor de retorno al intentar insertar un elemento

con una clave ya existente. Note cmo se accede al iterador en la lnea 17 para
obtener el valor previamente almacenado en la entrada con clave 2.

La lnea 20 muestra un ejemplo de insercin con el operador [], ya que la clave
3 no se haba utilizado previamente.

La lnea 23 ejemplica la insercin de un elemento por defecto. Al tratarse de
cadenas de texto, el valor por defecto es la cadena vaca.

Listado 5.6: Uso bsico de map con STL


1 #include <iostream>
2 #include <map>
3
4 using namespace std;
5
6 int main () {
7 map<int, string> jugadores;
8 pair<map<int, string>::iterator, bool> ret;
9
10 // Insertar elementos.
11 jugadores.insert(pair<int, string>(1, "Luis"));
12 jugadores.insert(pair<int, string>(2, "Sergio"));
13
14 // Comprobando elementos ya insertados...
15 ret = jugadores.insert(pair<int, string>(2, "David"));
16
17 if (ret.second == false) {
18 cout << "El elemento 2 ya existe ";
19 cout << "con un valor de " << ret.first->second << endl;
20 }
21
[166] CAPTULO 5. LA BIBLIOTECA STL

Propiedad Set Map


I/E elementos O(logn) O(logn)
Bsqueda O(logn) O(logn)
Recorrido Ms lento que lista Ms lento que lista
Asignacin memoria Con cada I/E Con cada I/E
Acceso memoria sec. No No
Invalidacin iterador Nunca Nunca

Cuadro 5.2: Resumen de las principales propiedades de los contenedores asociativos previamente estudia-
dos (I/E = insercin/eliminacin).

22 jugadores[3] = "Alfredo"; // Insercin con []...


23
24 // Caso excepcional; se aade valor por defecto...
25 const string& j_aux = jugadores[4]; // jugadores[4] =
26
27 return 0;
28 }

Ante esta situacin, resulta deseable hacer uso de la operacin nd para determi- Usando referencias
nar si un elemento pertenece o no al contenedor, accediendo al mismo con el iterador Recuerde consultar informacin so-
devuelto por dicha operacin. As mismo, la insercin de elementos es ms eciente bre las operaciones de cada uno
con insert en lugar de con el operador [], debido a que este ltimo implica un ma- de los contenedores estudiados para
yor nmero de copias del elemento a insertar. Por el contrario, [] es ligeramente ms conocer exactamente su signatura y
cmo invocarlas de manera ecien-
eciente a la hora de actualizar los contenidos del contenedor en lugar de insert. te.
Respecto al uso de memoria, los mapas tienen un comportamiento idntico al de
los conjuntos, por lo que se puede suponer los mismos criterios de aplicacin.

Cundo usar maps y multimaps?

En general, estos contenedores se utilizan como diccionarios de rpido acceso


que permiten indexar elementos a partir de manejadores o identicadores nicos. Sin
embargo, tambin es posible utilizar claves ms complejas, como por ejemplo cade-
nas. Algunas de las principales aplicaciones en el desarrollo de videojuegos son las
siguientes:

Mantenimiento de un diccionario con identicadores nicos para mapear las


distintas entidades del juego. De este modo, es posible evitar el uso de punteros
a dichas entidades.
Mecanismo de traduccin entre elementos del juego, como por ejemplo entre
identicadores numricos y cadenas de texto vinculadas a los nombres de los
personajes.
5.5. Adaptadores de secuencia [167]

5.5. Adaptadores de secuencia


STL proporciona el concepto de adaptadores para proporcionar una funcionali-
dad ms restringida sobre un contenedor existente sin la necesidad de que el propio

C5
desarrollador tenga que especicarla. Este enfoque se basa en que, por ejemplo, los
contenederos de secuencia existentes tienen la exibilidad necesaria para comportar-
se como otras estructuras de datos bien conocidas, como por ejemplo las pilas o las
colas.
LIFO Un pila es una estructura de datos que solamente permite aadir y eliminar ele-
La pila est disea para operar en un mentos por un extremo, la cima. En este contexto, cualquier conteneder de secuencia
contexto LIFO (Last In, First Out) discutido anteriormente se puede utilizar para proporcionar dicha funcionalidad. En
(last-in rst-out), es decir, el ele- el caso particular de STL, es posible manejar pilas e incluso especicar la imple-
mento que se apil ms reciente- mentacin subyacente que se utilizar, especicando de manera explcita cul ser el
mente ser el primero en salir de la
pila. contenedor de secuencia usado.
Aunque sera perfectamente posible delegar en el programador el manejo del con-
tenedor de secuencia para que se comporte como, por ejemplo, una pila, en la prctica
existen dos razones importantes para la denicin de adaptadores:

1. La declaracin explcita de un adaptador, como una pila o stack, hace que sea el
Cima cdigo sea mucho ms claro y legible en trminos de funcionalidad. Por ejem-
plo, declarar una pila proporciona ms informacin que declarar un vector que
se comporte como una pila. Esta aproximacin facilita la interoperabilidad con
otros programadores.
2. El compilador tiene ms informacin sobre la estructura de datos y, por lo tanto,
puede contribuir a la deteccin de errores con ms ecacia. Si se utiliza un vec-
tor para modelar una pila, es posible utilizar alguna operacin que no pertenezca
Elemento 1 a la interfaz de la pila.

5.5.1. Stack
Elemento 2
El adaptador ms sencillo en STL es la pila o stack9 . Las principales operaciones
sobre una pila son la insercin y eliminacin de elementos por uno de sus extremos:
Elemento 3 la cima. En la literatura, estas operaciones se conocen como push y pop, respectiva-
mente.

... La pila es ms restrictiva en trminos funcionales que los distintos contenedores de


secuencia previamente estudiados y, por lo tanto, se puede implementar con cualquiera
de ellos. Obviamente, el rendimiento de las distintas versiones vendr determinado por
el contenedor elegido. Por defecto, la pila se implementa utilizando una cola de doble
Elemento i n.
El siguiente listado de cdigo muestra cmo hacer uso de algunas de las operacio-
... nes ms relevantes de la pila para invertir el contenido de un vector.

Listado 5.7: Inversin del contenido de un vector con una pila


Elemento n 1 #include <iostream>
2 #include <stack>
3 #include <vector>
4 using namespace std;
5
Figura 5.11: Visin abstracta de
6 int main () {
una pila o stack. Los elementos so- 7 vector<int> fichas;
lamente se aaden o eliminan por
un extremo: la cima. 9 http://www.cplusplus.com/reference/stl/stack/
[168] CAPTULO 5. LA BIBLIOTECA STL

8 vector<int>::iterator it;
9 stack<int> pila;
10
11 for (int i = 0; i < 10; i++) // Rellenar el vector
12 fichas.push_back(i);
13
14 for (it = fichas.begin(); it != fichas.end(); ++it)
15 pila.push(*it); // Apilar elementos para invertir
16
17 fichas.clear(); // Limpiar el vector
18
19 while (!pila.empty()) { // Rellenar el vector
20 fichas.push_back(pila.top());
21 pila.pop();
22 }
23
24 return 0;
25 }

5.5.2. Queue

Al igual que ocurre con la pila, la cola o queue10 es otra estructura de datos de
uso muy comn que est representada en STL mediante un adaptador de secuencia.
Bsicamente, la cola mantiene una interfaz que permite la insercin de elementos al Insercin
nal y la extraccin de elementos slo por el principio. En este contexto, no es posible
acceder, insertar y eliminar elementos que estn en otra posicin.
Por defecto, la cola utiliza la implementacin de la cola de doble n, siendo posible
utilizar la implementacin de la lista. Sin embargo, no es posible utilizar el vector
debido a que este contenedor no proporciona la operacin push_front, requerida por
el propio adaptador. De cualquier modo, el rendimiento de un vector para eliminar
elementos al principio no es particularmente bueno, ya que tiene una complejidad Elemento 1
lineal.
Elemento 2
5.5.3. Cola de prioridad Elemento 3

STL tambin proporciona el adaptador cola de prioridad o priority queue11 como ...
caso especial de cola previamente discutido. La diferencia entre los dos adaptadores
reside en que el elemento listo para ser eliminado de la cola es aqul que tiene una
mayor prioridad, no el elemento que se aadi en primer lugar.
Elemento i
As mismo, la cola con prioridad incluye cierta funcionalidad especca, a diferen-
cia del resto de adaptadores estudiados. Bsicamente, cuando un elemento se aade a ...
la cola, entonces ste se ordena de acuerdo a una funcin de prioridad.
Por defecto, la cola con prioridad se apoya en la implementacin de vector, pero es Elemento n
posible utilizarla tambin con deque. Sin embargo, no se puede utilizar una lista debi-
do a que la cola con prioridad requiere un acceso aleatorio para insertar ecientemente
elementos ordenados.
La comparacin de elementos sigue el mismo esquema que el estudiado en la sec-
cin 5.4.1 y que permita denir el criterio de inclusin de elementos en un conjunto,
el cual estaba basado en el uso del operador menor que.
Extraccin
10 http://www.cplusplus.com/reference/stl/queue/
11 http://www.cplusplus.com/reference/stl/priority_queue/ Figura 5.12: Visin abstracta de
una cola o queue. Los elementos so-
lamente se aaden por el nal y se
eliminan por el principio.
5.5. Adaptadores de secuencia [169]

La cola de prioridad se puede utilizar para mltiples aspectos en el desarrollo de


videojuegos, como por ejemplo la posibilidad de mantener una estructura ordenada
por prioridad con aquellos objetos que estn ms cercanos a la posicin de la cmara
virtual.

C5
Gestin de Recursos
Captulo 6
David Vallejo Fernndez

E
n este captulo se cubren aspectos esenciales a la hora de afrontar el diseo
de un juego. En particular, se discutirn las arquitecturas tpicas del bucle de
juego, haciendo especial hincapi en un esquema basado en la gestin de los
estados de un juego. Como caso de estudio concreto, en este captulo se propone una
posible implementacin del bucle principal mediante este esquema haciendo uso de
las bibliotecas que Ogre3D proporciona.
As mismo, este captulo discute la gestin de recursos y, en concreto, la gestin
de sonido y de efectos especiales. La gestin de recursos es especialmente importante
en el mbito del desarrollo de videojuegos, ya que el rendimiento de un juego depende
en gran medida de la eciencia del subsistema de gestin de recursos.
Finalmente, la gestin del sistema de archivos, junto con aspectos bsicos de en-
trada/salida, tambin se estudia en este captulo. Adems, se plantea el diseo e imple-
mentacin de un importador de datos que se puede utilizar para integrar informacin
multimedia a nivel de cdigo fuente.

171
[172] CAPTULO 6. GESTIN DE RECURSOS

6.1. El bucle de juego

6.1.1. El bucle de renderizado


Hace aos, cuando an el desarrollo de videojuegos 2D era el estndar en la
industria, uno de los principales objetivos de diseo de los juegos era minimizar el n-
mero de pxeles a dibujar por el pipeline de renderizado con el objetivo de maximizar
la tasa de fps del juego. Evidentemente, si en cada una de las iteraciones del bucle de
renderizado el nmero de pxeles que cambia es mnimo, el juego correr a una mayor
velocidad.
Figura 6.1: El bucle de juego repre-
Esta tcnica es en realidad muy parecida a la que se plantea en el desarrollo de senta la estructura de control princi-
interfaces grcas de usuario (GUI (Graphical User Interface)), donde gran parte de pal de cualquier juego y gobierna su
las mismas es esttica y slo se producen cambios, generalmente, en algunas partes funcionamiento y la transicin entre
bien denidas. Este planteamiento, similar al utilizado en el desarrollo de videojuegos los distintos estados del mismo.
2D antiguos, est basado en redibujar nicamente aquellas partes de la pantalla cuyo
contenido cambia.
En el desarrollo de videojuegos 3D, aunque manteniendo la idea de dibujar el
mnimo nmero de primitivas necesarias en cada iteracin del bucle de renderizado,
la losofa es radicalmente distinta. En general, al mismo tiempo que la cmara se
mueve en el espacio tridimensional, el contenido audiovisual cambia continuamente,
por lo que no es viable aplicar tcnicas tan simples como la mencionada anteriormente.
La consecuencia directa de este esquema es la necesidad de un bucle de renderi- Rectangle invalidation
zado que muestre los distintas imgenes o frames percibidas por la cmara virtual con
una velocidad lo sucientemente elevada para transmitir una sensacin de realidad. La tcnica basada en redibujar ni-
camente aquellas partes de la panta-
El siguiente listado de cdigo muestra la estructura general de un bucle de ren- lla cuyo contenido cambia realmen-
te se suele denominar rectangle in-
derizado. validation.

Listado 6.1: Esquema general de un bucle de renderizado.


1 while (true) {
2 // Actualizar la cmara,
3 // normalmente de acuerdo a un camino prefijado.
4 update_camera ();
5
6 // Actualizar la posicin, orientacin y
7 // resto de estado de las entidades del juego.
8 update_scene_entities ();
9
10 // Renderizar un frame en el buffer trasero.
11 render_scene ();
12
13 // Intercambiar el contenido del buffer trasero
14 // con el que se utilizar para actualizar el
15 // dispositivo de visualizacin.
16 swap_buffers ();
17 }

6.1.2. Visin general del bucle de juego Keep it simple, Stupid!


Como ya se introdujo en la seccin 1.2, en un motor de juegos existe una gran va- La losofa KISS (Keep it simple,
riedad de subsistemas o componentes con distintas necesidades. Algunos de los ms Stupid!) se adapta perfectamente al
importantes son el motor de renderizado, el sistema de deteccin y gestin de colisio- planteamiento del bucle de juego,
en el que idealmente se implementa
nes, el subsistema de juego o el subsistema de soporte a la Inteligencia Articial. un enfoque sencillo, exible y es-
calable para gestionar los distintos
estados de un juego.
6.1. El bucle de juego [173]

La mayora de estos componentes han de actualizarse peridicamente mientras el


juego se encuentra en ejecucin. Por ejemplo, el sistema de animacin, de manera sin-
cronizada con respecto al motor de renderizado, ha de actualizarse con una frecuencia
de 30 60 Hz con el objetivo de obtener una tasa de frames por segundo lo sucien-
temente elevada para garantizar una sensacin de realismo adecuada. Sin embargo,
no es necesario mantener este nivel de exigencia para otros componentes, como por
ejemplo el de Inteligencia Articial.

C6
De cualquier modo, es necesario un planteamiento que permita actualizar el es-
tado de cada uno de los subsistemas y que considere las restricciones temporales de
los mismos. Tpicamente, este planteamiento se suele abordar mediante el bucle de
juego, cuya principal responsabilidad consiste en actualizar el estado de los distintos
componentes del motor tanto desde el punto de vista interno (ej. coordinacin en-
tre subsistemas) como desde el punto de vista externo (ej. tratamiento de eventos de
teclado o ratn).

Listado 6.2: Esquema general del bucle de juego.


1 // Pseudocdigo de un juego tipo "Pong".
2 int main (int argc, char* argv[]) {
3 init_game(); // Inicializacin del juego.
4
5 // Bucle del juego.
6 while (1) {
7 capture_events(); // Capturar eventos externos.
8
9 if (exitKeyPressed()) // Salida.
10 break;
11
12 move_paddles(); // Actualizar palas.
13 move_ball(); // Actualizar bola.
14 collision_detection(); // Tratamiento de colisiones.
15
16 // Anot algn jugador?
17 if (ballReachedBorder(LEFT_PLAYER)) {
18 score(RIGHT_PLAYER);
19 reset_ball();
20 }
21 if (ballReachedBorder(RIGHT_PLAYER)) {
22 score(LEFT_PLAYER);
23 reset_ball();
24 }
25
26 render(); // Renderizado.
27 }
28 }

Antes de discutir algunas de las arquitecturas ms utilizadas para modelar el bucle


de juego, resulta interesante estudiar el anterior listado de cdigo, el cual muestra una
manera muy simple de gestionar el bucle de juego a travs de una sencilla estructura de
control iterativa. Evidentemente, la complejidad actual de los videojuegos comerciales
requiere un esquema que sea ms general y escalable. Sin embargo, es muy importante
mantener la simplicidad del mismo para garantizar su mantenibilidad.

Figura 6.2: El hardware de Atari


Pong estaba especialmente pensado
para el manejo de las dos palas que
forman el juego.
[174] CAPTULO 6. GESTIN DE RECURSOS

6.1.3. Arquitecturas tpicas del bucle de juego


La arquitectura del bucle de juego se puede implementar de diferentes formas me-
diante distintos planteamientos. Sin embargo, la mayora de ellos tienen en comn el
uso de uno o varios bucles de control que gobiernan la actualizacin e interaccin con
los distintos componentes del motor de juegos. En esta seccin se realiza un breve re-
corrido por las alternativas ms populares, resaltando especialmente un planteamiento
basado en la gestin de los distintos estados por los que puede atravesar un juego. Esta
ltima alternativa se discutir con un caso de estudio detallado que hace uso de Ogre.

Tratamiento de mensajes en Windows

En las plataformas WindowsTM , los juegos han de atender los mensajes recibidos
por el propio sistema operativo y dar soporte a los distintos componentes del propio
motor de juego. Tpicamente, en estas plataformas se implementan los denominados
message pumps [42], como responsables del tratamiento de este tipo de mensajes.
Desde un punto de vista general, el planteamiento de este esquema consiste en
atender los mensajes del propio sistema operativo cuando llegan, interactuando con el
motor de juegos cuando no existan mensajes del sistema operativo por procesar. En
ese caso se ejecuta una iteracin del bucle de juego y se repite el mismo proceso.
La principal consecuencia de este enfoque es que los mensajes del sistema ope-

message dispatching
rativo tienen prioridad con respecto a aspectos crticos como el bucle de renderizado.
Por ejemplo, si la propia ventana en la que se est ejecutando el juego se arrastra o su
tamao cambia, entonces el juego se congelar a la espera de nalizar el tratamiento
de eventos recibidos por el propio sistema operativo.

Esquemas basados en retrollamadas

El concepto de retrollamada o callback, introducido de manera implcita en la dis-


cusin del patrn MVC de la seccin 4.3.4, consiste en asociar una porcin de cdigo
para atender un determinado evento o situacin. Este concepto se puede asociar a una
funcin en particular o a un objeto. En este ltimo caso, dicho objeto se denominar
callback object, trmino muy usado en el desarrollo de interfaces grcas de usuario
y en el rea de los sistemas distribuidos.
A continuacin se muestra un ejemplo de uso de funciones de retrollamada plan-
teado en la biblioteca GLUT, la cual est estrechamente ligada a la biblioteca GL,
utilizada para tratar de manera simple eventos bsicos.

Listado 6.3: Ejemplo de uso de retrollamadas con OpenGL.


1 #include <GL/glut.h>
2 #include <GL/glu.h>
3 #include <GL/gl.h>
4
5 // Se omite parte del cdigo fuente...
6
7 void update (unsigned char key, int x, int y) {
8 Rearthyear += 0.2; Sistema
9 Rearthday += 5.8;
10 glutPostRedisplay(); operativo
11 }
12
13 int main (int argc, char** argv) {
14 glutInit(&argc, argv); Figura 6.3: Esquema grco de
15 una arquitectura basada en message
16 glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); pumps.
6.1. El bucle de juego [175]

17 glutInitWindowSize(640, 480);
18 glutCreateWindow("Session #04 - Solar System");
19
20 // Definicin de las funciones de retrollamada.
21 glutDisplayFunc(display);
22 glutReshapeFunc(resize);
23 // Eg. update se ejecutar cuando el sistema
24 // capture un evento de teclado.

C6
25 // Signatura de glutKeyboardFunc:
26 // void glutKeyboardFunc(void (*func)
27 // (unsigned char key, int x, int y));
28 glutKeyboardFunc(update);
29
30 glutMainLoop();
31
32 return 0;
33 }

Desde un punto de vista abstracto, las funciones de retrollamada se suelen utilizar


como mecanismo para rellenar el cdigo fuente necesario para tratar un determinado
tipo de evento. Este esquema est directamente ligado al concepto de framework,
entendido como una aplicacin construida parcialmente y que el desarrollador ha de
completar para proporcionar una funcionalidad especca.
Algunos autores [42] denen a Ogre3D como un framework que envuelve a una
biblioteca que proporciona, principalmente, funcionalidad asociada a un motor de ren-
derizado. Sin embargo, ya se ha discutido cmo Ogre3D proporciona una gran canti-
dad de herramientas para el desarrollo de aplicaciones grcas interactivas en 3D.
Las instancias de la clase Ogre::FrameListener son un ejemplo representativo
Figura 6.4: Un framework propor- de objetos de retrollamada, con el objetivo de que el desarrollador pueda decidir las
ciona al desarrollador una serie de acciones que se han de ejecutar antes y despus de que se produzca una iteracin en el
herramientas para solucionar pro- bucle de renderizado. Dicha funcionalidad est representada por las funciones virtua-
blemas dependientes de un domi- les frameStarted() y frameEnded(), respectivamente. En el Mdulo 2, Programacin
nio. El propio desarrollador tendr Grca, se discute en profundidad un ejemplo de uso de esta clase.
que utilizar las herramientas dispo-
nibles y ajustarlas para proporcio-
nar una buena solucin.
Tratamiento de eventos

En el mbito de los juegos, un evento representa un cambio en el estado del propio


juego o en el entorno. Un ejemplo muy comn est representado por el jugador cuando
pulsa un botn del joystick, pero tambin se pueden identicar eventos a nivel interno,
como por ejemplo la reaparicin o respawn de un NPC en el juego.
Gran parte de los motores de juegos incluyen un subsistema especco para el
tratamiento de eventos, permitiendo al resto de componentes del motor o incluso a
entidades especcas registrarse como partes interesadas en un determinado tipo de
eventos. Este planteamiento est muy estrechamente relacionado con el patrn Obser-
ver.
Canales de eventos El tratamiento de eventos es un aspecto transversal a otras arquitecturas diseadas
Con el objetivo de independizar los para tratar el bucle de juego, por lo que es bastante comn integrarlo dentro de otros
publicadores y los suscriptores de esquemas ms generales, como por ejemplo el que se discute a continuacin y que
eventos, se suele utilizar el concep- est basado en la gestin de distintos estados dentro del juego.
to de canal de eventos como meca-
nismo de abstraccin.
[176] CAPTULO 6. GESTIN DE RECURSOS

Men Game
Intro Juego
ppal over

Pausa

Figura 6.5: Visin general de una mquina de estados nita que representa los estados ms comunes en
cualquier juego.

Esquema basado en estados

Desde un punto de vista general, los juegos se pueden dividir en una serie de
etapas o estados que se caracterizan no slo por su funcionamiento sino tambin por
la interaccin con el usuario o jugador. Tpicamente, en la mayor parte de los juegos
es posible diferenciar los siguientes estados:

Introduccin o presentacin, en la que se muestra al usuario aspectos generales


del juego, como por ejemplo la temtica del mismo o incluso cmo jugar.
Men principal, en la que el usuario ya puede elegir entre los distintos mo-
dos de juegos y que, normalmente, consiste en una serie de entradas textuales
identicando las opciones posibles.
Juego, donde ya es posible interactuar con la propia aplicacin e ir completando
los objetivos marcados.
Finalizacin o game over, donde se puede mostrar informacin sobre la partida
previamente jugada.

Evidentemente, esta clasicacin es muy general ya que est planteada desde un


punto de vista muy abstracto. Por ejemplo, si consideramos aspectos ms especcos Finite-state machines
como por ejemplo el uso de dispositivos como PlayStation MoveTM , WiimoteTM o Ki- Las mquinas de estados o autma-
nectTM , sera necesario incluir un estado de calibracin antes de poder utilizar estos tas representan modelos matemti-
dispositivos de manera satisfactoria. cos utilizados para disear progra-
mas y lgica digital. En el caso del
Por otra parte, existe una relacin entre cada uno de estos estados que se mani- desarrollo de videojuegos se pue-
esta en forma de transiciones entre los mismos. Por ejemplo, desde el estado de den usar para modelar diagramas de
estados para, por ejemplo, denir
introduccin slo ser posible acceder al estado de men principal, pero no ser posi- los distintos comportamientos de un
ble acceder al resto de estados. En otras palabras, existir una transicin que va desde personaje.
introduccin a men principal. Otro ejemplo podra ser la transicin existente entre
nalizacin y men principal.
Este planteamiento basado en estados tambin debera poder manejar varios es-
tados de manera simultnea para, por ejemplo, contemplar situaciones en las que sea
necesario ofrecer algn tipo de men sobre el propio juego en cuestin.
6.1. El bucle de juego [177]

Ogre:: OIS:: OIS:: Ogre:: OIS:: OIS:: Ogre::


Singleton KeyListener MouseListener FrameListener KeyListener MouseListener Singleton

C6
1 1 1 1..*
InputManager GameManager GameState

1 1
OIS:: OIS::
IntroState PlayState PauseState
Keyboard Mouse

Ogre::
Singleton

Figura 6.6: Diagrama de clases del esquema de gestin de estados de juego con Ogre3D. En un tono ms
oscuro se reejan las clases especcas de dominio.

En la siguiente seccin se discute en profundidad un caso de estudio en el que


se utiliza Ogre3D para implementar un sencillo mecanismo basado en la gestin de
estados. En dicha discusin se incluye un gestor responsable de capturar los eventos
externos, como por ejemplo las pulsaciones de teclado o la interaccin mediante el
ratn.

6.1.4. Gestin de estados de juego con Ogre3D


Como ya se ha comentado, los juegos normalmente atraviesan una serie de estados
durante su funcionamiento normal. En funcin del tipo de juego y de sus caractersti-
cas, el nmero de estados variar signicativamente. Sin embargo, es posible plantear
un esquema comn, compartido por todos los estados de un juego, que sirva para de-
nir un modelo de gestin general, tanto para la interaccin con los estados como para
las transiciones existentes entre ellos.
La solucin discutida en esta seccin1 , que a su vez est basada en el artculo
Managing Game States in C++. se basa en denir una clase abstracta, GameState,
que contiene una serie de funciones virtuales a implementar en los estados especcos
de un juego.
1 La solucin discutida aqu se basa en la planteada en el WiKi de Ogre3D, disponible en http://

www.ogre3d.org/tikiwiki/Managing+Game+States+with+OGRE
[178] CAPTULO 6. GESTIN DE RECURSOS

El siguiente listado de cdigo muestra el esqueleto de la clase GameState imple-


mentada en C++. Como se puede apreciar, esta clase es abstracta ya que mantiene
una serie de funciones miembro como virtuales puras, con el objetivo de forzar su
implementacin en las clases que deriven de ella y que, por lo tanto, denan esta-
dos especcos de un juego. Estas funciones virtuales puras se pueden dividir en tres
grandes bloques:

1. Gestin bsica del estado (lneas 19-22 ), para denir qu hacer cuando se
Clase abstracta en C++
En C++, las clases abstractas se de-
entra, sale, pausa o reanuda el estado.

nen mediante funciones virtuales
2. Gestin bsica de tratamiento de eventos (lneas 26-33 ), para denir qu
puras y sirven para explicitar el con-
trato funcional entre una clase y sus
hacer cuando se recibe un evento de teclado o de ratn.

clases derivadas.

3. Gestin bsica de eventos antes y despus del renderizado (lneas 37-38 ),


operaciones tpicas de la clase Ogre::FrameListener.

Adicionalmente, existe otro bloque de funciones relativas a la gestin bsica de


transiciones (lneas 41-48 ), con operaciones para cambiar de estado, aadir un esta-
do a la pila de estados y volver a un estado anterior, respectivamente. Las transiciones
implican una interaccin con la entidad GameManager, que se discutir posteriormen-
te.
La gura 6.6 muestra la relacin de la clase GameState con el resto de clases, as
como tres posibles especializaciones de la misma. Como se puede observar, esta clase
est relacionada con GameManager, responsable de la gestin de los distintos estados
y de sus transiciones.

Listado 6.4: Clase GameState.


1 #ifndef GameState_H
2 #define GameState_H
3
4 #include <Ogre.h>
5 #include <OIS/OIS.h>
6
7 #include "GameManager.h"
8 #include "InputManager.h"
9
10 // Clase abstracta de estado bsico.
11 // Definicin base sobre la que extender
12 // los estados del juego.
13 class GameState {
14
15 public:
16 GameState() {}
17
18 // Gestin bsica del estado.
19 virtual void enter () = 0;
20 virtual void exit () = 0;
21 virtual void pause () = 0;
22 virtual void resume () = 0;
23
24 // Gestin bsica para el tratamiento
25 // de eventos de teclado y ratn.
26 virtual void keyPressed (const OIS::KeyEvent &e) = 0;
27 virtual void keyReleased (const OIS::KeyEvent &e) = 0;
28
29 virtual void mouseMoved (const OIS::MouseEvent &e) = 0;
30 virtual void mousePressed (const OIS::MouseEvent &e,
31 OIS::MouseButtonID id) = 0;
32 virtual void mouseReleased (const OIS::MouseEvent &e,
33 OIS::MouseButtonID id) = 0;
34
35 // Gestin bsica para la gestin
6.1. El bucle de juego [179]

36 // de eventos antes y despus de renderizar un frame.


37 virtual bool frameStarted (const Ogre::FrameEvent& evt) = 0;
38 virtual bool frameEnded (const Ogre::FrameEvent& evt) = 0;
39
40 // Gestin bsica de transiciones.
41 void changeState (GameState* state) {
42 GameManager::getSingletonPtr()->changeState(state);
43 }

C6
44 void pushState (GameState* state) {
45 GameManager::getSingletonPtr()->pushState(state);
46 }
47 void popState () {
48 GameManager::getSingletonPtr()->popState();
49 }
50
51 };
52
53 #endif

Sin embargo, antes de pasar a discutir esta clase, en el diseo discutido se con-
templa la denicin explcita de la clase InputManager, como punto central para la
gestin de eventos de entrada, como por ejemplo los de teclado o de ratn.
Ogre::Singleton? El InputManager sirve como interfaz para aquellas entidades que estn interesa-
das en procesar eventos de entrada (como se discutir ms adelante), ya que mantiene
La clase InputManager implementa operaciones para aadir y eliminar listeners de dos tipos: i) OIS::KeyListener y ii)
el patrn Singleton mediante las uti-
lidades de Ogre3D. Es posible utili- OIS::MouseListener. De hecho, esta clase hereda de ambas clases. Adems, imple-
zar otros esquemas para que su im- menta el patrn Singleton con el objetivo de que slo exista una nica instancia de la
plementacin no depende de Ogre misma.
y se pueda utilizar con otros frame-
works.
Listado 6.5: Clase InputManager.
1 // SE OMITE PARTE DEL CDIGO FUENTE.
2 // Gestor para los eventos de entrada (teclado y ratn).
3 class InputManager : public Ogre::Singleton<InputManager>,
4 public OIS::KeyListener, public OIS::MouseListener {
5 public:
6 InputManager ();
7 virtual ~InputManager ();
8
9 void initialise (Ogre::RenderWindow *renderWindow);
10 void capture ();
11
12 // Gestin de listeners.
13 void addKeyListener (OIS::KeyListener *keyListener,
14 const std::string& instanceName);
15 void addMouseListener (OIS::MouseListener *mouseListener,
16 const std::string& instanceName );
17 void removeKeyListener (const std::string& instanceName);
18 void removeMouseListener (const std::string& instanceName);
19 void removeKeyListener (OIS::KeyListener *keyListener);
20 void removeMouseListener (OIS::MouseListener *mouseListener);
21
22 OIS::Keyboard* getKeyboard ();
23 OIS::Mouse* getMouse ();
24
25 // Heredados de Ogre::Singleton.
26 static InputManager& getSingleton ();
27 static InputManager* getSingletonPtr ();
28
29 private:
30 // Tratamiento de eventos.
31 // Delegar en los listeners.
32 bool keyPressed (const OIS::KeyEvent &e);
33 bool keyReleased (const OIS::KeyEvent &e);
34
35 bool mouseMoved (const OIS::MouseEvent &e);
[180] CAPTULO 6. GESTIN DE RECURSOS

36 bool mousePressed (const OIS::MouseEvent &e,


37 OIS::MouseButtonID id);
38 bool mouseReleased (const OIS::MouseEvent &e,
39 OIS::MouseButtonID id);
40
41 OIS::InputManager *_inputSystem;
42 OIS::Keyboard *_keyboard;
43 OIS::Mouse *_mouse;
44 std::map<std::string, OIS::KeyListener*> _keyListeners;
45 std::map<std::string, OIS::MouseListener*> _mouseListeners;
46 };

Enla seccin privada de InputManager se declaran funciones miembro (lneas


40-47 ) que se utilizarn para noticar a los listeners registrados en el InputManager
acerca de la existencia de eventos de entrada, tanto de teclado como de ratn. Este
planteamiento garantiza la escalabilidad respecto a las entidades interesadas en proce-
sar eventos de entrada. Las estructuras de datos utilizadas para almacenar los distintos
tipos de listeners son elementos de la clase map de la biblioteca estndar de C++,
indexando los mismos por el identicador textual asociado a los listeners.
Por otra parte, es importante destacar las variables miembro _keyboard y _mouse
que se utilizarn para capturar los eventos de teclado y ratn, respectivamente. Dicha
captura se realizar en cada frame mediante la funcin capture(), denida tanto en el
InputManager como en OIS::Keyboard y OIS::Mouse.
El siguiente listado de cdigo muestra la clase GameManager, que representa
a la entidad principal de gestin del esquema basado en estados que se discute en
esta seccin. Esta clase es una derivacin de de Ogre::Singleton para manejar una
nica instancia y de tres clases clave para el tratamiento de eventos. En concreto,
GameManager hereda de los dos tipos de listeners previamente comentados con el
objetivo de permitir el registro con el InputManager.

Listado 6.6: Clase GameManager.


1 // SE OMITE PARTE DEL CDIGO FUENTE.
2 class GameState;
3
4 class GameManager : public Ogre::FrameListener, public Ogre::
Singleton<GameManager>,
5 public OIS::KeyListener, public OIS::MouseListener
6 {
7 public:
8 GameManager ();
9 ~GameManager (); // Limpieza de todos los estados.
10
11 // Para el estado inicial.
12 void start (GameState* state);
13
14 // Funcionalidad para transiciones de estados.
15 void changeState (GameState* state);
16 void pushState (GameState* state);
17 void popState ();
18
19 // Heredados de Ogre::Singleton...
20
21 protected:
22 Ogre::Root* _root;
23 Ogre::SceneManager* _sceneManager;
24 Ogre::RenderWindow* _renderWindow;
25
26 // Funciones de configuracin.
27 void loadResources ();
28 bool configure ();
29
30 // Heredados de FrameListener.
6.1. El bucle de juego [181]

31 bool frameStarted (const Ogre::FrameEvent& evt);


32 bool frameEnded (const Ogre::FrameEvent& evt);
33
34 private:
35 // Funciones para delegar eventos de teclado
36 // y ratn en el estado actual.
37 bool keyPressed (const OIS::KeyEvent &e);
38 bool keyReleased (const OIS::KeyEvent &e);

C6
39
40 bool mouseMoved (const OIS::MouseEvent &e);
41 bool mousePressed (const OIS::MouseEvent &e, OIS::MouseButtonID
id);
42 bool mouseReleased (const OIS::MouseEvent &e, OIS::MouseButtonID
id);
43
44 // Gestor de eventos de entrada.
45 InputManager *_inputMgr;
46 // Estados del juego.
47 std::stack<GameState*> _states;
48 };


Note que esta clase contiene una funcin miembro start() (lnea 12 ), denida de
manera explcita para inicializar el gestor de juego, establecer el estado inicial (pasado
como parmetro) y arrancar el bucle de renderizado.

Listado 6.7: Clase GameManager. Funcin start().


1 void
2 GameManager::start
3 (GameState* state)
4 {
5 // Creacin del objeto Ogre::Root.
6 _root = new Ogre::Root();
7
8 if (!configure())
9 return;
10
11 loadResources();
12
13 _inputMgr = new InputManager;
14 _inputMgr->initialise(_renderWindow);
15
16 // Registro como key y mouse listener...
17 _inputMgr->addKeyListener(this, "GameManager");
18 _inputMgr->addMouseListener(this, "GameManager");
19
20 // El GameManager es un FrameListener.
21 _root->addFrameListener(this);
22
23 // Transicin al estado inicial.
24 changeState(state);
25
26 // Bucle de rendering.
27 _root->startRendering();
28 }

La clase GameManager mantiene una estructura de datos denominada _states que


reeja las transiciones entre los diversos estados del juego. Dicha estructura se ha im-
plementado mediante una pila (clase stack de STL) ya que reeja elmente la natura-
leza de cambio y gestin de estados. Para cambiar de un estado A a otro B, suponiendo
que A sea la cima de la pila, habr que realizar las operaciones siguientes:

1. Ejecutar exit() sobre A.


2. Desapilar A.
[182] CAPTULO 6. GESTIN DE RECURSOS

3. Apilar B (pasara a ser el estado activo).


4. Ejecutar enter() sobre B.

El siguiente listado de cdigo muestra una posible implementacin de la funcin


changeState() de la clase GameManager. Note cmo la estructura de pila de estados
permite un acceso directo al estado actual (cima) para llevar a cabo las operaciones de PauseState
gestin necesarias. Las transiciones se realizan con las tpicas operaciones de push y
pop.

PlayState
Listado 6.8: Clase GameManager. Funcin changeState().
1 void
2 GameManager::changeState
3 (GameState* state) IntroState
4 {
5 // Limpieza del estado actual.
6 if (!_states.empty()) {
7 // exit() sobre el ltimo estado.
8 _states.top()->exit();

tecla 'p'
9 // Elimina el ltimo estado.
10 _states.pop();
11 }
12 // Transicin al nuevo estado.
13 _states.push(state);
14 // enter() sobre el nuevo estado.
15 _states.top()->enter();
16 }
PauseState
Otro aspecto relevante del diseo de esta clase es la delegacin de eventos de en-
trada asociados a la interaccin por parte del usuario con el teclado y el ratn. El
diseo discutido permite delegar directamente el tratamiento del evento al estado ac-
tivo, es decir, al estado que ocupe la cima de la pila. Del mismo modo, se traslada
a dicho estado la implementacin de las funciones frameStarted() y frameEnded().
El siguiente listado de cdigo muestra cmo la implementacin de, por ejemplo, la
funcin keyPressed() es trivial.
PlayState
Listado 6.9: Clase GameManager. Funcin keyPressed().
1 bool
2 GameManager::keyPressed
3 (const OIS::KeyEvent &e)
IntroState
4 {
5 _states.top()->keyPressed(e);
6 return true;
7 }
Figura 6.7: Actualizacin de la pi-
la de estados para reanudar el juego
(evento teclado p).
6.1.5. Denicin de estados concretos
Este esquema de gestin de estados general, el cual contiene una clase genrica
GameState, permite la denicin de estados especcos vinculados a un juego en par-
ticular. En la gura 6.6 se muestra grcamente cmo la clase GameState se extiende
para denir tres estados:

IntroState, que dene un estado de introduccin o inicializacin.


PlayState, que dene el estado principal de un juego y en el que se desarrollar
la lgica del mismo.
6.1. El bucle de juego [183]

PauseState, que dene un estado de pausa tpico en cualquier tipo de juego.

La implementacin de estos estados permite hacer explcito el comportamiento del


juego en cada uno de ellos, al mismo tiempo que posibilita las transiciones entre los
mismos. Por ejemplo, una transicin tpica ser pasar del estado PlayState al estado
PauseState.

C6
A la hora de llevar a cabo dicha implementacin, se ha optado por utilizar el pa-
trn Singleton, mediante la clase Ogre::Singleton, para garantizar que slo existe una
instacia de un objeto por estado.
El siguiente listado de cdigo muestra una posible declaracin de la clase IntroS-
tate. Como se puede apreciar, esta clase hace uso de las funciones tpicas para el
tratamiento de eventos de teclado y ratn.

Listado 6.10: Clase IntroState.


1 // SE OMITE PARTE DEL CDIGO FUENTE.
2 class IntroState : public Ogre::Singleton<IntroState>,
3 public GameState
4 {
5 public:
6 IntroState() {}
7
8 void enter (); void exit ();
9 void pause (); void resume ();
10
11 void keyPressed (const OIS::KeyEvent &e);
12 void keyReleased (const OIS::KeyEvent &e);
13 // Tratamiento de eventos de ratn...
14 // frameStarted(), frameEnded()..
15
Figura 6.8: Capturas de pantalla 16 // Heredados de Ogre::Singleton.
del juego Supertux. Arriba, estado 17 static IntroState& getSingleton ();
de introduccin; medio, estado de 18 static IntroState* getSingletonPtr ();
juego; abajo, estado de pausa. 19
20 protected:
21 Ogre::Root* _root;
22 Ogre::SceneManager* _sceneMgr;
23 Ogre::Viewport* _viewport;
24 Ogre::Camera* _camera;
25 bool _exitGame;
26 };

As mismo, tambin especifca la funcionalidad vinculada a la gestin bsica de


estados. Recuerde que, al extender la clase GameState, los estados especcos han de
cumplir el contrato funcional denido por dicha clase abstracta.
Finalmente, la transicin de estados se puede gestionar de diversas formas. Por
ejemplo, es perfectamente posible realizarla mediante los eventos de teclado. En otras
palabras, la pulsacin de una tecla en un determinado estado sirve como disparador
para pasar a otro estado. Recuerde que el paso de un estado a otro puede implicar la
ejecucin de las funciones exit() o pause(), dependiendo de la transicin concreta.
Figura 6.9: Pantalla de presenta-
El siguiente listado de cdigo muestra las transiciones entre el estado de juego y
cin del juego Frozen Bubble. el estado de pausa y entre ste ltimo y el primero, es decir, la accin comnmente
conocida como reanudacin (resume). Para ello, se hace uso de la tecla p.

Listado 6.11: Transiciones entre estados.


1 void PlayState::keyPressed (const OIS::KeyEvent &e) {
2 if (e.key == OIS::KC_P) // Tecla p ->PauseState.
3 pushState(PauseState::getSingletonPtr());
[184] CAPTULO 6. GESTIN DE RECURSOS

4 }
5
6 void PauseState::keyPressed (const OIS::KeyEvent &e) {
7 if (e.key == OIS::KC_P) // Tecla p ->Estado anterior.
8 popState();
9 }

del estado de pausa provoca apilar dicho estado en la pila


Note cmo la activacin

(pushState() enla lnea 7 ), mientras que la reanudacin implica desapilarlo (popSta-
te() en la lnea 16 ).

6.2. Gestin bsica de recursos


En esta seccin se discute la gestin bsica de los recursos, haciendo especial hin-
capi en dos casos de estudio concretos: i) la gestin bsica del sonido y ii) el sistema
de archivos. Antes de llevar a cabo un estudio especco de estas dos cuestiones, la
primera seccin de este captulo introduce la problemtica de la gestin de recursos
en los motores de juegos. As mismo, se discuten las posibilidades que el framework
Ogre3D ofrece para gestionar dicha problemtica.
En el caso de la gestin bsica del sonido, el lector ser capaz de manejar una
serie de abstracciones, en torno a la biblioteca multimedia SDL, para integrar msica
y efectos de sonido en los juegos que desarrolle. Por otra parte, en la seccin relativa
a la gestin del sistema de archivos se plantear la problemtica del tratamiento de
archivos y se estudiarn tcnicas de entrada/salida asncrona.
Debido a la naturaleza multimedia de los motores de juegos, una consecuencia di-
recta es la necesidad de gestionar distintos tipos de datos, como por ejemplo geometra
tridimensional, texturas, animaciones, sonidos, datos relativos a la gestin de fsica y
Figura 6.10: Captura de pantalla
colisiones, etc. Evidentemente, esta naturaleza tan variada se ha de gestionar de forma del videojuego Lemmings 2. Nun-
consistente y garantizando la integridad de los datos. ca la gestin de recursos fue tan
Por otra parte, las potenciales limitaciones hardware de la plataforma sobre la que importante...
se ejecutar el juego implica que sea necesario plantear un mecanismo eciente para
cargar y liberar los recursos asociados a dichos datos multimedia. Por lo tanto, una
mxima del motor de juegos es asegurarse que solamente existe una copia en me-
moria de un determinado recurso multimedia, de manera independiente al nmero de
instancias que lo estn utilizando en un determinado momento. Este esquema permite
optimizar los recursos y manejarlos de una forma adecuada.
Un ejemplo representativo de esta cuestin est representado por las mallas poli- Media manager
gonales y las texturas. Si, por ejemplo, siete mallas comparten la misma textura, es
decir, la misma imagen bidimensional, entonces es deseable mantener una nica co- El gestor de recursos o resource
manager se suele denominar co-
pia de la textura en memoria principal, en lugar de mantener siete. En este contexto, la mnmente media manager o asset
mayora de motores de juegos integran algn tipo de gestor de recursos para cargar y manager.
gestionar la diversidad de recursos que se utilizan en los juegos de ltima generacin.

6.2.1. Gestin de recursos con Ogre3D


Ogre facilita la carga, liberacin y gestin de recursos mediante un planteamien-
to bastante sencillo centrado en optimizar el consumo de memoria de la aplicacin
grca. Recuerde que la memoria es un recurso escaso en el mbito del desarrollo de
videojuegos.
6.2. Gestin bsica de recursos [185]

Ogre:: Ogre:: Ogre:: Ogre::


Singleton ResourceManager SharedPtr Resource

C6
1 0..*
NuevoGestor NuevoRecursoPtr NuevoRecurso

Figura 6.11: Diagrama de clases de las principales clases utilizadas en Ogre3D para la gestin de recursos.
En un tono ms oscuro se reejan las clases especcas de dominio.

Una de las ideas fundamentales de la carga de recursos se basa en almacenar


en memoria principal una nica copia de cada recurso. ste se puede utilizar
para representar o gestionar mltiples entidades.

Carga de niveles Debido a las restricciones hardware que una determinada plataforma de juegos
puede imponer, resulta fundamental hacer uso de un mecanismo de gestin de recur-
Una de las tcnicas bastantes comu-
nes a la hora de cargar niveles de sos que sea exible y escalable para garantizar el diseo y gestin de cualquier tipo
juego consiste en realizarla de ma- de recurso, junto con la posibilidad de cargarlo y liberarlo en tiempo de ejecucin,
nera previa al acceso a dicho nivel. respectivamente. Por ejemplo, si piensa en un juego de plataformas estructurado en
Por ejemplo, se pueden utilizar es- diversos niveles, entonces no sera lgico hacer una carga inicial de todos los nive-
tructuras de datos para representar
los puntos de acceso de un nivel al les al iniciar el juego. Por el contrario, sera ms adecuado cargar un nivel cuando el
siguiente para adelantar el proceso jugador vaya a acceder al mismo.
de carga. El mismo planteamiento
se puede utilizar para la carga de En Ogre, la gestin de recursos se lleva a cabo utilizando principalmente cuatro
texturas en escenarios. clases (ver gura 6.11):

Ogre::Singleton, con el objetivo de garantizar que slo existe una instancia de


un determinado recurso o gestor.
Ogre::ResourceManager, con el objetivo de centralizar la gestin del pool de
recursos de un determinado tipo.
Ogre::Resource, entidad que representa a una clase abstracta que se puede aso-
ciar a recursos especcos.
Ogre::SharedPtr, clase que permite la gestin inteligente de recursos que ne-
cesitan una destruccin implcita.

Bsicamente, el desarrollador que haga uso del planteamiento que Ogre ofrece
para la gestin de recursos tendr que implementar algunas funciones heredadas de
las clases anteriormente mencionadas, completando as la implementacin especca
del dominio, es decir, especca de los recursos a gestionar. Ms adelante se discute
en detalle un ejemplo relativo a los recursos de sonido.
[186] CAPTULO 6. GESTIN DE RECURSOS

6.2.2. Gestin bsica del sonido


Introduccin a SDL

SDL (Simple Directmedia Layer) es una biblioteca multimedia y multiplataforma


ampliamente utilizada en el mbito del desarrollo de aplicaciones multimedia. Desde
un punto de vista funcional, SDL proporciona una serie de APIs para el manejo de
vdeo, audio, eventos de entrada, multi-hilo o renderizado con OpenGL, entre otros
aspectos. Desde un punto de vista abstracto, SDL proporciona una API consistente de
manera independiente a la plataforma de desarrollo.
SDL est bien estructurado y es fcil de utilizar. La losofa de su diseo se pue- Figura 6.12: Logo de la bibliote-
de resumir en ofrecer al desarrollador diversas herramientas que se pueden utilizar ca multiplataforma Simple Direct-
de manera independiente, en lugar de manejar una biblioteca software de mayor en- media Layer.
vergadura. Por ejemplo, un juego podra hacer uso de la biblioteca SDL nicamente
para la gestin de sonido, mientras que la parte grca sera gestionada de manera
independiente (utilizando Ogre3D, por ejemplo).
SDL se puede integrar perfectamente con OpenGL para llevar a cabo la iniciali- SDL y Civilization
zacin de la parte grca de una aplicacin interactiva, delegando en SDL el propio
tratamiento de los eventos de entrada. Hasta ahora, en el mdulo 2 (Programacin El port a sistemas GNU Linux del
juego Civilization se realiz hacien-
Grca) se haba utilizado la biblioteca GLUT (OpenGL Utility Toolkit) para imple- do uso de SDL. En este caso par-
mentar programas que hicieran uso de OpenGL. Sin embargo, SDL proporciona un ticular, los personajes se renderiza-
gran nmero de ventajas con respecto a esta alternativa2 : ban haciendo uso de supercies.

SDL fue diseado para programadores de videojuegos.


Es modular, simple y portable.
Proporciona soporte para la gestin de eventos, la gestin del tiempo, el mane-
jo de sonido, la integracin con dispositivos de almacenamiento externos como
por ejemplo CDs, el renderizado con OpenGL e incluso la gestin de red (net-
working). Event handlers
SDL posibilita la denicin de ar-
El concepto de supercie como estructura de datos es esencial en SDL, ya que quitecturas multi-capa para el trata-
miento de eventos, facilitando as su
posibilita el tratamiento de la parte grca. En esencia, una supercie representa un delegacin por parte del cdigo de-
bloque de memoria utilizado para almacenar una regin rectangular de pxeles. El pendiente del dominio.
principal objetivo de diseo es agilizar la copia de supercies tanto como sea posible.
Por ejemplo, una supercie se puede utilizar para renderizar los propios caracteres de
un juego.
El siguiente listado de cdigo muestra un ejemplo de integracin de OpenGL en
SDL, de manera que SDL oculta todos los aspectos dependientes de la plataforma a
la hora de inicializar el ncleo de OpenGL. Es importante resaltar que OpenGL toma
el control del subsistema de vdeo de SDL. Sin embargo, el resto de componentes de
SDL no se ven afectados por esta cuestin.
La instalacin de SDL en sistemas Debian y derivados es trivial mediante los si-
guientes comandos:

$ sudo apt-get update


$ sudo apt-get install libsdl1.2-dev
$ sudo apt-get install libsdl-image1.2-dev
$ sudo apt-get install libsdl-sound1.2-dev libsdl-mixer1.2-dev
Figura 6.13: Una vez ms, el
sistema de paquetes de Debian
Para generar un chero ejecutable, simplemente es necesario enlazar con las bi- GNU/Linux y distros derivadas sim-
bliotecas necesarias. plica enormemente la instalacin
2 GLUT fue concebido para utilizarse en un entorno ms acadmico, simplicando el tratamiento de y conguracin de bibliotecas de
eventos y la gestin de ventanas. desarrollo. Long live Debian!
6.2. Gestin bsica de recursos [187]

Listado 6.12: Ejemplo sencillo SDL + OpenGL.


1 #include <SDL/SDL.h>
2 #include <GL/gl.h>
3 #include <stdio.h>
4
5 int main (int argc, char *argv[]) {
6 SDL_Surface *screen;

C6
7 // Inicializacin de SDL.
8 if (SDL_Init(SDL_INIT_VIDEO) != 0) {
9 fprintf(stderr, "Unable to initialize SDL: %s\n",
10 SDL_GetError());
11 return -1;
12 }
13
14 // Cuando termine el programa, llamada a SQLQuit().
15 atexit(SDL_Quit);
16 // Activacin del double buffering.
17 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
18
19 // Establecimiento del modo de vdeo con soporte para OpenGL.
20 screen = SDL_SetVideoMode(640, 480, 16, SDL_OPENGL);
21 if (screen == NULL) {
22 fprintf(stderr, "Unable to set video mode: %s\n",
23 SDL_GetError());
24 return -1;
25 }
26
27 SDL_WM_SetCaption("OpenGL with SDL!", "OpenGL");
28 // Ya es posible utilizar comandos OpenGL!
29 glViewport(80, 0, 480, 480);
30
31 glMatrixMode(GL_PROJECTION);
32 glLoadIdentity();
33 glFrustum(-1.0, 1.0, -1.0, 1.0, 1.0, 100.0);
34 glClearColor(1, 1, 1, 0);
35
36 glMatrixMode(GL_MODELVIEW); glLoadIdentity();
37 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
38
39 // Renderizado de un tringulo.
40 glBegin(GL_TRIANGLES);
41 glColor3f(1.0, 0.0, 0.0); glVertex3f(0.0, 1.0, -2.0);
42 glColor3f(0.0, 1.0, 0.0); glVertex3f(1.0, -1.0, -2.0);
43 glColor3f(0.0, 0.0, 1.0); glVertex3f(-1.0, -1.0, -2.0);
44 glEnd();
45
46 glFlush();
47 SDL_GL_SwapBuffers(); // Intercambio de buffers.
48 SDL_Delay(5000); // Espera de 5 seg.
49
50 return 0;
51 }


Como se puede apreciar en el listado anterior, la lnea 20 establece el modo de v-
deo con soporte para
OpenGL para, posteriormente, hacer uso de comandos OpenGL
a partir de la lnea 29 . Sin embargo, note cmo el
intercambio del contenido entre el
front buffer y el back buffer se realiza en la lnea 47 mediante una primitiva de SDL.

Listado 6.13: Ejemplo de Makele para integrar SDL.


1 CFLAGS := -c -Wall
2 LDFLAGS := sdl-config --cflags --libs -lSDL_image -lGL
3 LDLIBS := -lSDL_image -lGL
4 CC := gcc
5
6 all: basic_sdl_opengl
7
[188] CAPTULO 6. GESTIN DE RECURSOS

8 basic_sdl_opengl: basic_sdl_opengl.o
9 $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
10
11 basic_sdl_opengl.o: basic_sdl_opengl.c
12 $(CC) $(CFLAGS) $^ -o $@
13
14 clean:
15 @echo Cleaning up...
16 rm -f *~ *.o basic_sdl_opengl
17 @echo Done.
18
19 vclean: clean

Reproduccin de msica

En esta seccin se discute cmo implementar3 un nuevo recurso que permita la


reproduccin de archivos de msica dentro de un juego. En este ejemplo se har uso
de la biblioteca SDL_mixer para llevar a cabo la reproduccin de archivos de sonido4 .
En primer lugar se dene una clase Track que ser utilizada para gestionar el re-
curso asociado a una cancin. Como ya se ha comentado anteriormente, esta clase
hereda de la clase Ogre::Resource, por lo que ser necesario implementar las fun-
ciones necesarias para el nuevo tipo de recurso. Adems, se incluir la funcionalidad
tpica asociada a una cancin, como las clsicas operaciones play, pause o stop. El Figura 6.14: La manipulacin bsi-
ca de sonido incluye las tpicas ope-
siguiente listado de cdigo muestra la declaracin de la clase Track. raciones de play, pause y stop.

Listado 6.14: Clase Track.


1 #include <SDL/SDL_mixer.h>
2 #include <OGRE/Ogre.h>
3
4 class Track : public Ogre::Resource {
5 public:
6 // Constructor (ver Ogre::Resource).
7 Track (Ogre::ResourceManager* pManager,
8 const Ogre::String& resource_name,
9 Ogre::ResourceHandle handle,
10 const Ogre::String& resource_group,
11 bool manual_load = false,
12 Ogre::ManualResourceLoader* pLoader = 0);
13 ~Track ();
14
15 // Manejo bsico del track.
16 void play (int loop = -1);
17 void pause ();
18 void stop ();
19
20 void fadeIn (int ms, int loop);
21 void fadeOut (int ms);
22 static bool isPlaying ();
23
24 private:
25 // Funcionalidad de Ogre::Resource.
26 void loadImpl ();
27 void unloadImpl ();
28 size_t calculateSize () const;
29
30 // Variables miembro.
3 La implementacin de los recursos de sonido est basada en el artculo titulado Extender la gestin

de recursos, audio del portal IberOgre, el cual se encuentra disponible en http://osl2.uca.es/


iberogre/index.php/Extender_la_gestin_de_recursos,_audio.
4 SDL_mixer slo permite la reproduccin de un clip de sonido de manera simultnea, por lo que no ser

posible realizar mezclas de canciones.


6.2. Gestin bsica de recursos [189]

31 Mix_Music* _pTrack; // SDL


32 Ogre::String _path; // Ruta al track.
33 size_t _size; // Tamao.
34 };

El constructor de esta clase viene determinado por la especicacin del constructor


de la clase Ogre::Resource. De hecho, su implementacin delega directamente en el

C6
constructor de la clase padre para poder instanciar un recurso, adems de inicializar

las propias variables miembro.
Por otra parte, las funciones de manejo bsico del track (lneas 16-22 ) son en
realidad una interfaz para manejar de una manera adecuada la biblioteca SDL_mixer.
Por ejemplo, la funcin miembro play simplemente interacta con SDL para com-
probar si la cancin estaba pausada y, en ese caso, reanudarla. Si la cancin no estaba
pausada, entonces dicha funcin la reproduce desde el principio. Note cmo se hace
uso del gestor de logs de Ogre3D para almacenar en un archivo la posible ocurrencia
de algn tipo de error.

Figura 6.15: Es posible incluir Listado 6.15: Clase Track. Funcin play().
efectos de sonidos ms avanzados, 1 void
como por ejemplo reducir el nivel 2 Track::play
de volumen a la hora de nalizar la 3 (int loop)
reproduccin de una cancin. 4 {
5 Ogre::LogManager* pLogManager =
6 Ogre::LogManager::getSingletonPtr();
7
8 if(Mix_PausedMusic()) // Estaba pausada?
9 Mix_ResumeMusic(); // Reanudacin.
10
11 // Si no, se reproduce desde el principio.
12 else {
13 if (Mix_PlayMusic(_pTrack, loop) == -1) {
14 pLogManager->logMessage("Track::play() Error al....");
15 throw (Ogre::Exception(Ogre::Exception::ERR_FILE_NOT_FOUND,
16 "Imposible reproducir...",
17 "Track::play()"));
18 }
19 }
20 }

Dos de las funciones ms importantes de cualquier tipo de recurso que extienda de


Ogre::Resource son loadImpl() y unloadImpl(), utilizadas para cargar y liberar el re-
curso, respectivamente. Obviamente, cada tipo de recurso delegar en la funcionalidad
necesaria para llevar a cabo dichas tareas. En el caso de la gestin bsica del sonido,
estas funciones delegarn principalmente en SDL_mixer. A continuacin se muestra
el cdigo fuente de dichas funciones.
Debido a la necesidad de manejar de manera eciente los recursos de sonido, la
solucin discutida en esta seccin contempla la denicin de punteros inteligentes a
travs de la clase TrackPtr. La justicacin de esta propuesta, como ya se introdujo
anteriormente, consiste en evitar la duplicidad de recursos, es decir, en evitar que un
mismo recurso est cargado en memoria principal ms de una vez.

Listado 6.16: Clase Track. Funciones loadImpl() y unloadImpl().


1 void
2 Track::loadImpl () // Carga del recurso.
3 {
4 // Ruta al archivo.
5 Ogre::FileInfoListPtr info;
6 info = Ogre::ResourceGroupManager::getSingleton().
[190] CAPTULO 6. GESTIN DE RECURSOS

7 findResourceFileInfo(mGroup, mName);
8
9 for (Ogre::FileInfoList::const_iterator i = info->begin();
10 i != info->end(); ++i) {
11 _path = i->archive->getName() + "/" + i->filename;
12 }
13
14 if (_path == "") { // Archivo no encontrado...
15 // Volcar en el log y lanzar excepcin.
16 }
17
18 // Cargar el recurso de sonido.
19 if ((_pTrack = Mix_LoadMUS(_path.c_str())) == NULL) {
20 // Si se produce un error al cargar el recurso,
21 // volcar en el log y lanzar excepcin.
22 }
23
24 // Clculo del tamao del recurso de sonido.
25 _size = ...
26 }
27
28 void
29 Track::unloadImpl()
30 {
31 if (_pTrack) {
32 // Liberar el recurso de sonido.
33 Mix_FreeMusic(_pTrack);
34 }
35 }

Ogre3D permite el uso de punteros inteligentes compartidos, denidos en la clase resource 2 references
Ogre::SharedPtr, con el objetivo de parametrizar el recurso denido por el desarro-
llador, por ejemplo Track, y almacenar, internamente, un contador de referencias a
dicho recurso. Bsicamente, cuando el recurso se copia, este contador se incrementa.
Si se destruye alguna referencia al recurso, entonces el contador se decrementa. Este
esquema permite liberar recursos cuando no se estn utilizando en un determinado ptr refs ptr refs
momento y compartir un mismo recurso entre varias entidades.
El listado de cdigo 6.17 muestra la implementacin de la clase TrackPtr, la cual Figura 6.16: El uso de smart poin-
incluye una serie de funciones (bsicamente constructores y asignador de copia) here- ters optimiza la gestin de recursos
dadas de Ogre::SharedPtr. A modo de ejemplo, tambin se incluye el cdigo asociado y facilita su liberacin.
al constructor de copia. Como se puede apreciar, en l se incrementa el contador de
referencias al recurso.
Una vez implementada la lgica necesaria para instanciar y manejar recursos de
sonido, el siguiente paso consiste en denir un gestor o manager especco para cen-
tralizar la administracin del nuevo tipo de recurso. Ogre3D facilita enormemente esta
tarea gracias a la clase Ogre::ResourceManager. En el caso particular de los recursos
de sonido se dene la clase TrackManager, cuyo esqueleto se muestra en el listado
de cdigo 6.18.
Esta clase no slo hereda del gestor de recursos de Ogre, sino que tambin lo hace
de la clase Ogre::Singleton con el objetivo de manejar una nica instancia del gestor
de recursos de sonido. Las funciones ms relevantes son las siguientes:

load() (lneas 10-11 ), que permite la carga de canciones por parte del desarro-
llador. Si el recurso a cargar no existe, entonces lo crear internamente utilizan-
do la funcin que se comenta a continuacin.

createImpl() (lneas 18-23 ), funcin que posibilita la creacin de un nuevo
recurso, es decir, una nueva instancia de la clase Track. El desarrollador es res-
ponsable de realizar la carga del recurso una vez que ha sido creado.
6.2. Gestin bsica de recursos [191]

Listado 6.17: Clase TrackPtr y constructor de copia.


1 // Smart pointer a Track.
2 class TrackPtr: public Ogre::SharedPtr<Track> {
3 public:
4 // Es necesario implementar constructores

C6
5 // y operador de asignacin.
6 TrackPtr(): Ogre::SharedPtr<Track>() {}
7 explicit TrackPtr(Track* m): Ogre::SharedPtr<Track>(m) {}
8 TrackPtr(const TrackPtr &m): Ogre::SharedPtr<Track>(m) {}
9 TrackPtr(const Ogre::ResourcePtr &r);
10 TrackPtr& operator= (const Ogre::ResourcePtr& r);
11 };
12
13 TrackPtr::TrackPtr
14 (const Ogre::ResourcePtr &resource):
15 Ogre::SharedPtr<Track>()
16 {
17 // Comprobar la validez del recurso.
18 if (resource.isNull())
19 return;
20
21 // Para garantizar la exclusin mutua...
22 OGRE_LOCK_MUTEX(*resource.OGRE_AUTO_MUTEX_NAME)
23 OGRE_COPY_AUTO_SHARED_MUTEX(resource.OGRE_AUTO_MUTEX_NAME)
24
25 pRep = static_cast<Track*>(resource.getPointer());
26 pUseCount = resource.useCountPointer();
27 useFreeMethod = resource.freeMethod();
28
29 // Incremento del contador de referencias.
30 if (pUseCount)
31 ++(*pUseCount);
32 }

Con las tres clases que se han discutido en esta seccin ya es posible realizar la
carga de recursos de sonido, delegando en la biblioteca SDL_mixer, junto con su ges-
tin y administracin bsicas. Este esquema encapsula la complejidad del tratamiento
del sonido, por lo que en cualquier momento se podra sustituir dicha biblioteca por
otra.
Ms adelante se muestra un ejemplo concreto en el que se hace uso de este tipo
de recursos sonoros. Sin embargo, antes de discutir este ejemplo de integracin se
plantear el soporte de efectos de sonido, los cuales se podrn mezclar con el tema o
track principal a la hora de desarrollar un juego. Como se plantear a continuacin, la
losofa de diseo es exactamente igual que la planteada en esta seccin.

POW! Soporte de efectos de sonido

Adems de llevar a cabo la reproduccin del algn tema musical durante la eje-
cucin de un juego, la incorporacin de efectos de sonido es esencial para alcanzar
un buen grado de inmersin y que, de esta forma, el jugador se sienta como parte del
Figura 6.17: La integracin de propio juego. Desde un punto de vista tcnico, este esquema implica que la biblioteca
efectos de sonido es esencial para de desarrollo permita la mezcla de sonidos. En el caso de SDL_mixer es posible llevar
dotar de realismo la parte sonora del a cabo dicha integracin.
videojuego y complementar la parte
grca.
[192] CAPTULO 6. GESTIN DE RECURSOS

Listado 6.18: Clase TrackManager. Funciones load() y createImpl().


1 // Clase encargada de gestionar recursos del tipo "Track".
2 // Funcionalidad heredada de Ogre::ResourceManager
3 // y Ogre::Singleton.
4 class TrackManager: public Ogre::ResourceManager,
5 public Ogre::Singleton<TrackManager> {
6 public:
7 TrackManager();
8 virtual ~TrackManager();
9 // Funcin de carga genrica.
10 virtual TrackPtr load (const Ogre::String& name,
11 const Ogre::String& group);
12 static TrackManager& getSingleton ();
13 static TrackManager* getSingletonPtr ();
14
15 protected:
16 // Crea una nueva instancia del recurso.
17 Ogre::Resource* createImpl (const Ogre::String& name,
18 Ogre::ResourceHandle handle,
19 const Ogre::String& group,
20 bool isManual,
21 Ogre::ManualResourceLoader* loader,
22 const Ogre::NameValuePairList* createParams);
23 };
24
25 TrackPtr
26 TrackManager::load
27 (const Ogre::String& name, const Ogre::String& group)
28 {
29 // Obtencin del recurso por nombre...
30 TrackPtr trackPtr = getByName(name);
31
32 // Si no ha sido creado, se crea.
33 if (trackPtr.isNull())
34 trackPtr = create(name, group);
35
36 // Carga explcita del recurso.
37 trackPtr->load();
38
39 return trackPtr;
40 }
41
42 // Creacin de un nuevo recurso.
43 Ogre::Resource*
44 TrackManager::createImpl (const Ogre::String& resource_name,
45 Ogre::ResourceHandle handle,
46 const Ogre::String& resource_group,
47 bool isManual,
48 Ogre::ManualResourceLoader* loader,
49 const Ogre::NameValuePairList* createParams)
50 {
51 return new Track(this, resource_name, handle,
52 resource_group, isManual, loader);
53 }

Como se ha comentado anteriormente, a efectos de implementacin, la integra-


cin de efectos de sonidos (FX effects) sigue el mismo planteamiento que el adoptado
para la gestin bsica de sonido. Para ello, en primer lugar se han creado las clases
SoundFX y SoundFXPtr, con el objetivo de gestionar y manipular las distintas ins-
6.2. Gestin bsica de recursos [193]

tancias de los efectos de sonido, respectivamente. En el siguiente listado de cdigo se


muestra la clase SoundFX, que se puede entender como una simplicacin de la clase
Track, ya que los efectos de sonido se reproducirn puntualmente, mediante la funcin
miembro play, cuando as sea necesario.
La manipulacin de efectos de sonido se centraliza en la clase SoundFXManager,
la cual implementa el patrn Singleton y hereda de la clase Ogre::ResourceManager.

C6
La diferencia ms sustancial con respecto al gestor de sonido reside en que el tipo de
recurso mantiene un identicador textual distinto y que, en el caso de los efectos de
sonido, se lleva a cabo una reserva explcita de 32 canales de audio. Para ello, se hace
uso de una funcin especca de la biblioteca SDL_mixer.

Listado 6.19: Clase SoundFX.


1 class SoundFX: public Ogre::Resource {
2 public:
3 // Constructor (ver Ogre::Resource).
4 SoundFX(Ogre::ResourceManager* creator,
5 // Igual que en Track...
6 );
7 ~SoundFX();
8
9 int play(int loop = 0); // Reproduccin puntual.
10
11 protected:
12 void loadImpl();
13 void unloadImpl();
14 size_t calculateSize() const;
15
16 private:
17 Mix_Chunk* _pSound; // Info sobre el efecto de sonido.
18 Ogre::String _path; // Ruta completa al efecto de sonido.
19 size_t _size; // Tamao del efecto (bytes).
20 };

Integrando msica y efectos

Para llevar a cabo la integracin de los aspectos bsicos previamente discutidos so-
bre la gestin de msica y efectos de sonido se ha tomado como base el cdigo fuente
de la sesin de iluminacin del mdulo 2, Programacin Grca. En este ejemplo se
haca uso de una clase MyFrameListener para llevar a cabo la gestin de los eventos
de teclado.
Por una parte, este ejemplo se ha extendido para incluir la reproduccin ininte-
rrumpida de un tema musical, es decir, un tema que se estar reproduciendo desde que
se inicia la aplicacin hasta que sta naliza su ejecucin. Por otra parte, la reproduc-
cin de efectos de sonido adicionales est vinculada a la generacin de ciertos eventos
de teclado. En concreto, cada vez que el usuario pulsa las teclas 1 2, las cuales
estn asociadas a dos esquemas diferentes de clculo del sombreado, la aplicacin re-
producir un efecto de sonido puntual. Este efecto de sonido se mezclar de manera
adecuada con el track principal.
El siguiente listado de cdigo muestra la nueva funcin miembro introducida en la
clase MyApp para realizar la carga de recursos asociada a la biblioteca SDL_mixer.

Listado 6.20: Clase MyApp. Funcin initSDL()


Figura 6.19: Captura de pantalla de
la ejecucin del ejemplo de la se- 1 bool
2 MyApp::initSDL () {
sin de iluminacin del mdulo 2, 3
Programacin Grca. 4 // Inicializando SDL...
[194] CAPTULO 6. GESTIN DE RECURSOS

Ogre::
TrackManager
FrameListener
1

1
1 1 1 1
SoundFXManager MyApp MyFrameListener

Figura 6.18: Diagrama simplicado de clases de las principales entidades utilizadas para llevar a cabo la
integracin de msica y efectos de sonido.

5 if (SDL_Init(SDL_INIT_AUDIO) < 0)
6 return false;
7 // Llamar a SDL_Quit al terminar.
8 atexit(SDL_Quit);
9
10 // Inicializando SDL mixer...
11 if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT,
12 MIX_DEFAULT_CHANNELS, 4096) < 0)
13 return false;
14
15 // Llamar a Mix_CloseAudio al terminar.
16 atexit(Mix_CloseAudio);
17
18 return true;
19
20 }

Es importante resaltar que a la hora de arrancar la instancia de la clase MyApp


mediante la funcin start(), los gestores de sonido y de efectos se instancian. Adems,
se lleva a cabo la reproduccin del track principal.

Listado 6.21: Clase MyApp. Funcin start()


1 // SE OMITE PARTE DEL CDIGO FUENTE.
2 int
3 MyApp::start() {
4
5 _root = new Ogre::Root();
6 _pTrackManager = new TrackManager;
7 _pSoundFXManager = new SoundFXManager;
8
9 // Window, cmara y viewport...
10
11 loadResources();
12 createScene();
13 createOverlay();
14
15 // FrameListener...
16
17 // Reproduccin del track principal...
18 this->_mainTrack->play();
19
20 _root->startRendering();
21 return 0;
22 }
6.3. El sistema de archivos [195]

Finalmente, slo hay que reproducir los eventos de sonido cuando as sea necesa-
rio. En este ejemplo, dichos eventos se reproducirn cuando se indique, por parte del
usuario, el esquema de clculo de sombreado mediante las teclas 1 2. Dicha ac-
tivacin se realiza, por simplicacin, en la propia clase FrameListener; en concreto,
en la funcin miembro frameStarted a la hora de capturar los eventos de teclado.
El esquema planteado para la gestin de sonido mantiene la losofa de delegar el

C6
tratamiento de eventos de teclado, al menos los relativos a la parte sonora, en la clase
principal (MyApp). Idealmente, si la gestin del bucle de juego se plantea en base a
un esquema basado en estados, la reproduccin de sonido estara condicionada por el
estado actual. Este planteamiento tambin es escalable a la hora de integrar nuevos
estados de juegos y sus eventos de sonido asociados. La activacin de dichos eventos
depender no slo del estado actual, sino tambin de la propia interaccin por parte
del usuario.

Listado 6.22: Clase MyApp. Funcin frameStarted()


1 // SE OMITE PARTE DEL CDIGO FUENTE.
2
3 bool
4 MyFrameListener::frameStarted
5 (const Ogre::FrameEvent& evt) {
6
7 _keyboard->capture();
8 // Captura de las teclas de fecha...
9
10 if(_keyboard->isKeyDown(OIS::KC_1)) {
11 _sceneManager->setShadowTechnique(Ogre::
SHADOWTYPE_TEXTURE_MODULATIVE);
12 _shadowInfo = "TEXTURE_MODULATIVE";
13 _pMyApp->getSoundFXPtr()->play(); // REPRODUCCIN.
14 }
15
16 if(_keyboard->isKeyDown(OIS::KC_2)) {
17 _sceneManager->setShadowTechnique(Ogre::
SHADOWTYPE_STENCIL_MODULATIVE);
18 _shadowInfo = "STENCIL_MODULATIVE";
19 _pMyApp->getSoundFXPtr()->play(); // REPRODUCCIN.
20 }
21
22 // Tratamiento del resto de eventos...
23 }

6.3. El sistema de archivos


El gestor de recursos hace un uso extensivo del sistema de archivos. Tpicamen-
/
te, en los PCs los sistemas de archivos son accesibles mediante llamadas al sistema,
proporcionadas por el propio sistema operativo. Sin embargo, en el mbito de los mo-
tores de juegos se suelen plantear esquemas ms generales y escalables debido a la
diversidad de plataformas existente para la ejecucin de un juego.
home lib media
Esta idea, discutida anteriormente a lo largo del curso, se basa en hacer uso de
wrappers o de capas software adicionales para considerar aspectos clave como el desa-
rrollo multiplataforma. El planteamiento ms comn consiste en envolver la API del
david luis andrea sistema de archivos con una API especca del motor de juegos con un doble objetivo:

Figura 6.20: Generalmente, los sis- En el desarrollo multiplataforma, esta API especca proporciona el nivel de
temas de archivos mantienen es- abstraccin necesario para no depender del sistema de archivos y de la platafor-
tructuras de rbol o de grafo. ma subyacente.
[196] CAPTULO 6. GESTIN DE RECURSOS

La API vinculada al sistema de archivos puede no proporcionar toda la fun-


cionalidad necesaria por el motor de juegos. En este caso, la API especca
complementa a la nativa.

Por ejemplo, la mayora de sistemas operativos no proporcionan mecanismos para


cargar datos on the y mientras un juego est en ejecucin. Por lo tanto, el motor de
juegos debera ser capaz de soportar streaming de cheros.
Por otra parte, cada plataforma de juegos maneja un sistema de archivos distinto
e incluso necesita gestionar distintos dispositivos de entrada/salida (E/S), como por
ejemplo discos blu-ray o tarjetas de memoria. Esta variedad se puede ocultar gracias
al nivel extra de abstraccin proporcionado por la API del propio motor de juegos.

6.3.1. Gestin y tratamiento de archivos


El sistema de archivos dene la organizacin de los datos y proporciona meca-
nismos para almacenar, recuperar y actualizar informacin. As mismo, tambin es el
encargado de gestionar el espacio disponible en un determinado dispositivo de almace-
namiento. Idealmente, el sistema de archivos ha de organizar los datos de una manera
eciente, teniendo en cuenta incluso las caractersticas especcas del dispositivo de
almacenamiento.
En el mbito de un motor de juegos, la API vinculada a la gestin del sistema de
archivos proporciona la siguiente funcionalidad [42]: Figura 6.21: Tras acabar con el for-
mato HD-DVD, el Blu-Ray sucede-
r al formato DVD como principal
Manipular nombres de archivos y rutas. alternativa en el soporte fsico de las
consolas de videojuegos de sobre-
Abrir, cerrar, leer y escribir sobre archivos. mesa.

Listar el contenido de un directorio.


Manejar peticiones de archivos de manera asncrona.

Una ruta o path dene la localizacin de un determinado archivo o directorio y,


normalmente, est compuesta por una etiqueta que dene el volumen y una serie de
componentes separados por algn separador, como por ejemplo la barra invertida. En
sistemas UNIX, un ejemplo tpico estara representado por la siguiente ruta:

/home/david/apps/firefox/firefox-bin

En el caso de las consolas, las rutas suelen seguir un convenio muy similar inclu- Encapsulacin
so para referirse a distintos volmenes. Por ejemplo, PlayStation3TM utiliza el prejo
/dev_bdvd para referirse al lector de blu-ray, mientras que el prejo /dev_hddx per- Una vez ms, el principio de encap-
sulacin resulta fundamental para
mite el acceso a un determinado disco duro. abstraerse de la complejidad asocia-
Las rutas o paths pueden ser absolutas o relativas, en funcin de si estn denidas da al tratamiento del sistema de ar-
chivos y del sistema operativo sub-
teniendo como referencia el directorio raz del sistema de archivos u otro directorio, yacente.
respectivamente. En sistemas UNIX, dos ejemplos tpicos seran los siguientes:

/usr/share/doc/ogre-doc/api/html/index.html
apps/firefox/firefox-bin

El primero de ellos sera una ruta absoluta, ya que se dene en base al directorio
raz. Por otra parte, la segunda sera una ruta relativa, ya que se dene en relacin al
directorio /home/david.
6.3. El sistema de archivos [197]

C6
bin boot dev etc home lib media usr var

david luis andrea bin include

Figura 6.22: Jerarqua tpica del sistema de archivos UNIX.

Antes de pasar a discutir aspectos de la gestin de E/S, resulta importante comen-


tar la existencia de APIs especcas para la gestin de rutas. Aunque la gestin
bsica de tratamiento de rutas se puede realizar mediante cadenas de texto, su com-
plejidad hace necesaria, normalmente, la utilizacin de APIs especcas para abstraer
dicha complejidad. La funcionalidad relevante, como por ejemplo la obtencin del di-
rectorio, nombre y extensin de un archivo, o la conversin entre paths absolutos y
relativos, se puede encapsular en una API que facilite la interaccin con este tipo de
componentes.
Instalacin En el caso del desarrollo de videojuegos multiplataforma, se suele hacer uso de
La instalacin de la biblioteca
otra API adicional para envolver a las APIs especcas para el tratamiento de archivos
Boost.Filesystem en sistemas De- en diversos sistemas operativos.
bian y derivados es trivial median-
te cualquier gestor de paquetes. Una
bsqueda con apt-cache search es Caso de estudio. La biblioteca Boost.Filesystem
todo lo que necesitas para averiguar
qu paquete/s hay que instalar.
En el contexto de APIs especcas para la gestin de rutas, el uso de la biblioteca
Boost.Filesystem5 es un ejemplo representativo de API multiplataforma para el tra-
tamiento de rutas en C++. Dicha biblioteca es compatible con el estndar de C++,
es portable a diversos sistemas operativos y permite el manejo de errores mediante
excepciones.
El siguiente listado de cdigo muestra un programa que realiza un procesamiento
recursivo de archivos, mostrando su nombre independientemente del tipo de archivo
(regular o directorio). Como se puede apreciar, con un programa trivial es posible
llevar a cabo tareas bsicas para el tratamiento de rutas.

Listado 6.23: Ejemplo de uso de boost::lesystem.


1 #include <iostream>
2 #include <boost/filesystem.hpp>
3
4 using namespace std;

5 www.boost.org/libs/lesystem/
[198] CAPTULO 6. GESTIN DE RECURSOS

5 using namespace boost::filesystem;


6
7 void list_directory (const path& dir, const int& tabs);
8
9 int main (int argc, char* argv[]) {
10 if (argc < 2) {
11 cout << "Uso: ./exec/Simple <path>" << endl;
12 return 1;
13 }
14 path p(argv[1]); // Instancia de clase boost::path.
15
16 if (is_regular_file(p))
17 cout << " " << p << " " << file_size(p) << " B" << endl;
18 else if (is_directory(p)) // Listado recursivo.
19 list_directory(p, 0);
20
21 return 0;
22 }
23
24 void
25 print_tabs (const int& tabs) {
26 for (int i = 0; i < tabs; ++i) cout << "\t";
27 }
28
29 void list_directory
30 (const path& p, const int& tabs) {
31 vector<path> paths;
32 // directory iterator para iterar sobre los contenidos del dir.
33 copy(directory_iterator(p), directory_iterator(),
34 back_inserter(paths));
35 sort(paths.begin(), paths.end()); // Se fuerza el orden.
36
37 // Pretty print ;-)
38 for (vector<path>::const_iterator it = paths.begin();
39 it != paths.end(); ++it) {
40 if (is_directory(*it)) {
41 print_tabs(tabs);
42 cout << *it << endl;
43 list_directory(*it, (tabs + 1));
44 }
45 else if (is_regular_file(*it)) {
46 print_tabs(tabs);
47 cout << (*it) << " " << file_size(*it) << " B" << endl;
48 }
49 }
50 }

6.3.2. E/S bsica


Para conseguir que la E/S asociada a un motor de juegos sea eciente, resulta im-
prescindible plantear un esquema de memoria intermedia que permita gestionar tanto
los datos escritos (ledos) como el destino en el que dichos datos se escriben (leen). T-
picamente, la solucin a este problema est basada en el uso de buffers. Bsicamente,
un buffer representa un puente de comunicacin entre el propio programa y un archivo
en disco. En lugar de, por ejemplo, realizar la escritura byte a byte en un archivo de
texto, se suelen emplear buffers intermedios para reducir el nmero de escrituras en
disco y, de este modo, mejorar la eciencia de un programa.
Stream I/O API
En esta seccin se prestar especial atencin a la biblioteca estndar de C, ya que
La API de E/S con buffers que pro-
es la ms comnmente utilizada para llevar a cabo la gestin de E/S en el desarrollo porciona la biblioteca estndar de
de videojuegos. C se denomina comnmente Stream
I/O API, debido a que proporciona
una abstraccin que permite gestio-
nar los archivos en disco como u-
jos de bytes.
6.3. El sistema de archivos [199]

El lenguaje de programacin C permite manejar dos APIs para gestionar las opera-
ciones ms relevantes en relacin al contenido de un archivo, como la apertura, lectura,
escritura o el cierre. La primera de ellas proporciona E/S con buffers mientras que la
segunda no.
La diferencia entre ambas reside en que la API con E/S mediante buffer gestiona de
manera automtica el uso de los propios buffers sin necesidad de que el programador

C6
asuma dicha responsabilidad. Por el contrario, la API de C que no proporciona E/S con
buffers tiene como consecuencia directa que el programador ha de asignar memoria
para dichos buffers y gestionarlos. La tabla 6.1 muestra las principales operaciones de
dichas APIs.

Listado 6.24: Ejemplo de operacin de E/S sncrona


1 #include <stdio.h>
2
3 int lectura_sincrona (const char* archivo, char* buffer,
4 size_t tamanyo_buffer, size_t* p_bytes_leidos);
5
6 int main (int argc, const char* argv[]) {
7 char buffer[256];
8 size_t bytes_leidos = 0;
9
10 if (lectura_sincrona("test.txt", buffer, sizeof(buffer), &
bytes_leidos))
11 printf(" %u bytes leidos!\n", bytes_leidos);
12
13 return 0;
14 }
15
16 int lectura_sincrona (const char* archivo, char* buffer,
17 size_t tamanyo_buffer, size_t* p_bytes_leidos) {
18 FILE* manejador = NULL;
19
20 if ((manejador = fopen(archivo, "rb"))) {
21 // Llamada bloqueante en fread,
22 // hasta que se lean todos los datos.
23 size_t bytes_leidos = fread(buffer, 1, tamanyo_buffer,
manejador);
24
25 // Ignoramos errores...
26 fclose(manejador);
27
28 *p_bytes_leidos = bytes_leidos;
29 return 1;
30 }
31 return -1;
32 }

Dependiendo del sistema operativo en cuestin, las invocaciones sobre operacio-


nes de E/S se traducirn en llamadas nativas al sistema operativo, como ocurre en
el caso de UNIX y variantes, o en envolturas sobre alguna otra API de ms bajo ni-
Sender Receiver vel, como es el caso de sistemas Microsoft WindowsTM . Es posible aprovecharse del
recibir (msg) hecho de utilizar las APIs de ms bajo nivel, ya que exponen todos los detalles de
implementacin del sistema de archivos nativo [42].
Procesar
mensaje Una opcin relevante vinculada al uso de buffers consiste en gestionarlos para
reducir el impacto que la generacin de archivos de log produce. Por ejemplo, se puede
obtener un resultado ms eciente si antes de volcar informacin sobre un archivo en
disco se utiliza un buffer intermedio. As, cuando ste se llena, entonces se vuelca
Figura 6.23: El uso de invocacio- sobre el propio archivo para mejorar el rendimiento de la aplicacin. En este contexto,
nes sncronas tiene como conse- se puede considerar la delegacin de esta tarea en un hilo externo al bucle principal
cuencia el bloqueo de la entidad que del juego.
realiza la invocacin.
[200] CAPTULO 6. GESTIN DE RECURSOS

En relacin a lo comentado anteriormente sobre el uso de APIs de ms bajo nivel,


surge de manera inherente la cuestin relativa al uso de envolturas o wrappers sobre
la propia biblioteca de E/S, ya sea de bajo nivel y dependiente del sistema operativo
o de ms alto nivel y vinculada al estndar de C. Comnmente, los motores de juegos
hacen uso de algn tipo de capa software que sirva como envoltura de la API de E/S.
Este planteamiento proporcionar tres ventajas importantes [42]:

1. Es posible garantizar que el comportamiento del motor en distintas platafor-


mas sea idntico, incluso cuando la biblioteca nativa de ms bajo nivel presente
algn tipo de cuestin problemtica.
2. Es posible adaptar la API de ms alto nivel a las necesidades del motor de
juegos. De este modo, se mejora la mantenibilidad del mismo ya que slo
se presta atencin a los aspectos del sistema de E/S realmente utilizados.
3. Es un esquema escalable que permite integrar nueva funcionalidad, como por
ejemplo la necesidad de tratar con dispositivos de almacenamiento externos,
como unidades de DVD o Blu-Ray.

Finalmente, es muy importante tener en cuenta que las bibliotecas de E/S estndar
de C son sncronas. En otras palabras, ante una invocacin de E/S, el programa se
queda bloqueado hasta que se atiende por completo la peticin. El listado de cdigo
anterior muestra un ejemplo de llamada bloqueante mediante la operacin fread().
Evidentemente, el esquema sncrono presenta un inconveniente muy importante,
ya que bloquea al programa mientras la operacin de E/S se efecta, degradando as
el rendimiento global de la aplicacin. Un posible solucin a este problema consiste
en adoptar un enfoque asncrono, tal y como se discute en la siguiente seccin.

6.3.3. E/S asncrona


La E/S asncrona gira en torno al concepto de streaming, el cual se reere a la
carga de contenido en un segundo plano, es decir, mientras el programa principal
est en un primer plano de ejecucin. Este esquema posibilita la carga de contenido
en tiempo de ejecucin, es decir, permite obtener contenido que ser utilizado en un
futuro inmediato por la propia aplicacin, evitando as potenciales cuellos de botella
y garantizando que la tasa por segundos del juego no decaiga.
Normalmente, cuando un juego se ejecuta, se hace uso tanto del disco duro como
de algn tipo de dispositivo de almacenamiento externo, como por ejemplo unidades
pticas, para cargar contenido audiovisual en la memoria principal. ltimamente, uno
de los esquemas ms utilizados, incluso en consolas de sobremesa, consiste en instalar
el juego en el disco duro a partir de la copia fsica del juego, que suele estar en DVD
o Blu-Ray. Este proceso consiste en almacenar determinados elementos del juego en
el disco duro con el objetivo de agilizar los tiempos de carga en memoria principal.
El uso del disco duro permite la carga de elementos en segundo plano mientras
el juego contina su ujo de ejecucin normal. Por ejemplo, es perfectamente posible
cargar las texturas que se utilizarn en el siguiente nivel mientras el jugador est ter-
minando el nivel actual. De este modo, el usuario no se desconecta del juego debido a
cuestiones externas.
Para implementar este esquema, la E/S asncrona resulta fundamental, ya que es
una aproximacin que permite que el ujo de un programa no se bloquee cuando es
necesario llevar a cabo una operacin de E/S. En realidad, lo que ocurre es que el
ujo del programa contina mientras se ejecuta la operacin de E/S en segundo plano.
Cuando sta naliza, entonces notica dicha nalizacin mediante una funcin de
retrollamada.
6.3. El sistema de archivos [201]

API E/S con buffers


Operacin Signatura
Abrir archivo FILE *fopen(const char *path, const char *mode);
Cerrar archivo int fclose(FILE *fp);
Leer archivo size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
Escribir archivo size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

C6
Desplazar int fseek(FILE *stream, long offset, int whence);
Obtener offset long ftell(FILE *stream);
Lectura lnea char *fgets(char *s, int size, FILE *stream);
Escritura lnea int fputs(const char *s, FILE *stream);
Lectura cadena int fscanf(FILE *stream, const char *format, ...);
Escritura cadena int fprintf(FILE *stream, const char *format, ...);
Obtener estado int fstat(int fd, struct stat *buf);

API E/S sin buffers


Operacin Signatura
Abrir archivo int open(const char *pathname, int ags, mode_t mode);
Cerrar archivo int close(int fd);
Leer archivo ssize_t read(int fd, void *buf, size_t count);
Escribir archivo ssize_t write(int fd, const void *buf, size_t count);
Desplazar off_t lseek(int fd, off_t offset, int whence);
Obtener offset off_t tell(int fd);
Lectura lnea No disponible
Escritura lnea No disponible
Lectura cadena No disponible
Escritura cadena No disponible
Obtener estado int stat(const char *path, struct stat *buf);

Cuadro 6.1: Resumen de las principales operaciones de las APIs de C para la gestin de E/S (con y sin buffers).

La gura 6.24 muestra de manera grca cmo dos agentes se comunican de ma-
nera asncrona mediante un objeto de retrollamada. Bsicamente, el agente noticador
enva un mensaje al agente receptor de manera asncrona. Esta invocacin tiene dos
parmetros: i) el propio mensaje m y ii) un objeto de retrollamada cb. Dicho objeto
ser usado por el agente receptor cuando ste termine de procesar el mensaje. Mien-
tras tanto, el agente noticador continuar su ujo normal de ejecucin, sin necesidad
de bloquearse por el envo del mensaje.
Callbacks La mayora de las bibliotecas de E/S asncrona existentes permiten que el progra-
Las funciones de retrollamada se
ma principal espera una cierta cantidad de tiempo antes de que una operacin de E/S
suelen denominar comnmente se complete. Este planteamiento puede ser til en situaciones en las que los datos de
callbacks. Este concepto no slo dicha operacin se necesitan para continuar con el ujo normal de trabajo. Otras APIs
se usa en el mbito de la E/S asn- pueden proporcionar incluso una estimacin del tiempo que tardar en completarse
crona, sino tambin en el campo
de los sistemas distribuidos y los
una operacin de E/S, con el objetivo de que el programador cuente con la mayor
middlewares de comunicaciones. cantidad posible de informacin. As mismo, tambin es posible encontrarse con ope-
raciones que asignen deadlines sobre las propias peticiones, con el objetivo de plantear
un esquema basado en prioridades. Si el deadline se cumple, entonces tambin suele
ser posible asignar el cdigo a ejecutar para contemplar ese caso en particular.
[202] CAPTULO 6. GESTIN DE RECURSOS

Agente 1
: Agente n
Notificador Receptor

1
<<crear ()>> : Objeto
callback

recibir_mensaje (cb, m)

Procesar
mensaje

responder ()
responder ()

Figura 6.24: Esquema grco representativo de una comunicacin asncrona basada en objetos de retrolla-
mada.

Desde un punto de vista general, la E/S asncrona se suele implementar mediante


hilos auxiliares encargados de atender las peticiones de E/S. De este modo, el hilo
principal ejecuta funciones que tendrn como consecuencia peticiones que sern en-
coladas para atenderlas posteriormente. Este hilo principal retornar inmediatamente
despus de llevar a cabo la invocacin.
El hilo de E/S obtendr la siguiente peticin de la cola y la atender utilizando
funciones de E/S bloqueantes, como la funcin fread discutida en el anterior listado
de cdigo. Cuando se completa una peticin, entonces se invoca al objeto o a la fun-
cin de retrollamada proporcionada anteriormente por el hilo principal (justo cuando
se realiz la peticin inicial). Este objeto o funcin noticar la nalizacin de la
peticin.
En caso de que el hilo principal tenga que esperar a que la peticin de E/S se
complete antes de continuar su ejecucin ser necesario proporcionar algn tipo de
mecanismo para garantizar dicho bloqueo. Normalmente, se suele hacer uso de sem-
foros [89] (ver gura 6.25), vinculando un semforo a cada una de las peticiones. De
este modo, el hilo principal puede hacer uso de wait hasta que el hilo de E/S haga uso
de signal, posibilitando as que se reanude el ujo de ejecucin.
6.3. El sistema de archivos [203]

6.3.4. Caso de estudio. La biblioteca Boost.Asio C++

Boost.Asio6 es una biblioteca multiplataforma desarrollada en C++ con el objetivo


de dar soporte a operaciones de red y de E/S de bajo nivel a travs de un modelo asn-
crono consistente y bajo un enfoque moderno. Asio se enmarca dentro del proyecto
Boost.

C6
El desarrollo de esta biblioteca se justica por la necesidad de interaccin en-
tre programas, ya sea mediante cheros, redes o mediante la propia consola, que no
pueden quedarse a la espera ante operaciones de E/S cuyo tiempo de ejecucin sea
elevado. En el caso del desarrollo de videojuegos, una posible aplicacin de este en-
foque, tal y como se ha introducido anteriormente, sera la carga en segundo plano
de recursos que sern utilizados en el futuro inmediato. La situacin tpica en este
contexto sera la carga de los datos del siguiente nivel de un determinado juego.
Segn el propio desarrollador de la biblioteca7 , Asio proporciona las herramientas
necesarias para manejar este tipo de problemtica sin la necesidad de utilizar, por parte
del programador, modelos de concurrencia basados en el uso de hilos y mecanismos
de exclusin mutua. Aunque la biblioteca fue inicialmente concebida para la proble-
mtica asociada a las redes de comunicaciones, sta es perfectamente aplicable para
wait llevar a cabo una E/S asncrona asociada a descriptores de cheros o incluso a puertos
serie.
Los principales objetivos de diseo de Boost.Asio son los siguientes:

Portabilidad, gracias a que est soportada en un amplio rango de sistemas ope-


rativos, proporcionando un comportamiento consistente en los mismos.
Escalabilidad, debido a que facilita el desarrollo de aplicaciones de red que
escalen a un gran nmero de conexiones concurrentes. En principio, la imple-
mentacin de la biblioteca en un determinado sistema operativo se benecia del
soporte nativo de ste para garantizar esta propiedad.
Eciencia, gracias a que la biblioteca soporta aspectos relevantes, como por
signal ejemplo la reduccin de las copias de datos.
Reutilizacin, debido a que Asio se basa en modelos y conceptos ya estableci-
dos, como la API BSD Socket.
Facilidad de uso, ya que la biblioteca est orientada a proporcionar un kit de
herramientas, en lugar de basarse en el modelo framework.

La instalacin de la biblioteca Boost.Asio en sistemas Debian y derivados es trivial


mediante los siguientes comandos:

Figura 6.25: Esquema general de $ sudo apt-get update


uso de un semforo para controlar $ sudo apt-get install libasio-dev
el acceso a determinadas secciones
de cdigo. A continuacin se plantean algunos ejemplos ya implementados con el objetivo
de ilustrar al lector a la hora de plantear un esquema basado en el asincronismo para,
por ejemplo, llevar a cabo operaciones de E/S asncrona o cargar recursos en segundo
plano8 . El siguiente listado muestra un programa de uso bsico de la biblioteca Asio,
en el que se muestra la consecuencia directa de una llamada bloqueante.
Figura 6.26: Las bibliotecas del
6 http://think-async.com/Asio/
proyecto Boost representan, gene-
7 http://www.boost.org/doc/libs/1_48_0/doc/html/boost_asio.html
ralmente, una excelente alternati-
8 La nocin de segundo plano en este contexto est vinculada a independizar ciertas tareas de la ejecucin
va para solucionar un determinado
problema. del ujo principal de un programa.
[204] CAPTULO 6. GESTIN DE RECURSOS

Listado 6.25: Biblioteca Asio. Espera explcita.


1 #include <iostream>
2 #include <boost/asio.hpp>
3 #include <boost/date_time/posix_time/posix_time.hpp>
4
5 int main () {
6 // Todo programa que haga uso de asio ha de instanciar
7 // un objeto del tipo io service para manejar la E/S.
8 boost::asio::io_service io;
9
10 // Instancia de un timer (3 seg.)
11 // El primer argumento siempre es un io service.
12 boost::asio::deadline_timer t(io, boost::posix_time::seconds(3));
13
14 // Espera explcita.
15 t.wait();
16
17 std::cout << "Hola Mundo!" << std::endl;
18
19 return 0;
20 }

Para compilar, enlazar y generar el archivo ejecutable, simplemente es necesario


ejecutar las siguientes instrucciones:

$ g++ Simple.cpp -o Simple -lboost_system -lboost_date_time


$ ./Simple

En determinados contextos resulta muy deseable llevar a cabo una espera asncro- In the meantime...
na, es decir, continuar ejecutando instrucciones mientras en otro nivel de ejecucin se
Recuerde que mientras se lleva a
realizan otras tareas. Si se utiliza la biblioteca Boost.Asio, es posible denir mane- cabo una espera asncrona es posi-
jadores de cdigo asociados a funciones de retrollamada que se ejecuten mientras el ble continuar ejecutando operacio-
programa contina la ejecucin de su ujo principal. Asio tambin proporciona meca- nes en el hilo principal. Aproveche
nismos para que el programa no termine mientras haya tareas por nalizar. este esquema para obtener un mejor
rendimiento en sistemas con ms de
El siguiente listado de cdigo muestra cmo el ejemplo anterior se puede modicar un procesador.
para llevar a cabo una espera asncrona, asociando en este caso un temporizador que
controla la ejecucin de una funcin de retrollamada.

Listado 6.26: Biblioteca Asio. Espera asncrona.


1 #include <iostream>
2 #include <boost/asio.hpp>
3 #include <boost/date_time/posix_time/posix_time.hpp>
4
5 // Funcin de retrollamada.
6 void print (const boost::system::error_code& e) {
7 std::cout << "Hola Mundo!" << std::endl;
8 }
9
10 int main () {
11 boost::asio::io_service io;
12
13 boost::asio::deadline_timer t(io, boost::posix_time::seconds(3));
14 // Espera asncrona.
15 t.async_wait(&print);
16
17 std::cout << "Esperando a print()..." << std::endl;
18 // Pasarn casi 3 seg. hasta que print se ejecute...
19
20 // io.run() para garantizar que los manejadores se llamen desde
21 // hilos que llamen a run().
22 // run() se ejecutar mientras haya cosas por hacer,
23 // en este caso la espera asncrona a print.
24 io.run();
6.3. El sistema de archivos [205]

25
26 return 0;
27 }

La ejecucin de este programa generar la siguiente salida:

C6
$ Esperando a print()...
$ Hola Mundo!

Sin embargo, pasarn casi 3 segundos entre la impresin de una secuenciay otra,
ya que inmediatamente despus de la llamada a la
espera asncrona (lnea 15 ) se
ejecutar la primera secuencia de impresin (lnea
17 ), mientras que la ejecucin de
la funcin de retrollamada print() (lneas 6-8 ) se demorar 3 segundos.
Otra opcin imprescindible a la hora de gestionar estos manejadores reside en la
posibilidad de realizar un paso de parmetros, en funcin del dominio de la aplica-
cin que se est desarrollando. El siguiente listado de cdigo muestra cmo llevar a
cabo dicha tarea.

Listado 6.27: Biblioteca Asio. Espera asncrona y paso de parmetros.


1 #define THRESHOLD 5
2
3 // Funcin de retrollamada con paso de parmetros.
4 void count (const boost::system::error_code& e,
5 boost::asio::deadline_timer* t, int* counter) {
6 if (*counter < THRESHOLD) {
7 std::cout << "Contador... " << *counter << std::endl;
8 ++(*counter);
9
10 // El timer se prolonga un segundo...
11 t->expires_at(t->expires_at() + boost::posix_time::seconds(1));
12 // Llamada asncrona con paso de argumentos.
13 t->async_wait(boost::bind
14 (count, boost::asio::placeholders::error, t, counter));
15 }
16 }
17
18 int main () {
19 int counter = 0;
20 boost::asio::deadline_timer t(io, boost::posix_time::seconds(1));
21 // Llamada inicial.
22 t.async_wait(boost::bind
23 (count, boost::asio::placeholders::error,
24 &t, &counter));
25 // ...
26 }

Como se puede apreciar, las llamadas a la funcin async_wait() varan con res-
pecto a otros ejemplos, ya que se indica de manera explcita los argumentos relevantes
para la funcin de retrollamada. En este caso, dichos argumentos son la propia funcin
de retrollamada, para realizar llamadas recursivas, el timer para poder prolongar en un
segundo su duracin en cada llamada, y una variable entera que se ir incrementando
en cada llamada.
Flexibilidad en Asio La biblioteca Boost.Asio tambin permite encapsular las funciones de retrollamada
que se ejecutan de manera asncrona como funciones miembro de una clase. Este
La biblioteca Boost.Asio es muy esquema mejora el diseo de la aplicacin y permite que el desarrollador se abstraiga
exible y posibilita la llamada asn-
crona a una funcin que acepta un de la implementacin interna de la clase. El siguiente listado de cdigo muestra una
nmero arbitrario de parmetros. posible modicacin del ejemplo anterior mediante la denicin de una clase Counter,
stos pueden ser variables, punte- de manera que la funcin count pasa a ser una funcin miembro de dicha clase.
ros, o funciones, entre otros.
[206] CAPTULO 6. GESTIN DE RECURSOS

Listado 6.28: Biblioteca Asio. Espera asncrona y clases.


1 class Counter {
2 public:
3 Counter (boost::asio::io_service& io)
4 : _timer(io, boost::posix_time::seconds(1)), _count(0) {
5 _timer.async_wait(boost::bind(&Counter::count, this));
6 }
7 ~Counter () { cout << "Valor final: " << _count << endl; }
8
9 void count () {
10 if (_count < 5) {
11 std::cout << _count++ << std::endl;
12 _timer.expires_at(_timer.expires_at() +
13 boost::posix_time::seconds(1));
14 // Manejo de funciones miembro.
15 _timer.async_wait(boost::bind(&Counter::count, this));
16 }
17 }
18 private:
19 boost::asio::deadline_timer _timer;
20 int _count;
21 };
22
23 int main () {
24 boost::asio::io_service io;
25 Counter c(io); // Instancia de la clase Counter.
26 io.run();
27 return 0;
28 }

En el contexto de carga de contenido en un juego de manera independiente al


hilo de control principal, el ejemplo anterior no servira debido a que presenta dos
importantes limitaciones. Por una parte, la respuesta de la funcin de retrollamada
no es controlable si dicha retrollamada tarda mucho tiempo en completarse. Por otra
parte, este planteamiento no es escalable a sistemas multiproceso.
Para ello, una posible solucin consiste en hacer uso de un pool de hilos que inter-
acten con io_service::run(). Sin embargo, este esquema plantea un nuevo problema:
la necesidad de sincronizar el acceso concurrente de varios manejadores a los recursos
compartidos, como por ejemplo una variable miembro de una clase.
El siguiente listado de cdigo extiende la denicin de la clase Counter para ma-
nejar de manera concurrente dos timers que irn incrementado la variable miembro
_count.

Listado 6.29: Biblioteca Asio. Espera asncrona y clases.


1 class Counter {
2 public:
3 Counter (boost::asio::io_service& io)
4 : _strand(io), // Para garantizar la exclusin mutua.
5 _timer1(io, boost::posix_time::seconds(1)),
6 _timer2(io, boost::posix_time::seconds(1)),
7 _count(0) {
8 // Los manejadores se envuelven por strand para
9 // que no se ejecuten de manera concurrente.
10 _timer1.async_wait(_strand.wrap
11 (boost::bind(&Counter::count1, this)));
12 _timer2.async_wait(_strand.wrap
13 (boost::bind(&Counter::count2, this)));
14 }
15
16 // count1 y count2 nunca se ejecutarn en paralelo.
17 void count1() {
18 if (_count < 10) {
19 // IDEM que en el ejemplo anterior.
6.4. Importador de datos de intercambio [207]

20 _timer1.async_wait(_strand.wrap
21 (boost::bind(&Counter::count1, this)));
22 }
23 }
24
25 // IDEM que count1 pero sobre timer2 y count2
26 void count2() { /* src */ }
27

C6
28 private:
29 boost::asio::strand _strand;
30 boost::asio::deadline_timer _timer1, _timer2;
31 int _count;
32 };
33
34 int main() {
35 boost::asio::io_service io;
36 // run() se llamar desde dos threads (principal y boost)
37 Counter c(io);
38 boost::thread t(boost::bind(&boost::asio::io_service::run, &io));
39 io.run();
40 t.join();
41
42 return 0;
43 }

En este caso, la biblioteca proporciona wrappers para envolver las funciones de


retrollamada count1 y count2, con el objetivo de que, internamente, Asio controle que
dichos manejadores no se ejecutan de manera concurrente. En el ejemplo propuesto,
la variable miembro _count no se actualizar, simultneamente, por dichas funciones
miembro.
Tambin resulta interesante destacar la instanciacin de un hilo adicional a travs
de la clase boost::thread, de manera que la funcin io_service::run() se llame tanto
desde este hilo como desde el principal. La losofa de trabajo se mantiene, es decir,
los hilos se seguirn ejecutando mientras haya trabajo pendiente. En concreto, el hi-
lo en segundo plano no nalizar hasta que todas las operaciones asncronas hayan
terminado su ejecucin.

6.3.5. Consideraciones nales


En el siguiente captulo se discutir otro enfoque basado en la concurrencia me-
diante hilos en C++ para llevar a cabo tareas como por ejemplo la carga de contenido,
en segundo plano, de un juego. En dicho captulo se utilizar como soporte la bibliote-
ca de utilidades del middleware ZeroC I CE, el cual tambin se estudiar en el mdulo
3, Tcnicas Avanzadas de Desarrollo.

6.4. Importador de datos de intercambio


En esta seccin se justica la necesidad de plantear esquemas de datos para impor-
tar contenido desde entornos de creacin de contenido 3D, como por ejemplo Blen-
der. Aunque existen diversos estndares para la denicin de datos multimedia, en la
prctica cada aplicacin grca interactiva tiene necesidades especcas que han de
cubrirse con un software particular para importar dichos datos.
Por otra parte, el formato de los datos es otro aspecto relevante, existiendo dis-
tintas aproximaciones para codicar la informacin multimedia a importar. Uno de
los esquemas ms utilizados consiste en hacer uso del metalenguaje XML (eXtensi-
Figura 6.27: Serializacin de esce-
narios de Matrix... ;-)
ble Markup Language), por lo que en este captulo se discutir esta opcin en detalle.
[208] CAPTULO 6. GESTIN DE RECURSOS

XML mantiene un alto nivel semntico, est bien soportado y estandarizado y permite
asociar estructuras de rbol bien denidas para encapsular la informacin relevante.
Adems, posibilita que el contenido a importar sea legible por los propios programa-
dores.
Es importante resaltar que este captulo est centrado en la parte de importacin
de datos hacia el motor de juegos, por lo que el proceso de exportacin no se discutir
a continuacin (si se har, no obstante, en el mdulo 2 de Programacin Grca).

6.4.1. Formatos de intercambio


La necesidad de intercambiar informacin es una constante no slo entre los
distintos componentes de un motor de juegos, sino tambin entre entornos de creacin
de contenido 3D y el propio motor. El modelado y la animacin de personajes y la
creacin de escenarios se realiza mediante este tipo de suites de creacin 3D para,
posteriormente, integrarlos en el juego. Este planteamiento tiene como consecuencia
la necesidad de abordar un doble proceso (ver gura 6.28) para llevar a cabo la inter-
accin entre las partes implicadas:

Ogre Exporter
1. Exportacin, con el objetivo de obtener una representacin de los datos 3D.
2. Importacin, con el objetivo de acceder a dichos datos desde el motor de juegos
o desde el propio juego.

En el caso particular de Ogre3D, existen diversas herramientas9 que posibilitan


la exportacin de datos a partir de un determinado entorno de creacin de contenido
3D, como por ejemplo Blender, 3DS Max, Maya o Softimage, entre otros. El proceso
inverso tambin se puede ejecutar haciendo uso de herramientas como OgreXmlCon-
verter10 , que permiten la conversin entre archivos .mesh y .skeleton a cheros XML
y viceversa.
El proceso de importacin en Ogre3D est directamente relacionado con el for-
mato XML a travs de una serie de estructuras bien denidas para manejar escenas,
mallas o esqueletos, entre otros.
Aunque el uso del metalenguaje XML es una de las opciones ms extendidas, a
continuacin se discute brevemente el uso de otras alternativas.

Importer
Archivos binarios

El mismo planteamiento discutido que se usa para llevar a cabo el almacenamiento


de las variables de conguracin de un motor de juegos, utilizando un formato binario,
se puede aplicar para almacenar informacin multimedia con el objetivo de importarla
posteriormente.
Este esquema basado en un formato binario es muy eciente y permite hacer uso
de tcnicas de serializacin de objetos, que a su vez incrementan la portabilidad y la
sencillez de los formatos binarios. En el caso de hacer uso de orientacin a objetos, la
funcionalidad necesaria para serializar y de-serializar objetos se puede encapsular en
la propia denicin de clase.
Es importante destacar que la serializacin de objetos puede generar informacin
en formato XML, es decir, no tiene por qu estar ligada a un formato binario.
9 http://www.ogre3d.org/tikiwiki/OGRE+Exporters Figura 6.28: Procesos de importan-
10 http://www.ogre3d.org/tikiwiki/OgreXmlConverter cin y exportacin de datos 3D ha-
ciendo uso de documentos XML.
6.4. Importador de datos de intercambio [209]

Archivos en texto plano

El uso de archivos en texto plano es otra posible alternativa a la hora de importar


datos. Sin embargo, esta aproximacin tiene dos importantes desventajas: i) resulta
menos eciente que el uso de un formato binario e ii) implica la utilizacin de un
procesador de texto especco.

C6
En el caso de utilizar un formato no estandarizado, es necesario explicitar los
separadores o tags existentes entre los distintos campos del archivo en texto plano.
Por ejemplo, se podra pensar en separadores como los dos puntos, el punto y coma y
el retorno de carro para delimitar los campos de una determinada entidad y una entidad
de la siguiente, respectivamente.

XML

EXtensible Markup Language (XML) se suele denir como un metalenguaje, es


decir, como un lenguaje que permite la denicin de otros lenguajes. En el caso parti-
cular de este captulo sobre datos de intercambio, XML se podra utilizar para denir
un lenguaje propio que facilite la importacin de datos 3D al motor de juegos o a un
juego en particular.
Actualmente, XML es un formato muy popular debido a los siguientes motivos:
SGML
Es un estndar.
Est muy bien soportado, tanto a nivel de programacin como a nivel de usuario.
HTML XML Es legible.
Tiene un soporte excelente para estructuras de datos jerrquicas; en concreto, de
tipo arbreas. Esta propiedad es especialmente relevante en el dominio de los
XHTML videojuegos.

Figura 6.29: Visin conceptual de Sin embargo, no todo son ventajas. El proceso de parseado o parsing es rela-
la relacin de XML con otros len-
guajes.
tivamente lento. Esto implica que algunos motores hagan uso de formatos binarios
propietarios, los cuales son ms rpidos de parsear y mucho ms compactos que los
archivos XML, reduciendo as los tiempos de importacin y de carga.
El parser Xerces-C++
Como se ha comentado anteriormente, una de los motivos por los que XML est
tan extendido es su amplio soporte a nivel de programacin. En otras palabras, prc-
ticamente la mayora de lenguajes de programacin proporcionan bibliotecas, APIs y
herramientas para procesar y generar contenidos en formato XML.
En el caso del lenguaje C++, estndar de facto en la industria del videojuego, el
parser Xerces-C++11 es una de las alternativas ms completas para llevar a cabo el
procesamiento de cheros XML. En concreto, esta herramienta forma parte del pro-
Figura 6.30: Logo principal del yecto Apache XML12 , el cual gestiona un nmero relevante de subproyectos vincula-
proyecto Apache. dos al estndar XML.
11 http://xerces.apache.org/xerces-c/
12 http://xml.apache.org/
[210] CAPTULO 6. GESTIN DE RECURSOS

Xerces-C++ est escrito en un subconjunto portable de C++ y tiene como obje-


tivo facilitar la lectura y escritura de archivos XML. Desde un punto de vista tcni-
co, Xerces-C++ proporcionar una biblioteca con funcionalidad para parsear, generar,
manipular y validar documentos XML utilizando las APIs DOM, SAX (Simple API
for XML) y SAX2. Estas APIs son las ms populares para manipular documentos
XML y dieren, sin competir entre s, en aspectos como el origen, alcance o estilo de
programacin.
Por ejemplo, mientras DOM (Document Object Model) est siendo desarrollada
por el consorcio W3, SAX (Simple API for XML) ) no lo est. Sin embargo, la mayor
diferencia reside en el modelo de programacin, ya que SAX presenta el documento
XML como una cadena de eventos serializada, mientras que DOM lo trata a travs
de una estructura de rbol. La principal desventaja del enfoque planteado en SAX es
que no permite un acceso aleatorio a los elementos del documento. No obstante, este
enfoque posibilita que el desarrollador no se preocupe de informacin irrelevante,
reduciendo as el tamao en memoria necesario por el programa.

Desde un punto de vista general, se recomienda el uso de DOM en proyectos


en los que se vayan a integrar distintos componentes de cdigo y las necesi-
dades funcionales sean ms exigentes. Por el contrario, SAX podra ser ms
adecuado para ujos de trabajo ms acotados y denidos.

La instalacin de Xerces-C++, incluyendo documentacin y ejemplos de referen-


cia, en sistemas Debian y derivados es trivial mediante los siguientes comandos:

$ sudo apt-get update


$ sudo apt-get install libxerces-c-dev
$ sudo apt-get install libxerces-c-doc
$ sudo apt-get install libxerces-samples

6.4.2. Creacin de un importador


Justicacin y estructura XML Figura 6.31: La instalacin de pa-
quetes en sistemas Debian tambin
se puede realizar a travs de ges-
En esta seccin se discute el diseo e implementacin de un importador especco tores de ms alto nivel, como por
para un juego muy sencillo titulado NoEscapeDemo, el cual har uso de la biblioteca ejemplo Synaptic.
Xerces-C++ para llevar a cabo la manipulacin de documentos XML.
Bsicamente, el juego consiste en interactuar con una serie de wumpus o fantas-
mas que aparecen de manera automtica en determinados puntos de un escenario y
que desaparecen en otros. La interaccin por parte del usuario consiste en modicar
el comportamiento de dichos fantasmas cambiando, por ejemplo, el sentido de nave-
gacin de los mismos.
La gura 6.32 muestra, desde un punto de vista abstracto, la representacin del
escenario en el que los fantasmas habitan. Como se puede apreciar, la representacin
interna de dicho escenario es un grafo, el cual dene la navegabilidad de un punto
en el espacio 3D a otro. En el grafo, algunos de los nodos estn etiquetados con los
identicadores S o D, representando puntos de aparicin y desaparicin de fantasmas.
El contenido grco de esta sencilla demo est compuesto de los siguientes ele-
mentos:
6.4. Importador de datos de intercambio [211]

C6
S

Figura 6.32: Representacin interna bidimensional en forma de grafo del escenario de NoEscapeDemo.
Los nodos de tipo S (spawn) representan puntos de nacimiento, mientras que los nodos de tipo D (drain)
representan sumideros, es decir, puntos en los que los fantasmas desaparecen.

El propio escenario tridimensional, considerando los puntos o nodos de gene-


racin y destruccin de fantasmas.
Los propios fantasmas.
Una o ms cmaras virtuales, que servirn para visualizar el juego. Cada c-
mara tendr asociado un camino o path, compuesto a su vez por una serie de
puntos clave que indican la situacin (posicin y rotacin) de la cmara en cada
momento.

El nodo raz En principio, la informacin del escenario y de las cmaras virtuales ser impor-
La etiqueta <data> es el nodo raz tada al juego, haciendo uso del importador cuyo diseo se discute a continuacin. Sin
del documento XML. Sus posibles embargo, antes se muestra un posible ejemplo de la representacin de estos mediante
nodos hijo estn asociados con las el formato denido para la demo en cuestin.
etiquetas <graph> y <camera>.
En concreto, el siguiente listado muestra la parte de informacin asociada a la
estructura de grafo que conforma el escenario. Como se puede apreciar, la estructura
graph est compuesta por una serie de vrtices (vertex) y de arcos (edge).
Por una parte, en los vrtices del grafo se incluye su posicin en el espacio 3D
mediante las etiquetas <x>, <y> y <z>. Adems, cada vrtice tiene como atributo
un ndice, que lo identica unvocamente, y un tipo, el cual puede ser spawn (punto
de generacin) o drain (punto de desaparicin), respectivamente. Por otra parte, los
arcos permiten denir la estructura concreta del grafo a importar mediante la etiqueta
<vertex>.
Vrtices y arcos En caso de que sea necesario incluir ms contenido, simplemente habr que ex-
tender el formato denido. XML facilita enormemente la escalabilidad gracias a su
En el formato denido, los vrtices
se identican a travs del atributo esquema basado en el uso de etiquetas y a su estructura jerrquica.
index. Estos IDs se utilizan en la eti-
queta <edge> para especicar los
dos vrtices que conforman un ar-
co.
[212] CAPTULO 6. GESTIN DE RECURSOS

Listado 6.30: Ejemplo de chero XML usado para exportar e importar contenido asociado al
grafo del escenario.
1 <?xml version=1.0 encoding=UTF-8?>
2 <data>
3
4 <graph>
5
6 <vertex index="1" type="spawn">
7 <x>1.5</x> <y>2.5</y> <z>-3</z>
8 </vertex>
9 <!-- More vertexes... -->
10 <vertex index="4" type="drain">
11 <x>1.5</x> <y>2.5</y> <z>-3</z>
12 </vertex>
13
14 <edge>
15 <vertex>1</vertex> <vertex>2</vertex>
16 </edge>
17 <edge>
18 <vertex>2</vertex> <vertex>4</vertex>
19 </edge>
20 <!-- More edges... -->
21
22 </graph>
23
24 <!-- Definition of virtual cameras -->
25 </data>

El siguiente listado muestra la otra parte de la estructura del documento XML


utilizado para importar contenido. ste contiene la informacin relativa a las cmaras
virtuales. Como se puede apreciar, cada cmara tiene asociado un camino, denido
con la etiqueta <path>, el cual consiste en una serie de puntos o frames clave que
determinan la animacin de la cmara.
Cada punto clave del camino de una cmara contiene la posicin de la misma
en el espacio 3D (etiqueta <frame>) y la rotacin asociada, expresada mediante un
cuaternin (etiqueta <rotation>).

Lgica de dominio

La gura 6.33 muestra el diagrama de las principales clases vinculadas al impor-


tador de datos. Como se puede apreciar, las entidades ms relevantes que forman parte
del documento XML se han modelado como clases con el objetivo de facilitar no s-
lo la obtencin de los datos sino tambin su integracin en el despliegue nal con
Ogre3D.
La clase que centraliza la lgica de dominio es la clase Scene, la cual mantiene La clase Scene
una relacin de asociacin con las clases Graph y Camera. Recuerde que el contenido La encapsulacin de los datos im-
a importar ms relevante sobre el escenario era la estructura de grafo del mismo y la portados de un documento XML se
informacin de las cmaras virtuales. El listado de cdigo 6.32 muestra la declaracin centraliza en la clase Scene, la cual
de la clase Scene. mantiene como estado un puntero a
un objeto de tipo Graph y una es-
tructura con punteros a objetos de
Listado 6.32: Clase Scene. tipo Camera.

1 #include <vector>
2 #include <Camera.h>
6.4. Importador de datos de intercambio [213]

Listado 6.31: Ejemplo de chero XML usado para exportar e importar contenido asociado a
las cmaras virtuales.
1 <?xml version=1.0 encoding=UTF-8?>
2 <data>
3 <!-- Graph definition here-->

C6
4
5 <camera index="1" fps="25">
6
7 <path>
8
9 <frame index="1">
10 <position>
11 <x>1.5</x> <y>2.5</y> <z>-3</z>
12 </position>
13 <rotation>
14 <x>0.17</x> <y>0.33</y> <z>0.33</z> <w>0.92</w>
15 </rotation>
16 </frame>
17
18 <frame index="2">
19 <position>
20 <x>2.5</x> <y>2.5</y> <z>-3</z>
21 </position>
22 <rotation>
23 <x>0.17</x> <y>0.33</y> <z>0.33</z> <w>0.92</w>
24 </rotation>
25 </frame>
26
27 <!-- More frames here... -->
28
29 </path>
30
31 </camera>
32
33 <!-- More cameras here... -->
34 </data>

3 #include <Node.h>
4 #include <Graph.h>
5
6 class Scene
7 {
8 public:
9 Scene ();
10 ~Scene ();
11
12 void addCamera (Camera* camera);
13 Graph* getGraph () { return _graph;}
14 std::vector<Camera*> getCameras () { return _cameras; }
15
16 private:
17 Graph *_graph;
18 std::vector<Camera*> _cameras;
19 };

Por otra parte, la clase Graph mantiene la lgica de gestin bsica para imple-
mentar una estructura de tipo grafo mediante listas de adyacencia. El siguiente listado
de cdigo muestra dicha clase e integra una funcin miembro para obtener la lista
de
vrtices o nodos adyacentes a partir del identicador de uno de ellos (lnea 17 ).
[214] CAPTULO 6. GESTIN DE RECURSOS

1 1
Scene Importer Ogre::Singleton
1
1

1..* 1
1 1..*
Camera Graph GraphVertex

1 2 1
1

1..* 0..* 0..* 1

Frame GraphEdge Node

Figura 6.33: Diagrama de clases de las entidades ms relevantes del importador de datos.

Las clases GraphVertex y GraphEdge no se mostrarn ya que son relativamente


triviales. Sin embargo, resulta interesante resaltar la clase Node, la cual contiene infor-
macin relevante sobre cada uno de los vrtices del grafo. Esta clase permite generar
instancias de los distintos tipos de nodos, es decir, nodos que permiten la generacin
y destruccin de los fantasmas de la demo planteada.

Listado 6.33: Clase Graph.


1 #include <iostream>
2 #include <vector>
3 #include <GraphVertex.h>
4 #include <GraphEdge.h>
5
6 class Graph
7 {
8 public:
9 Graph ();
10 ~Graph ();
11
12 void addVertex (GraphVertex* pVertex);
13 void addEdge (GraphVertex* pOrigin, GraphVertex* pDestination,
14 bool undirected = true);
15
16 // Lista de vrtices adyacentes a uno dado.
17 std::vector<GraphVertex*> adjacents (int index);
18
19 GraphVertex* getVertex (int index);
20 std::vector<GraphVertex*> getVertexes () const
21 {return _vertexes;}
22 std::vector<GraphEdge*> getEdges () const { return _edges; }
23
24 private:
25 std::vector<GraphVertex*> _vertexes;
6.4. Importador de datos de intercambio [215]

26 std::vector<GraphEdge*> _edges;
27 };

Listado 6.34: Clase Node.


1 // SE OMITE PARTE DEL CDIGO FUENTE.

C6
2 class Node
3 {
4 public:
5 Node ();
6 Node (const int& index, const string& type,
7 const Ogre::Vector3& pos);
8 ~Node ();
9
10 int getIndex () const { return _index; }
11 string getType () const { return _type; }
12 Ogre::Vector3 getPosition () const { return _position; }
13
14 private:
15 int _index; // ndice del nodo (id nico)
16 string _type; // Tipo: generador (spawn), sumidero
(drain)
17 Ogre::Vector3 _position; // Posicin del nodo en el espacio 3D
18 };

GraphVertex y Node Respecto al diseo de las cmaras virtuales, las clases Camera y Frame son las
utilizadas para encapsular la informacin y funcionalidad de las mismas. En esencia,
La clase GraphVertex mantiene co- una cmara consiste en un identicador, un atributo que determina la tasa de frames
mo variable de clase un objeto de ti-
po Node, el cual alberga la informa- por segundo a la que se mueve y una secuencia de puntos clave que conforman el
cin de un nodo en el espacio 3D. camino asociado a la cmara.

La clase Importer

API DOM y memoria El punto de interaccin entre los datos contenidos en el documento XML y la
lgica de dominio previamente discutida est representado por la clase Importer. Esta
El rbol generado por el API DOM clase proporciona la funcionalidad necesaria para parsear documentos XML con la
a la hora de parsear un documento
XML puede ser costoso en memo-
estructura planteada anteriormente y rellenar las estructuras de datos diseadas en la
ria. En ese caso, se podra plantear anterior seccin.
otras opciones, como por ejemplo el
uso del API SAX. El siguiente listado de cdigo muestra la declaracin de la clase Importer. Como se
puede apreciar, dicha clase hereda de Ogre::Singleton para garantizar que solamente
existe una instancia de dicha clase, accesible con las funciones miembro getSingleton()
y getSingletonPtr()13 .
Note cmo, adems de estas dos funciones miembro, la nica funcin miembro
pblica es parseScene() (lnea 9 ), la cual se puede utilizar para parsear un docu-
mento XML cuya ruta se especica en el primer parmetro. El efecto de realizar una
llamada a esta funcin tendr como resultado el segundo parmetro de la misma, de ti-
po puntero a objeto de clase Scene, con la informacin obtenida a partir del documento
XML (siempre y cuando no se produzca ningn error).

Listado 6.35: Clase Importer.


1 #include <OGRE/Ogre.h>
2 #include <xercesc/dom/DOM.hpp>
3 #include <Scene.h>
4
5 class Importer: public Ogre::Singleton<Importer> {
13 Se podra haber desligado la implementacin del patrn Singleton y Ogre3D.
[216] CAPTULO 6. GESTIN DE RECURSOS

6 public:
7 // nica funcin miembro pblica para parsear.
8 void parseScene (const char* path, Scene *scn);
9
10 static Importer& getSingleton (); // Ogre::Singleton.
11 static Importer* getSingletonPtr (); // Ogre::Singleton.
12
13 private:
14 // Funcionalidad oculta al exterior.
15 // Facilita el parseo de las diversas estructuras
16 // del documento XML.
17 void parseCamera (xercesc::DOMNode* cameraNode, Scene* scn);
18
19 void addPathToCamera (xercesc::DOMNode* pathNode, Camera *cam);
20 void getFramePosition (xercesc::DOMNode* node,
21 Ogre::Vector3* position);
22 void getFrameRotation (xercesc::DOMNode* node,
23 Ogre::Vector4* rotation);
24
25 void parseGraph (xercesc::DOMNode* graphNode, Scene* scn);
26 void addVertexToScene (xercesc::DOMNode* vertexNode, Scene* scn);
27 void addEdgeToScene (xercesc::DOMNode* edgeNode, Scene* scn);
28
29 // Funcin auxiliar para recuperar valores en punto flotante
30 // asociados a una determinada etiqueta (tag).
31 float getValueFromTag (xercesc::DOMNode* node, const XMLCh *tag);
32 };

El cdigo del importador se ha estructurado de acuerdo a la denicin del propio


documento XML, es decir, teniendo en cuenta las etiquetas ms relevantes dentro del
mismo. As, dos de las funciones miembro privadas relevantes son, por ejemplo, las
funciones parseCamera() y parseGraph(), usadas para procesar la informacin de una
cmara virtual y del grafo que determina el escenario de la demo.
La biblioteca Xerces-C++ hace uso de tipos de datos especcos para, por ejemplo, Estructurando cdigo...
manejar cadenas de texto o los distintos nodos del rbol cuando se hace uso del API
DOM. De hecho, el API utilizada en este importador es DOM. El siguiente listado de Una buena programacin estructura
es esencial para facilitar el mante-
cdigo muestra cmo se ha realizado el procesamiento inicial del rbol asociado al nimiento del cdigo y balancear de
documento XML. manera adecuada la complejidad de
las distintas funciones que forman
Aunque se ha omitido gran parte del cdigo, incluido el tratamiento de errores, parte del mismo.
el esquema de procesamiento representa muy bien la forma de manejar este tipo de
documentos haciendo uso del API DOM. Bsicamente, la idea general consiste en
obtener una referencia a la raz del documento (lneas 13-14 ) y, a partir de ah, ir

recuperando la informacin contenida en cada uno de los nodos del mismo.
El bucle for (lneas 20-21 ) permite recorrer los nodos hijo del nodo raz. Si se
detecta un nodo con la etiqueta <camera>, entonces se llama a la funcin miembro
parseCamera(). Si por el contrario se encuentra la etiqueta <graph>, entonces se
llama a la funcin parseGraph(). En ambos casos, estas funciones irn poblando de
contenido el puntero a objeto de tipo Scene que se pasa como segundo argumento.

Listado 6.36: Clase Importer. Funcin parseScene


1 // SE OMITE PARTE DEL CDIGO FUENTE (bloques try-catch)
2 void Importer::parseScene (const char* path, Scene *scene) {
3 // Inicializacin.
4 XMLPlatformUtils::Initialize();
5
6 XercesDOMParser* parser = new XercesDOMParser();
7 parser->parse(path);
8
9 DOMDocument* xmlDoc;
10 DOMElement* elementRoot;
11
6.4. Importador de datos de intercambio [217]

12 // Obtener el elemento raz del documento.


13 xmlDoc = parser->getDocument();
14 elementRoot = xmlDoc->getDocumentElement();
15
16 XMLCh* camera_ch = XMLString::transcode("camera");
17 XMLCh* graph_ch = XMLString::transcode("graph");
18
19 // Procesando los nodos hijos del raz...

C6
20 for (XMLSize_t i = 0;
21 i < elementRoot->getChildNodes()->getLength(); ++i ) {
22 DOMNode* node = elementRoot->getChildNodes()->item(i);
23
24 if (node->getNodeType() == DOMNode::ELEMENT_NODE) {
25 // Nodo <camera>?
26 if (XMLString::equals(node->getNodeName(), camera_ch))
27 parseCamera(node, scene);
28 else
29 // Nodo <graph>?
30 if (XMLString::equals(node->getNodeName(), graph_ch))
31 parseGraph(node, scene);
32 }
33 }// Fin for
34 // Liberar recursos.
35 }
Bajo Nivel y Concurrencia
Captulo 7
David Vallejo Fernndez

E
ste captulo realiza un recorrido por los sistemas de soporte de bajo nivel ne-
cesarios para efectuar diversas tareas crticas para cualquier motor de juegos.
Algunos ejemplos representativos de dichos subsistemas estn vinculados al
arranque y la parada del motor, su conguracin y a la gestin de cuestiones de ms
bajo nivel.
El objetivo principal del presente captulo consiste en profundizar en dichas tareas
y en proporcionar al lector una visin ms detallada de los subsistemas bsicos sobre
los que se apoyan el resto de elementos del motor de juegos.
From the ground up! En concreto, en este captulo se discutirn los siguientes subsistemas:
Los subsistemas de bajo nivel del Subsistema de arranque y parada.
motor de juegos resultan esenciales
para la adecuada integracin de ele- Subsistema de gestin de contenedores.
mentos de ms alto nivel. Algunos
de ellos son simples, pero la funcio- Subsistema de gestin de cadenas.
nalidad que proporcionan es crtica
para el correcto funcionamiento de
otros subsistemas. El subsistema de gestin relativo a la gestin de contenedores de datos se discuti
en el captulo 5. En este captulo se llev a cabo un estudio de la biblioteca STL y
se plantearon unos criterios bsicos para la utilizacin de contenedores de datos en
el mbito del desarrollo de videojuegos con C++. Sin embargo, este captulo dedica
una breve seccin a algunos aspectos relacionados con los contenedores que no se
discutieron anteriormente.
Por otra parte, el subsistema de gestin de memoria se estudiar en el mdulo 3,
titulado Tcnicas Avanzadas de Desarrollo, del presente curso de desarrollo de video-
juegos. Respecto a esta cuestin particular, se har especial hincapi en las tcnicas y
las posibilidades que ofrece C++ en relacin a la adecuada gestin de la memoria del
sistema.

219
[220] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

As mismo, en este captulo se aborda la problemtica relativa a la gestin de la


concurrencia mediante un esquema basado en el uso de hilos y de mecanismos tpicos
de sincronizacin. CPU CPU
En los ltimos aos, el desarrollo de los procesadores, tanto de ordenadores per-
sonales, consolas de sobremesa e incluso telfonos mviles, ha estado marcado por L1 Cach L1 Cach
un modelo basado en la integracin de varios ncleos fsicos de ejecucin. El objeti-
vo principal de este diseo es la paralelizacin de las tareas a ejecutar por parte del
procesador, permitiendo as incrementar el rendimiento global del sistema a travs del
paralelismo a nivel de ejecucin.
L2 Cach
Este esquema, unido al concepto de hilo como unidad bsica de ejecucin de la
CPU, ha permitido que aplicaciones tan exigentes como los videojuegos se pueden
aprovechar de esta sustancial mejora de rendimiento. Desafortunadamente, no todo
son buenas noticias. Este modelo de desarrollo basado en la ejecucin concurrente
de mltiples hilos de control tiene como consecuencia directa el incremento de la
complejidad a la hora de desarrollar dichas aplicaciones. L1 Cach L1 Cach
Adems de plantear soluciones que sean paralelizables y escalables respecto al CPU CPU
nmero de unidades de procesamiento, un aspecto crtico a considerar es el acceso
concurrente a los datos. Por ejemplo, si dos hilos de ejecucin distintos comparten
un fragmento de datos sobre el que leen y escriben indistintamente, entonces el progra- Figura 7.1: Esquema general de
una CPU con varios procesadores.
mador ha de integrar soluciones que garanticen la consistencia de los datos, es decir,
soluciones que eviten situaciones en las que un hilo est escribiendo sobre dichos
datos y otro est accediendo a los mismos. First things rst!

Con el objetivo de abordar esta problemtica desde un punto de vista prctico en El arranque y la parada de subsiste-
mas representa una tarea bsica y, al
el mbito del desarrollo de videojuegos, en este captulo se plantean distintos meca- mismo tiempo, esencial para la co-
nismos de sincronizacin de hilos haciendo uso de los mecanismos nativos ofrecidos rrecta gestin de los distintos com-
en C++11 y, por otra parte, de la biblioteca de hilos de ZeroC I CE, un middleware de ponentes de la arquitectura de un
comunicaciones que proporciona una biblioteca de hilos que abstrae al desarrollador motor de juegos.
de la plataforma y el sistema operativo subyacentes. Subsistema S

7.1. Subsistema de arranque y parada Subsistema T

7.1.1. Aspectos fundamentales


Subsistema U
El subsistema de arranque y parada es el responsable de llevar a cabo la iniciali-
zacin y conguracin de los distintos subsistemas que forman parte del motor de
juegos, as como de realizar la parada de los mismos cuando as sea necesario. Este Subsistema U
sistema forma parte de la capa de subsistemas principales que se introdujo brevemente Orden
en la seccin 1.2.4. La gura 7.2 muestra la interaccin del subsistema de arranque y de
Subsistema T
arranque
parada con el resto de componentes de la arquitectura general del motor de juegos.
Subsistema S
Este subsistema juega un papel bsico pero fundamental dentro de la arquitec-
tura del motor de juegos, ya que disponer de una entidad software que conozca las
interdependencias entre el resto de subsistemas es crucial para efectuar su arranque,
conguracin y parada de una manera adecuada. Desde un punto de vista general, si Subsistema S
un subsistema S tiene una dependencia con respecto a un subsistema T , entonces el Orden
subsistema de arranque ha de tener en cuenta dicha dependencia para arrancar primero de
Subsistema T
parada
T y, a continuacin, S. As mismo, la parada de dichos subsistemas se suele realizar
generalmente en orden inverso, es decir, primero se parara S y, posteriormente, T Subsistema U
(ver gura 7.3).
Figura 7.3: Esquema grco de or-
den de arranque y de parada de tres
subsistemas dependientes entre s.
7.1. Subsistema de arranque y parada [221]

Subsistemas especficos de juego

Sistema de Manejo de
Motor de rendering, motor de fsica,
arranque y parada cadenas
herramientas de desarrollo, networking,
Gestin de Gestin de audio, subsistema de juego...
memoria archivos
Gestor de recursos
Biblioteca

C7
...
matemtica

Subsistemas principales

Capa independiente de la plataforma

SDKs y middlewares

Figura 7.2: Interaccin del subsistema de arranque y parada con el resto de subsistemas de la arquitectura
general del motor de juegos [42].

En el mbito del desarrollo de videojuegos, el subsistema de arranque y parada


se suele implementar haciendo uso del patrn singleton, discutido en la seccin 4.2.1,
con el objetivo de manejar una nica instancia de dicho subsistema que represente el
nico punto de gestin y evitando la reserva dinmica de memoria. Este planteamien-
to se extiende a otros subsistemas relevantes que forman parte del motor de juegos.
Comnmente, este tipo de subsistemas tambin se suelen denominar gestores o ma-
nagers. La implementacin tpica de este tipo de elementos se muestra en el siguiente
listado de cdigo.

Listado 7.1: Esqueleto bsico del subsistema de gestin de memoria.


1 class MemoryManager {
2 public:
3 MemoryManager () {
4 // Inicializacin gestor de memoria...
5 }
6 ~MemoryManager () {
7 // Parada gestor de memoria...
8 }
9
10 // ...
11 };
12
13 // Instancia nica.
Variables globales 14 static MemoryManager gMemoryManager;

Recuerde que idealmente las varia-


bles globales se deberan limitar en
la medida de lo posible con el obje-
tivo de evitar efectos colaterales.
[222] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Debido a que C++ es el lenguaje estndar para el desarrollo de videojuegos moder-


nos, una posible alternativa para gestionar de manera correcta tanto el arranque como
la parada de subsistemas podra consistir en hacer uso de los mecanismos nativos de
C++ de construccin y destruccin de elementos.
Desde un punto de vista general, los objetos globales y estticos se instancian de
manera previa a la ejecucin de la funcin main. Del mismo modo, dichos objetos se
destruyen de manera posterior a la ejecucin de dicha funcin, es decir, inmediata-
mente despus de su retorno. Sin embargo, tanto la construccin como la destruccin
de dichos objetos se realizan en un orden impredecible.
La consecuencia directa del enfoque nativo proporcionado por C++ para la cons-
truccin y destruccin de objetos es que no se puede utilizar como solucin directa
para arrancar y parar los subsistema del motor de juegos. La razn se debe a que
no es posible establecer un orden que considere las posibles interdependencias entre
subsistemas.

Listado 7.2: Subsistema de gestin de memoria con acceso a instancia nica.


1 class MemoryManager {
2 public:
3 static MemoryManager& get () {
4 static MemoryManager gMemoryManager;
5 return gMemoryManager;
6 }
7 MemoryManager () {
8 // Arranque de otros subsistemas dependientes...
9 SubsistemaX::get();
10 SubsistemaY::get();
11
12 // Inicializacin gestor de memoria...
13 }
14 ~MemoryManager () {
15 // Parada gestor de memoria...
16 }
17 // ...
18 };

Una solucin directa a esta problemtica consiste en declarar la variable esttica


dentro de una funcin, con el objetivo de obtenerla cuando as sea necesario. De este
modo, dicha instancia no se instanciar antes de la funcin main, sino que lo har
cuando se efecte la primera llamada a la funcin implementada. El anterior listado
de cdigo muestra una posible implementacin de esta solucin.
Una posible variante de este diseo consiste en reservar memoria de manera di- Variables no locales
nmica para la instancia nica del gestor en cuestin, tal y como se muestra en el La inicializacin de variables estti-
siguiente listado de cdigo. cas no locales se controla mediante
el mecanismo que utilice la imple-
mentacin para arrancar un progra-
Listado 7.3: Subsistema de gestin de memoria. Reserva dinmica. ma en C++.
1 static MemoryManager& get () {
2 static MemoryManager *gMemoryManager = NULL;
3
4 if (gMemoryManager == NULL)
5 gMemoryManager = new MemoryManager();
6
7 assert(gMemoryManager);
8 return *gMemoryManager;
9 }
7.1. Subsistema de arranque y parada [223]

No obstante, aunque existe cierto control sobre el arranque de los subsistemas de-
pendientes de uno en concreto, esta alternativa no proporciona control sobre la parada
de los mismos. As, es posible que C++ destruya uno de los subsistemas dependientes
del gestor de memoria de manera previa a la destruccin de dicho subsistema.
Adems, este enfoque presenta el inconveniente asociado al momento de la cons-
truccin de la instancia global del subsistema de memoria. Evidentemente, la construc-
cin se efectuar a partir de la primera llamada a la funcin get, pero es complicado
controlar cundo se efectuar dicha llamada.

C7
Por otra parte, no es correcto realizar suposiciones sobre la complejidad vinculada
a la obtencin del singleton, ya que los distintos subsistemas tendrn necesidades muy
distintas entre s a la hora de inicializar su estado, considerando las interdependencias
con otros subsistemas. En general, este diseo puede ser problemtico y es necesa-
rio proporcionar un esquema sobre el que se pueda ejercer un mayor control y que
simplique los procesos de arranque y parada.

MemoryManager 7.1.2. Esquema tpico de arranque y parada


En esta subseccin se estudia un enfoque simple [42], aunque ampliamente utili-
zado, para gestionar tanto el arranque como la parada de los distintos subsistemas que
1 t () forman la arquitectura de un motor de juegos. La idea principal de este enfoque reside
en explicitar el arranque y la parada de dichos subsistemas, que a su vez se gestionan
mediante managers implementados de acuerdo al patrn singleton.
Funcin main () Para ello, tanto el arranque como la parada de un subsistema se implementa me-
diante funciones explcitas de arranque y parada, tpicamente denominadas startUp
y shutDown, respectivamente. En esencia, estas funciones representan al constructor
y al destructor de la clase. Sin embargo, la posibilidad de realizar llamadas permite
2 controlar de manera adecuada la inicializacin y parada de un subsistema.
startUp ()
El siguiente listado de cdigo muestra la implementacin tpica de un subsistema
de gestin haciendo uso del enfoque planteado en esta subseccin. Observe cmo tanto
el constructor y el destructor de clase estn vacos, ya que se delega la inicializacin
Funcin main () del subsistema en las funciones startUp() y shutDown().

Listado 7.4: Implementacin de funciones startUp() y shutDown().


Figura 7.4: Esquema grco de ob-
tencin de un singleton y su poste- 1 class MemoryManager {
rior utilizacin. 2 public:
3 MemoryManager () {}
4 ~MemoryManager () {}
Interdependencias 5
6 void startUp () {
El orden del arranque, inicializa- 7 // Inicializacin gestor de memoria...
cin y parada de subsistemas de- 8 }
pendern de las relaciones entre los 9 void shutDown () {
mismos. Por ejemplo, el subsiste- 10 // Parada gestor de memoria...
ma de gestin de memoria ser t- 11 }
picamente uno de los primeros en 12 };
arrancarse, debido a que gran parte 13
del resto de subsistemas tienen una 14 class RenderManager { /* IDEM */ };
dependencia directa respecto al pri- 15 class TextureManager { /* IDEM */ };
mero. 16 class AnimationManager { /* IDEM */ };
17 // ...
18
19 MemoryManager gMemoryManager;
20 RenderManager gRenderManager;
21 TextureManager gTextureManager;
22 AnimationManager gAnimationManager;
23 // ...
[224] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Recuerde que la simplicidad suele ser deseable en la mayora de los casos,


aunque el rendimiento o el propio diseo de un programa se vean degradados.
Este enfoque facilita el mantenimiento del cdigo. El subsistema de arranque
y parada es un caso tpico en el mbito de desarrollo de videojuegos.

Mediante este enfoque, la inicializacin y parada de subsistemas es trivial y permi-


te un mayor control sobre dichas tareas. En el siguiente listado de cdigo se muestran
de manera explcita las dependencias entre algunos de los subsistemas tpicos de la
arquitectura de motor, como por ejemplo los subsistemas de gestin de memoria, ma-
nejo de texturas, renderizado o animacin.

Listado 7.5: Arranque y parada de subsistemas tpicos.


1 int main () {
2 // Arranque de subsistemas en orden.
3 gMemoryManager.startUp();
4 gTextureManager.startUp();
5 gRenderManager.startUp();
6 gAnimationManager.startUp();
7 // ...
8
9 // Bucle principal.
10 gSimulationManager.run();
11
12 // Parada de subsistemas en orden.
13 // ...
14 gAnimationManager.startUp();
15 gRenderManager.startUp();
16 gTextureManager.startUp();
17 gMemoryManager.startUp();
18
19 return 0;
20 }

7.1.3. Caso de estudio. Ogre 3D


Figura 7.5: Aunque Ogre no es un
Aunque Ogre 3D es en realidad un motor de renderizado en lugar de un com- motor de juegos propiamente di-
pleto motor de juegos, dicho entorno proporciona una gran cantidad de subsistemas cho, su complejidad hace necesaria
relevantes para facilitar el desarrollo de videojuegos. Entre ellos, tambin existe un la integracin de una gran cantidad
subsistema de arranque y parada que hace gala de una gran simplicidad y sencillez. de subsistemas. Dichos subsistemas
han de permitir la adecuada gestin
Bsicamente, el arranque y la parada en Ogre se realiza a travs del uso de la clase de su arranque y su parada.
Ogre::Root, la cual implementa el patrn singleton con el objetivo de asegurar una
nica instancia de dicha clase, la cual acta como punto central de gestin.
Ogre::Singleton
Para ello, la clase Root almacena punteros, como variables miembro privadas, a
La clase Ogre::Singleton est basa-
todos los subsistemas soportados por Ogre con el objetivo de gestionar su creacin y da en el uso de plantillas para poder
su destruccin. El siguiente listado de cdigo muestra algunos aspectos relevantes de instanciarla para tipos particulares.
la declaracin de esta clase. Dicha clase proporciona las tpicas
funciones getSingleton() y getSin-
gletonPtr para acceder a la referen-
cia y al puntero, respectivamente,
de la nica instancia de clase crea-
da.
7.1. Subsistema de arranque y parada [225]

Ogre::Singleton<Root> RootAlloc

C7
Ogre::Root

Figura 7.6: Clase Ogre::Root y su relacin con las clases Ogre::Singleton y RootAlloc.

Como se puede apreciar, la clase Root hereda de la clase Ogre::Singleton, que a


su vez hace uso de plantillas y que, en este caso, se utiliza para especicar el singleton
asociado a la clase Root. As mismo, dicha clase hereda de RootAlloc, que se utiliza
como superclase para todos los objetos que deseen utilizar un asignador de memoria
personalizado.

Listado 7.6: Clase Ogre::Root.


1 class _OgreExport Root : public Singleton<Root>, public RootAlloc
2 {
3 protected:
4 // Ms declaraciones...
5 // Gestores (implementan el patrn Singleton).
6 LogManager* mLogManager;
7 ControllerManager* mControllerManager;
8 SceneManagerEnumerator* mSceneManagerEnum;
9 DynLibManager* mDynLibManager;
10 ArchiveManager* mArchiveManager;
11 MaterialManager* mMaterialManager;
12 MeshManager* mMeshManager;
13 ParticleSystemManager* mParticleManager;
14 SkeletonManager* mSkeletonManager;
15 OverlayElementFactory* mPanelFactory;
16 OverlayElementFactory* mBorderPanelFactory;
17 OverlayElementFactory* mTextAreaFactory;
18 OverlayManager* mOverlayManager;
19 FontManager* mFontManager;
20 ArchiveFactory *mZipArchiveFactory;
21 ArchiveFactory *mFileSystemArchiveFactory;
22 ResourceGroupManager* mResourceGroupManager;
23 ResourceBackgroundQueue* mResourceBackgroundQueue;
24 ShadowTextureManager* mShadowTextureManager;
25 RenderSystemCapabilitiesManager*
mRenderSystemCapabilitiesManager;
26 ScriptCompilerManager *mCompilerManager;
27 // Ms declaraciones...
28 };
[226] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

El objeto Root representa el punto de entrada de Ogre y ha de ser el primer


objeto instanciado en una aplicacin y el ltimo en ser destruido. Tpicamente, la
clase principal de los ejemplos bsicos desarrollados tendrn como variable miembro
una instancia de la clase Root, con el objetivo de facilitar la administracin del juego
en cuestin.
Desde el punto de vista del renderizado, el objeto de tipo Root proporciona la Start your engine!
funcin startRendering. Cuando se realiza una llamada a dicha funcin, la aplicacin
Aunque el arranque y la parada
entrar en un bucle de renderizado continuo que nalizar cuando todas las ventanas de los motores de juegos actua-
grcas se hayan cerrado o cuando todos los objetos del tipo FrameListener nalicen les suelen estar centralizados me-
su ejecucin (ver mdulo 2, Programacin Grca). diante la gestin de algn elemento
central, como por ejemplo la clase
La implementacin del constructor de la clase Ogre::Root tiene como objetivo Ogre::Root, es responsabilidad del
principal instanciar y arrancar los distintos subsistemas previamente declarados en el desarrollador conocer los elemen-
tos accesibles desde dicha entidad
anterior listado de cdigo. A continuacin se muestran algunos aspectos relevantes de de control central.
la implementacin de dicho constructor.

Listado 7.7: Clase Ogre::Root. Constructor.


1 Root::Root(const String& pluginFileName, const String&
configFileName,
2 const String& logFileName)
3 // Inicializaciones...
4 {
5 // Comprobacin del singleton en clase padre.
6 // Inicializacin.
7 // src ...
8
9 // Creacin del log manager y archivo de log por defecto.
10 if(LogManager::getSingletonPtr() == 0) {
11 mLogManager = OGRE_NEW LogManager();
12 mLogManager->createLog(logFileName, true, true);
13 }
14
15 // Gestor de biblioteca dinmica.
16 mDynLibManager = OGRE_NEW DynLibManager();
17 mArchiveManager = OGRE_NEW ArchiveManager();
18
19 // ResourceGroupManager.
20 mResourceGroupManager = OGRE_NEW ResourceGroupManager();
21
22 // src...
23
24 // Material manager.
25 mMaterialManager = OGRE_NEW MaterialManager();
26 // Mesh manager.
27 mMeshManager = OGRE_NEW MeshManager();
28 // Skeleton manager.
29 mSkeletonManager = OGRE_NEW SkeletonManager();
30 // Particle system manager.
31 mParticleManager = OGRE_NEW ParticleSystemManager();
32 // src...
33 }

Figura 7.7: Quake III Arena es un


juego multijugador en primera per-
sona desarrollado por IdSoftware y
lanzado el 2 de Diciembre de 1999.
El modo de juego online fue uno de
los ms populares en su poca.
7.1. Subsistema de arranque y parada [227]

7.1.4. Caso de estudio. Quake III

El cdigo fuente de Quake III1 fue liberado bajo licencia GPLv2 el da 20 de


Agosto de 2005. Desde entonces, la comunidad amateur de desarrollo ha realizado
modicaciones y mejoras e incluso el propio motor se ha reutilizado para el desarrollo
de otros juegos. El diseo de los motores de Quake es un muy buen ejemplo de
arquitectura bien estructurada y modularizada, hecho que posibilita el estudio de su
cdigo y la adquisicin de experiencia por el desarrollador de videojuegos.

C7
En esta seccin se estudiar desde un punto de vista general el sistema de arranque
y de parada de Quake III. Al igual que ocurre en el caso de Ogre, el esquema planteado
consiste en hacer uso de funciones especcas para el arranque, inicializacin y parada
de las distintas entidades o subsistemas involucrados.
El siguiente listado de cdigo muestra el punto de entrada de Quake III para sis-
temas WindowsTM . Como se puede apreciar, la funcin principal consiste en una serie
de casos que determinan el ujo de control del juego. Es importante destacar los ca-
sos GAME_INIT y GAME_SHUTDOWN que, como su nombre indica, estn vin-
culados al arranque y la parada del juego mediante las funciones G_InitGame() y
G_ShutdownGame(), respectivamente.
20 de Agosto
Listado 7.8: Quake 3. Funcin vmMain().
La fecha de liberacin del cdigo de
Quake III no es casual. En realidad, 1 int vmMain( int command, int arg0, int arg1, ..., int arg11 ) {
coincide con el cumpleaos de John 2 switch ( command ) {
Carmack, nacido el 20 de Agosto de 3 // Se omiten algunos casos.
1970. Actualmente, John Carmack 4 case GAME_INIT:
es una de las guras ms reconoci- 5 G_InitGame( arg0, arg1, arg2 );
das dentro del mbito del desarrollo 6 return 0;
de videojuegos. 7 case GAME_SHUTDOWN:
8 G_ShutdownGame( arg0 );
9 return 0;
10 case GAME_CLIENT_CONNECT:
11 return (int)ClientConnect( arg0, arg1, arg2 );
12 case GAME_CLIENT_DISCONNECT:
13 ClientDisconnect( arg0 );
14 return 0;
15 case GAME_CLIENT_BEGIN:
16 ClientBegin( arg0 );
17 return 0;
18 case GAME_RUN_FRAME:
19 G_RunFrame( arg0 );
20 return 0;
21 case BOTAI_START_FRAME:
22 return BotAIStartFrame( arg0 );
23 }
24
25 return -1;
26 }

Recuerde que Quake III est desarrollado utilizando el lenguaje C. El cdigo


fuente est bien diseado y estructurado. Para tener una visin global de su
estructura se recomienda visualizar el archivo g_local.h, en el que se explicita
el chero de las funciones ms relevantes del cdigo.

1 https://github.com/id-Software/Quake-III-Arena
[228] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

A continuacin se muestran los aspectos ms destacados de la funcin de iniciali-


zacin de Quake III. Como se puede apreciar, la funcin G_InitGame() se encarga de
inicializar los aspectos ms relevantes a la hora de arrancar el juego. Por ejemplo, se
inicializan punteros relevantes para manejar elementos del juego en G_InitMemory(),
se resetean valores si la sesin de juego ha cambiado en G_InitWorldSession(), se
inicializan las entidades del juego, etctera.

Listado 7.9: Quake 3. Funcin G_InitGame.


1 void G_InitGame( int levelTime, int randomSeed, int restart ) {
2 // Se omite parte del cdigo fuente...
3
4 G_InitMemory();
5
6 // ...
7
8 G_InitWorldSession();
9
10 // Inicializacin de entidades del juego.
11 memset( g_entities, 0, MAX_GENTITIES * sizeof(g_entities[0]) );
12 level.gentities = g_entities;
13
14 // Inicializacin de los clientes.
15 level.maxclients = g_maxclients.integer;
16 memset( g_clients, 0, MAX_CLIENTS * sizeof(g_clients[0]) );
17 level.clients = g_clients;
18
19 // Establecimiento de campos cuando entra un cliente.
20 for ( i=0 ; i<level.maxclients ; i++ ) {
21 g_entities[i].client = level.clients + i;
22 }
23 vwMain ()
24 // Reservar puntos para jugadores eliminados.
25 InitBodyQue();
26 // Inicializacin general.
27 G_FindTeams();
28
29 // ...
30 } G_InitGame ()

La losofa seguida para parar de manera adecuada el juego es anloga al arranca e


inicializacin, es decir, se basa en centralizar dicha funcionalidad en sendas funciones.
En este caso concreto, la funcin G_ShutdownGame() es la responsable de llevar a
cabo dicha parada.
G_RunFrame ()

Listado 7.10: Quake 3. Funcin G_ShutdownGame.


1 void G_ShutdownGame( int restart ) {
2 G_Printf ("==== ShutdownGame ====\n");
3 if ( level.logFile ) {
4 G_LogPrintf("ShutdownGame:\n" );
5 trap_FS_FCloseFile( level.logFile ); G_ShutdownGame ()
6 }
7
8 // Escritura de los datos de sesin de los clientes,
9 // para su posterior recuperacin. Figura 7.8: Visin abstracta del u-
10 G_WriteSessionData(); jo general de ejecucin de Quake III
11 Arena (no se considera la conexin
12 if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { y desconexin de clientes).
7.2. Contenedores [229]

13 BotAIShutdown( restart );
14 }
15 }

Como se puede apreciar en la funcin de parada, bsicamente se escribe informa-


cin en el chero de log y se almacenan los datos de las sesiones de
los clientes para,
posteriormente, permitir su recuperacin. Note cmo en las lneas 14-15 se delega la
parada de los bots, es decir, de los NPCs en la funcin BotAIShutdown(). De nuevo, el
esquema planteado se basa en delegar la parada en distintas funciones en funcin de

C7
la entidad afectada.
Programacin estructura A su vez, la funcin BotAIShutdown() delega en otra funcin que es la encargada,
nalmente, de liberar la memoria y los recursos previamente reservados para el caso
El cdigo de Quake es un buen
ejemplo de programacin estructu- particular de los jugadores que participan en el juego, utilizando para ello la funcin
rada que gira en torno a una adecua- BotAIShutdownClient().
da denicin de funciones y a un
equilibrio respecto a la complejidad
de las mismas. Listado 7.11: Quake 3. Funciones BotAIShutdown y BotAIShutdownClient.
1 int BotAIShutdown( int restart ) {
2 // Si el juego se resetea para torneo...
3 if ( restart ) {
4 // Parar todos los bots en botlib.
5 for (i = 0; i < MAX_CLIENTS; i++)
6 if (botstates[i] && botstates[i]->inuse)
7 BotAIShutdownClient(botstates[i]->client, restart);
8 }
9 // ...
10 }
11
12 int BotAIShutdownClient(int client, qboolean restart) {
13 // Se omite parte del cdigo.
14 // Liberar armas...
15 trap_BotFreeWeaponState(bs->ws);
16 // Liberar el bot...
17 trap_BotFreeCharacter(bs->character);
18 // ..
19 // Liberar el estado del bot...
20 memset(bs, 0, sizeof(bot_state_t));
21 // Un bot menos...
22 numbots--;
23 // ...
24 // Todo OK.
25 return qtrue;
26 }

7.2. Contenedores
Como ya se introdujo en el captulo 5, los contenedores son simplemente objetos
que contienen otros objetos. En el mundo del desarrollo de videojuegos, y en el de
las aplicaciones software en general, los contenedores se utilizan extensivamente para
almacenar las estructuras de datos que conforman la base del diseo que soluciona un
determinado problema.
Algunos de los contenedores ms conocidos ya se comentaron en las seccio-
nes 5.3, 5.4 y 5.5. En dichas secciones se hizo un especial hincapi en relacin a la
utilizacin de estos contenedores, atendiendo a sus principales caracteristicas, como
la denicin subyacente en la biblioteca STL.
[230] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Acceso
Bidireccional Unidireccional Entrada
directo

Salida

Figura 7.9: Esquema grca de las relaciones entre las distintas categoras de iteradores en STL. Cada
categora implementa la funcionalidad de todas las categoras que se encuentran a su derecha.

En esta seccin se introducirn algunos aspectos fundamentales que no se discutie-


ron anteriormente y se mencionarn algunas bibliotecas tiles para el uso de algunas
estructuras de datos que son esenciales en el desarrollo de videojuegos, como por
ejemplo los grafos. Estos aspectos se estudiarn con mayor profundidad en el mdulo
3, Tcnicas Avanzadas de Desarrollo.

7.2.1. Iteradores
Desde un punto de vista abstracto, un iterador se puede denir como una clase Patrn Iterator
que permite acceder de manera eciente a los elementos de un contenedor especco.
Normalmente, los iteradores se im-
Segn [94], un iterador es una abstraccin pura, es decir, cualquier elemento que se plementan haciendo uso del pa-
comporte como un iterador se dene como un iterador. En otras palabras, un itera- trn que lleva su mismo nombre,
dor es una abstraccin del concepto de puntero a un elemento de una secuencia. Los tal y como se discuti en la sec-
principales elementos clave de un iterador son los siguientes: cin 4.4.3.

El elemento al que apunta (desreferenciado mediante los operadores * y ->).


La posibilidad de apuntar al siguiente elemento (incrementndolo mediante el
operador ++).
La igualdad (representada por el operador ==).

De este modo, un elemento primitivo int* se puede denir como un iterador sobre
un array de enteros, es decir, sobre int[]. As mismo, std::list<std::string>::iterator Rasgos de iterador
es un iterador sobre la clase list. En STL, los tipos relacionados con
Un iterador representa la abstraccin de un puntero dentro de un array, de manera un iterador se describen a partir
de una serie de declaraciones en
que no existe el concepto de iterador nulo. Como ya se introdujo anteriormente, la la plantilla de clase iterator_traits.
condicin para determinar si un iterador apunta o no a un determinado elemento se Por ejemplo, es posible acceder al
evala mediante una comparacin al elemento nal de una secuencia (end). tipo de elemento manejado o el tipo
de las operaciones soportadas.
Las principales ventajas de utilizar un iterador respecto a intentar el acceso sobre
un elemento de un contenedor son las siguientes [42]:

El acceso directo rompe la encapsulacin de la clase contenedora. Por el contra-


rio, el iterador suele ser amigo de dicha clase y puede iterar de manera eciente
sin exponer detalles de implementacin. De hecho, la mayora de los contene-
dores ocultan los detalles internos de implementacin y no se puede iterar sobre
ellos sin un iterador.
El iterador simplica el proceso de iteracin. La mayora de iteradores actan
como ndices o punteros, permitiendo el uso de estructuras de bucle para reco-
rrer un contenedor mediante operadores de incremento y comparacin.
7.2. Contenedores [231]

Categoras de iteradores y operaciones


Categora Salida Entrada Unidireccional Bidireccional Acceso directo
Lectura =*p =*p =*p =*p
Acceso -> -> -> ->[]
Escritura *p= *p= *p= *p=
Iteracin ++ ++ ++ ++ ++ + - += -=
Comparacin == != == != == != == != <><= >=

C7
Cuadro 7.1: Resumen de las principales categoras de iteradores en STL junto con sus operaciones [94].

Dentro de la biblioteca STL existen distintos tipos de contenedores y no todos


ellos mantienen el mismo juego de operaciones. Los iteradores se clasican en cin-
co categoras diferentes dependiendo de las operaciones que proporcionan de manera
eciente, es decir, en tiempo constante (O(1)). La tabla 7.1 resume las distintas cate-
goras de iteradores junto con sus operaciones2 .
Recuerde que tanto la lectura como la escritura se realizan mediante el iterador
desreferenciado por el operador *. Adems, de manera independiente a su categora,
un iterador puede soportar acceso const respecto al objeto al que apunta. No es posible
escribir sobre un determinado elemento utilizando para ello un operador const, sea
cual sea la categora del iterador.

No olvide implementar el constructor de copia y denir el operador de asig-


nacin para el tipo que se utilizar en un contenedor y que ser manipulado
mediante iteradores, ya que las lecturas y escrituras copian objetos.

Los iteradores tambin se pueden aplicar sobre ujos de E/S gracias a que la
biblioteca estndar proporciona cuatro tipos de iteradores enmarcados en el esquema
general de contenedores y algoritmos:

ostream_iterator, para escribir en un ostream.


istream_iterator, para leer de un istream.
ostreambuf_iterator, para escribir en un buffer de ujo.
istreambuf_iterator, para leer de un buffer de ujo.

El siguiente listado de cdigo muestra un ejemplo en el que se utiliza un iterador


para escribir sobre la salida estndar. Como se puede apreciar, el operador ++ sirve
para desplazar el iterador de manera que sea posible llevar a cabo dos asignaciones
Uso comercial de STL
consecutivas sobre el propio ujo. De no ser as, el cdigo no sera portable.
Aunque STL se ha utilizado en el
desarrollo de videojuegos comer-
ciales, actualmente es ms comn
7.2.2. Ms all de STL
que el motor de juegos haga uso de
una biblioteca propia para la ges- Como ya se discuti anteriormente en el captulo 5, gran parte de los motores de
tin de contenedores bsicos. Sin juego tienen sus propias implementaciones de los contenedores ms utilizados para
embargo, es bastante comn encon-
trar esquemas que siguen la loso- el manejo de sus estructuras de datos. Este planteamiento est muy extendido en el
fa de STL, aunque optimizados en mbito de las consolas de sobremesa, las consolas porttiles, los telfonos mviles y
aspectos crticos como por ejemplo las PDA (Personal Digital Assistant)s. Los principales motivos son los siguientes:
la gestin y asignacin de memo-
ria. 2 http://www.cplusplus.com/reference/std/iterator/
[232] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Listado 7.12: Ejemplo de uso de iteradores para llevar a cabo operaciones de E/S.
1 #include <iostream>
2 #include <iterator>
3
4 using namespace std;
5
6 int main () {
7 // Escritura de enteros en cout.
8 ostream_iterator<int> fs(cout);
9
10 *fs = 7; // Escribe 7 (usa cout).
11 ++fs; // Preparado para siguiente salida.
12 *fs = 6; // Escribe 6.
13
14 return 0;
15 }

Control total sobre los contenedores y estructuras de datos desarrolladas, es-


pecialmente sobre el mecanismo de asignacin de memoria, aunque sin olvidar
aspectos como los propios algoritmos. Aunque STL permite crear asignadores
de memoria personalizados (custom allocators), en ocasiones los propios patro-
nes de los contenedores de STL pueden ser insucientes.
Optimizaciones, considerando el propio hardware sobre el que se ejecutar el
motor. STL es un estndar independiente de la plataforma y el sistema operativo,
por lo que no es posible aplicar optimizaciones de manera directa sobre la propia
biblioteca.
Personalizacin debido a la necesidad de incluir funcionalidad sobre un con-
tenedor que no est inicialmente considerada en otras bibliotecas como STL.
Por ejemplo, en un determinado problema puede ser necesario obtener los n
elementos ms adecuados para satisfacer una necesidad.
Independencia funcional, ya que es posible depurar y arreglar cualquier pro-
blema sin necesidad de esperar a que sea solucionado por terceras partes.

De manera independiente al hecho de considerar la opcin de usar STL, existen Estandarizando Boost
otras bibliotecas relacionadas que pueden resultar tiles para el desarrollador de vi-
deojuegos. Una de ellas es STLPort, una implementacin de STL especcamente Gran parte de las bibliotecas del
proyecto Boost estn en proceso de
ideada para ser portable a un amplio rango de compiladores y plataformas. Esta im- estandarizacin con el objetivo de
plementacin proporciona una funcionalidad ms rica que la que se puede encontrar incluirse en el propio estndar de
en otras implementaciones de STL. C++.

La otra opcin que se discutir brevemente en esta seccin es el proyecto Boost,


cuyo principal objetivo es extender la funcionalidad de STL. Las principales caracte-
rsticas de Boost se resumen a continuacin:

Inclusin de funcionalidad no disponible en STL.


En ocasiones, Boost proporciona algunas alternativas de diseo e implementa-
cin respecto a STL.
Gestin de aspectos complejos, como por ejemplos los smart pointers.
Documentacin de gran calidad que tambin incluye discusiones sobre las de-
cisiones de diseo tomadas.
7.2. Contenedores [233]

7
A B A C

1 2 B A D

2 12 4

C7
E
C D
3
3
D B E
C D
2
E A B C

4
Figura 7.10: Representacin grca del grafo de ejemplo utilizado para el clculo de caminos mnimos. La
parte derecha muestra la implementacin mediante listas de adyacencia planteada en la biblioteca Boost.

Para llevar a cabo la instalacin de STLPort3 y Boost4 (includa la biblioteca para


manejo de grafos) en sistemas operativos Debian y derivados es necesarios ejecutar
los siguientes comandos:

$ sudo apt-get update


$ sudo apt-get install libstlport5.2-dev
$ sudo apt-get install libboost-dev
$ sudo apt-get install libboost-graph-dev

La gura 7.10 muestra un grafo dirigido que servir como base para la discusin
del siguiente fragmento de cdigo, en el cual se hace uso de la biblioteca Boost para
llevar a cabo el clculo de los caminos mnimos desde un vrtice al resto mediante el
algoritmo de Dijkstra.

Las bibliotecas de Boost se distribuyen bajo la Boost Software Licence, la


cual permite el uso comercial y no-comercial.

Edsger W. Dijkstra Recuerde que un grafo es un conjunto no vaco de nodos o vrtices y un conjunto
de pares de vrtices o aristas que unen dichos nodos. Un grafo puede ser dirigido o
Una de las personas ms relevan-
tes en el mbito de la computacin no dirigido, en funcin de si las aristas son unidireccionales o bidireccionales, y es
es Dijkstra. Entre sus aportaciones, posible asignar pesos a las aristas, conformando as un grafo valorado.
destacan la solucin al camino ms
corto, la notacin polaca inversa, el 3 http://www.stlport.org
algoritmo del banquero, la deni- 4 http://www.boost.org
cin de semforo como mecanismo
de sincronizacin e incluso aspec-
tos de computacin distribuida, en-
tre otras muchas contribuciones.
[234] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

A continuacin se muestra un listado de cdigo (adaptado a partir de uno de los


ejemplos de la biblioteca5 ) que permite la denicin bsica de un grafo dirigido y
valorado. El cdigo tambin incluye cmo hacer uso de funciones relevantes asociadas
los grafos, como el clculo de los cminos mnimos de un determinado nodo al resto.
Como se puede apreciar en el siguiente cdigo, es posible diferenciar cuatro blo-
ques bsicos. Los tres primeros se suelen repetir a la hora de manejar estructuras

mediante la biblioteca Boost Graph. En primer lugar, se lleva a cabo la denicin
grafo (lneas 15-16
de los tipos de datos que se utilizarn, destacando el propio ) y
los descriptores para manejar tanto los vrtices (lnea 17 ) y las aristas (lnea 18 ).
A continuacin se especican los elementos concretos del grafo, es decir, las etique-
y los pesos de las mismas.
tas textuales asociadas a los vrtices, las aristas o arcos
Finalmente, ya es posible instanciar el grafo (lnea 32 ), eldescriptor usado para es-

pecicar el clculo de caminos mnimos desde A (lnea 38 ) y la llamada a la funcin
para obtener dichos caminos (41 ).

Listado 7.13: Ejemplo de uso de la biblioteca Boost (grafos).


1 #include <boost/config.hpp>
2 #include <iostream>
3
4 #include <boost/graph/graph_traits.hpp>
5 #include <boost/graph/adjacency_list.hpp>
6 #include <boost/graph/dijkstra_shortest_paths.hpp>
7
8 using namespace boost;
9
10 int
11 main(int, char *[])
12 {
13 // Definicin de estructuras de datos...
14 typedef adjacency_list <listS, vecS, directedS,
15 no_property, property <edge_weight_t, int> > graph_t;
16 typedef graph_traits <graph_t>::vertex_descriptor
vertex_descriptor;
17 typedef graph_traits <graph_t>::edge_descriptor edge_descriptor;
18 typedef std::pair<int, int> Edge;
19
20 // Parmetros bsicos del grafo.
21 const int num_nodes = 5;
22 enum nodes {A, B, C, D, E};
23 char name[] = "ABCDE";
24 Edge edge_array[] = {
25 Edge(A, C), Edge(B, A), Edge(B, D), Edge(C, D), Edge(D, B),
26 Edge(D, C), Edge(D, E), Edge(E, A), Edge(E, B), Edge(E, C)
27 };
28 int weights[] = {2, 7, 4, 2, 12, 4, 3, 1, 2, 3};
29 int num_arcs = sizeof(edge_array) / sizeof(Edge);
30
31 // Instanciacin del grafo.
32 graph_t g(edge_array, edge_array + num_arcs, weights, num_nodes);
33 property_map<graph_t, edge_weight_t>::type weightmap =
34 get(edge_weight, g);
35
36 std::vector<vertex_descriptor> p(num_vertices(g));
37 std::vector<int> d(num_vertices(g));
38 vertex_descriptor s = vertex(A, g); // CAMINOS DESDE A.
39
40 // Algoritmo de Dijkstra.
41 dijkstra_shortest_paths(g, s,
42 predecessor_map(&p[0]).distance_map(&d[0]));
43
44 std::cout << "Distancias y nodos padre:" << std::endl;
5 http://www.boost.org/doc/libs/1_55_0/libs/graph/example/

dijkstra-example.cpp
7.3. Subsistema de gestin de cadenas [235]

45 graph_traits <graph_t>::vertex_iterator vi, vend;


46 for (boost::tie(vi, vend) = vertices(g); vi != vend; ++vi) {
47 std::cout << "Distancia(" << name[*vi] << ") = "
48 << d[*vi] << ", ";
49 std::cout << "Padre(" << name[*vi] << ") = " << name[p[*vi]]
50 << std:: endl;
51 }
52 std::cout << std::endl;
53
54 return EXIT_SUCCESS;
55 }

C7

El ltimo bloque de cdigo (lneas 44-52 ) permite obtener la informacin rele-
vante a partir del clculo de los caminos mnimos desde A, es decir, la distancia que
existe desde el propio nodo A hasta cualquier otro y el nodo a partir del cual se accede
al destino nal (partiendo de A).
Para generar un ejecutable, simplemente es necesario enlazar con la biblioteca
boost_graph:

$ g++ dijkstra.cpp -o dijkstra -lboost_graph


$ ./dijkstra

Al ejecutar, la salida del programa mostrar todos los caminos mnimos desde el
vrtice especicado; en este caso, desde el vrtice A.
cad
Distancias y nodos padre:
'H' Distancia(A) = 0, Padre(A) = A
Distancia(B) = 9, Padre(B) = E
p Distancia(C) = 2, Padre(C) = A
'o' Distancia(D) = 4, Padre(D) = C
Distancia(E) = 7, Padre(E) = D

'l'
7.3. Subsistema de gestin de cadenas
'a'
Al igual que ocurre con otros elementos transversales a la arquitectura de un motor
de juegos, como por ejemplos los contenedores de datos (ver captulo 5), las cadenas
'm' de texto se utilizan extensivamente en cualquier proyecto software vinculado al desa-
rrollo de videojuegos. Aunque en un principio pueda parecer que la utilizacin y la
gestin de cadenas de texto es una cuestin trivial, en realidad existe una gran varie-
dad de tcnicas y restricciones vinculadas a este tipo de datos. Su consideracin puede
'u'
ser muy relevante a la hora de mejorar el rendimiento de la aplicacin.

'n' 7.3.1. Cuestiones especcas


Uno de los aspectos crticos a la hora de tratar con cadenas de texto est vinculado
'd' con el almacenamiento y gestin de las mismas. Si se considera el hecho de utilizar
C/C++ para desarrollar un videojuego, entonces es importante no olvidar que en estos
lenguajes de programacin las cadenas de texto no son un tipo de datos atmica, ya
'o' que se implementan mediante un array de caracteres.

typedef basic_string<char> string;


Figura 7.11: Esquema grco de la
implementacin de una cadena me- La consecuencia directa de esta implementacin es que el desarrollador ha de con-
diante un array de caracteres. siderar cmo gestionar la reserva de memoria para dar soporte al uso de cadenas:
[236] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

De manera esttica, es decir, reservando una cantidad ja e inicial de memoria


para el array de caracteres.
De manera dinmica, es decir, asignando en tiempo de ejecucin la cantidad de
memoria que se necesite para cubrir una necesidad.

Tpicamente, la opcin ms utilizada por los programadores de C++ consiste en


hacer uso de una clase string. En particular, la clase string proporcionada por la bi-
blioteca estndar de C++. Sin embargo, en el contexto del desarrollo de videojuegos
es posible que el desarrollador haya decidido no hacer uso de STL, por lo que sera
necesario llevar a cabo una implementacin nativa.
Otro aspecto fundamental relativo a las cadenas es la localizacin, es decir, el pro- Internationalization
ceso de adaptar el software desarrollado a mltiples lenguajes. Bsicamente, cualquier
La localizacin, en el mbito de
representacin textual del juego, normalmente en ingls en un principio, ha de tradu- gestin de mltiples lenguajes, se
cirse a los idiomas soportados por el juego. Esta cuestin plantea una problemtica suele conocer como internationali-
variada, como por ejemplo la necesidad de tener en cuenta diversos alfabetos con ca- zation, o I18N, y es esencial pa-
racteres especcos (por ejemplo chino o japons), la posibilidad de que el texto tenga ra garantizar la inexistencia de pro-
blemas al tratar con diferentes idio-
una orientacin distinta a la occidental o aspectos ms especcos, como por ejemplo mas.
que la traduccin de un trmino tenga una longitud signicativamente ms larga, en
trminos de longitud de caracteres, que su forma original.
Desde un punto de vista ms interno al propio desarrollo, tambin hay que consi-
derar que las cadenas se utilizan para identicar las distintas entidades del juego. Por
ejemplo, es bastante comn adoptar un convenio para nombrar a los posibles enemigos
del personaje principal en el juego. Un ejemplo podra ser ying-enemy-robot-02.

El tratamiento de cadenas en tiempo de ejecucin es costoso. Por lo tanto,


evaluar su impacto es esencial para obtener un buen rendimiento.

Finalmente, el impacto de las operaciones tpicas de cadenas es muy importan-


te con el objetivo de obtener un buen rendimiento. Por ejemplo, la comparacin de
cadenas mediante la funcin strcmp() tiene una complejidad lineal respecto a la lon-
gitudad de las cadenas, es decir, una complejidad O(n). Del mismo modo, la copia de
cadenas tambin tiene una complejidad lineal, sin considerar la posibilidad de reser-
var memoria de manera dinmica. Por el contrario, la comparacin de tipos de datos
ms simples, como los enteros, es muy eciente y est soportado por instrucciones
mquina.

7.3.2. Optimizando el tratamiento de cadenas


El uso de una clase string que abstraiga de la complejidad subyacente de la imple-
mentacin de datos textuales es sin duda el mecanismo ms prctico para trabajar con
cadenas. Por ejemplo, en el caso de C++, el programador puede utilizar directamente
la clase string6 de la biblioteca estndar. Dicha clase proporciona una gran cantidad
de funcionalidad, estructurada en cinco bloques bien diferenciados:

Iteradores, que permiten recorrer de manera eciente el contenido de las ins-


tancias de la clase string. Un ejemplo es la funcin begin(), que devuelve un
iterador al comienzo de la cadena.
6 http://www.cplusplus.com/reference/string/string/
7.3. Subsistema de gestin de cadenas [237]

Capacidad, que proporciona operaciones para obtener informacin sobre el ta-


mao de la cadena, redimensionarla e incluso modicar la cantidad de memoria
reservada para la misma. Un ejemplo es la funcin size(), que devuelve la lon-
gitudad de la cadena.
Acceso, que permite obtener el valor de caracteres concretos dentro de la ca-
dena. Un ejemplo es la funcin at(), que devuelve el carcter de la posicin
especicada como parmetro.

C7
Modicadores, que proporcionan la funcionalidad necesaria para alterar el con-
tenido de la cadena de texto. Un ejemplo es la funcin swap(), que intercambia
el contenido de la cadena por otra especicada como parmetro.
Operadores de cadena, que dene operaciones auxiliares para tratar con cade-
nas. Un ejemplo es la funcin compare(), que permite comparar cadenas.

No obstante, tal y como se introdujo anteriormente, la abstraccin proporcionada


por este tipo de clases puede ocultar el coste computacional asociado a la misma y, en
consecuencia, condicionar el rendimiento del juego. Por ejemplo, pasar una cadena de
texto como parmetro a una funcin utilizando el estilo C, es decir, pasando un puntero
al primer elemento de la cadena, es una operacin muy eciente. Sin embargo, pasar
un objeto cadena puede ser ineciente debido a la generacin de copias mediante
los constructores de copia, posiblemente provocando reserva dinmica de memoria y
penalizando an ms el rendimiento de la aplicacin.
Proling strings! Por este tipo de motivos, es bastante comn que en el desarrollo profesional de
videojuegos se evite el uso de clases string [42], especialmente en plataformas de pro-
El uso de herramientas de pro- psito especco como las consolas de sobremesa. Si, por el contrario, en el desarrollo
ling para evaluar el impacto del uso
y gestin de una implementacin de un juego se hace uso de una clase de esta caractersticas, es importante considerar
concreta de cadenas de texto puede los siguientes aspectos:
ser esencial para mejorar el frame
rate del juego.
Realizar un estudio del impacto en complejidad espacial y temporal que tiene la
adopcin de una determinada implementacin.
Informar al equipo de desarrollo de la opcin elegida para tener una perspectiva
global de dicho impacto.
Realizar un estudio de la opcin elegida considerando aspectos especcos, co-
mo por ejemplo si todos los buffers son de slo-lectura.

Como regla general, pase los objetos de tipo string como referencia y no
como valor, ya que esto incurrira en una penalizacin debido al uso de cons-
tructores de copia.

En el mbito del desarrollo de videojuegos, la identicacin de entidades o ele-


mentos est estrechamente ligada con el uso de cadenas de texto. Desde un punto de
vista general, la identicacin unvoca de objetos en el mundo virtual de un juego
es una parte esencial en el desarrollo como soporte a otro tipo de operaciones, como
por ejemplo la localizacin de entidades en tiempo de ejecucin por parte del motor
de juegos. As mismo, las entidades ms van all de los objetos virtuales. En realidad,
tambin abarcan mallas poligonales, materiales, texturas, luces virtuales, animaciones,
sonidos, etctera.
[238] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

En este contexto, las cadenas de texto representan la manera ms natural de lle-


var a cabo dicha identicacin y tratamiento. Otra opcin podra consistir en manejar
una tabla con valores enteros que sirviesen como identicadores de las distintas en-
tidadas. Sin embargo, los valores enteros no permiten expresar valores semnticos,
mientras que las cadenas de texto s.
Debido a la importancia del tratamiento y manipulacin de cadenas, las operacio-
nes asociadas han de ser ecientes. Desafortunadamente, este criterio no se cumple en
el caso de funciones como strcmp(). La solucin ideal debera combinar la exibilidad
y poder descriptivo de las cadenas con la eciencia de un tipo de datos primitivo, como
los enteros. Este planteamiento es precisamente la base del esquema que se discute a
continuacin.

7.3.3. Hashing de cadenas


El uso de una estructura asociativa (ver seccin 5.4) se puede utilizar para ma- String id
nejar de manera eciente cadenas textuales. La idea se puede resumir en utilizar una
estructura asociativa en la que la clave sea un valor numrico y la clase sea la propia En el mbito profesional, el trmino
string id se utiliza comnmente pa-
cadena de texto. De este modo, la comparacin entre los cdigos hash de las cadenas, ra referirse a la cadena accesible
es decir, los enteros, es muy rpida. mediante un determinado valor o
cdigo hash.
Si las cadenas se almacenan en una tabla hash, entonces el contenido original se
puede recuperar a partir del cdigo hash. Este esquema es especialmente til en el
proceso de depuracin para mostrar el contenido textual, ya sea de manera directa por
un dispositivo de visualizacin o mediante los tpicos archivos de log.
Una de las principales cuestiones a considerar cuando se utilizan este tipo de plan- Interning the string
teamientos es el diseo de la funcin de hashing. En otras palabras, la funcin que
traduce las claves, representadas normalmente mediante tipos de datos numricos, en El proceso que permite la genera-
cin de un identicador nmero a
valores, representados por cadenas textuales en esta seccin. El principal objetivo con- partir de una cadena de texto se sue-
siste en evitar colisiones, es decir, evitar que dos cadenas distintas tengan asociadas el le denominar interning, ya que im-
mismo cdigo o clave. Esta ltima situacin se dene como perfect hashing y existe plica, adems de realizar el hashing,
una gran cantidad de informacin en la literatura7 . almacenar la cadena en una tabla de
visibilidad global.
Respecto a la implementacin de un esquema basado en hashing de cadenas, uno
de los aspectos clave consiste en decidir cundo llamar a la funcin de hashing. Desde
un punto de vista general, la mayora de los motores de juegos permiten obtener el
identicador asociado a una cadena en tiempo de ejecucin. Sin embargo, es posible
aplicar algunos trucos para optimizar dicha funcionalidad. Por ejemplo, es bastante
comn hacer uso de macros para obtener el identicador a partir de una cadena para
su posterior uso en una sentencia switch.
Tambin es importante tener en cuenta que el resultado de una funcin de has-
hing se suele almacenar en algn tipo de estructura de datos, en ocasiones global, con
el objetivo de incrementar la eciencia en consultas posteriores. Este planteamien-
to, parecido a un esquema de cach para las cadenas textuales, permite mejorar el
rendimiento de la aplicacin. El siguiente listado de cdigo muestra una posible im-
plementacin que permite el manejo de cadenas de texto mediante una estructura de
datos global.

Listado 7.14: Ejemplo de hashing de cadenas.


1 static StringId sid_hola = internString("hola");
2 static StringId sid_mundo = internString("mundo");
3
4 // ...

7 Se recomienda la visualizacin del curso Introduction to Algorithms del MIT, en concreto las clases 7
y 8, disponible en la web.
7.4. Conguracin del motor [239]

5
6 void funcion (StringId id) {
7 // Ms eficiente que...
8 // if (id == internString ("hola"))
9 if (id == sid_hola)
10 // ...
11 else if (id == sid_mundo)
12 // ...
13 }

C7
7.4. Conguracin del motor
Debido a la complejidad inherente a un motor de juegos, existe una gran variedad
de variables de conguracin que se utilizan para especicar el comportamiento de
determinadas partes o ajustar cuestiones especcas. Desde un punto de vista general,
en la conguracin del motor se pueden distinguir dos grandes bloques:

Externa, es decir, la conguracin vinculada al juego, no tanto al motor, que


el usuario puede modicar directamente. Ejemplos representativos pueden ser
la conguracin del nivel de dicultad de un juego, los ajustes ms bsicos de
sonido, como el control de volumen, o incluso el nivel de calidad grca.
Interna, es decir, aquellos aspectos de conguracin utilizados por los desarro-
lladores para realizar ajustes que afecten directamente al comportamiento del
juego. Ejemplos representativos pueden ser la cantidad de tiempo que el perso-
naje principal puede estar corriendo sin cansarse o la vitalidad del mismo. Este
tipo de parmetros permiten completar el proceso de depuracin permanecen
ocultos al usuario del juego.

Tricks
En muchos juegos es bastante co- 7.4.1. Esquemas tpicos de conguracin
mn encontrar trucos que permi-
ten modicar signicativamente al-
gunos aspectos internos del juego, Las variables de conguracin se pueden denir de manera trivial mediante el
como por ejemplo la capacidad de uso de variables globales o variables miembro de una clase que implemente el patrn
desplazamiento del personaje prin- singleton. Sin embargo, idealmente debera ser posible modicar dichas variables de
cipal. Tradicionalmente, la activa- conguracin sin necesidad de modicar el cdigo fuente y, por lo tanto, volver a
cin de trucos se ha realizado me-
diante combinaciones de teclas. compilar para generar un ejecutable.
Normalmente, las variables o parmetros de conguracin residen en algn tipo de
dispositivo de almacenamiento externo, como por ejemplo un disco duro o una tarjeta
de memoria. De este modo, es posible almacenar y recuperar la informacin asociada
a la conguracin de una manera prctica y directa. A continuacin se discute breve-
mente los distintas aproximaciones ms utilizadas en el desarrollo de videojuegos.
En primer lugar, uno de los esquems tpicos de recuperacin y almacenamiento de
informacin est representado por los cheros de conguracin. La principal ventaja
de este enfoque es que son perfectamente legibles ya que suelen especicar de manera
explcita la variable de conguracin a modicar y el valor asociada a la misma. En
general, cada motor de juegos mantiene su propio convenio aunque, normalmente,
todos se basan en una secuencia de pares clave-valor. Por ejemplo, Ogre3D utiliza la
siguiente nomenclatura:

Render System=OpenGL Rendering Subsystem

[OpenGL Rendering Subsystem]


[240] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Display Frequency=56 MHz


FSAA=0
Full Screen=No
RTT Preferred Mode=FBO
VSync=No
Video Mode= 800 x 600
sRGB Gamma Conversion=No

Dentro del mbito de los cheros de conguracin, los archivos XML representan
otra posibilidad para establecer los parmetros de un motor de juegos. En este caso, es
necesario llevar a cabo un proceso de parsing para obtener la informacin asociada a
dichos parmetros.
Tradicionalmente, las plataformas de juego que sufran restricciones de memoria, El lenguaje XML
debido a limitaciones en su capacidad, hacan uso de un formato binario para alma-
cenar la informacin asociada a la conguracin. Tpicamente, dicha informacin se eXtensible Markup Language es un
metalenguaje extensible basado en
almacenaba en tarjetas de memoria externas que permitan salvar las partidas guarda- etiquetas que permite la denicin
das y la conguracin proporcionada por el usuario de un determinado juego. de lenguajes especcos de un de-
terminado dominio. Debido a su po-
Este planteamiento tiene como principal ventaja la eciencia en el almacenamien- pularidad, existen multitud de he-
to de informacin. Actualmente, y debido en parte a la convergencia de la consola de rramientas que permiten el trata-
sobremesa a estaciones de juegos con servicios ms generales, la limitacin de alma- miento y la generacin de archivos
bajo este formato.
cenamiento en memoria permanente no es un problema, normalmente. De hecho, la
mayora de consolas de nueva generacin vienen con discos duros integrados.
Los propios registros del sistema operativo tambin se pueden utilizar como so-
porte al almacenamiento de parmetros de conguracin. Por ejemplo, los sistemas
operativos de la familia de Microsoft WindowsTM proporcionan una base de datos glo-
bal implementada mediante una estructura de rbol. Los nodos internos de dicha es-
tructura actan como directorios mientras que los nodos hoja representan pares clave-
valor.
Tambin es posible utilizar la lnea de rdenes o incluso la denicin de variables
de entorno para llevar a cabo la conguracin de un motor de juegos.
Finalmente, es importante reexionar sobre el futuro de los esquemas de almace-
namiento. Actualmente, es bastante comn encontrar servicios que permitan el alma-
cenamiento de partidas e incluso de preferencias del usuario en la red, haciendo uso de
servidores en Internet normalmente gestionados por la propia compaa de juegos.
Este tipo de aproximaciones tienen la ventaja de la redundancia de datos, sacricando
el control de los datos por parte del usuario.

7.4.2. Caso de estudio. Esquemas de denicin.


Una tcnica bastante comn que est directamente relacionada con la congu- LISP
racin de un motor de juegos reside en el uso de esquemas de denicin de datos.
Junto con Fortran, LISP es ac-
Bsicamente, la idea general reside en hacer uso de algn tipo de lenguaje de pro- tualmente uno de los lenguajes de
gramacin declarativo, como por ejemplo LISP, para llevar a cabo la denicin de programacin ms antiguos que se
datos que permitan su posterior exportacin a otros lenguajes de programacin. Este siguen utilizando comercialmente.
tipo de lenguajes son muy exibles y proporcionan una gran potencia a la hora de Sus orgenes estn fuertemente li-
gados con la Inteligencia Articial
denir estructuras de datos. y fue pionero de ideas fundamenta-
A continuacin se muestra un ejemplo real del videojuego Uncharted: Drakes les en el mbito de la computacin,
como los rboles, la gestin auto-
Fortune, desarrollado por Naughty Dog para la consola de sobremesa Playstation 3TM , mtica del almacenamiento o el ti-
y discutido en profundidad en [42]. En dicho ejemplo se dene una estructura bsica pado dinmico.
para almacenar las propiedades de una animacin y dos instancias vinculadas a un tipo
particular de animacin, utilizando para ello un lenguaje propietario.
7.5. Fundamentos bsicos de concurrencia [241]

Listado 7.15: Denicin de una animacin bsica [42]


1 ;; Estructura bsica de animacin.
2 (define simple-animation ()
3 (
4 (name string)
5 (speed float :default 1.0)
6 (fade-in-seconds float :default 0.25)

C7
7 (fade-out-seconds float :default 0.25)
8 )
9 )
10
11 ;; Instancia especfica para andar.
12 (define-export anim-walk
13 (new simple-animation
14 :name "walk"
15 :speed 1.0
16 )
17 )
18 ;; Instancia especfica para andar rpido.
19 (define-export anim-walk-fast
20 (new simple-animation
21 :name "walk-fast"
22 :speed 2.0
23 )
24 )

Evidentemente, la consecuencia directa de denir un lenguaje propio de denicin


de datos implica el desarrollo de un compilador especco que sea capaz de generar
de manera automtica cdigo fuente a partir de las deniciones creadas. En el caso de
la denicin bsica de animacin planteada en el anterior listado de cdigo, la salida
de dicho compilador podra ser similar a la mostrada a continuacin.

Listado 7.16: Estructura generada por el compilador.


1 // Cdigo generado a partir del compilador.
2 // Estructura bsica de animacin.
3
4 struct SimpleAnimation {
5 const char* m_name;
6 float m_speed;
7 float m_fadeInSeconds;
8 float m_fadeOutSeconds;
9 };

7.5. Fundamentos bsicos de concurrencia

7.5.1. El concepto de hilo


Los sistemas operativos modernos se basan en el principio de multiprograma-
Elementos de un hilo cin, es decir, en la posibilidad de manejar distintos hilos de ejecucin de manera
Un hilo est compuesto por un iden- simultnea con el objetivo de paralelizar el cdigo e incrementar el rendimiento de la
ticador nico de hilo, un contador aplicacin.
de programa, un conjunto de regis-
tros y una pila.
[242] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Esta idea tambin se plasma a nivel de lenguaje de programacin. Algunos ejem-


plos representativos son las APIs de las bibliotecas de hilos Pthread, Win32 o Java.
Incluso existen bibliotecas de gestin de hilos que se enmarcan en capas software si-
tuadas sobre la capa del sistema operativo, con el objetivo de independizar el modelo
de programacin del propio sistema operativo subyacente.
Este ltimo planteamiento es uno de los ms utilizados en el desarrollo de video-
juegos con el objetivo de evitar posibles problemas a la hora de portarlos a diversas
plataformas. Recuerde que en la seccin 1.2.3, donde se discuti la arquitectura ge-
neral de un motor de juegos, se justicaba la necesidad de incluir una capa inde-
pendiente de la plataforma con el objetivo de garantizar la portabilidad del motor.
Evidentemente, la inclusin de una nueva capa software degrada el rendimiento nal
de la aplicacin.
Es importante considerar que, a diferencia de un proceso, los hilos que pertenecen
a un mismo proceso comparten la seccin de cdigo, la seccin de datos y otros recur-
sos proporcionados por el sistema operativo (ver gura 7.12), como los manejadores
de los archivos abiertos. Precisamente, esta diferencia con respecto a un proceso es lo
que supone su principal ventaja a la hora de utilizar un elemento u otro.
Informalmente, un hilo se puede denir como un proceso ligero que tiene la misma
funcionalidad que un proceso pesado, es decir, los mismo estados. Por ejemplo, si un
hilo abre un chero, ste estar disponible para el resto de hilos de una tarea. Las
ventajas de la programacin multihilo se pueden resumir en las tres siguientes:
Estados de un hilo
Capacidad de respuesta, ya que el uso de mltiples hilos proporciona un en-
Un hilo mantiene los mismos esta-
foque muy exible. As, es posible que un hilo se encuentra atendiendo una dos internos que un proceso: nuevo,
peticin de E/S mientras otro contina con la ejecucin de otra funcionalidad ejecucin, espera, preparado y ter-
distinta. Adems, es posible plantear un esquema basado en el paralelismo no minado.
bloqueante en llamadas al sistema, es decir, un esquema basado en el bloqueo
de un hilo a nivel individual.
Comparticin de recursos, posibilitando que varios hilos manejen el mismo
espacio de direcciones.
Ecacia, ya que tanto la creacin, el cambio de contexto, la destruccin y la
liberacin de hilos es un orden de magnitud ms rpida que en el caso de los
procesos pesados. Recuerde que las operaciones ms costosas implican el ma-
nejo de operaciones de E/S. Por otra parte, el uso de este tipo de programacin
en arquitecturas de varios procesadores (o ncleos) incrementa enormemente el
rendimiento de la aplicacin.

7.5.2. El problema de la seccin crtica


El segmento de cdigo en el que un proceso puede modicar variables comparti-
das con otros procesos se denomina seccin crtica. Un ejemplo tpico en el mbito
de la orientacin a objetos son las variables miembro de una clase, suponiendo que
las instancias de la misma pueden atender mltiples peticiones de manera simultnea
mediante distintos hilos de control.
Para evitar inconsistencias, una de las ideas que se plantean es que cuando un Condicin de carrera
hilo est ejecutando su seccin crtica, ningn otro hilo puede acceder a dicha seccin Si no se protege adecuadamente la
crtica. As, si un objeto puede manera mltiples peticiones, no debera ser posible que seccin crtica, distintas ejecucio-
un cliente modique el estado de dicha instancia mientras otro intenta leerlo. nes de un mismo cdigo pueden ge-
nerar distintos resultados, generan-
do as una condicin de carrera.
7.5. Fundamentos bsicos de concurrencia [243]

cdigo datos archivos cdigo datos archivos

registros registros registros


registros pila
pila pila pila

C7
hilo

(a) (b)

Figura 7.12: Esquema grco de los modelos de programacin monohilo (a) y multihilo (b).

SECCIN_ENTRADA
SECCIN_CRTICA

SECCIN_SALIDA

SECCIN_RESTANTE

Figura 7.13: Estructura general del cdigo vinculado a la seccin crtica.

El problema de la seccin crtica consiste en disear algn tipo de solucin para


garantizar que los elementos involucrados puedan operar sin generar ningn tipo de
inconsistencia. Una posible estructura para abordar esta problemtica se plantea en la
gura 7.13, en la que el cdigo se divide en las siguientes secciones:

Seccin de entrada, en la que se solicita el acceso a la seccin crtica.


Seccin crtica, en la que se realiza la modicacin efectiva de los datos com-
partidos.
Seccin de salida, en la que tpicamente se har explcita la salida de la seccin
crtica.
Seccin restante, que comprende el resto del cdigo fuente.
[244] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Nombre Internet Communications Engine (I CE)


Denido por ZeroC Inc. (http://www.zeroc.com)
Documentacin http://doc.zeroc.com/display/Doc/Home
Lenguajes C++, Java, C#, Visual Basic, Python, PHP, Ruby
Windows, Windows CE, Unix, GNU/Linux, *BSD
Plataformas
OSX, Symbian OS, J2RE 1.4 o superior, J2ME
APIs claras y bien diseadas
Destacado Conjunto de servicios muy cohesionados
Despliegue, persistencia, cifrado...
Descargas http://zeroc.com/download.html

Cuadro 7.2: ZeroC I CE. Resumen de caractersticas.

7.6. La biblioteca de hilos de I CE

7.6.1. Internet Communication Engine


I CE (Internet Communication Engine) es un middleware de comunicaciones orien-
tado a objetos, es decir, I CE proporciona herramientas, APIs, y soporte de bibliotecas
para construir aplicaciones distribuidas cliente-servidor orientadas a objetos (ver gu-
ra 7.14).
Una aplicacin I CE se puede usar en entornos heterogneos. Los clientes y los ser-
vidores pueden escribirse en diferentes lenguajes de programacin, pueden ejecutarse
en distintos sistemas operativos y en distintas arquitecturas, y pueden comunicarse
empleando diferentes tecnologas de red. La tabla 7.2 resume las principales caracte-
rsticas de I CE.
Los principales objetivos de diseo de I CE son los siguientes:

Middleware listo para usarse en sistemas heterogneos.


Proveee un conjunto completo de caractersticas que soporten el desarrollo de
aplicaciones distribuidas reales en un amplio rango de dominios.
Es fcil de aprender y de usar.
Proporciona una implementacin eciente en ancho de banda, uso de memoria
y CPU.
Implementacin basada en la seguridad.

Para instalar I CE en sistemas operativos Debian GNU/Linux, ejecute los siguientes


comandos:

$ sudo apt-get update


$ sudo apt-get install zeroc-ice35

En el mdulo 4, Desarrollo de Componentes, se estudiarn los aspectos bsicos


de este potente middleware para construir aplicaciones distribuidas. Sin embargo, en
la presente seccin se estudiar la biblioteca de hilos que proporciona I CE para dar
soporte a la gestin de la concurrencia y al desarrollo multihilo cuando se utiliza el
lenguaje de programacin C++.
7.6. La biblioteca de hilos de I CE [245]

Aplicacin cliente Aplicacin servidora

Esqueleto

C7
Proxy API ICE API ICE Adaptador de objetos
Red de
comunicaciones ICE runtime (servidor)
ICE runtime (cliente)

Figura 7.14: Arquitectura general de una aplicacin distribuida desarrollada con el middleware ZeroC I CE.

El objetivo principal de esta biblioteca es abstraer al desarrollador de las capas


inferiores e independizar el desarrollo de la aplicacin del sistema operativo y de la
plataforma hardware subyacentes. De este modo, la portabilidad de las aplicaciones
desarrolladas con esta biblioteca se garantiza, evitando posibles problemas de incom-
patibilidad entre sistemas operativos.
Thread-safety
I CE maneja las peticiones a los ser- 7.6.2. Manejo de hilos
vidores mediante un pool de hi-
los con el objetivo de incrementar I CE proporciona distintas utilidades para la gestin de la concurrencia y el manejo
el rendimiento de la aplicacin. El
desarrollador es el responsable de de hilos. Respecto a este ltimo aspecto, I CE proporciona una abstraccin muy sen-
gestionar el acceso concurrente a cilla para el manejo de hilos con el objetivo de explotar el paralelismo mediante la
los datos. creacin de hilos dedicados. Por ejemplo, sera posible crear un hilo especco que
atenda las peticiones de una interfaz grca o crear una serie de hilos encargados de
tratar con aquellas operaciones que requieren una gran cantidad de tiempo, ejecutn-
dolas en segundo plano.
En este contexto, I CE proporciona una abstraccin de hilo muy sencilla que po-
sibilita el desarrollo de aplicaciones multihilo altamente portables e independientes
de la plataforma de hilos nativa. Esta abstraccin est representada por la clase IceU-
til::Thread.
Como se puede apreciar en el siguiente listado de cdigo, Thread es una clase
abstracta con una funcin virtual pura denominada run(). El desarrollador ha de im-
plementar esta funcin para poder crear un hilo, de manera que run() se convierta en
el punto de inicio de la ejecucin de dicho hilo. Note que no es posible arrojar ex-
cepciones desde esta funcin. El ncleo de ejecucin de I CE instala un manejador de
excepciones que llama a la funcin ::std::terminate() si se arroja alguna excepcin.
El resto de funciones de Thread son las siguientes:

start(), cuya responsabilidad es arrancar al hilo y llamar a la funcin run(). Es


posible especicar el tamao en bytes de la pila del nuevo hilo, as como un valor
de prioridad. El valor de retorno de start() es un objeto de tipo ThreadControl,
el cual se discutir ms adelante.
getThreadControl(), que devuelve el objeto de la clase ThreadControl asocia-
do al hilo.
[246] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Listado 7.17: La clase IceUtil::Thread


1 class Thread :virtual public Shared {
2 public:
3
4 // Funcin a implementar por el desarrollador.
5 virtual void run () = 0;
6
7 ThreadControl start (size_t stBytes = 0);
8 ThreadControl start (size_t stBytes, int priority);
9 ThreadControl getThreadControl () const;
10 bool isAlive () const;
11
12 bool operator== (const Thread&) const;
13 bool operator!= (const Thread&) const;
14 bool operator< (const Thread&) const;
15 };
16
17 typedef Handle<Thread> ThreadPtr;

id(), que devuelve el identicador nico asociado al hilo. Este valor depender
del soporte de hilos nativo (por ejemplo, POSIX pthreads).
isAlive(), que permite conocer si un hilo est en ejecucin, es decir, si ya se
llam a start() y la funcin run() no termin de ejecutarse.
La sobrecarga de los operadores de comparacin permiten hacer uso de hilos
en contenedores de STL que mantienen relaciones de orden.

Para mostrar cmo llevar a cabo la implementacin de un hilo especco a par-


tir de la biblioteca de I CE se usar como ejemplo uno de los problemas clsicos de
sincronizacin: el problema de los lsofos comensales.
Bsicamente, los lsofos se encuentran comiendo o pensando. Todos comparten
una mesa redonda con cinco sillas, una para cada lsofo. Cada lsofo tiene un plato
individual con arroz y en la mesa slo hay cinco palillos, de manera que cada lsofo
tiene un palillo a su izquierda y otro a su derecha.

Listado 7.18: La clase FilosofoThread


1 #ifndef __FILOSOFO__
2 #define __FILOSOFO__
3
4 #include <iostream>
5 #include <IceUtil/Thread.h>
6 #include <Palillo.h> Figura 7.15: Abstraccin gr-
7 ca del problema de los lsofos
8 #define MAX_COMER 3 comensales, donde cinco lsofos
9 #define MAX_PENSAR 7 piensan y comparten cinco palillos
10 para comer.
11 using namespace std;
12
13 class FilosofoThread : public IceUtil::Thread {
14
15 public:
16 FilosofoThread (const int& id, Palillo* izq, Palillo *der);
17
18 virtual void run ();
19
20 private:
7.6. La biblioteca de hilos de I CE [247]

21 void coger_palillos ();


22 void dejar_palillos ();
23 void comer () const;
24 void pensar () const;
25
26 int _id;
27 Palillo *_pIzq, *_pDer;
28 };
29
30 #endif

C7
Sincronizacin Cuando un lsofo piensa, entonces se abstrae del mundo y no se relaciona con
El problema de los lsofos comen- ningn otro lsofo. Cuando tiene hambre, entonces intenta coger a los palillos que
sales es uno de los problemas clsi- tiene a su izquierda y a su derecha (necesita ambos). Naturalmente, un lsofo no
cos de sincronizacin y existen mu- puede quitarle un palillo a otro lsofo y slo puede comer cuando ha cogido los dos
chas modicaciones del mismo que palillos. Cuando un lsofo termina de comer, deja los palillos y se pone a pensar.
permiten asimilar mejor los con-
ceptos tericos de la programacin La solucin que se discutir en esta seccin se basa en implementar el lsofo
concurrente. como un hilo independiente. Para ello, se crea la clase FilosofoThread que se expone
en el anterior listado de cdigo.
La implementacin de la funcin run() es trivial a partir de la descripcin del
enunciado del problema.

Listado 7.19: La funcin FilosofoThread::run()


1 void
2 FilosofoThread::run ()
3 {
4 while (true) {
5 coger_palillos();
6 comer();
7 dejar_palillos();
8 pensar();
9 }
10 }

El problema de concurrencia viene determinado por el acceso de los lsofos a los


palillos, los cuales representan la seccin crtica asociada a cada uno de los hilos que
implementan la vida de un lsofo. En otras palabras, es necesario establecer algn
tipo de mecanismo de sincronizacin para garantizar que dos lsofos no cogen un
mismo palillo de manera simultnea. Antes de abordar esta problemtica, se mostrar
cmo lanzar los hilos que representan a los cinco lsofos.
El siguiente listado de cdigo muestra el cdigo bsico necesario para lanzar los
lsofos (hilos). Note cmo en la lnea 25 se llama a la funcin start() de Thread
para comenzar la ejecucin del mismo. Los objetos de tipo ThreadControl devueltos
se almacenan en un vector para, posteriormente, unir los hilos creados. Para ello, se
de la funcin join() de la clase ThreadControl, tal y como se muestra en la
hace uso
lnea 31 .

7.6.3. Exclusin mutua bsica


Locking and unlocking
El problema de los lsofos plantea la necesidad de algn tipo de mecanismo de
Tpicamente, las operaciones para sincronizacin bsico para garantizar el acceso exclusivo sobre cada uno de los pali-
adquirir y liberar un cerrojo se de- llos. La opcin ms directa consiste en asociar un cerrojo a cada uno de los palillos
nominan lock() y unlock(), respec-
tivamente. La metfora de cerrojo de manera individual. As, si un lsofo intenta coger un palillo que est libre, enton-
representa perfectamente la adqui- ces lo coger adquiriendo el cerrojo, es decir, cerrndolo. Si el palillo est ocupado,
sicin y liberacin de recursos com- entonces el lsofo esperar hasta que est libre.
partidos.
[248] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Listado 7.20: Creacin y control de hilos con I CE


1 #include <IceUtil/Thread.h>
2 #include <vector>
3 #include <Palillo.h>
4 #include <Filosofo.h>
5
6 #define NUM 5
7
8 int main
9 (int argc, char *argv[]) {
10 std::vector<Palillo*> palillos;
11 std::vector<IceUtil::ThreadControl> threads;
12 int i;
13
14 // Se instancian los palillos.
15 for (i = 0; i < NUM; i++)
16 palillos.push_back(new Palillo);
17
18 // Se instancian los filsofos.
19 for (i = 0; i < NUM; i++) {
20 // Cada filsofo conoce los palillos
21 // que tiene a su izda y derecha.
22 IceUtil::ThreadPtr t =
23 new FilosofoThread(i, palillos[i], palillos[(i + 1) % NUM]);
24 // start sobre hilo devuelve un objeto ThreadControl.
25 threads.push_back(t->start());
26 }
27
28 // Unin de los hilos creados.
29 std::vector<IceUtil::ThreadControl>::iterator it;
30 for (it = threads.begin(); it != threads.end(); ++it)
31 it->join();
32
33 return 0;
34 }

I CE proporciona la clase Mutex para modelar esta problemtica de una forma sen-
cilla y directa.
Las funciones miembro ms importantes de esta clase son las que permiten adqui-
rir y liberar el cerrojo:

lock(), que intenta adquirir el cerrojo. Si ste ya estaba cerrado, entonces el hilo
que invoc a la funcin se suspende hasta que el cerrojo quede libre. La llamada
a dicha funcin retorna cuando el hilo ha adquirido el cerrojo.
tryLock(), que intenta adquirir el cerrojo. A diferencia de lock(), si el cerrojo
est cerrado, la funcin devuelve false. En caso contrario, devuelve true con el
cerrojo cerrado.
unlock(), que libera el cerrojo.

Listado 7.21: La clase IceUtil::Mutex


1 class Mutex {
2 public:
3 Mutex (); Figura 7.16: Los cerrojos o mutex
4 Mutex (MutexProtocol p); representan un mecanismo de sin-
5 ~Mutex ();
6
cronizacin muy bsico pero am-
7 void lock () const; pliamente utilizado.
8 bool tryLock () const;
9 void unlock () const;
10
7.6. La biblioteca de hilos de I CE [249]

11 typedef LockT<Mutex> Lock;


12 typedef TryLockT<Mutex> TryLock;
13 };

Es importante considerar que la clase Mutex proporciona un mecanismo de ex-


clusin mutua bsico y no recursivo, es decir, no se debe llamar a lock() ms de una
vez desde un hilo, ya que esto provocar un comportamiento inesperado. Del mismo
modo, no se debera llamar a unlock() a menos que un hilo haya adquirido previamen-
te el cerrojo mediante lock(). En la siguiente seccin se estudiar otro mecanismo de

C7
sincronizacin que mantiene una semntica recursiva.
La clase Mutex se puede utilizar para gestionar el acceso concurrente a los palillos.
En la solucin planteada a continuacin, un palillo es simplemente una especializacin
de IceUtil::Mutex con el objetivo de incrementar la semntica de dicha solucin.
Riesgo de interbloqueo
Listado 7.22: La clase Palillo
Si todos los lsofos cogen al mis-
mo tiempo el palillo que est a su 1 #ifndef __PALILLO__
izquierda se producir un interblo- 2 #define __PALILLO__
queo ya que la solucin planteada 3
no podra avanzar hasta que un l- 4 #include <IceUtil/Mutex.h>
sofo coja ambos palillos. 5
6 class Palillo : public IceUtil::Mutex {
7 };
8
9 #endif

Para que los lsofos puedan utilizar los palillos, habr que utilizar la funcionali-
dad previamente discutida, es decir, las funciones lock() y unlock(), en las funciones
coger_palillos() y dejar_palillos(). La solucin planteada garantiza que no se ejecuta-
rn dos llamadas a lock() sobre un palillo por parte de un mismo hilo, ni tampoco una
llamada sobre unlock() si previamente no se adquiri el palillo.

Listado 7.23: Acceso concurrente a los palillos


1 void
2 FilosofoThread::coger_palillos ()
3 {
4 _pIzq->lock();
5 _pDer->lock();
6 }
7
8 void
9 FilosofoThread::dejar_palillos ()
10 {
11 _pIzq->unlock();
12 _pDer->unlock();
13 }

La solucin planteada es poco exible debido a que los lsofos estn inactivos
durante el periodo de tiempo que pasa desde que dejan de pensar hasta que cogen
los dos palillos. Una posible variacin a la solucin planteada hubiera sido continuar
pensando (al menos hasta un nmero mximo de ocasiones) si los palillos estn ocu-
Gestin del deadlock pados. En esta variacin se podra utilizar la funcin tryLock() para modelar dicha
Aunque existen diversos esquemas problemtica.
para evitar y recuperarse de un in-
terbloqueo, los sistemas operativos
modernos suelen optar por asumir
que nunca se producirn, delegan-
do en el programador la implemen-
tacin de soluciones seguras.
[250] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Evitando interbloqueos

El uso de las funciones lock() y unlock() puede generar problemas importantes si,
por alguna situacin no controlada, un cerrojo previamente adquirido con lock() no
se libera posteriormente con una llamada a unlock(). El siguiente listado de cdigo
muestra esta problemtica.

Listado 7.24: Potencial interbloqueo


1 #include <IceUtil/Mutex.h>
2
3 class Test {
4 public:
5 void mi_funcion () {
6 _mutex.lock();
7 for (int i = 0; i < 5; i++)
8 if (i == 3) return; // Generar un problema...
9 _mutex.unlock();
10 }
11
12 private:
13 IceUtil::Mutex _mutex;
14 };
15
16 int main (int argc, char *argv[]) {
17 Test t;
18 t.mi_funcion();
19 return 0;
20 }


En la lnea 8 , la sentencia return implica abandonar
mi_funcion() sin haber li-
berado el cerrojo previamente adquirido en la lnea 6 . En este contexto, es bastante
probable que se genere una potencial situacin de interbloqueo que paralizara la eje-
cucin del programa. La generacin de excepciones no controladas representa otro
caso representativo de esta problemtica.
Para evitar este tipo de problemas, la clase Mutex proporciona las deniciones
de tipo Lock y TryLock, que representan plantillas muy sencillas compuestas de un
constructor en el que se llama a lock() y tryLock(), respectivamente. En el destructor se
llama a unlock() si el cerrojo fue previamente adquirido cuando la plantilla quede fuera
de mbito. En el ejemplo anterior, sera posible garantizar la liberacin del cerrojo al
ejecutar return, ya que quedara fuera del alcance de la funcin. El siguiente listado
de cdigo muestra la modicacin realizada para evitar interbloqueos.

Listado 7.25: Evitando interbloqueos con Lock


1 #include <IceUtil/Mutex.h>
2
3 class Test {
4 public:
5 void mi_funcion () {
6 IceUtil::Mutex::Lock lock(_mutex);
7 for (int i = 0; i < 5; i++)
8 if (i == 3) return; // Ningn problema...
9 } // El destructor de lock libera el cerrojo.
10
11 private:
12 IceUtil::Mutex _mutex;
13 };
7.6. La biblioteca de hilos de I CE [251]

Es recomendable usar siempre Lock y TryLock en lugar de las funciones lock()


y unlock para facilitar el entendimiento y la mantenibilidad del cdigo.

7.6.4. Flexibilizando el concepto de mutex

C7
No olvides... Adems de proporcionar cerrojos con una semntica no recursiva, I CE tambin
proporciona la clase IceUtil::RecMutex con el objetivo de que el desarrollador pueda
Liberar un cerrojo slo si fue pre-
viamente adquirido y llamar a un- manejar cerrojos recursivos. La interfaz de esta nueva clase es exactamente igual que
lock() tantas veces como a lock() la clase IceUtil::Mutex.
para que el cerrojo quede disponi-
ble para otro hilo. Sin embargo, existe una diferencia fundamental entre ambas. Internamente, el ce-
rrojo recursivo est implementado con un contador inicializado a cero. Cada llamada
a lock() incrementa el contador, mientras que cada llamada a unlock() lo decrementa.
El cerrojo estar disponible para otro hilo cuando el contador alcance el valor de cero.

Listado 7.26: La clase IceUtil::RecMutex


1 class RecMutex {
2 public:
3 RecMutex ();
4 RecMutex (MutexProtocol p);
5 ~RecMutex ();
6
7 void lock () const;
8 bool tryLock () const;
9 void unlock () const;
10
11 typedef LockT<RecMutex> Lock;
12 typedef TryLockT<RecMutex> TryLock;
13 };

7.6.5. Introduciendo monitores


Tanto la clase Mutex como la clase MutexRec son mecanismos de sincronizacin
bsicos que permiten que slo un hilo est activo, en un instante de tiempo, dentro
de la seccin crtica. En otras palabras, para que un hilo pueda acceder a la seccin
crtica, otro ha de abandonarla. Esto implica que, cuando se usan cerrojos, no resulta
posible suspender un hilo dentro de la seccin crtica para, posteriormente, despertarlo
cuando se cumpla una determinada condicin.
Para tratar este tipo de problemas, la biblioteca de hilos de I CE proporciona la clase
Monitor. En esencia, un monitor es un mecanismo de sincronizacin de ms alto nivel
que, al igual que un cerrojo, protege la seccin crtica y garantiza que solamente pueda
existir un hilo activo dentro de la misma. Sin embargo, un monitor permite suspender
un hilo dentro de la seccin crtica posibilitando que otro hilo pueda acceder a la
misma. Este segundo hilo puede abandonar el monitor, liberndolo, o suspenderse
Solucin de alto nivel dentro del monitor. De cualquier modo, el hilo original se despierta y continua su
ejecucin dentro del monitor. Este esquema es escalable a mltiples hilos, es decir,
Las soluciones de alto nivel permi- varios hilos pueden suspenderse dentro de un monitor.
ten que el desarrollador tenga ms
exibilidad a la hora de solucionar
un problema. Este planteamiento se
aplica perfectamente al uso de mo-
nitores.
[252] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Datos
compartidos
x
Condiciones
y

Operaciones

Cdigo
inicializacin

Figura 7.17: Representacin grca del concepto de monitor.

Desde un punto de vista general, los monitores proporcionan un mecanismo de


sincronizacin ms exible que los cerrojos, ya que es posible que un hilo compruebe
una condicin y, si sta es falsa, el hilo se pause. Si otro hilo cambia dicha condicin,
entonces el hilo original contina su ejecucin.
El siguiente listado de cdigo muestra la declaracin de la clase IceUtil::Monitor.
Note que se trata de una clase que hace uso de plantillas y que requiere como parme-
tro bien Mutex o RecMutex, en funcin de si el monitor mantendr una semntica no
recursiva o recursiva, respectivamente.

Listado 7.27: La clase IceUtil::Monitor


1 template <class T>
2 class Monitor {
3 public:
4 void lock () const;
5 void unlock () const;
6 bool tryLock () const;
7
8 void wait () const;
9 bool timedWait (const Time&) const;
10 void notify ();
11 void notifyAll ();
12
13 typedef LockT<Monitor<T> > Lock;
14 typedef TryLockT<Monitor<T> > TryLock;
15 };

Las funciones miembro de esta clase son las siguientes:

lock(), que intenta adquirir el monitor. Si ste ya estaba cerrado, entonces el hilo
que la invoca se suspende hasta que el monitor quede disponible. La llamada
retorna con el monitor cerrado.
tryLock(), que intenta adquirir el monitor. Si est disponible, la llamada de- No olvides...
vuelve true con el monitor cerrado. Si ste ya estaba cerrado antes de relizar la
Comprobar la condicin asociada al
llamada, la funcin devuelve false. uso de wait siempre que se retorne
de una llamada a la misma.
unlock(), que libera el monitor. Si existen hilos bloqueados por el mismo, en-
tonces uno de ellos se despertar y cerrar de nuevo el monitor.
7.6. La biblioteca de hilos de I CE [253]

wait(), que suspende al hilo que invoca a la funcin y, de manera simultnea,


libera el monitor. Un hilo suspendido por wait() se puede despertar por otro hilo
que invoque a la funcin notify() o notifyAll(). Cuando la llamada retorna, el
hilo suspendido contina su ejecucin con el monitor cerrado.
timedWait(), que suspende al hilo que invoca a la funcin hasta un tiempo es-
pecicado por parmetro. Si otro hilo invoca a notify() o notifyAll() despertando
al hilo suspendido antes de que el timeout expire, la funcin devuelve true y el
hilo suspendido resume su ejecucin con el monitor cerrado. En otro caso, es

C7
decir, si el timeout expira, timedWait() devuelve false.
notify(), que despierta a un nico hilo suspendido debido a una invocacin so-
bre wait() o timedWait(). Si no existiera ningn hilo suspendido, entonces la
invocacin sobre notify() se pierde. Llevar a cabo una noticacin no implica
que otro hilo reanude su ejecucin inmediatamente. En realidad, esto ocurrira
cuando el hilo que invoca a wait() o timedWait() libera el monitor.
notifyAll(), que despierta a todos los hilos suspendidos por wait() o timedWait().
El resto del comportamiento derivado de invocar a esta funcin es idntico a
notify().

Ejemplo de uso de monitores

Imagine que desea modelar una situacin en un videojuego en la que se controlen


los recursos disponibles para acometer una determinada tarea. Estos recursos se pue-
den manipular desde el punto de vista de la generacin o produccin y desde el punto
de vista de la destruccin o consumicin. En otras palabras, el problema clsico del
productor/consumidor.
Por ejemplo, imagine un juego de accin de tercera persona en el que el personaje
es capaz de acumular slots para desplegar algn tipo de poder especial. Inicialmente,
el personaje tiene los slots vacos pero, conforme el juego evoluciona, dichos slots
se pueden rellenar atendiendo a varios criterios independientes. Por ejemplo, si el
jugador acumula una cantidad determinada de puntos, entonces obtendra un slot. Si
el personaje principal vence a un enemigo con un alto nivel, entonces obtendra otro
slot. Por otra parte, el jugador podra hacer uso de estas habilidades especiales cuando
as lo considere, siempre y cuando tenga al menos un slot relleno.
Este supuesto plantea la problemtica de sincronizar el acceso concurrente a di-
chos slots, tanto para su consumo como para su generacin. Adems, hay que tener en
cuenta que slo ser posible consumir un slot cuando haya al menos uno disponible.
Es decir, la problemtica discutida tambin plantea ciertas restricciones o condiciones
que se han de satisfacer para que el jugador pueda lanzar una habilidad especial.
En este contexto, el uso de los monitores proporciona gran exibilidad para mo-
delar una solucin a este supuesto. El siguiente listado de cdigo muestra una posible
implementacin de la estructura de datos que podra dar soporte a la solucin plantea-
da, haciendo uso de los monitores de la biblioteca de hilos de I CE.
Como se puede apreciar, la clase denida es un tipo particular de monitor sin
semntica recursiva, es decir, denido a partir de IceUtil::Mutex. Dicha clase tiene
como variable miembro una cola de doble entrada que maneja tipos de datos genricos,
ya que la clase denida hace uso de una plantilla. Adems, esta clase proporciona las
dos operaciones tpicas de put() y get() para aadir y obtener elementos. Hay, sin
Figura 7.18: En los juegos arca- embargo, dos caractersticas importantes a destacar en el diseo de esta estructura de
de de aviones, los poderes especia- datos:
les se suelen representar con cohe-
tes para denotar la potencia de los
mediante el
1. El acceso concurrente a la variable miembro de la clase se controla
mismos. propio monitor, haciendo uso de la funcin lock() (lneas 11 y 17 ).
[254] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

: Consumidor : Queue : Productor

get ()

wait ()

notify put ()

return item

Figura 7.19: Representacin grca del uso de un monitor.

Listado 7.28: Utilizando monitores


1 #include <IceUtil/Monitor.h>
2 #include <IceUtil/Mutex.h>
3 #include <deque>
4
5 using namespace std;
6
7 template<class T>
8 class Queue : public IceUtil::Monitor<IceUtil::Mutex> {
9 public:
10 void put (const T& item) { // Aade un nuevo item.
11 IceUtil::Monitor<IceUtil::Mutex>::Lock lock(*this);
12 _queue.push_back(item);
13 notify();
14 }
15
16 T get () { // Consume un item.
17 IceUtil::Monitor<IceUtil::Mutex>::Lock lock(*this);
18 while (_queue.size() == 0)
19 wait();
20 T item = _queue.front();
21 _queue.pop_front();
22 return item;
23 }
24
25 private:
26 deque<T> _queue; // Cola de doble entrada.
27 };

no contiene
2. Si la estructura elementos, entonces el hilo se suspende mediante
wait() (lneas 18-19 ) hasta
que otro hilo que ejecute put() realice la invocacin
sobre notify() (lnea 13 ).
7.6. La biblioteca de hilos de I CE [255]

Recuerde que para que un hilo bloqueado por wait() reanude su ejecucin, otro
hilo ha de ejecutar notify() y liberar el monitor mediante unlock(). Sin embargo, en
el anterior listado de cdigo no existe ninguna llamada explcita a unlock(). Es inco-
rrecta la solucin? La respuesta es no, ya que la liberacin del monitor se delega en
Lock cuando la funcin put() naliza, es decir, justo despus de ejecutar la operacin
notify() en este caso particular.
No olvides... Volviendo al ejemplo anterior, considere dos hilos distintos que interactan con
Usar Lock y TryLock para evitar po-
la estructura creada, de manera genrica, para almacenar los slots que permitirn la

C7
sibles interbloqueos causados por la activacin de habilidades especiales por parte del jugador virtual. Por ejemplo, el hilo
generacin de alguna excepcin o asociado al productor podra implementarse como se muestra en el siguiente listado.
una terminacin de la funcin no
prevista inicialmente.

Listado 7.29: Hilo productor de slots


1 class Productor : public IceUtil::Thread {
2 public:
3 Productor (Queue<string> *_q):
4 _queue(_q) {}
5
6 void run () {
7 for (int i = 0; i < 5; i++) {
8 IceUtil::ThreadControl::sleep
9 (IceUtil::Time::seconds(rand() % 7));
10 _queue->put("TestSlot");
11 }
12 }
13
14 private:
15 Queue<string> *_queue;
16 };

Suponiendo que el cdigo del consumidor sigue la misma estructura, pero extra-
yendo elementos de la estructura de datos compartida, entonces sera posible lanzar
distintos hilos para comprobar que el acceso concurrente sobre los distintos slots se
realiza de manera adecuada. Adems, sera sencillo visualizar, mediante mensajes por
la salida estndar, que efectivamente los hilos consumidores se suspenden en wait()
hasta que hay al menos algn elemento en la estructura de datos compartida con los
productores.
Antes de pasar a discutir en la seccin 7.9 un caso de estudio para llevar a cabo
el procesamiento de alguna tarea en segundo plano, resulta importante volver a dis-
cutir la solucin planteada inicialmente para el manejo de monitores. En particular,
la implementacin de las funciones miembro put() y get() puede generar sobrecarga
debido a que, cada vez que se aade un nuevo elemento a la estructura de datos, se
realiza una invocacin. Si no existen hilos esperando, la noticacin se pierde. Aun-
que este hecho no conlleva ningn efecto no deseado, puede generar una reduccin
del rendimiento si el nmero de noticaciones se dispara.
Una posible solucin a este problema consiste en llevar un control explcito de
Informacin adicional la existencia de consumidores de informacin, es decir, de hilos que invoquen a la
funcin get(). Para ello, se puede incluir una variable miembro en la clase Queue,
Una vez el ms, el uso de alguna planteando un esquema mucho ms eciente.
estructura de datos adicional puede
facilitar el diseo de una solucin. Como se puede apreciar en el siguiente listado de cdigo, el hilo productor slo
Este tipo de planteamientos puede
incrementar la eciencia de la so-
llevar a cabo una noticacin en el caso de que haya algn hilo consumidor en espera.
lucin planteada aunque haya una Para ello, consulta el valor de la variable miembro _consumidoresEsperando.
mnima sobrecarga debido al uso y
procesamiento de datos extra.
[256] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Por otra parte, los hilos consumidores, es decir, los que invoquen a la funcin get()
incrementan dicha variable antes de realizar un wait(), decrementndola cuando se
despierten.
Note que el acceso a la variable miembro _consumidoresEsperando es exclusivo
y se garantiza gracias a la adquisicin del monitor justo al ejecutar la operacin de
generacin o consumo de informacin por parte de algn hilo.

Listado 7.30: Utilizando monitores (II)


1 #include <IceUtil/Monitor.h>
2 #include <IceUtil/Mutex.h>
3 #include <deque>
4 using namespace std;
5
6 template<class T>
7 class Queue : public IceUtil::Monitor<IceUtil::Mutex> {
8 public:
9 Queue () : _consumidoresEsperando(0) {}
10
11 void put (const T& item) { // Aade un nuevo item.
12 IceUtil::Monitor<IceUtil::Mutex>::Lock lock(*this);
13 _queue.push_back(item);
14 if (_consumidoresEsperando) notify();
15 }
16
17 T get () { // Consume un item.
18 IceUtil::Monitor<IceUtil::Mutex>::Lock lock(*this);
19 while (_queue.size() == 0) {
20 try {
21 _consumidoresEsperando++;
22 wait();
23 _consumidoresEsperando--;
24 }
25 catch (...) {
26 _consumidoresEsperando--;
27 throw;
28 }
29 }
30 T item = _queue.front();
31 _queue.pop_front();
32 return item;
33 }
34
35 private:
36 deque<T> _queue; // Cola de doble entrada.
37 int _consumidoresEsperando;
38 };

7.7. Concurrencia en C++11


Una de las principales mejoras aportadas por C++11 es el soporte a la progra-
macin concurrente. De hecho, la creacin de hilos resulta trivial, gracias a la clase
Thread8 , y sigue la misma losofa que en otras bibliotecas ms tradicionales (como
pthread):
Para poder compilar este programa con la ltima versin del estndar puede utili-
zar el siguiente comando:

$ g++ -std=c++11 Thread_c++11.cpp -o Thread -pthread


8 http://es.cppreference.com/w/cpp/thread
7.7. Concurrencia en C++11 [257]

Listado 7.31: Uso bsico de la clase Thread


1 #include <iostream>
2 #include <thread>
3 using namespace std;
4
5 void func (int x) {
6 cout << "Dentro del hilo " << x << endl;

C7
7 }
8
9 int main() {
10 thread th(&func, 100);
11 th.join();
12 cout << "Fuera del hilo" << endl;
13 return 0;
14 }

El concepto de mutex9 como mecanismo bsico de exclusin mutua tambin est


contemplado por el lenguaje y permite acotar, de una manera sencilla y directa, aque-
llas partes del cdigo que solamente deben ejecutarse por un hilo en un instante de
tiempo (seccin crtica). Por ejemplo, el siguiente fragmento de cdigo muestra c-
mo utilizar un mutex para controlar el acceso exclusivo a una variable compartida por
varios hilos:

Listado 7.32: Uso bsico de la clase Mutex


1 int contador = 0;
2 mutex contador_mutex;
3
4 void anyadir_doble (int x) {
5 int tmp = 2 * x;
6 contador_mutex.lock();
7 contador += tmp;
8 contador_mutex.unlock();
9 }

C++11 ofrece incluso mecanismos que facilitan y simplican el cdigo de manera


sustancial. Por ejemplo, la funcionalidad asociada al listado anterior se puede obtener,
de manera alternativa, mediante el contenedor atomic:

Listado 7.33: Utilizando el contenedor atomic


1 #include <atomic>
2
3 atomic<int> contador(0);
4
5 void anyadir_doble (int x) {
6 contador += 2 * x;
7 }

9 http://es.cppreference.com/w/cpp/thread/mutex
[258] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

7.7.1. Filsofos comensales en C++11


En esta seccin se discutir una posible implementacin del problema de los l-
sofos comensales utilizando la funcionalidad proporcionada, desde el punto de vista
de la programacin concurrente, de C++11. As mismo, tambin se introducir el uso
de algunas de las mejoras que incluye esta nueva versin del estndar, las cuales se
discutirn con mayor profundidad en el mdulo 3, Tcnicas Avanzadas de Desarrollo.
El siguiente listado de cdigo muestra la implementacin de la clase Palillo. A di-
ferencia del esquema planteado en la seccin 7.6.3, esta implementacin se basa en la
composicin en lugar de herencia con el objetivo de discutir las diferencias existentes
entre ambos enfoques a la hora de manipular mutex. En principio, la primera opcin
a considerar debera ser la composicin, es decir, un esquema basado en incluir una
variable miembro de tipo mutex dentro de la declaracin de la clase asociada al recur-
so compartido. Recuerde que las relaciones de herencia implican fuertes restricciones
desde el punto de vista del diseo y se deberan establecer cuidadosamente.

Listado 7.34: Filsofos comensales en C++11 (Palillo)


1 class Palillo {
2 public:
3 mutex _mutex;
4 };

A continuacin se muestra la funcin comer(), la cual incluye la funcionalidad


asociada a coger los palillos. Note cmo esta funcin lambda o funcin annima se
dene dentro del propio cdigo de la funcin main (en lnea) y propicia la genera-
cin de un cdigo ms claro y directo. Adems, el uso de auto oculta la complejidad

asociada al manejo de punteros a funciones.
Esta funcin recibe como argumentos (lneas 4-5 ) apuntadores a dos palillos,
junto a sus identicadores, y el identicador del lsofo que va a intentar cogerlos para
es interesante resaltar el uso de std::lock sobre los propios
comer. En primer lugar,
palillos en la lnea 7 para evitar el problema clsico del interbloqueo de los lsofos
comensales. Esta funcin hace uso de un algoritmo de evasin del interbloqueo y
permite adquirir dos o ms mutex mediante una
nica instruccin. As mismo, tambin
se utiliza el wrapper lock_guard (lneas 12 y 17 ) con el objetivo de indicar de manera
explcita que los mutex han sido adquiridos y que stos deben adoptar el propietario
de dicha adquisicin.
El resto del cdigo de esta funcin muestra por la salida estndar mediante std::cout
mensajes que indican el lsofo ha adquirido los palillos.
Una vez denida la funcionalidad general asociada a un lsofo, el siguiente paso
consiste en instanciar los palillos a los propios lsofos.

Listado 7.35: Filsofos comensales en C++11 (Funcin comer)


1 int main () {
2 /* Funcin lambda (funcin annima) */
3 /* Adis a los punteros a funciones con auto */
4 auto comer = [](Palillo* pIzquierdo, Palillo* pDerecho,
5 int id_filosofo, int id_pIzquierdo, int id_pDerecho) {
6 /* Para evitar interbloqueos */
7 lock(pIzquierdo->_mutex, pDerecho->_mutex);
8
9 /* Wrapper para adquirir el mutex en un bloque de cdigo */
10 /* Indica que el mutex ha sido adquirido y que debe
11 adoptar al propietario del cierre */
12 lock_guard<mutex> izquierdo(pIzquierdo->_mutex, adopt_lock);
13 string si = "\tFilsofo " + to_string(id_filosofo) +
7.7. Concurrencia en C++11 [259]

14 " cogi el palillo " + to_string(id_pIzquierdo) + ".\n";


15 cout << si.c_str();
16
17 lock_guard<mutex> derecho(pDerecho->_mutex, adopt_lock);

18 string sd = "\tFilsofo " + to_string(id_filosofo) +


19 " cogi el palillo " + to_string(id_pDerecho) + ".\n";
20 cout << sd.c_str();
21
22 string pe = "Filsofo " + to_string(id_filosofo) + " come.\n";
23 cout << pe;

C7
24
25 std::chrono::milliseconds espera(1250);
26 std::this_thread::sleep_for(espera);
27
28 /* Los mutex se desbloquean al salir de la funcin */
29 };


El listado que se muestra a continuacin lleva a cabo la instanciacin de los pali-
llos (ver lnea 9 ). stos se almacenan en un contenedor
vector que inicialmente tiene
una capacidad igual al nmero de lsofos (lnea 1 ). Resulta interesante destacar el
uso de unique_ptr en la lnea 4 para que el programador se olvide de la liberacin de
los punteros asociados, la cual tendr lugar, gracias a este enfoque, cuando queden
fuera
del mbito de declaracin. Tambin se utiliza la funcin std::move en la lnea
, novedad en C++11, para copiar el puntero a p1 en el vector palillos, anulndolo
12
tras su uso.

Listado 7.36: Filsofos comensales en C++11 (Creacin palillos)


1 static const int numero_filosofos = 5;
2
3 /* Vector de palillos */
4 vector< unique_ptr<Palillo> > palillos(numero_filosofos);
5
6 for (int i = 0; i < numero_filosofos; i++) {
7 /* El compilador infiere el tipo de la variable :-) */
8 /* unique_ptr para destruccin de objetos fuera del scope */
9 auto p1 = unique_ptr<Palillo>(new Palillo());
10 /* move copia c1 en la posicin adecuada de palillos y
11 lo anula para usos posteriores */
12 palillos[i] = move(p1);
13 }

Finalmente, el cdigo restante que se expone a continuacin es el responsable de


de los lsofos. stos se almacenan en un vector que se instancia
la instanciacin
en la lnea 2 , el cual se rellena mediante un bucle for a partir de la lnea 20 . Sin
embargo, antes se inserta el lsofo
que tiene a su derecha el primer palillo y a su
izquierda el ltimo (lneas 6-17 ).
Note cmo en cada iteracin
de este bucle se crea un nuevo hilo, instanciando
la clase thread (lnea 21 ). Cada instancia de esta clase maneja un apuntador a la
funcin comer, la cual recibe como argumentos los apuntadores a los palillos que
correspondan a cada lsofo y los identicadores correspondientes para mostrar por

la salida estndar los mensajes reejan la evolucin de la simulacin.
La ltima parte del cdigo, en las lneas 32-34 , lleva a cabo la ejecucin efec-
tiva de los hilos y la espera a la nalizacin de los mismos mediante la funcin th-
read::join().

Listado 7.37: Filsofos comensales en C++11 (Creacin lsofos)


1 /* Vector de filsofos */
[260] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

2 vector<thread> filosofos(numero_filosofos);
3
4 /* Primer filsofo */
5 /* A su derecha el palillo 1 y a su izquierda el palillo 5 */
6 filosofos[0] = thread(comer,
7 /* Palillo derecho (1) */
8 palillos[0].get(),
9 /* Palillo izquierdo (5) */
10 palillos[numero_filosofos - 1].get(),
11 /* Id filsofo */
12 1,
13 /* Id palillo derecho */
14 1,
15 /* Id palillo izquierdo */
16 numero_filosofos
17 );
18
19 /* Restos de filsofos */
20 for (int i = 1; i < numero_filosofos; i++) {
21 filosofos[i] = (thread(comer,
22 palillos[i - 1].get(),
23 palillos[i].get(),
24 i + 1,
25 i,
26 i + 1
27 )
28 );
29 }
30
31 /* A comer... */
32 for_each(filosofos.begin(),
33 filosofos.end(),
34 mem_fn(&thread::join));

7.8. Multi-threading en Ogre3D


Desde un punto de vista general, la posible integracin de hilos de ejecucin adi-
cionales con respecto al ncleo de ejecucin de Ogre se suele plantear con aspectos
diferentes al meramente grco. De hecho, es poco comn que el proceso de rende-
ring se delegue en un segundo plano debido a la propia naturaleza asncrona de la
GPU (Graphic Processing Unit). Por lo tanto, no existe una necesidad real de utili-
zar algn esquema de sincronizacin para evitar condiciones de carrera o asegurar la
exclusin mutua en determinadas partes del cdigo.
Sin embargo, s que es posible delegar en un segundo plano aspectos tradicional-
mente independientes de la parte grca, como por ejemplo el mdulo de Inteligencia
Articial o la carga de recursos. En esta seccin, se discutirn las caractersticas bsi-
cas que Ogre3D ofrece para la carga de recursos en segundo plano.
En primer lugar, puede ser necesario compilar Ogre con soporte para multi-threading,
tpicamente soportado por la biblioteca boost. Para ejemplicar este problema, a con-
tinuacin se detallan las instrucciones para compilar el cdigo fuente de Ogre 1.8.110 .
Antes de nada, es necesario asegurarse de que la biblioteca de hilos de boost est
instalada y es recomendable utilizar cmake para generar el Makele que se utilizar
para compilar el cdigo fuente. Para ello, en sistemas operativos Debian GNU/Linux
y derivados, hay que ejecutar los siguientes comandos: Figura 7.20: Ogre proporciona me-
canismos para llevar a cabo la carga
de recursos en segundo plano.
$ sudo apt-get install libboost-thread-dev
$ sudo apt-get install cmake cmake-qt-gui
10 http://www.ogre3d.org/download/source
7.8. Multi-threading en Ogre3D [261]

A continuacin, es necesario descomprimir el cdigo de Ogre y crear un directorio


en el que se generar el resultado de la compilacin:

$ tar xjf ogre_src_v1-8-1.tar.bz2


$ mkdir ogre_build_v1-8-1

El siguiente paso consiste en ejecutar la interfaz grca que nos ofrece cmake para
hacer explcito el soporte multi-hilo de Ogre. Para ello, simplemente hay que ejecutar
el comando cmake-gui y, a continuacin, especicar la ruta en la que se encuentra el

C7
cdigo fuente de Ogre y la ruta en la que se generar el resultado de la compilacin,
como se muestra en la gura 7.21. Despus, hay que realizar las siguientes acciones:

1. Hacer click en el botn Congure.


2. Ajustar el parmetro OGRE_CONFIG_THREADS11 .
3. Hacer click de nuevo en el botn Congure.
4. Hacer click en el botn Generate para generar el archivo Makele.

Es importante recalcar que el parmetro OGRE_CONFIG_THREADS establece


el tipo de soporte de Ogre respecto al modelo de hilos. Un valor de 0 desabilita el
soporte de hilos. Un valor de 1 habilita la carga completa de recursos en segundo
plano. Un valor de 2 activa solamente la preparacin de recursos en segundo plano12 .
Finalmente, tan slo es necesario compilar y esperar a la generacin de todos
los ejecutables y las bibliotecas de Ogre. Note que es posible optimizar este proceso
indicando el nmero de procesadores fsicos de la mquina mediante la opcin j de
make.

$ cd ogre_build_v1-8-1
$ make -j 2

Para ejemplicar la carga de recursos en segundo plano, se retomar el ejemplo


discutido en la seccin 6.2.2. En concreto, el siguiente listado de cdigo muestra cmo
se ha modicado la carga de un track de msica para explicitar que se haga en segundo

plano.
Como se puede apreciar, en la lnea 13 se hace uso de la funcin setBackground-
Loaded() con el objetivo de indicar que el recurso se cargar en segundo plano. A
continuacin, en la siguiente lnea, se aade un objeto de la clase MyListener, la cual
deriva a su vez de la clase Ogre::Resource::Listener para controlar la noticacin de
la carga del recurso en segundo plano.
Callbacks En este ejemplo se ha sobreescrito la funcin backgroundLoadingComplete() que
permite conocer cundo se ha completado la carga en segundo plano del recurso. La
El uso de funciones de retrollama-
da permite especicar el cdigo que documentacin relativa a la clase Ogre::Resource::Listener13 discute el uso de otras
se ejecutar cuando haya nalizado funciones de retrollamada que resultan tiles para cargar contenido en segundo plano.
la carga de un recurso en segundo
plano. A partir de entonces, ser po-
sible utilizarlo de manera normal. Listado 7.38: Carga en segundo plano de un recurso en Ogre
1 TrackPtr
2 TrackManager::load

11 http://www.ogre3d.org/tikiwiki/tiki-index.php?page=Building+Ogre+With+CMake
12 Segn los desarrolladores de Ogre, slo se recomienda utilizar un valor de 0 o de 2 en sistemas GNU/-

Linux
13 http://www.ogre3d.org/docs/api/html/classOgre_1_1Resource_1_

1Listener.html
[262] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

Figura 7.21: Generando el chero Makele mediante cmake para compilar Ogre 1.8.1.

3 (const Ogre::String& name, const Ogre::String& group)


4 {
5 // Obtencin del recurso por nombre...
6 TrackPtr trackPtr = getByName(name);
7
8 // Si no ha sido creado, se crea.
9 if (trackPtr.isNull())
10 trackPtr = create(name, group);
11
12 // Carga en segundo plano y listener.
13 trackPtr->setBackgroundLoaded(true);
14 trackPtr->addListener(new MyListener);
15
16 // Escala la carga del recurso en segundo plano.
17 if (trackPtr->isBackgroundLoaded())
18 trackPtr->escalateLoading();
19 else
20 trackPtr->load();
21
22 return trackPtr;
23 }

Otra posible variacin en el diseo podra haber consistido en delegar la notica-


cin de la funcin de retrollamada a la propia clase TrackPtr, es decir, que esta clase
heredase de Ogre::Resource::Listener.
7.9. Caso de estudio. Procesamiento en segundo plano mediante hilos

Listado 7.39: Clase MyListener


1 #ifndef __MYLISTENERH__
2 #define __MYLISTENERH__
3
4 #include <OGRE/OgreResource.h>
5 #include <iostream>
6
7 using namespace std;
8
9 class MyListener : public Ogre::Resource::Listener {
10
11 void backgroundLoadingComplete (Ogre::Resource* r) {
12 cout << "Carga en segundo plano completada..." << endl;
13 }
14
15 };
16
17 #endif

7.9. Caso de estudio. Procesamiento en segundo plano


mediante hilos
En esta seccin se discute cmo llevar a cabo un procesamiento adicional al hilo de
control manejado por Ogre. Para ello, se hace uso de las herramientas proporcionadas
por la biblioteca de hilos de ZeroC I CE, estudiadas en las secciones anteriores.
En concreto, el problema que se plantea consiste en disear e implementar un
sencillo mdulo de Inteligencia Articial para un juego de tipo Simon14 , el cual
ser el responsable de la generacin de las distintas secuencias de juego. Ms all de
la complejidad del mdulo de IA, resulta ms interesante en este punto afrontar el
problema de la creacin y gestin de hilos de control adicionales. En el mdulo 4,
Desarrollo de Componentes, se discuten diversas tcnicas de IA que se pueden aplicar
a la hora de implementar un juego.
Antes de pasar a discutir la implementacin planteada, recuerde que en el juego
Figura 7.22: Aspecto grco de un Simon el jugador ha de ser capaz de memorizar y repetir una secuencia de colores
juego tipo Simon. generada por el dispositivo de juego. En este contexto, el mdulo de IA sera el encar-
gado de ir generando la secuencia a repetir por el jugador.
El siguiente listado de cdigo muestra la declaracin del hilo dedicado al mdulo
de IA. Como se puede apreciar, la clase AIThread hereda de IceUtil::Thread. Las
variables miembro de esta clase son las siguientes:
Updating threads...
_delay, que mantiene un valor temporal utilizado para dormir al mdulo de IA
Idealmente, el mdulo de IA debe- entre actualizaciones.
ra aprovechar los recursos disponi-
bles, no utilizados por el motor de
rendering, para actualizar su esta- _mutex, que permite controlar el acceso concurrente al estado de la clase.
do. Adems, se debera contemplar
la posibilidad de sincronizacin en- _seq, que representa la secuencia de colores, mediante valores enteros, generada
tre los mismos. por el mdulo de IA.
14 http://code.google.com/p/videojuegos-2011-12
[264] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

En la solucin planteada se ha optado por utilizar el mecanismo de exclusin mu-


tua proporcionado por I CE para sincronizar el acceso concurrente al estado de AITh-
read con un doble propsito: i) ilustrar de nuevo su utilizacin en otro ejemplo y ii)

recordar que es posible que distintos hilos de control interacten con el mdulo de IA.
Note cmo en la lnea 23 se ha denido un manejador para las instancias de
la clase AIThread utilizando IceUtil::Handle con el objetivo de gestionar de manera
inteligente dichas instancias. Recuerde que la funcin miembro run() especicar el
comportamiento del hilo y es necesario implementarla debido al contracto funcional
heredado de la clase IceUtil::Thread.

Listado 7.40: Clase AIThread


1 #ifndef __AI__
2 #define __AI__
3
4 #include <IceUtil/IceUtil.h>
5 #include <vector>
6
7 class AIThread : public IceUtil::Thread {
8
9 public:
10 AIThread (const IceUtil::Time& delay);
11 int getColorAt (const int& index) const;
12 void reset ();
13 void update ();
14
15 virtual void run ();
16
17 private:
18 IceUtil::Time _delay; // Tiempo entre actualizaciones.
19 IceUtil::Mutex _mutex; // Cerrojo para acceso exclusivo.
20 std::vector<int> _seq; // Secuencia de colores.
21 };
22
23 typedef IceUtil::Handle<AIThread> AIThreadPtr; // Smart pointer.
24
25 #endif

Actualmente, las responsabilidades del hilo encargado de procesar la IA del jue-


go Simon son dos. Por una parte, actualizar peridicamente la secuencia de colores
a repetir por el usuario. Esta actualizacin se llevara a cabo en la funcin run(), pu-
diendo ser ms o menos sosticada en funcin del nivel de IA a implementar. Por otra
parte, facilitar la obtencin del siguiente color de la secuencia cuando el jugador haya
completado la subsecuencia anterior.
El siguiente listado de cdigo muestra una posible implementacin de dichas fun-
ciones. Bsicamente, el hilo de IA genera los colores de manera aleatoria, pero sera
posible realizar una implementacin ms sosticada para, por ejemplo, modicar o
invertir de algn modo la secuencia de colores que se va generando con el paso del
tiempo.
Considere que el delay de tiempo existente entre actualizacin y actualizacin del
mdulo de IA debera estar condicionado por la actual tasa de frames por segundo
del juego. En otras palabras, la tasa de actualizacin del mdulo de IA ser ms o
menos exigente en funcin de los recursos disponibles con el objetivo de no penalizar
el rendimiento global del juego. Uso de hilos
Considere el uso de hilos cuando
realmente vaya a mejorar el rendi-
miento de su aplicacin. Si desde
el hilo de control principal se pue-
de atender la lgica de IA, entonces
no sera necesario delegarla en hilos
adicionales.
7.9. Caso de estudio. Procesamiento en segundo plano mediante hilos

Tambin es muy importante considerar la naturaleza del juego, es decir, las ne-
cesidades reales de actualizar el mdulo de IA. En el caso del juego Simon, est nece-
sidad no sera especialmente relevante considerando el intervalo de tiempo existente
entre la generacin de una secuencia y la generacin del siguiente elemento que exten-
der la misma. Sin embargo, en juegos en los que intervienen un gran nmero de bots
con una gran interactividad, la actualizacin del mdulo de IA se debera producir con
mayor frecuencia.

Listado 7.41: AIThread::run() y AIThread::update()


1 int AIThread::getColorAt (const int& index) const {
2 IceUtil::Mutex::Lock lock(_mutex);
3 return _seq[index];
4 }
5
6 void AIThread::update () {
7 IceUtil::Mutex::Lock lock(_mutex);
8 // Clculos complejos del mdulo de IA.
9 _seq.push_back(rand() % 4);
10 }
11
12 void AIThread::run () {
13 while (true) {
14 // Calcular nueva secuencia...
15 std::cout << "Updating..." << std::endl;
16 update();
17 IceUtil::ThreadControl::sleep(_delay);
18 }
19 }

La siguiente gura muestra el diagrama de interaccin que reeja la gestin del


hilo de IA desde la clase principal MyApp (en este ejemplo no se considera el esque-
ma discutido en la seccin 6.1.4 relativo a los estados de juego con Ogre). Como se
puede apreciar, desde dicha clase principal se crea la instancia de AIThread y se eje-
cuta la funcin start(). A partir de ah, el hilo continuar su ejecucin de acuerdo al
comportamiento denido en su funcin run().
Rendimiento con hilos Tpicamente, el mdulo de IA actualizar parte del estado del juego en cuestin,
El uso de hilos implica cambios de
en funcin de las caractersticas de ste. Por ejemplo, en un juego de accin, el com-
contexto y otras operaciones a nivel portamiento internos de los personajes, implementado en el mdulo de IA, gobernar
de sistema operativo que consumen parte de las acciones de los mismos, como por ejemplo la disposicin para avanzar
miles de ciclos de ejecucin. Eva- fsicamente hacia un objetivo. Evidentemente, este tipo de acciones implican una in-
le siempre el impacto de usar una
solucin multi-hilo.
teraccin con el motor grco.
El siguiente listado de cdigo muestra la parte de la clase MyApp desde la que se
crea el hilo responsable del procesamiento de la IA.
Note cmo en este ejemplo se utiliza la funcin detach() para desligar el hilo de la
IA respecto del hilo de control principal. Este planteamiento implica que, para evitar
comportamientos inesperados, el desarrollador ha de asegurarse de que el hijo pre-
viamente desligado termine su ejecucin antes de que el programa principal lo haga.
En el cdigo fuente desarrollado esta condicin se garantiza al utilizar el manejador
IceUtil::Handle para la clase AIThread.

Listado 7.42: MyApp::start(); creacin de AIThread


1 int MyApp::start() {
2 _root = new Ogre::Root();
3 if(!_root->restoreConfig())
4 {
5 _root->showConfigDialog();
6 _root->saveConfig();
[266] CAPTULO 7. BAJO NIVEL Y CONCURRENCIA

: MyApp

<<create ()>> : AIThread

start ()

update ()

getColorAt (int index)

update_state (TState new)

Figura 7.23: Diagrama de interaccin entre la clase principal del juego y AIThread.

7 }
8
9 _AI = new AIThread(IceUtil::Time::seconds(1));
10 IceUtil::ThreadControl tc = _AI->start();
11 // Se desliga del hilo principal.
12 tc.detach();
13
14 // ...
15 }
Programacin Grfica
El objetivo de este mdulo titulado Programacin Grfica del Cur
so de Experto en Desarrollo de Videojuegos es cubrir los aspectos
esenciales relativos al desarrollo de un motor grfico interactivo. En
este contexto, el presente mdulo cubre aspectos esenciales y bsicos
relativos a los fundamentos del desarrollo de la parte grfica, como
por ejemplo el pipeline grfico, como elemento fundamental de la
arquitectura de un motor de juegos, las bases matemticas, las APIs
de programacin grfica, el uso de materiales y texturas, la
iluminacin o los sistemas de partculas. As mismo, el presente
mdulo tambin discute aspectos relativos a la exportacin e
importacin de datos, haciendo especial hincapi en los formatos
existentes para tratar con informacin multimedia. Finalmente, se
pone de manifiesto la importancia del uso de elementos directamente
conectados con el motor grfico, como la simulacin fsica, con el
objetivo de dotar de ms realismo en el comportamiento de los
objetos y actores que intervienen en el juego.
Fundamentos de Grcos
Captulo 8
Tridimensionales
Carlos Gonzlez Morcillo

U
no de los aspectos que ms llaman la atencin en los videojuegos actuales son
sus impactantes grcos 3D. En este primer captulo introduciremos los con-
ceptos bsicos asociados al pipeline en grcos 3D. Se estudiarn las diferen-
tes etapas de transformacin de la geometra hasta su despliegue nal en coordenadas
de pantalla. En la segunda parte del captulo estudiaremos algunas de las capacidades
bsicas del motor grco de videojuegos y veremos los primeros ejemplos bsicos de
uso de Ogre.

8.1. Introduccin

An ms rpido!! Desde el punto de vista del usuario, un videojuego puede denirse como una apli-
cacin software que responde a una serie de eventos, redibujando la escena y ge-
Si cada frame tarda en desplegarse nerando una serie de respuestas adicionales (sonido en los altavoces, vibraciones en
ms de 40ms, no conseguiremos el
mnimo de 25 fps (frames per se- dispositivos de control, etc...).
cond) establecido como estndar en El redibujado de esta escena debe realizarse lo ms rpidamente posible. La capa
cine. En videojuegos, la frecuencia
recomendable mnima es de unos de aplicacin en el despliegue grco es una de las actividades que ms ciclos de CPU
50 fps. consumen (incluso, como veremos en la seccin 8.3, con el apoyo de las modernas
GPUs Graphics Processing Unit existentes en el mercado).

269
[270] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

Habitualmente el motor grco trabaja con geometra descrita mediante mallas


triangulares. Las tcnicas empleadas para optimizar el despliegue de esta geometra,
junto con las propiedades de materiales, texturas e iluminacin, varan dependiendo
del tipo de videojuego que se est desarrollando. Por ejemplo, en un simulador de
vuelo, el tratamiento que debe darse de la geometra distante (respecto de la posicin
de la cmara virtual) debe ser diferente que la empleada para optimizar la visualizacin
de interiores en un videojuego de primera persona.

Uno de los errores habituales en videojuegos amateur es la falta de comuni-


cacin clara entre programadores y diseadores. El desarrollador debe espe-
cicar claramente las capacidades soportadas por el motor grco al equipo
de modeladores, animadores y equipo artstico en general. Esta informacin
debe comprender tanto las tcnicas de despliegue soportadas, como el nmero
de polgonos disponibles para el personaje principal, enemigos, fondos, etc...

A un alto nivel de abstraccin podemos ver el proceso de render como el encarga-


do de convertir la descripcin de una escena tridimensional en una imagen bidimen-
sional. En los primeros aos de estudio de esta disciplina, la investigacin se centr en
cmo resolver problemas relacionados con la deteccin de supercies visibles, som-
breado bsico, etc. Segn se encontraban soluciones a estos problemas, se continu el
estudio de algoritmos ms precisos que simularan el comportamiento de la luz de una
forma ms precisa.
En esencia, el proceso de rendering de una escena 3D requiere los siguientes ele-
mentos:

Supercies. La geometra de los objetos que forman la escena debe ser denida
empleando alguna representacin matemtica, para su posterior procesamiento
por parte del ordenador.
Cmara. La situacin del visor debe ser denida mediante un par (posicin,
rotacin) en el espacio 3D. El plano de imagen de esta cmara virtual denir
el resultado del proceso de rendering. Como se muestra en la Figura 8.1, para
imgenes generadas en perspectiva, el volumen de visualizacin dene una pir-
mide truncada que selecciona los objetos que sern representados en la escena.
Esta pirmide se denomina Frustum.
Figura 8.1: Descripcin general
Fuentes de luz. Las fuentes de luz emiten rayos que interactan con las super- del proceso de rendering.
cies e impactarn en el plano de imagen. Dependiendo del modo de simulacin
de estos impactos de luz (de la resolucin de la denominada ecuacin de ren- Tiempo de cmputo
der), tendremos diferentes mtodos de rendering.
En sntesis de imagen realista (co-
Propiedades de las supercies. En este apartado se incluyen las propiedades mo por ejemplo, las tcnicas de ren-
dering empleadas en pelculas de
de materiales y texturas que describen el modelo de rebote de los fotones sobre animacin) el clculo de un solo fo-
las supercies. tograma de la animacin puede re-
querir desde varias horas hasta das,
empleando computadores de altas
Uno de los principales objetivos en sntesis de imagen es el realismo. En gene- prestaciones.
ral, segn el mtodo empleado para la resolucin de la ecuacin de render tendremos
diferentes niveles de realismo (y diferentes tiempos de cmputo asociados). El prin-
cipal problema en grcos en tiempo real es que las imgenes deben ser generadas
muy rpidamente. Eso signica, como hemos visto anteriormente, que el motor gr-
co dispone de menos de 40 ms para generar cada imagen. Habitualmente este tiempo
es incluso menor, ya que es necesario reservar tiempo de CPU para otras tareas como
el clculo de la Inteligencia Articial, simulacin fsica, sonido...
8.1. Introduccin [271]

Plano de Lista de Plano de


Imagen despliegue Imagen

Pipeline
Raster
Punto
Punto de
de Vista
Vista

C8
Pipeline Hardware (Interactivo) RayCasting
Mapeado hacia delante Mapeado Inverso

Figura 8.2: Diferencias entre las tcnicas de despliegue interactivas (mtodos basados en ScanLine) y
realistas (mtodos basados en RayCasting). En el Pipeline Hardware, la seleccin del pxel ms cercano
relativo a cada tringulo se realiza directamente en Hardware, empleando informacin de los fragmentos
(ver Seccin 8.2).

Los primeros mtodos de sombreado de supercies propuestos por Gouraud y


Phong no realizaban ninguna simulacin fsica de la reexin de la luz, calculando
nicamente las contribuciones locales de iluminacin. Estos modelos tienen en cuenta
la posicin de la luz, el observador y el vector normal de la supercie. Pese a su falta
de realismo, la facilidad de su cmputo hace que estas aproximaciones sigan siendo
ampliamente utilizadas en el desarrollo de videojuegos.
RayTracing En 1968 Arthur Appel describi el primer mtodo para generar imgenes por
computador lanzando rayos desde el punto de vista del observador. En este trabajo,
El algoritmo original del RayCas- generaba la imagen resultado en un plotter donde dibujaba punto a punto el resulta-
ting de Appel, fue el precursor del
mtodo de RayTracing (Trazado de do del proceso de render. La idea general del mtodo de RayCasting es lanzar rayos
Rayos) de Whitted de 1980. El m- desde el plano de imagen, uno por cada pxel, y encontrar el punto de interseccin
todo de RayTracing sirvi de base ms cercano con los objetos de la escena. La principal ventaja de este mtodo frente a
para los principales mtodos de sn- los mtodos de tipo scanline que emplean zbuffer es que es posible generar de forma
tesis de imagen hiperrealistas que se
emplean en la actualidad (Metrpo- consistente la imagen que represente el mundo 3D, ya que cualquier objeto que pueda
lis, Path Tracing, etc...). ser descrito mediante una ecuacin puede ser representado de forma correcta mediante
RayCasting.
Como puede verse en la Figura 8.2, existen diferencias importantes entre el mto-
do de despliegue que implementan las tarjetas aceleradoras 3D (y en general los mo-
tores de visualizacin para aplicaciones interactivas) y el mtodo de RayCasting. El
pipeline grco de aplicaciones interactivas (como veremos en la seccin 8.2) puede
describirse de forma general como el que, a partir de una lista de objetos geomtricos
a representar y, tras aplicar la serie de transformaciones geomtricas sobre los objetos,
la vista y la perspectiva, obtienen una imagen raster dependiente del dispositivo de vi-
sualizacin. En este enfoque, las primitivas se ordenan segn la posicin de la cmara
y slo las visibles sern dibujadas. Por el contrario, en mtodos de sntesis de imagen
realista (como la aproximacin inicial de RayCasting) calcula los rayos que pasan por
cada pxel de la imagen y recorre la lista de objetos, calculando la interseccin (si
hay alguna) con el objeto ms cercano. Una vez obtenido el punto de interseccin, se
evala (empleando un modelo de iluminacin) el valor de sombreado correspondiente
a ese pxel.
[272] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

8.2. El Pipeline Grco


Para obtener una imagen de una escena 3D denida en el Sistema de Referencia
Universal, necesitamos denir un sistema de referencia de coordenadas para los par-
metros de visualizacin (tambin denominados parmetros de cmara). Este sistema
de referencia nos denir el plano de proyeccin, que sera el equivalente de la zona
de la cmara sobre la que se registrar la imagen1 . De este modo se transeren los
objetos al sistema de coordenadas de visualizacin y nalmente se proyectan sobre el
plano de visualizacin (ver Figura 8.4).
El proceso de visualizar una escena en 3D mediante grcos por computador es
similar al que se realiza cuando se toma una fotografa real. En primer lugar hay que
situar el trpode con la cmara en un lugar del espacio, eligiendo as una posicin
de visualizacin. A continuacin, rotamos la cmara eligiendo si la fotografa la to-
maremos en vertical o en apaisado, y apuntando al motivo que queremos fotograar.
Finalmente, cuando disparamos la fotografa, slo una pequea parte del mundo que-
da representado en la imagen 2D nal (el resto de elementos son recortados y no
aparecen en la imagen).
La Figura 8.3 muestra los pasos generales del Pipeline asociado a la transforma-
cin de una escena 3D hasta su representacin nal en el dispositivo de visualizacin
(tpicamente una pantalla con una determinada resolucin).

App
El Pipeline est dividido en etapas funcionales. Al igual que ocurre en los pipe- Aplicacin
line de fabricacin industrial, algunas de estas etapas se realizan en paralelo y otras
secuencialmente. Idealmente, si dividimos un proceso en n etapas se incrementar la
Coord. Modelo (Locales)
velocidad del proceso en ese factor n. As, la velocidad de la cadena viene determinada
por el tiempo requerido por la etapa ms lenta. Transf Modelado
Como seala Akenine-Mler [8], el pipeline interactivo se divide en tres etapas Coord. Universales
conceptuales de Aplicacin, Geometra y Rasterizacin (ver Figura 8.3). A continua-

Etapa de Geometra
cin estudiaremos estas etapas. Transf Visualizacin
Coord. Visualizacin
8.2.1. Etapa de Aplicacin Vertex Shader

La etapa de aplicacin se ejecuta en la CPU. Actualmente la mayora de las CPUs Transf Proyeccin
son multincleo, por lo que el diseo de esta aplicacin se realiza mediante diferentes
hilos de ejecucin en paralelo. Habitualmente en esta etapa se ejecutan tareas aso- Coord. Normalizadas
ciadas al clculo de la posicin de los modelos 3D mediante simulaciones fsicas, Transf Recorte
deteccin de colisiones, gestin de la entrada del usuario (teclado, ratn, joystick...).
De igual modo, el uso de estructuras de datos de alto nivel para la aceleracin del des- Coord. Recortadas
pliegue (reduciendo el nmero de polgonos que se envan a la GPU) se implementan
Transf Pantalla
en la etapa de aplicacin.
Coord. Pantalla

8.2.2. Etapa de Geometra Config. Tringulo


Rasterizacin

En su tortuoso viaje hasta la pantalla, cada objeto 3D se transforma en diferentes


sistemas de coordenadas. Originalmente, como se muestra en la Figura 8.4, un objeto Recorrido Tring.
tiene su propio Sistema de Coordenadas Local que nos denen las Coordenadas de
Modelo, por lo que desde su punto de vista no est transformado. Pixel Shader
1 En el mundo fsico, la pelcula en antiguas cmaras analgicas, o el sensor de imagen de las cmaras
Fusin (Merging)
digitales.

Figura 8.3: Pipeline general en gr-


cos 3D.
8.2. El Pipeline Grco [273]

Sistema de
Coordenadas Sistema de
Y Universal (SRU) Coordenadas de
Visualizacin
Z
Sistema de X
Coordenadas Local Y
(Objeto)
Z Z
X

C8
Y Plano de
X visualizacin

Figura 8.4: Sistema de coordenadas de visualizacin y su relacin con otros sistemas de coordenadas de la
escena.

A los vrtices de cada modelo se le aplican la denominada Transformacin de


Modelado para posicionarlo y orientarlo respecto del Sistema de Coordenadas Uni-
versal, obteniendo as las denominadas Coordenadas Universales o Coordenadas del
Mundo.
Instancias Como este sistema de coordenadas es nico, tras aplicar la transformacin de
Gracias a la separacin entre Coor- modelado a cada objeto, ahora todas las coordenadas estarn expresadas en el mis-
denadas de Modelo y Transforma- mo espacio.
cin de Modelado podemos tener
diferentes instancias de un mismo La posicin y orientacin de la cmara nos determinar qu objetos aparecern en
modelo para construir una esce- la imagen nal. Esta cmara tendr igualmente unas coordenadas universales. El pro-
na a las que aplicamos diferentes psito de la Transformacin de Visualizacin es posicionar la cmara en el origen
transformaciones. Por ejemplo, pa-
ra construir un templo romano ten- del SRU, apuntando en la direccin negativa del eje Z y el eje Y hacia arriba. Obte-
dramos un nico objeto de colum- nemos de este modo las Coordenadas de Visualizacin o Coordenadas en Espacio
na y varias instancias de la columna Cmara (ver Figura 8.5).
a las que hemos aplicado diferentes
traslaciones. Habitualmente el pipeline contiene una etapa adicional intermedia que se denomi-
na Vertex Shader Sombreado de Vrtice que consiste en obtener la representacin del
material del objeto modelando las transformaciones en las fuentes de luz, utilizando
los vectores normales a los puntos de la supercie, informacin de color, etc. Es con-
veniente en muchas ocasiones transformar las posiciones de estos elementos (fuentes
de luz, cmara, ...) a otro espacio (como Coordenadas de Modelo) para realizar los
clculos.
La Transformacin de Proyeccin convierte el volumen de visualizacin en un
cubo unitario (ver Seccin 8.2.4). Este volumen de visualizacin se dene mediante
planos de recorte 3D y dene todos los elementos que sern visualizados. En la gu-
ra 8.5 se representa mediante el volumen sombreado. Existen multitud de mtodos de
proyeccin, aunque como veremos ms adelante, los ms empleados son la ortogrca
(o paralela) y la perspectiva.
[274] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

Y
X

Z
Y
X
Transformacin
de Visualizacin
Z

Esquema Superior: Representacin en espacio 3D.


Esquema Inferior: Vista de planta desde eje Y del SRU.

X X
Transformacin
de Visualizacin

Z Z

Figura 8.5: El usuario especica la posicin de la cmara (izquierda) que se transforma, junto con los
objetos de la escena, para posicionarlos a partir del origen del SRU y mirando en la direccin negativa del
eje Z. El rea sombreada de la cmara se corresponde con el volumen de visualizacin de la misma (slo
los objetos que estn contenidos en esa pirmide sern nalmente representados).

En la seccin 8.2.4 estudiaremos cmo se realiza la proyeccin en perspectiva de


un modo simplicado. Cuando veamos en el captulo 9, determinaremos la expresin
mediante la que los objetos de la escena se proyectan en un volumen simple (el cubo
unitario) antes de proceder al recorte y su posterior rasterizacin.
Tras la proyeccin, el volumen de visualizacin se transforma en Coordenadas
Normalizadas (obteniendo el cubo unitario), donde los modelos son proyectados de
3D a 2D. La coordenada Z se guarda habitualmente en un buffer de profundidad
llamado Z-Buffer.
8.2. El Pipeline Grco [275]

nicamente los objetos que estn dentro del volumen de visualizacin deben ser
generados en la imagen nal. Los objetos que estn totalmente dentro del volumen de
visualizacin sern copiados ntegramente a la siguiente etapa del pipeline. Sin em-
bargo, aquellos que estn parcialmente incluidas necesitan ser recortadas, generando
nuevos vrtices en el lmite del recorte. Esta operacin de Transformacin de Recor-
te se realiza automticamente por el hardware de la tarjeta grca. En la Figura 8.6 se
muestra un ejemplo simplicado de recorte.
X
Finalmente la Transformacin de Pantalla toma como entrada las coordenadas
de la etapa anterior y produce las denominadas Coordenadas de Pantalla, que ajustan
Z las coordenadas x e y del cubo unitario a las dimensiones de ventana nales.

C8
Vrtices
Nuevos
8.2.3. Etapa Rasterizacin
A partir de los vrtices proyectados (en Coordenadas de Pantalla) y la informacin
asociada a su sombreado obtenidas de la etapa anterior, la etapa de rasterizacin se
encarga de calcular los colores nales que se asignarn a los pxeles de los objetos.
X Esta etapa de rasterizacin se divide normalmente en las siguientes etapas funciones
para lograr mayor paralelismo.
En la primera etapa del pipeline llamada Conguracin de Tringulos (Triangle
Z Setup), se calculan las coordenadas 2D que denen el contorno de cada tringulo (el
primer y ltimo punto de cada vrtice). Esta informacin es utilizada en la siguiente
Figura 8.6: Los objetos que inter- etapa (y en la interpolacin), y normalmente se implementa directamente en hardware
secan con los lmites del cubo uni- dedicado.
tario (arriba) son recortados, aa-
diendo nuevos vrtices. Los objetos A continuacin, en la etapa del Recorrido de Tringulo (Triangle Traversal) se
que estn totalmente dentro del cu- generan fragmentos para la parte de cada pxel que pertenece al tringulo. El recorrido
bo unitario se pasan directamente a del tringulo se basa por tanto en encontrar los pxeles que forman parte del tringulo,
la siguiente etapa. Los objetos que y se denomina Triangle Traversal (o Scan Conversion). El fragmento se calcula inter-
estn totalmente fuera del cubo uni- polando la informacin de los tres vrtices denidos en la etapa de Conguracin de
tario son descartados. Tringulos y contiene informacin calculada sobre la profundidad desde la cmara y
el sombreado (obtenida en la etapa de geometra a nivel de todo el tringulo).
La informacin interpolada de la etapa anterior se utiliza en el Pixel Shader (Som-
breado de Pxel) para aplicar el sombreado a nivel de pxel. Esta etapa habitualmente
se ejecuta en ncleos de la GPU programables, y permite implementaciones propias
por parte del usuario. En esta etapa se aplican las texturas empleando diversos mtodos
de proyeccin (ver Figura 8.7).
Finalmente en la etapa de Fusin (Merging) se almacena la informacin del color
de cada pxel en un array de colores denominado Color Buffer. Para ello, se combina
el resultado de los fragmentos que son visibles de la etapa de Sombreado de Pxel.
La visibilidad se suele resolver en la mayora de los casos mediante un buffer de
profundidad Z-Buffer, empleando la informacin que almacenan los fragmentos.

El Z-Buffer es un buffer ampliamente empleado en grcos por computador.


Tiene el mismo tamao en pxeles que el buffer de color, pero almacena la
menor distancia para cada pxel a todos los fragmentos de la escena. Habi-
tualmente se representa como una imagen en escala de grises, y asocia valores
ms cercanos a blanco a distancias menores.
[276] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

8.2.4. Proyeccin en Perspectiva


En grcos por computador es posible elegir entre diferentes modelos de proyec-
cin de los objetos sobre el plano de visualizacin. Un modo muy utilizado en apli-
caciones de CAD es la proyeccin de los objetos empleando lneas paralelas sobre el
plano de proyeccin, mediante la denominada proyeccin paralela. En este modo de Config.
Tringulos
proyeccin se conservan las proporciones relativas entre objetos, independientemente (Tr. Setup)
de su distancia.
Mediante la proyeccin en perspectiva se proyectan los puntos hasta el plano
de visualizacin empleando trayectorias convergentes en un punto. Esto hace que los
objetos situados ms distantes del plano de visualizacin aparezcan ms pequeos en
la imagen. Las escenas generadas utilizando este modelo de proyeccin son ms rea-
listas, ya que sta es la manera en que el ojo humano y las cmaras fsicas forman Recorrido
Tringulos
imgenes. (Scan Conv.)
En la proyeccin en perspectiva, las lneas paralelas convergen en un punto, de
forma que los objetos ms cercanos se muestran de un tamao mayor que los lejanos.
Desde el 500aC, los griegos estudiaron el fenmeno que ocurra cuando la luz pasaba
a travs de pequeas aberturas. La primera descripcin de una cmara estenopeica se
atribuye al atrnomo y matemtico holands Gemma Frisius que en 1545 public la
Sombreado
primera descripcin de una cmara oscura en la observacin de un eclipse solar (ver de Pxel
Figura 8.8). En las cmaras esteneopeicas la luz pasa a travs de un pequeo agujero (P. Shading)
para formar la imagen en la pelcula fotosensible, que aparece invertida. Para que la
imagen sea ntida, la abertura debe ser muy pequea. Figura 8.7: Representacin del re-
sultado de las principales etapas del
Siguiendo la misma idea y desplazando el plano de proyeccin delante del origen, Pipeline de Rasterizacin.
tenemos el modelo general proyeccin en perspectiva.
Consideraremos en el resto de la seccin que ya se ha realizado la transformacin Perspectiva
de visualizacin alineando la cmara y los objetos de la escena mirando en direccin
La mayora de los juegos 3D se ba-
al eje negativo Z, que el eje Y est apuntando hacia arriba y el eje X positivo a la san en el uso de cmaras de proyec-
derecha (como se muestra en la Figura 8.5). cin en perspectiva. Estudiaremos
con ms detalle este tipo de mode-
los de proyeccin en el captulo 9.
p
Y
p
pz

p'

P'z = -d p' Figura 8.8: Descripcin de la pri-


d mera cmara estenopeica (pinho-
le camera o camera obscura) por
d Gemma Frisius.
X
p'x px
X
Z
Z

Figura 8.9: Modelo de proyeccin en perspectiva simple. El plano de proyeccin innito est denido en
z = d, de forma que el punto p se proyecta sobre p .

En la Figura 8.9 se muestra un ejemplo de proyeccin simple, en la que los vrtices


de los objetos del mundo se proyectan sobre un plano innito situado en z = d
(con d > 0). Suponiendo que la transformacin de visualizacin se ha realizado,
proyectamos un punto p sobre el plano de proyeccin, obteniendo un punto p =
(px , py , d).
8.3. Implementacin del Pipeline en GPU [277]

Empleando tringulos semejantes (ver Figura 8.9 derecha), obtenemos las siguien-
tes coordenadas:
px d d px
= px = (8.1)
px pz pz

De igual forma obtenemos la coordenada py = d py /pz , y pz = d. Como


veremos en el Captulo 9, estas ecuaciones se pueden expresar fcilmente de forma
matricial (que es la forma habitual de trabajar internamente en el pipeline). Estudiare-
mos ms detalles sobre el modelo de proyeccin en perspectiva en el Captulo 9, as
como la transformacin que se realiza de la pirmide de visualizacin (Frustum) al
cubo unitario.

C8
8.3. Implementacin del Pipeline en GPU
El hardware de aceleracin grca ha sufrido una importante transformacin en la
P Vertex Shader ltima dcada. Como se muestra en la Figura 8.11, en los ltimos aos el potencial de
las GPUs ha superado con creces al de la CPU.
P Geometry Shader
GK110
F Transf. Recorte 7

C Transf. Pantalla
Itanium
Poulson
C
Nmero de Transistores ( Miles de Millones)

Config. Tringulo 3 GF100

U
GP
zEC12
C Recorrido Tring.

P Pixel Shader 2

F Fusin (Merger)
GT200 IBM z196

U
CP
Figura 8.10: Implementacin tpi- 1 Opteron 2400
ca del pipeline en GPU con so-
porte Hbrido (OpenGL 2). La le- AMD K10
G80
tra asociada a cada etapa indica el
nivel de modicacin permitida al Core 2 Duo
usuario; P indica totalmente progra- Pentium IV Itanium 2
Pentium III GF4
mable, F indica jo (no programa-
ble), y C indica congurable pero Ao 1999 2001 2003 2005 2007 2009 2011 2013
no programable.
Figura 8.11: Evolucin del nmero de transistores en GPU y CPU (1999-2013)

Resulta de especial inters conocer aquellas partes del Pipeline de la GPU que
puede ser programable por el usuario mediante el desarrollo de shaders. Este cdigo
se ejecuta directamente en la GPU, y permite realizar operaciones a diferentes niveles
con alta eciencia.
[278] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

En GPU, las etapas del Pipeline grco (estudiadas en la seccin 8.2) se imple-
mentan en un conjunto de etapas diferente, que adems pueden o no ser programables
por parte del usuario (ver Figura 8.10). Por cuestiones de eciencia, algunas partes
del Pipeline en GPU no son programables, aunque se prevee que la tendencia en los
prximos aos sea permitir su modicacin.
La etapa asociada al Vertex Shader es totalmente programable, y encapsula las
cuatro primeras etapas del pipeline grco que estudiamos en la seccin 8.2: Trans-
formacin de Modelado, Transformacin de Visualizacin, Sombreado de Vrtices
(Vertex Shader) y Transformacin de Proyeccin.
La etapa referente al Geometry Shader es otra etapa programable que permite El trmino GPU
realizar operaciones sobre las primitivas geomtricas. Desde el 1999, se utiliza el trmino
GPU (Grpahics Processing Unit)
acuado por NVIDIA para diferen-
ciar la primera tarjeta grca que
La evolucin natural de las dos plataformas principales de grcos 3D en permita al programador implemen-
tiempo real (tanto OpenGL como Direct3D) ha sido el soporte nico de etapas tar sus propios algoritmos (GeForce
totalmente programables. Desde 2009, OpenGL en su versin 3.2 incluye un 256).
modo Compatibility Prole manteniendo el soporte de llamadas a las etapas
no programables. De forma similar, en 2009 Direct 3D versin 10 elimin las
llamadas a las etapas jas del pipeline.

Las etapas de Transformacin de Recorte, Transformacin de Pantalla, Congu-


racin de Tringulo, Recorrido de Tringulo y Fusin tienen un comportamiento fun-
cional similar al estudiado en la seccin 8.2, por lo que no sern descritas de nuevo.
Finalmente, el Pixel Shader es la ltima etapa totalmente programable del pipeline
en GPU y permite al programador desarrollar operaciones especcas a nivel de pxel.
El desarrollo de shaders se ha incorporado desde el 2009 en las especicaciones de
las pricipales APIs grcas (OpenGL y Direct3D). Estas llamadas son compiladas a un
lenguaje ensamblador intermedio independiente de la tarjeta grca. Son los drivers
de cada tarjeta grca los que transforman este lenguaje intermedio en instrucciones
especcas para cada tarjeta.
Veremos a continuacin brevemente algunas caractersticas de estas etapas pro- Ensamblador?
gramables de la GPU. En realidad este cdigo ensambla-
dor intermedio puede verse como
una especie de cdigo de mquina
8.3.1. Vertex Shader virtual que garantiza la compatibi-
lidad entre diferentes dispositivos
hardware.
Los vertex shaders permiten aplicar transformaciones y deformaciones a nivel de
vrtice. Este shader se aplica en la primera etapa del pipeline de la GPU. En esta
etapa, los ujos de datos que la CPU enva a la tarjeta son procesados y se aplican las
matrices de transformacin especicadas por el usuario.
En esta etapa se aplican las instancias sobre los datos enviados a la GPU (evitando
enviar varias veces las mismas primitivas geomtricas). A este nivel, el vertex shader
nicamente trabaja con la informacin relativa a los vrtices (posicin, vector normal,
color y coordenadas de textura). El vertex shader no conoce nada sobre la conexin
de estos vrtices entre s para formar tringulos.
Algunas operaciones clsicas que se implementan empleando vertex shader son
efectos de lente (como por ejemplo, de ojo de pez o distorsiones como las causadas en
escenas submarinas), deformaciones de objetos, animaciones de textura, etc.
8.4. Arquitectura del motor grco [279]

8.3.2. Geometry Shader


Los geometry shaders facilitan la creacin y destruccin de primitivas geomtricas
en la GPU en tiempo de ejecucin (vrtices, lneas y tringulos).
La entrada de este mdulo lo forman la especicacin de los objetos con sus vr-
tices asociados. Para cada primitiva de entrada, el geometry shader devolver cero o
ms primitivas de salida. Las primitivas de entrada y salida no tienen por qu ser del
mismo tipo. Por ejemplo, es posible indicar un tringulo como entrada (tres vrtices
3d) y devolver el centroide (un punto 3D) como salida. Las primitivas del ujo de
salida del geometry shader se obtienen en el mismo orden que se especicaron las

C8
primitivas de entrada.
Este tipo de shaders se emplean para la simulacin de pelo, para encontrar los
bordes de los objetos, o para implementar algunas tcnicas de visualizacin avanzadas
como metabolas o simulacin de telas.

8.3.3. Pixel Shader


A nivel de pixel shader se pueden aplicar operaciones a nivel de pxel, permitiendo
denir complejas ecuaciones de sombreado que sern evaluadas para cada pxel de la
imagen.
El Pixel Shader tiene inuencia nicamente sobre el fragmento que est mane-
jando. Esto implica que no puede aplicar ninguna transformacin sobre fragmentos
vecinos.
El uso principal que se da a este tipo de shaders es el establecimiento mediante
cdigo del color y la profundidad asociada al fragmento. Actualmente se emplea para
aplicar multitud de efectos de representacin no realista, reexiones, etc.

8.4. Arquitectura del motor grco


Figura 8.12: Resultado de aplicar
un Vertex Shader para deformar un
modelo. El objetivo de esta seccin es proporcionar una primera visin general sobre los
conceptos generales subyacentes en cualquier motor grco 3D interactivo. Estos con-
Notacin... ceptos sern estudiados en profundidad a lo largo de este documento, mostrando su
En OpenGL al Pixel Shader se
uso prctico mediante ejemplos desarrollados en C++ empleando el motor grco
le denomina Fragment Shader. En OGRE.
realidad es un mejor nombre, por-
que se trabaja a nivel de fragmento. Como se ha visto en la introduccin del captulo, los videojuegos requieren hacer
un uso eciente de los recursos grcos. Hace dos dcadas, los videojuegos se dise-
aban especcamente para una plataforma hardware especca, y las optimizaciones
podan realizarse a muy bajo nivel. Actualmente, el desarrollo de un videojuego tien-
de a realizarse para varias plataformas, por lo que el uso de un motor grco que
nos abstraiga de las particularidades de cada plataforma no es una opcin, sino una
necesidad.
Compatiblidad En estos desarrollos multiplataforma es necesario abordar aproximaciones de di-
seo que permitan emplear diferentes perles de ejecucin. Por ejemplo, en mquinas
En algunos casos, como en el desa-
rrollo de videojuegos para PC, el con grandes prestaciones se emplearn efectos y tcnicas de despliegue ms realistas,
programador no puede hacer casi mientras que en mquinas con recursos limitados se utilizarn algoritmos con me-
ninguna suposicin sobre el hard- nores requisitos computacionales y versiones de los recursos grcos adaptadas (con
ware subyacente en la mquina diferente nivel de detalle asociado).
donde se ejecutar nalmente el
programa. En el caso de desarrollo Las limitaciones asociadas a los recursos computacionales son una constante en el
para consolas, los entornos de eje-
cucin concretos estn mucho ms
rea del desarrollo de videojuegos. Cada plataforma conlleva sus propias limitaciones
controlados. y restricciones, que pueden asociarse en las categoras de:
[280] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

Figura 8.13: Capturas de algunos videojuegos desarrollados con motores libres. La imagen de la izquierda
se corresponde con Planeshift, realizado con Crystal Space. La captura de la derecha es del videojuego
H-Craft Championship, desarrollado con Irrlicht.

Tiempo de Procesamiento. El desarrollo de videojuegos en prcticamente cual-


quier plataforma actual require el manejo de mltiples ncleos de procesamien-
to (tanto de CPU como GPU). El manejo explcito de la concurrencia (habitual-
mente a nivel de hilos) es necesario para mantener una alta tasa de Frames por
Segundo.
Almacenamiento. En el caso de ciertos dispositivos como consolas, la variedad
de unidades de almacenamiento de los recursos del juego (con velocidades de
acceso y transferencia heterogneas), dicultan el desarrollo del videojuego. En
ciertas plataformas, no se dispone de aproximaciones de memoria virtual, por
lo que el programador debe utilizar explcitamente superposiciones (overlays)
para cargar las zonas del juego que van a emplearse en cada momento.

Dada la gran cantidad de restricciones que deben manejarse, as como el manejo


de la heterogeneidad en trminos software y hardware, es necesario el uso de un mo-
tor de despliegue grco para desarrollar videojuegos. A continuacin enunciaremos
algunos de los ms empleados.

8.5. Casos de Estudio


En esta seccin se estudiarn algunos de los motores grcos 3D libres, comen-
tando brevemente algunas de sus caractersticas ms destacables.

Crystal Space. Crystal Space (http://www.crystalspace3d.org/) es un


framework completo para el desarrollo de videojuegos escrito en C++, desarro-
llado inicialmente en 1997. Se distribuye bajo licencia libre LGPL, y es multi-
plataforma (GNU/Linux, Windows y Mac).
Panda 3D. Panda 3D (http://www.panda3d.org/) es un motor para el desa-
rrollo de videojuegos multiplataforma. Inicialmente fue desarrollado por Disney
para la construccin del software asociado a las atracciones en parques tem-
ticos, y posteriormente liberado en 2002 bajo licencia BSD. Este motor mul-
8.6. Introduccin a OGRE [281]

tiplataforma (para GNU/Linux, Windows y Mac) incluye interfaces para C++


y Python. Su corta curva de aprendizaje hace que haya sido utilizado en varios
cursos universitarios, pero no ofrece caractersticas de representacin avanzadas
y el interfaz de alto nivel de Python conlleva una prdida de rendimiento.
Irrlicht. Este motor grco de renderizado 3D, con primera versin publicada
en el 2003 (http://irrlicht.sourceforge.net/), ofrece interfaces para
C++ y .NET. Existen gran cantidad de wrappers a diferentes lenguajes como
Java, Perl, Python o Lua. Irrlicht tiene una licencia Open Source basada en la
licencia de ZLib. Irrlicht es igualmente multiplataforma (GNU/Linux, Windows
y Mac).

C8
OGRE. OGRE (http://www.ogre3d.org/) es un motor para grcos 3D
libre multiplataforma. Sus caractersticas sern estudiadas en detalle en la sec-
cin 8.6.

De entre los motores estudiados, OGRE 3D ofrece una calidad de diseo superior,
con caractersticas tcnicas avanzadas que han permitido el desarrollo de varios vi-
deojuegos comerciales. Adems, el hecho de que se centre exclusivamente en el capa
grca permite utilizar gran variedad de bibliotecas externas (habitualmente accedidas
mediante plugins) para proporcionar funcionalidad adicional. En la siguiente seccin
daremos una primera introduccin y toma de contacto al motor libre OGRE.

8.6. Introduccin a OGRE


OGRE es un motor orientado a objetos libre para aplicaciones grcas 3D in-
teractivas. El nombre del motor OGRE es un acrnimo de Object-oriented Graphics
Rendering Engine. Como su propio nombre indica, OGRE no es un motor para el
desarrollo de videojuegos; se centra exclusivamente en la denicin de un middlewa-
re para el renderizado de grcos 3D en tiempo real.
El proyecto de OGRE comenz en el 2000 con el propsito de crear un motor gr-
Figura 8.14: El logotipo de OGRE co bien diseado. El lder del proyecto Steve Streeting dene el desarrollo de OGRE
3D es un... OGRO!
como un proyecto basado en la calidad ms que en la cantidad de caractersticas que
soporta, porque la cantidad viene con el tiempo, y la calidad nunca puede aadirse a
Slo Rendering! posteriori. La popularidad de OGRE se basa en los principios de meritocracia de los
El desarrollo de OGRE se centra proyectos de software libre. As, el sitio web de OGRE 2 recibe ms de 500.000 visitas
exclusivamente en la parte de des- diarias, con ms de 40.000 descargas mensuales.
pliegue grco. El motor no propor-
ciona mecanismos para capturar la La ltima versin la biblioteca en desarrollo de la biblioteca (1.9) ha sido publica-
interaccin del usuario, ni para re- da en Abril de 2013 3 .
produccin audio o gestin del es-
tado interno del videojuego. El ncleo principal de desarrolladores en OGRE se mantiene deliberadamente pe-
queo y est formado por profesionales con dilatada experiencia en proyectos de in-
geniera reales.
OGRE tiene una licencia LGPL Lesser GNU Public License. Esta licencia se uti-
liza con frecuencia en bibliotecas que ofrecen funcionalidad que es similar a la de
otras bibliotecas privativas. Por cuestin de estrategia, se publican bajo licencia LGPL
(o GPL Reducida) para permitir que se enlacen tanto por programas libres como no
libres. La nica restriccin que se aplica es que si el enlazado de las bibliotecas es es-
ttico, la aplicacin resultado debe ser igualmente LGPL (porque el enlazado esttico
tambin enlaza la licencia).
2 http://www.ogre3d.org
3 En el curso utilizaremos la ltima versin estable (Ogre 1.8)
[282] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

La versin ocial de OGRE est desarrollada en C++ (el lenguaje estndar en el


mbito del desarrollo de videojuegos). La rama ocial de OGRE nicamente se centra
en este lenguaje sobre los sistemas operativos GNU/Linux, Mac OS X y Microsoft
Windows. No obstante, existen wrappers de la API a otros lenguajes (como Java, Pyt-
hon o C#) que son mantenidos por la comunidad de usuarios (presentando diferentes
niveles de estabilidad y completitud), que no forman parte del ncleo ocial de la
biblioteca.

En el momento de la escritura de este texto, la ltima versin en desarrollo


1.9RC1 de Ogre (inestable), incluye soporte para nuevas plataformas como
Android, Windows Phone 8, as como la escritura del motor con soporte para
OpenGL 3, DirectX11, etc...

Algunas caractersticas destacables de OGRE son:


Portabilidad
Motor Multiplataforma. Aunque el desarrollo original de OGRE se realiz
Uno de los objetivos de diseo
bajo plataformas Microsoft Windows, la distribucin ocial ofrece versiones de OGRE es utilizar otras biblio-
binarias para GNU/Linux y Mac OS X. Adems, gracias al soporte nativo de tecas multiplataforma estables en
OpenGL, es posible compilar la biblioteca en multitud de plataformas (como su desarrollo, como FreeType pa-
diversas versiones de Unix, adems de algunos ports no ociales para Xbox y ra el despliegue de fuentes TrueTy-
pe, OpenIL para la carga y mani-
dispositivos porttiles). OGRE soporta la utilizacin de las APIs de despliegue pulacin de imgenes y ZLib para
grco de bajo nivel OpenGL y Direct3D. la gestin de archivos comprimidos
ZIP.
Diseo de Alto Nivel. OGRE encapsula la complejidad de acceder directamente
a las APIs de bajo nivel (como OpenGL y Direct3D) proporcionando mtodos
intuitivos para la manipulacin de objetos y sus propiedades relacionadas. De
este modo no es necesario gestionar manualmente la geometra o las matrices de
transformacin. Todos los objetos representables de la escena se abstraen en un
interfaz que encapsula las operaciones necesarias para su despliegue (tcnicas
empleadas y composicin).
OGRE hace un uso de varios patrones de diseo para mejorar la usabilidad y
la exibilidad de la bibloteca. Por ejemplo, para informar a la aplicacin sobre
eventos y cambios de estado utiliza el patrn Observador. El patrn Singleton
se emplea en gran nmero de Gestores para forzar que nicamente exista una
instancia de una clase. El patrn Visitor se emplea para permitir operaciones
sobre un objeto sin necesidad de modicarlo (como en el caso de los nodos
del grafo de escena), el patrn Facade para unicar el acceso a operaciones, SceneGraph
Factora para la creacin de instancias concretas de interfaces abstractos, etc. Adjunto
Estos patrones se estudian en detalle en el Mdulo 1 del curso. Attached
SceneNode SceneNode
Grafos de Escena. Prcticamente cualquier biblioteca de despliegue de gr- Adjunto
cos 3D utiliza un Grafo de Escena para organizar los elementos que sern Attached
representados en la misma. Un objetivo fundamental en el diseo de esta es- MovableObject MovableObject
tructura de datos es permitir bsquedas ecientes. Una de las caractersticas Implementa
ms potentes de OGRE es el desacople del grafo de escena del contenido de la Entity Entity
escena, deniendo una arquitectura de plugins. En pocas palabras, a diferencia Contiene
de otros motores grcos como Irrlicht3D, Blitz3D o TrueVision3D (o motores Subentity SubEntity
de videojuegos como Torque, CryEngine o Unreal), OGRE no se basa en la He- Implementa
rencia como principio de diseo del Grafo de Escena, sino en la Composicin. Renderable A Renderable B
Esto permite expandir el diseo cmodamente para soportar otros tipos de datos
(como audio o elementos de simulacin fsica). Figura 8.15: Esquema general de
la gestin del grafo de escena en
OGRE.
8.6. Introduccin a OGRE [283]

La Figura 8.15 muestra el esquema general de gestin del grafo de escena en


OGRE. Los Renderables manejan la geometra de la escena. Todas las propieda-
des para el despliegue de estos Renderables (como por ejemplo los materiales)
se gestionan en objetos de tipo Entidad (Entity) que pueden estar formados por
varios objetos SubEntidad (SubEntity). Como se ha comentado anteriormente,
la escena se Compone de nodos de escena. Estos SceneNodes se adjuntan a la
escena. Las propiedades de esos nodos de escena (geometra, materiales, etc...)
se ofrecen al SceneGraph mediante un MovableObject. De forma similar, los
MovableObjects no son subclases de SceneNode, sino que se adjuntan. Esto
permite realizar modicaciones en la implementacin del grafo de escena sin
necesidad de tocar ninguna lnea de cdigo en la implementacin de los objetos

C8
que contiene. Veremos ms detalles sobre el trabajo con el grafo de escena a lo
largo de este mdulo.
Aceleracin Hardware. OGRE necesita una tarjeta aceleradora grca para
poder ejecutarse (con soporte de direct rendering mode). OGRE permite denir
el comportamiento de la parte programable de la GPU mediante la denicin de
Shaders, estando al mismo nivel de otros motores como Unreal o CryEngine.
Materiales. Otro aspecto realmente potente en OGRE es la gestin de materia-
les. Es posible crear materiales sin modicar ni una lnea de cdigo a compilar.
El sistema de scripts para la denicin de materiales de OGRE es uno de los
ms potentes existentes en motores de rendering interactivo. Los materiales de
OGRE se denen mediante una o ms Tcnicas, que se componen a su vez de
una o ms Pasadas de rendering (el ejemplo de la Figura 8.16 utiliza una ni-
ca pasada para simular el sombreado a lpiz mediante hatching). OGRE busca
automticamente la mejor tcnica disponible en un material que est soportada
por el hardware de la mquina de forma transparente al programador. Adems,
es posible denir diferentes Esquemas asociados a modos de calidad en el des-
pliegue.
Figura 8.16: Ejemplo de Shader
desarrollado por Assaf Raman para Animacin. OGRE soporta tres tipos de animacin ampliamente utilizados en
simular trazos a Lpiz empleando el la construccin de videojuegos: basada en esqueletos (skeletal), basada en vr-
sistema de materiales de OGRE. tices (morph y pose). En la animacin mediante esqueletos, OGRE permite el
uso de esqueletos con animacin basada en cinemtica directa. Existen multi-
tud de exportadores para los principales paquetes de edicin 3D. En este mdulo
utilizaremos el exportador de Blender.
El sistema de animacin de OGRE se basa en el uso de controladores, que
modican el valor de una propiedad en funcin de otro valor. En el caso de
animaciones se utiliza el tiempo como valor para modicar otras propiedades
(como por ejemplo la posicin del objeto). El motor de OGRE soporta dos mo-
delos bsicos de interpolacin: lineal y basada en splines cbicas.
La animacin y la geometra asociada a los modelos se almacena en un nico
Optimizacin ofine formato binario optimizado. El proceso ms empleado se basa en la exportacin
desde la aplicacin de modelado y animacin 3D a un formato XML (Ogre
El proceso de optimizacin de al
formato binario de OGRE calcula XML) para convertirlo posteriormente al formato binario optimizado mediante
el orden adecuado de los vrtices la herramienta de lnea de rdenes OgreXMLConverter.
de la malla, calcula las normales de
las caras poligonales, as como ver- Composicin y Postprocesado. El framework de composicin facilita al pro-
siones de diferentes niveles de de- gramador incluir efectos de postprocesado en tiempo de ejecucin (siendo una
talle de la malla. Este proceso evi- extensin del pixel shader del pipeline estudiado en la seccin 8.3.3). La apro-
ta el clculo de estos parmetros en
tiempo de ejecucin. ximacin basada en pasadas y diferentes tcnicas es similar a la explicada para
el gestor de materiales.
Plugins. El diseo de OGRE facilita el diseo de Plugins como componentes
que cooperan y se comunican mediante un interfaz conocido. La gestin de
archivos, sistemas de rendering y el sistema de partculas estn implementados
basados en el diseo de Plugins.
[284] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

Gestin de Recursos. Para OGRE los recursos son los elementos necesarios pa-
ra representar un objetivo. Estos elementos son la geometra, materiales, esque-
letos, fuentes, scripts, texturas, etc. Cada uno de estos elementos tienen asociado
un gestor de recurso especco. Este gestor se encarga de controlar la cantidad
de memoria empleada por el recurso, y facilitar la carga, descarga, creacin e
inicializacin del recurso. OGRE organiza los recursos en niveles de gestin
superiores denominados grupos. Veremos en la seccin 8.6.1 los recursos ges-
tionados por OGRE.
Caractersticas especcas avanzadas. El motor soporta gran cantidad de ca-
ractersticas de visualizacin avanzadas, que estudiaremos a lo largo del m-
dulo, tales como sombras dinmicas (basadas en diversas tcnicas de clculo),
sistemas de partculas, animacin basada en esqueletos y de vrtices, y un largo
etctera. OGRE soporta adems el uso de otras bibliotecas auxiliares mediante
plugins y conectores. Entre los ms utilizados cabe destacar las bibliotecas de
simulacin fsica ODE, el soporte del metaformato Collada, o la reproduccin
de streaming de vdeo con Theora. Algunos de estos mdulos los utilizaremos
en el mdulo 3 del presente curso.

8.6.1. Arquitectura General


El diagrama de la Figura 8.17 resume algunos de los objetos principales del motor
OGRE. No es un diagrama exhaustivo, pero facilita la comprensin de los principales
mdulos que utilizaremos a lo largo del curso.
Objeto Root

Root El objeto Root es el eje pricipal so-


bre el que se dene una aplicacin
que utiliza OGRE. La creacin de
una instancia de esta clase har que
Gestin de Escena se inicie OGRE, y su destruccin
Plugin har que se libere la memoria aso-
Scene Node SceneManager ciada a todos los objetos que depen-
OctreeSceneManager
den de l.
Material MovableObject
Plugin
Entity Camera Light CustomMovable

Gestin de Recursos Rendering


ResourceGroupManager ResourceManager Renderable HwBufferManager

GPUProgram Mesh ArchiveFactory Texture RenderWindow RenderSystem

Plugin Plugin
CustomArchiveFactory GLTexture GLRenderSystem

Figura 8.17: Diagrama general de algunos de los objetos principales de OGRE


8.6. Introduccin a OGRE [285]

Uno de los objetos principales del sistema es el denominado Root. Root propor-
ciona mecanismos para la creacin de los objetos de alto nivel que nos permitirn
gestionar las escenas, ventanas, carga de plugins, etc. Gran parte de la funcionalidad
de Ogre se proporciona a travs del objeto Root. Como se muestra en el diagrama 8.17,
existen otros tres grupos de clases principales en OGRE:

Gestin de Escena: Los objetos de este grupo de clases se encargan de denir el


contenido de la escena virtual, su estructura, as como otras propiedades de alto
nivel de abstraccin (posicin de la cmara, posicin de los objetos, materiales,
etc...). Como se estudi en la Figura 8.15, el grafo de escena es un elemento
principal en la arquitectura de cualquier motor 3D. En el caso de OGRE, el

C8
Gestor de Escena (clase SceneManager) es el que se encarga de implementar el
grafo de escena. Los nodos de escena SceneNode son elementos relacionados
jerrquicamente, que pueden adjuntarse o desligarse de una escena en tiempo de
ejecucin. El contenido de estos SceneNode se adjunta en la forma de instancias
de Entidades (Entity), que son implementaciones de la clase MovableObject.
Gestin de Recursos: Este grupo de objetos se encarga de gestionar los recursos
necesarios para la representacin de la escena (geometra, texturas, tipografas,
etc...). Esta gestin permite su carga, descarga y reutilizacin (mediante cachs
de recursos). En la siguiente subseccin veremos en detalle los principales ges-
tores de recursos denidos en OGRE.
Entidades Procedurales Rendering: Este grupo de objetos sirve de intermediario entre los objetos de
La mayor parte de las entidades que Gestin de Escena y el pipeline grco de bajo nivel (con llamadas espec-
incluyas en el SceneNode sern car- cas a APIs grcas, trabajo con buffers, estados internos de despliegue, etc.).
gadas de disco (como por ejem-
plo, una malla binaria en formato La clase RenderSystem es un interfaz general entre OGRE y las APIs de bajo
.mesh). Sin embargo, OGRE per- nivel (OpenGL o Direct3D). La forma ms habitual de crear la RenderWindow
mite la dencin en cdigo de otras es a travs del objeto Root o mediante el RenderSystem (ver Figura 8.18). La
entidades, como una textura proce- creacin manual permite el establecimiento de mayor cantidad de parmetros,
dural, o un plano.
aunque para la mayora de las aplicaciones la creacin automtica es ms que
suciente.

Por ser la gestin de recursos uno de los ejes principales en la creacin de una
aplicacin grca interactiva, estudiaremos a continuacin los grupos de gestin de
recursos y las principales clases relacionadas en OGRE.

Gestin de Recursos

Como hemos comentado anteriormente, cualquier elemento necesario para repre-


sesntar una escena se denomina recurso. Todos los recursos son gestionados por un
nico objeto llamado ResourceGroupManager, que se encarga de buscar los recursos
denidos en la aplicacin e inicializarlos. Cada tipo de recurso tiene asociado un ges-
tor de recurso particular. Veamos a continuacin los tipos de recursos soportados en
OGRE:

Mallas. La geometra de los elementos de la escena se especica en un formato


de malla binario (.mesh). Este formato almacena la geometra y la animacin
asociada a los objetos.
Materiales. Como se ha descrito anteriormente, el material se especica habi-
tualmente mediante scripts (en cheros de extensin .material). Estos scripts
pueden estar referenciados en el propio archivo .mesh o pueden ser enlazados
a la malla mediante cdigo compilado.
[286] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

ResourceManager

FontManager MeshManager GpuProgramManager


TextureManager
SkeletonManager
MaterialManager HighLevelGpuProgramManager

RenderSystem

ExternalTextureSourceManager

SceneManager
ResourceGroupManager

RenderWindow Root ParticleSystemManager

OverlayManager
PlatformManager

LogManager DynLibManager ArchiveManager

Figura 8.18: Diagrama de clases simplicado de los Gestores de alto nivel que dan acceso a los diferentes
subsistemas denidos en OGRE

Texturas. OGRE soporta todos los formatos de texturas 2D admitidos por la


biblioteca OpenIL. El formato se reconoce por la extensin asociada al mismo.
Esqueletos. Habitualmente el esqueleto est referenciado en el chero .mesh.
El chero de denicin de esqueleto contiene la jerarqua de huesos asociada al
mismo y tiene una extensin .skeleton.
Fuentes. Las fuentes empleadas en la etapa de despliegue de Overlays se de-
nen en un archivo .fontdef.
Composicin. El framework de composicin de OGRE carga sus scripts con
extensin .compositor.
GPU. El cdigo de shaders denidos para la GPU (de HLSL, GLSL y Cg) se
describe en archivos con extensin .program. De igual modo se pueden denir
cdigo en ensamblador mediante archivos .asm. Los programas de GPU son
cargados antes que se procese cualquier archivo de material .material, de
forma que estos shaders puedan ser referenciados en los archivos de denicin
de material.

Un gestor (Manager) en OGRE es una clase que gestiona el acceso a otros tipos
de objetos (ver Figura 8.18). Los Managers de OGRE se implementan mediante el
patrn Singleton que garantiza que nicamente hay una istancia de esa clase en toda
la aplicacin. Este patrn permite el acceso a la instancia nica de la clase Manager
desde cualquier lugar del cdigo de la aplicacin.
8.6. Introduccin a OGRE [287]

Como puede verse en la Figura 8.18, el ResourceManager es una clase abstracta de


la que heredan un gran nmero de Managers, tales como el encargado de las Fuentes,
las Texturas o las Mallas. A continuacin se ofrece una descripcin general de los
Managers (por orden alfabtico) denidos en OGRE. A lo largo del documento se
describirn (y se utilizarn ampliamente) muchos de estos gestores.

Archive Manager. Se encarga de abstraer del uso de cheros con diferentes


extensiones, directorios y archivos empaquetados .zip.
CompositorManager. Esta clase proporciona acceso al framework de compo-
sicin y postprocesado de OGRE.

C8
ControllerManager. Es el gestor de los controladores que, como hemos in-
dicado anteriormente, se encargan de producir cambios en el estado de otras
clases dependiendo del valor de ciertas entradas.
DynLibManager. Esta clase es una de las principales en el diseo del sistema
de Plugins de OGRE. Se encarga de gestionar las bibliotecas de enlace dinmico
(DLLs en Windows y objetos compartidos en GNU/Linux).
ExternalTextureSourceManager. Gestiona las fuentes de textura externas (co-
mo por ejemplo, en el uso de video streaming).
FontManager. Gestiona las fuentes disponibles para su representacin en su-
perposiciones 2D (ver OverlayManager).
GpuProgramManager. Carga los programas de alto nivel de la GPU, denidos
en ensamblador. Se encarga igualmente de cargar los programas compilados por
el HighLevelGpuProgramManager.
HighLevelGpuProgramManager. Gestiona la carga y compilacin de los pro-
gramas de alto nivel en la GPU (shaders) utilizados en la aplicacin en HLSL,
GLSL o Cg.
LogManager. Se encarga de enviar mensajes de Log a la salida denida por
OGRE. Puede ser utilizado igualmente por cualquier cdigo de usuario que
quiera enviar eventos de Log.
MaterialManager. Esta clase mantiene las instancias de Material cargadas en
la aplicacin, permitiendo reutilizar los objetos de este tipo en diferentes obje-
tos.
MeshManager. De forma anloga al MaterialManager, esta clase mantiene las
instancias de Mesh permitiendo su reutilizacin entre diferentes objetos.
OverlayManager. Esta clase gestiona la carga y creacin de instancias de su-
perposiciones 2D, que permiten dibujar el interfaz de usuario (botones, iconos,
nmeros, radar...). En general, los elementos denidos como HUDs (Head Up
Display).
ParticleSystemManager. Gestiona los sistemas de partculas, permitiendo aa-
dir gran cantidad de efectos especiales en aplicaciones 3D. Esta clase gestiona
los emisores, los lmites de simulacin, etc.
PlatformManager. Esta clase abstrae de las particularidades del sistema opera-
tivo y del hardware subyacente de ejecucin, proporcionando rutinas indepen-
dientes del sistema de ventanas, temporizadores, etc.
[288] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

ResourceGroupManager. Esta clase gestiona la lista de grupos de recursos


y se encarga de noticar a los Managers de la necesidad de cargar o liberar
recursos en cada grupo.
SceneManager. Como se ha explicado anteriormente, esta clase se encarga de
la gestin, organizacin y rendering de la escena. Esta clase permite denir
subclases que organicen la escena de una forma ms eciente, dependiendo del
tipo de aplicacin. Por defecto, el SceneManager utiliza una jerarqua de cajas
lmite (bounding boxes) para optimizar el despliegue de los objetos.
SkeletonManager. Al igual que el MaterialManager y el MeshManager, esta SceneManager
clase mantiene las instancias de Skeleton cargadas en la aplicacin, permitiendo Los SceneManagers de OGRE son
su reutilizacin entre diferentes objetos. implementados como Plugins, de
forma que el usuario puede car-
TextureManager. Gestiona la carga y uso de las texturas de la aplicacin. gar varios gestores de escena en su
aplicacin. Si el videojuego requie-
re escenas de interiores con mucha
geometra, as como escenas de ex-
8.6.2. Instalacin teriores, puede ser interesante car-
gar dos gestores de escena diferen-
En esta seccin se detallar el proceso de instalacin de la biblioteca OGRE 1.8 tes optimizados para cada parte del
juego.
en sistemas GNU/Linux y Microsoft Windows. Nos centraremos en la instalacin en
distribuciones Debian, que servirn como base para el desarrollo del presente curso.
No obstante, se proporcionarn igualmente las herramientas necesarias y makeles
adaptados para Microsoft Windows empleando MinGW.

GNU/Linux (Debian)

Para comenzar, instalaremos las herramientas de compilacin bsicas necesarias


para compilar: gcc, g++ y make se encuentran disponibles en el metapaquete build-
essential:

apt-get install build-essential

A continuacin instalaremos los paquetes especcos de OGRE:

apt-get install libogre-1.8.0 libogre-1.8-dev ogre-1.8-doc ogre-1.8-


tools

El paquete libogre-1.8.0 contiene las bibliotecas necesarias para la ejecu-


cin de las aplicaciones desarrolladas con OGRE. El paquete libogre-dev contiene
los cheros de cabecera instalados en /usr/include/OGRE necesarios para com-
pilar nuestros propios ejemplos. El paquete de documentacin ogre-doc instala en
/usr/share/doc/ogre-doc la documentacin (manual de usuario y API). Final-
mente el paquete ogre-tools contiene las herramientas para convertir al formato de
malla binario optimizado de OGRE.
Como se coment en la introduccin, OGRE se centra en proporcionar exclusiva-
mente un motor de despliegue grco 3D interactivo. OGRE no proporciona meca-
nismos para gestionr la entrada del usuario. En este mdulo utilizaremos OIS (Object
Oriented Input System), una biblioteca desarrollada en C++ multiplataforma que per-
mite trabajar con teclado, ratn, joysticks y otros dispositivos de juego. Instalaremos
igualmente el paquete binario y las cabeceras.

apt-get install libois-1.3.0 libois-dev


8.7. Hola Mundo en OGRE [289]

Tanto OGRE como OIS puede ser igualmente instalado en cualquier distribucin
compilando directamente los fuentes. En el caso de OGRE, es necesario CMake para
generar el makele especco para el sistema donde va a ser instalado. En el caso de
OIS, es necesario autotools.

Microsoft Windows

Aunque no utilizaremos ningn entorno de desarrollo en esta plataforma, dada su


amplia comunidad de usuarios, puede ser conveniente generar los ejecutables para esta
plataforma. A continuacin se detallarn los pasos necesarios para instalar y compilar

C8
los desarrollos realizados con OGRE en plataformas Windows.
Como entorno de compilacin utilizaremos MinGW (Minimalist GNU for Win-
dows), que contiene las herramientas bsicas de compilacin de GNU para Windows.
El instalador de MinGW mingw-get-inst puede obtenerse de la pgina web
http://www.mingw.org/. Puedes instalar las herramientas en C:\MinGW\. Una
vez instaladas, debers aadir al path el directorio C:\MinGW\bin. Para ello, podrs
usar la siguiente orden en un terminal del sistema.

path = %PATH %;C:\MinGW\bin

De igual modo, hay que descargar el SDK de DirectX4 . Este paso es opcional
siempre que no queramos ejecutar ningn ejemplo de OGRE que utilice Direct3D.
Sobre Boost... A continuacin instalaremos la biblioteca OGRE3D para MinGW5 . Cuando acabe,
al igual que hicimos con el directorio bin de MinGW, hay que aadir los directorios
Boost es un conjunto de bibliotecas
libres que aaden multitud de fun-
boost_1_44\lib\ y bin\Release al path.
cionalidad a la biblioteca de C++
(estn en proceso de aceptacin por path = %PATH %;C:\Ogre3D\boost_1_44\lib\; C:\Ogre3D\bin\
el comit de estandarizacin del
lenguaje).

8.7. Hola Mundo en OGRE


A continuacin examinaremos un ejemplo bsico de funcionamiento de OGRE.
Utilizaremos la estructura de directorios mostrada en la Figura 8.19 en los ejemplos
desarrollados a lo largo de este mdulo.

En el directorio include se incluirn los archivos de cabecera. En este primer


ejemplo, no es necesario ningn archivo de cabecera adicional, por lo que este
directorio estar vaco.
El directorio media contendr los archivos de geometra, animaciones y textu-
ras necesarios para ejecutar el ejemplo. Todos estos archivos sern cargados por
el ResourceManager.
En obj se generarn automticamente los cheros objeto compilados a partir
de los fuentes existentes en src.
El diretorio plugins contendr los plugins de Ogre. En GNU/Linux, podemos
crear un enlace simblico de este directorio al lugar donde se encuentran los
plugins (archivos .so) en disco. En Windows deberemos copiar los .dll a
este directorio. La ruta de este directorio, como veremos ms adelante, se debe
indicar en el archivo plugins.cfg.
4 Puede descargarse de: http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=6812
5 Puede descargarse de: http://www.ogre3d.org/download/sdk
[290] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

El directorio src contiene los cheros fuente de la aplicacin. Gracias al make-


file que veremos a continuacin, la compilacin de todos los cheros fuente
en objetos binarios se realiza automticamente.

Para compilar el ejemplo, deniremos un makefile para ambos sistemas operati-


vos. En el siguiente listado se muestra el resultado para GNU/Linux. El formato y uso holaMundo

de make ser estudiado en detalle a lo largo del primer mdulo del curso.
En la lnea 0 se dene
el nombre del ejecutable que queremos obtener. A conti-
include *
nuacin en las lneas 1-3 se denen
los directorios para los archivos fuente, objetos
y cabeceras. Las lneas 8 y 11 denen los ags para la compilacin y enlazado res- media
pectivamente.
obj *
El makele construido permite indicar el modo de compilacin, utilizando unos
ags de compilacin en modo Debug y otros en modo Release. Si llamamos a make plugins *
con mode=release, se utilizarn los ags de compilacin optimizada. En otro caso,

utilizaremos la compilacin con smbolos para el depurado posterior con GDB. src
En las lneas 23-24 se utilizan las funciones de make para generar la lista de makefile
objetos a partir de los fuentes .cpp existentes en el directorio apuntado por DIRSRC.
De este modo, se obtienen los objetos con el mismo nombre que los fuentes, pero makefile.win

situados en el directorio indicado por DIROBJ. [ogre.cfg]
Finalmente, en las lneas 36-42 se emplean las reglas de compilacin implcitas plugins.cfg
de make para generar el ejecutable. Se incluye al nal la tpica regla de clean para
limpiar los temporales obtenidos. resources.cfg
De este modo, para compilar el ejemplo en modo release ejecutaremos en un ter-
Figura 8.19: Descripcin de direc-
minal torios del ejemplo Hola Mundo.

make mode=release

Si no indicamos modo, o indicamos explcitamente mode=debug, se utilizarn los


ags de compilacin en modo debug.

Listado 8.1: Makele genrico para GNU/Linux


1 EXEC := helloWorld
2 DIRSRC := src/
3 DIROBJ := obj/
4 DIRHEA := include/
5
6 CXX := g++
7
8 # Flags de compilacion -----------------------------
9 CXXFLAGS := -I $(DIRHEA) -Wall pkg-config --cflags OGRE Figura 8.20: Ventana de congura-
10 cin de las propiedades de Rende-
11 # Flags del linker --------------------------------
ring.
12 LDFLAGS := pkg-config --libs-only-L OGRE
13 LDLIBS := pkg-config --libs-only-l OGRE -lOIS -lGL -lstdc++
14
15 # Modo de compilacion (-mode=release -mode=debug) -----------
16 ifeq ($(mode), release)
17 CXXFLAGS += -O2 -D_RELEASE
18 else
19 CXXFLAGS += -g -D_DEBUG
20 mode := debug
21 endif
22
23 # Obtencion automatica de la lista de objetos a compilar -------
24 OBJS := $(subst $(DIRSRC), $(DIROBJ), \
25 $(patsubst %.cpp, %.o, $(wildcard $(DIRSRC)*.cpp)))
26
27 .PHONY: all clean
8.7. Hola Mundo en OGRE [291]

28
29 all: info $(EXEC)
30
31 info:
32 @echo ---------------------------------------------------
33 @echo >>> Using mode $(mode)
34 @echo (Please, call "make" with [mode=debug|release])
35 @echo ---------------------------------------------------
36
37 # Enlazado -------------------------------------
38 $(EXEC): $(OBJS)
39 $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS)
40

C8
41 # Compilacion -----------------------------------
42 $(DIROBJ) %.o: $(DIRSRC) %.cpp
43 $(CXX) $(CXXFLAGS) -c $< -o $@ $(LDLIBS)
44
45 # Limpieza de temporales ----------------------------
46 clean:
47 rm -f *.log $(EXEC) *~ $(DIROBJ)* $(DIRSRC)*~ $(DIRHEA)*~

El makele en su versin para plataformas windows se ha nombrado como ma-


kele.win. Bsicamente es similar al estudiado para GNU/Linux, pero cambian los
ags de compilacin. Para compilar, ejecutamos el make de MinGW que se denomina
mingw32make:

mingw32-make -f makefile-windows mode=release

Como se muestra en la Figura 8.19, adems de los archivos para make, en el direc-
torio raiz se encuentran tres archivos de conguracin. Estudiaremos a continuacin
su contenido:

ogre.cfg. Este archivo, si existe, intentar ser cargado por el objeto Root. Si el
archivo no existe, o est creado de forma incorrecta, se le mostrar al usuario
una ventana para congurar los parmetros (resolucin, mtodo de anti-aliasing,
profundidad de color, etc...). En el ejemplo que vamos a realizar, se fuerza a que
siempre se abra el dilogo (ver Figura 8.20), recuperando los parmetros que el
usuario especic en la ltima ejecucin.
Plugins.cfg en Windows plugins.cfg. Como hemos comentado anteriormente, un plugin es cualquier m-
dulo que implementa alguno de los interfaces de plugins de Ogre (como Sce-
En sistemas Windows, como no es
posible crear enlaces simblicos, se neManager o RenderSystem). En este archivo se indican los plugins que de-
deberan copiar los archivos .dll be cargar OGRE, as como su localizacin. En el ejemplo del holaMundo, el
al directorio plugins del proyecto e contenido del archivo indica la ruta al lugar donde se encuentran los plugins
indicar la ruta relativa a ese directo- (PluginFolder), as como el Plugin que emplearemos (pueden especicarse
rio en el archivo plugins.cfg.
Esta aproximacin tambin puede varios en una lista) para el rendering con OpenGL.
realizarse en GNU/Linux copiando
los archivos .so a dicho directo- resources.cfg. En esta primera versin del archivo de recursos, se especica el
rio. directorio general desde donde OGRE deber cargar los recursos asociados a los
nodos de la escena. A lo largo del curso veremos cmo especicar manualmente
ms propiedades a este archivo.

Nombres en entidades Una vez denida la estructura de directorios y los archivos que forman el ejemplo,
estudiaremos la docena de lneas de cdigo que tiene nuestro Hola Mundo.
Si no indicamos ningn nombre,
OGRE elegir automticamente un
nombre para la misma. El nombre Listado 8.2: El main.cpp del Hola Mundo en OGRE
debe ser nico. Si el nombre existe,
OGRE devolver una excepcin. 1 #include <ExampleApplication.h>
2
3 class SimpleExample : public ExampleApplication {
[292] CAPTULO 8. FUNDAMENTOS DE GRFICOS TRIDIMENSIONALES

4 public : void createScene() {


5 Ogre::Entity *ent = mSceneMgr->createEntity("Sinbad", "Sinbad.
mesh");
6 mSceneMgr->getRootSceneNode()->attachObject(ent);
7 }
8 };
9
10 int main(void) {
11 SimpleExample example;
12 example.go();
13
14 return 0;
15 }

en el main se crea una instancia de la clase SimpleExample (denida


Como vemos,
en las lneas 2-7 ). Esta clase es derivada de la clase base ExampleApplication, que se

proporciona en el SDK de OGRE para facilitar el aprendizaje de la biblioteca.
En esta clase denimos el mtodo pblico CreateScene. En la lnea 4 llama-
mos al SceneManager (creado automticamente por la clase base ExampleApplica-
tion) a travs de su puntero, solicitando la creacin de una entidad con nombre nico
Sinbad, asociado a Sinbad.mesh (que se encontrar en el directorio especicado en
resources.cfg). Esta llamada nos crear la entidad, y nos devolver un puntero a
un objeto de ese tipo.
A continuacin, adjuntamos la entidad creada a la escena. Para ello, accedemos al
mtodo attachObject() del nodo raz devuelto por getRootSceneNode().
Si compilamos y ejecutamos el holaMundo, primero nos aparecer una ventana

para congurar opciones de rendering (como se muestra en la Figura 8.20). Cuando
las
pulsemos Accept , se abrir una ventana con el modelo cargado (muy pequeito). Si

pulsamos la echa del cursor , el modelo se acercar. Podemos variar la posicin
su modo
como de rendering) con los otros
de la cmara y la rotacin del modelo (as
tres cursores , y las teclas A , S , D , W y R . La salida del modelo se Figura 8.21: Resultado, tras ajustar
muestra en la Figura 8.21. el modelo, del Hello World.

La clase ExampleApplication nos abstrae de la complejidad de crear una aplica-


cin desde cero con OGRE. Utilizaremos esta clase base en algunos ejemplos ms
del siguiente captulo, pero la abandonaremos rpidamente para controlar todos los
aspectos relativos a la inicializacin (carga de Plugins, denicin del RenderSystem,
gestin de la cmara virtual, control de eventos, etc).
8.7. Hola Mundo en OGRE [293]

C8
a b c

d e f

g h i

j k l m

Figura 8.22: Trabajos nales de los alumnos del Curso de Experto en Desarrollo de Videojuegos en las
Ediciones 2011/2012 y 2012/2013, que utilizaban OGRE como motor de representacin grca. a) Vault
Defense (D. Frutos, J. Lpez, D. Palomares), b) Robocorps (E. Aguilar, J. Alczar, R. Pozuelo, M. Romero),
c) Shadow City (L.M. Garca-Muoz, D. Martn-Lara, E. Monroy), d) Kartoon (R. Garca-Reina, E. Prieto,
J.M. Snchez), e) Ransom (M. Blanco, D. Moreno), f) Crazy Tennis (J. Angulo), g) Camel Race (F.J. Corts),
h) 4 Season Mini Golf (A. Blanco, J. Solana), i) Blood & Metal (J. Garca, F. Gaspar, F. Trivio), j) God
Mode On (J.M. Romero), k) Another Far West (R.C. Daz, J.M. Garca, A. Suarez), l) Impulse (I. Cuesta,
S. Fernndez) y m) Msica Maestro (P. Santos, C. de la Torre).
Matemticas para Videojuegos
Captulo 9
Carlos Gonzlez Morcillo

E
n este captulo comenzaremos a estudiar las transformaciones anes bsicas
que sern necesarias para en el desarrollo de Videojuegos. Las transformacio-
nes son herramientas imprescindibles para cualquier aplicacin que manipule
geometra o, en general, objetos descritos en el espacio 3D. La mayora de APIs y
motores grcos que trabajan con grcos 3D (como OGRE) implementan clases au-
xiliares para facilitar el trabajo con estos tipos de datos.

9.1. Puntos, Vectores y Coordenadas


Los videojuegos necesitan posicionar objetos en el espacio 3D. El motor gr-
co debe gestionar por tanto la posicin, orientacin y escala de estos objetos (y sus
cambios a lo largo del tiempo). Como vimos en el captulo 8, en grcos interactivos
suelen emplearse representaciones poligionales basadas en tringulos para mejorar
la eciencia. Los vrtices de estos tringulos se representan mediante puntos, y las
normales mediante vectores. Veamos a continuacin algunos conceptos bsicos rela-
cionados con los puntos y los vectores.

Figura 9.1: Aunque en desarrollo 9.1.1. Sistemas de Referencia


de videojuegos se hace uso de prc-
ticamente todas las reas de las ma-
temticas (desde trigonometra, l- En el inicio del libro Practical Linear Algebra, Farin y Hansford [32] reproducen
grebra, estadstica o clculo), en es- un bonito cuento sobre el pueblo de Schilda, perteneciente a Brandeburgo, que ilustra
te captulo nos centraremos en los a la perfeccin la necesidad de distinguir entre Sistemas de Referencia.
fundamentos ms bsicos del lge-
bra lineal.

295
[296] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS

En el siglo XVII, un ejrcito se aproximaba al municipio de Schilda para intentar


conquistarlo. El tesoro del pueblo tena que esconderse para protegerlo de los invaso-
res, por lo que el consejo asesor del ayuntamiento decidi hundirlo en el lago cercano
al pueblo. As, el equipo del ayuntamiento se montaron en un barco y navegaron hasta
la mitad del lago. Cuando llegaron, el tesorero del ayuntamiento sac una navaja de su
bolsillo e hizo una muesca profunda en el borde del barco y dej caer en esa posicin
el saco con el tesoro. Por qu has hecho eso? - preguntaron el resto de miembros del
equipo. Porque as podremos saber dnde hemos hundido el tesoro respondi el audaz
tesorero. Todos quedaron impresionados de la astucia del tesorero del pueblo.
Cuando naliz la guerra, el equipo del ayuntamiento volvi al lago a recuperar el
tesoro. Al subir al barco, el elaborado plan del tesorero dej de ser tan brillante porque
no importaba dnde se desplazaran con el barco porque la muesca en el borde de la
embarcacin marcaba en todos los sitios la posicin del tesoro!.
En el siglo XVII Ren Descartes invent la teora de los sistemas de coordenadas.
El tesoro del barco estaba especicado con respecto a su sistema de coordenadas local.
Sin embargo, es necesario conocer la posicin global del barco (relativa al origen del
lago) para realizar una interpretacin correcta de la posicin del tesoro.

9.1.2. Puntos
Un punto puede denirse como una localizacin en un espacio n-dimensional. Pa- SRU
ra identicar claramente p como un punto, decimos que p E2 que signica que un
punto 2D vive en el espacio eucldeo E2 . En el caso de los videojuegos, este espa- Figura 9.2: Sistemas de referencia
cio suele ser bidimensional o tridimensional. El tratamiento discreto de los valores Global (Sistema de Referencia Uni-
asociados a la posicin de los puntos en el espacio exige elegir el tamao mnimo y versal o SRU) y Local. Si describi-
mximo que tendrn los objetos en nuestro videojuego. Es crtico denir el convenio mos la posicin del tesoro con res-
pecto del sistema de coordenadas
empleado relativo al sistema de coordenadas y las unidades entre programadores y local del barco, el tesoro siempre se
artistas. Existen diferentes sistemas de coordenadas en los que pueden especicarse mover con el barco y ser imposi-
estas posiciones, como se puede ver en la Figura 9.4: ble volver a localizarlo.

Coordenadas Cartesianas. Es sin duda el sistema de coordenadas ms habi-


tual. Este sistema de coordenadas dene ejes perpediculares para especicar la y Mano
posicin del punto en el espacio. Como se muestra en la gura 9.3, existen dos
Izquierda
convenios para la denicin de los ejes de coordenadas cartesianas; segn la re-
gla de la mano derecha o de la mano izquierda. Es muy sencillo convertir entre
ambos convenios; basta con invertir el valor del eje que cambia.
z
Coordenadas Cilndricas. En este sistema se utiliza un eje vertical para denir
la altura h, un eje radial r que mide la distancia con ese eje h, y un ngulo de
rotacin denido en la circunferencia sobre ese radio. x
Coordenadas Esfricas. Este sistema emplea dos ngulos y , y una distancia
radial r. y

Mano
Coordenadas en OGRE. OGRE utiliza el convenio de la mano derecha. El Derecha
pulgar (eje positivo X) y el ndice (eje positivo Y ) denen el plano de la
pantalla. El pulgar apunta hacia la derecha de la pantalla, y el ndice hacia el
techo. El anular dene el eje positivo de las Z, saliendo de la pantalla hacia z x
el observador.
Figura 9.3: Convenios para esta-
blecer los sistemas de coordenadas
cartesianas.
9.1. Puntos, Vectores y Coordenadas [297]

y h
py
ph
p
p pr p p
pr p
pz p
px
z x
r
r
Coordenadas Coordenadas Coordenadas
Cartesianas Cilndricas Esfricas

C9
Figura 9.4: Representacin de un punto empleando diferentes sistemas de coordenadas.

9.1.3. Vectores
Un vector es una tupla n-dimensional que tiene una longitud (denominada m-
dulo), una direccin y un sentido. Puede ser representado mediante una echa. Dos
vectores son iguales si tienen la misma longitud, direccin y sentido. Los vectores son
entidades libres que no estn ancladas a ninguna posicin en el espacio. Para dife-
renciar un vector v bidimensional de un punto p, decimos que v R2 ; es decir, que
vive en un espacio lineal R2 .
Convenio en OGRE Como en algn momento es necesario representar los vectores como tuplas de
En OGRE se utilizan las clases
nmeros en nuestros programas, en muchas bibliotecas se emplea la misma clase
Vector2 y Vector3 para tra- Vector para representar puntos y vectores en el espacio. Es imprescindible que el
bajar indistintamente con puntos y programador distinga en cada momento con qu tipo de entidad est trabajando.
vectores en 2 y 3 dimensiones.
A continuacin describiremos brevemente algunas de las operaciones habituales
realizadas con vectores.

Suma y resta de vectores

La suma de vectores se puede realizar grcamente empleando la regla del parale-


logramo, de modo de la suma puede calcularse como el resultado de unir el inicio del
primero con el nal del segundo (situando el segundo a continuacin del primero). La
suma, al igual que con nmeros reales es conmutativa. El vector suma grcamente
completa el tringulo (ver Figura 9.5).
Dado un vector a, el vector a tiene el mismo mdulo y direccin que a, pero
sentido contrario.
La resta de un vector con otro puede igualmente representarse mediante un para-
lelogramo. En el diagrama de la Figura 9.5, puede verse cmo efectivamente la resta
del vector b a puede verse como la suma de (b a) + a, como hemos indicado
anteriormente.
El resultado de multiplicar un vector a por un valor escalar obtiene como resultado
un vector con la misma direccin y sentido, pero con el mdulo escalado al factor por
el que se ha multiplicado. Grcamente, la operacin de multiplicacin puede verse
como el efecto de estirar del vector manteniendo su direccin y sentido.
[298] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS

Las operaciones de suma y resta, multiplicacin por un escalar e inversin, se


realizan componente a componente del vector:
a + b = [(ax + bx ), (ay + by ), (az + bz )]
a b = [(ax bx ), (ay by ), (az bz )]
n a = (n ax , n ay , n az )
a = (ax , ay , az )

Recordemos que, la suma de dos vectores da como resultado un vector, mientras a


que la suma de un punto y un vector se interpreta como la obtencin del punto destino
de aplicar la transformacin del vector sobre el punto. La resta de dos puntos (pb pa ) b
da como resultado un vector, con mdulo igual a la distancia existente entre ambos b a+ b
puntos, la direccin de la recta que pasa por ambos puntos y el sentido de ir del punto
p a a pb .
a
Mdulo y normalizacin
c
El mdulo de un vector es un valor escalar que representa la longitud del mismo. d
Puede calcularse como:
|a| = a2x + a2y + a2z c-d
Si dividimos un vector por su longitud (es decir, escalamos el vector a por el factor a
1/|a|), obtenemos como resultado un vector con la misma direccin y sentido, pero de
mdulo la unidad (vector unitario). Esta operacin se denomina normalizacin, y da -a
como resultado un vector normalizado que se emplea en gran cantidad de algoritmos
en programacin de videojuegos.
e e
a
aN =
|a| 2e
Figura 9.5: Representacin de al-
Los vectores pueden multiplicarse entre s, pero a diferencia de los escalares, ad-
gunas operaciones con vectores.
miten dos operaciones de multiplicacin; el producto escalar (que da como resultado Comenzando por arriba: suma y
un escalar), y el producto vectorial (que lgicamente da como resultado otro vector). resta de vectores, inversin y mul-
Veamos a continuacin cmo se denen estas dos operaciones y algunas de sus apli- tiplicacin por escalar.
caciones prcticas.
a
Producto Escalar

El producto escalar (dot product) es conmutativo (a b = b a), y se calcula como:
a b b
a b = ax bx + ay by + az bz (9.1)

Tambin puede ser calculado mediante la siguiente expresin que relaciona el n- Figura 9.6: La proyeccin de a so-
gulo que forman ambos vectores. bre b obtiene como resultado un es-
calar.
a b = |a| |b| cos() (9.2)

Combinando las expresiones 9.1 y 9.2, puede calcularse el ngulo que forman dos
vectores (operacin muy utilizada en informtica grca).
9.1. Puntos, Vectores y Coordenadas [299]

Otra operacin muy til es el clculo de la proyeccin de un vector sobre otro, que
se dene a b como la longitud del vector a que se proyecta mediante un ngulo
recto sobre el vector b (ver Figura 9.6).
ab
a b = |a| cos() =
|b|

El producto escalar se emplea adems para detectar si dos vectores tienen la mis-
ma direccin, o si son perpendiculares, o si tienen la misma direccin o direcciones
opuestas (para, por ejemplo, eliminar geometra que no debe ser representada).
a
Colineal. Dos vectores son colineales si se encuentran denidos en la misma

C9
ab>0 lnea recta. Si normalizamos ambos vectores, y aplicamos el producto escalar
podemos saber si son colineales con el mismo sentido (a b = 1) o sentido
opuesto (a b = 1).
b
a Perpendicular. Dos vectores son perpendiculares si el ngulo que forman entre
ellos es 90 (o 270), por lo que a b = 0.
Misma direccin. Si el ngulo que forman entre ambos vectores est entre 270
ab=0 y 90 grados, por lo que a b > 0 (ver Figura 9.7).
b Direccin Opuesta. Si el ngulo que forman entre ambos vectores es mayor que
a 90 grados y menor que 270, por lo que a b < 0.

Esta interpretacin de misma direccin y direccin opuesta no es literal. Nos servi-


b r para comprobar si el vector normal de una cara poligonal est de frente a la cmara
ab<0 virtual (si tiene direccin opuesta al vector look) de la cmara, o por el contrario est
siendo visto de espaldas.

Figura 9.7: Algunos ejemplos de Producto Vectorial


uso del producto escalar.
Mediante el producto vectorial (cross product) de dos vectores se obtiene otro
vector que es perpendicular a los dos vectores originales.

a b = [(ay bz az by ), (az bx ax bz ), (ax by ay bx )]

El sentido del vector resultado del producto escalar sigue la regla de la mano de-
recha (ver Figura 9.8): si agarramos a b con la palma de la mano, el pulgar apunta
en el sentido positivo del vector producto si el giro del resto de los dedos va de a a b.
En caso contrario, el sentido del vector es el inverso. Por tanto, el producto vectorial
axb no es conmutativo.
El mdulo del producto vectorial est directamente relacionado con el ngulo que
forman ambos vectores . Adems, el mdulo del producto vectorial de dos vectores
a es igual al rea del paralelogramo formado por ambos vectores (ver Figura 9.8).
b
|a b| = |a| |b| sin

El producto vectorial se utiliza en gran cantidad de situaciones. El clculo del


Figura 9.8: Representacin del vector perpendicular a dos vectores es til para calcular el vector normal asociado a
produto vectorial. El mdulo del un tringulo (habitualmente se devuelve normalizado para su posterior utilizacin en
producto vectorial a b es igual al la etapa de shading).
rea del paralelogramo representa-
do en la gura.
[300] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS

Ms Mates?, S!! Existen multitud de objetos matemticos empleados en


grcos interactivos, como rayos, planos, segmentos, as como estructuras de
datos para la aceleracin de los clculos. Estos elementos sern estudiados en
cada seccin que haga uso de ellos.

A continuacin describiremos las transformaciones geomtricas ms comunes em-


pleadas en grcos por computador. Para introducir los conceptos generales asociados
a las transformaciones, comenzaremos con una discusin sobre las operaciones en 2D
para pasar a la notacin matricial 2D y, posteriormente, a la generalizacin tridimen-
sional empleando coordenadas homogeneas.

9.2. Transformaciones Geomtricas


En la representacin de grcos 3D es necesario contar con herramientas para la
transformacin de los objetos bsicos que compondrn la escena. En grcos interac-
tivos, estas primitivas son habitualmente conjuntos de tringulos que denen mallas
poligonales. Las operaciones que se aplican a estos tringulos para cambiar su posi- y
cin, orientacin y tamao se denominan transformaciones geomtricas. En general t p'
podemos decir que una transformacin toma como entrada elementos como vrtices y
vectores y los convierte de alguna manera. p
La transformacin bsica bidimensional ms sencilla es la traslacin. Se reali-
za la traslacin de un punto mediante la suma de un vector de desplazamiento a las x
coordenadas iniciales del punto, para obtener una nueva posicin de coordenadas. Si
aplicamos esta traslacin a todos los puntos del objeto, estaramos desplazando ese
objeto de una posicin a otra. De este modo, podemos denir la traslacin como la t
suma de un vector libre de traslacin t a un punto original p para obtener el punto y
trasladado p (ver Figura 9.9). Podemos expresar la operacin anterior como:
px = px + tx py = py + ty (9.3)

De igual modo podemos expresar una rotacin de un punto p = (x, y) a una nueva x
posicin rotando un ngulo respecto del origen de coordenadas, especicando el eje
de rotacin y un ngulo . Las coordenadas iniciales del punto se pueden expresar Figura 9.9: Arriba. Traslacin de
un punto p a p empleando el vec-
como (ver Figura 9.10): tor t. Abajo. Es posible trasladar un
objeto poligonal completo aplican-
px = d cos py = d sen (9.4) do la traslacin a todos sus vrtices.

Siendo d la distancia entre el punto y el origen del sistema de coordenadas. As,


usando identidades trigonomtricas se pueden expresar las coordenadas transformadas y p'
como la suma de los ngulos del punto original y el que queremos rotar como:
px = d cos( + ) = d cos cos d sen sen
py = d sen( + ) = d cos sen d sen cos
d p
d
Que sustituyendo en la ecuacin 9.4, obtenemos:
x
px = px cos py sen py = px sin py cos (9.5)
Figura 9.10: Rotacin del punto p
un ngulo respecto del origen de
coordenadas.
9.2. Transformaciones Geomtricas [301]

De forma similar, un cambio de escala de un objeto bidimensional puede llevarse


a cabo multiplicando las componentes x, y del objeto por el factor de escala Sx , Sy
en cada eje. As, como se muestra en la Figura 9.11 un cambio de escala se puede
expresar como:
px = px Sx py = py Sy (9.6)

y Cuando queremos cambiar la localizacin de un objeto, habitualmente necesita-


mos especicar una combinacin de traslaciones y rotaciones en el mismo (por
p' ejemplo, cuando cogemos el telfono mvil de encima de la mesa y nos lo guardamos
p en el bolsillo, sobre el objeto se aplican varias traslaciones y rotaciones). Es interesante
por tanto disponer de alguna representacin que nos permita combinar transformacio-
nes de una forma eciente.
x

C9
9.2.1. Representacin Matricial
Figura 9.11: Conversin de un cua- En muchas aplicaciones grcas los objetos deben transformarse geomtricamente
drado a un rectngulo emplean-
do los factores de escala Sx =
de forma constante (por ejemplo, en el caso de una animacin, en la que en cada frame
2, Sy = 1,5. el objeto debe cambiar de posicin. En el mbito de los videojuegos, es habitual que
aunque un objeto permanezca inmvil, es necesario cambiar la posicin de la cmara
virtual para que se ajuste a la interaccin con el jugador.
Homogenezate!!
De este modo resulta crtica la eciencia en la realizacin de estas transforma-
Gracias al uso de coordenadas ho- ciones. Como hemos visto en la seccin anterior, las ecuaciones 9.3, 9.5 y 9.6 nos
mogneas es posible representar las
ecuaciones de transformacin geo- describan las operaciones de traslacin, rotacin y escalado. Para la primera es nece-
mtricas como multiplicacin de sario realizar una suma, mientras que las dos ltimas requieren multiplicaciones. Sera
matrices, que es el mtodo estn- conveniente poder combinar las transformaciones de forma que la posicin nal de
dar en grcos por computador (so-
portado en hardware por las tarjetas
las coordenadas de cada punto se obtenga de forma directa a partir de las coordenadas
aceleradoras grcas). iniciales.
Si reformulamos la escritura de estas ecuaciones para que todas las operaciones se
realicen multiplicando, podramos conseguir homogeneizar estas transformaciones.
Si aadimos un trmino extra (parmetro homogneo h) a la representacin del
punto en el espacio (x, y), obtendremos la representacin homognea de la posicin
descrita como (xh , yh , h). Este parmetro homogneo h es un valor distinto de cero tal
que x = xh /h, y = yh /h. Existen, por tanto innitas representaciones homogneas
equivalentes de cada par de coordenadas, aunque se utiliza normalmente h = 1. Como
veremos en la seccin 9.3, no siempre el parmetro h es igual a uno.
Puntos y Vectores De este modo, la operacin de traslacin, que hemos visto anteriormente, puede
expresarse de forma matricial como:
En el caso de puntos, la componen-
te homognea h = 1. Los vectores
emplean como parmetro h = 0. x 1 0 Tx x
y = 0 1 Ty y (9.7)
1 0 0 1 1
y
Al resolver la multiplicacin matricial se obtienen un conjunto de ecuaciones equi-
valentes a las enunciadas en 9.3. De forma anloga, las operaciones de rotacin Tr y
escalado Ts tienen su equivalente matricial homogneo.

x cos sen 0 Sx 0 0
z Tr = sen cos 0 Ts = 0 Sy 0 (9.8)
0 0 1 0 0 1
Figura 9.12: Sentido de las rotacio- Las transformaciones inversas pueden realizarse sencillamente cambiando el signo en
nes positivas respecto de cada eje de el caso de la traslacin y rotacin (distancias y ngulos negativos), y en el caso de la
coordenadas. escala, utilizando los valores 1/Sx y 1/Sy .
[302] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS

Las transformaciones en el espacio 3D requieren simplemente aadir el parme-


tro homogneo y describir las matrices (en este caso 4x4). As, las traslaciones Tt y
escalados Ts en 3D pueden representarse de forma homognea mediante las siguientes
matrices:

1 0 0 Tx Sx 0 0 0
0 1 0 Ty 0 Sy 0 0
Tt = 0 0 1 Tz Ts = 0 0 Sz 0 (9.9)
0 0 0 1 0 0 0 1

Las rotaciones requieren distinguir el eje sobre el que se realizar la rotacin. Las
rotaciones positivas alrededor de un eje se realizan en sentido opuesto a las agujas
del reloj, cuando se est mirando a lo largo de la mitad positiva del eje hacia el origen
del sistema de coordenadas (ver Figura 9.12).
Las expresiones matriciales de las rotaciones son las siguientes:

1 0 0 0 cos 0 sen 0 cos sen 0 0
0 cos sen 0 0 1 0 0 sen cos 0 0
Rx = 0 sen cos 0 Ry = sen 0 cos 0 Rz = 0 0 1 0 (9.10)
0 0 0 1 0 0 0 1 0 0 0 1

Las tres transformaciones estudiadas (traslacin, rotacin y escalado) son ejem-


plos de transformaciones anes, en las que cada una de las coordenadas transforma-
das se pueden expresar como una funcin lineal de la posicin origen, y una serie de
constantes determinadas por el tipo de transformacin.
Una subclase de las transformaciones anes, es la formada por las transforma-
ciones lineales, que permiten aplicar todas las transformaciones mediante multiplica-
ciones. Como hemos visto anteriormente, las traslacin anes no homognea no es
una operacin lineal (porque no puede realizarse mediante una multiplicacin). Las
transformaciones anes mantienen la mayora de las propiedades de los objetos, ex-
cepto los ngulos y las distancias. Aplicando transformaciones anes se mantiene la
colineridad (ver Tabla 9.1), por lo que las lneas paralelas seguirn siendo paralelas.
Como caso particular de estudio, las transformaciones de cuerpo rgido preservan Afines
todas las propiedades geomtricas de los objetos. Cualquier combinacin de rotacio- Lineales
nes y traslaciones homogneas son transformaciones de cuerpo rgido.

Cuadro 9.1: Propiedades geomtricas preservadas segn la clase de transformacin: Transformaciones Esca-
C. Rg.
anes, Lineales y de Cuerpo Rgido. lado Traslacin
Rotacin
Propiedad T. Anes T. Lineales T. Cuerpo Rgido
ngulos No No S
Figura 9.13: Esquema de subclases
Distancias No No S de transformaciones anes y opera-
Ratios de distancias S S S ciones asociadas.
Lneas paralelas S S S
Lneas rectas S S S
9.2. Transformaciones Geomtricas [303]

T(-p) Rz() T(p)


p
y y y y


x x x x

C9
Figura 9.14: Secuencia de operaciones necesarias para rotar una gura respecto de un origen arbitrario.

9.2.2. Transformaciones Inversas


En muchas situaciones resulta interesante calcular la inversa de una matriz. Un
ejemplo tpico es en la resolucin de ecuaciones, como en el caso de la expresin
A = Bx. Si queremos obtener el valor de B, tendramos B = A/x. Por desgracia,
las matrices no tienen asociado un operador de divisin, por lo que debemos usar el
concepto de matriz inversa.
Para una matriz A, se dene su inversa A1 como la matriz que, multiplicada por
A da como resultado la matriz identidad I:
A A1 = A1 A = I (9.11)

Matrices Inversas Tanto la matriz A como su inversa deben ser cuadradas y del mismo tamao. Otra
No todas las matrices tienen inversa
propiedad interesante de la inversa es que la inversa de la inversa de una matriz es
(incluso siendo cuadradas). Un caso igual a la matriz original (A1 )1 = A.
muy simple es una matriz cuadrada
cuyos elementos son cero. En la ecuacin inicial, podemos resolver el sistema utilizando la matriz inversa.
Si partimos de A = B x, podemos multiplicar ambos trminos a la izquierda por la
inversa de B, teniendo B 1 A = B 1 B x, de forma que obtenemos la matriz
identidad B 1 A = I x, con el resultado nal de B 1 A = x.
En algunos casos el clculo de la matriz inversa es directo, y puede obtenerse de
forma intuitiva. Por ejemplo, en el caso de una traslacin pura (ver ecuacin 9.9),
basta con emplear como factor de traslacin el mismo valor en negativo. En el caso de
escalado, como hemos visto bastar con utilizar 1/S como factor de escala.
Cuando se trabaja con matrices compuestas, el clculo de la inversa tiene que
realizarse con mtodos generales, como por ejemplo el mtodo de eliminacin de
Gauss o la traspuesta de la matriz adjunta.

9.2.3. Composicin
Como hemos visto en la seccin anterior, una de las principales ventajas derivadas
del trabajo con sistemas homogneos es la composicin de matrices. Matemtica-
mente esta composicin se realiza multiplicando las matrices en un orden determina-
do, de forma que es posible obtener la denominada matriz de transformacin neta
MN resultante de realizar sucesivas transformaciones a los puntos. De este modo,
bastar con multiplicar la MN a cada punto del modelo para obtener directamente su
posicin nal. Por ejemplo, si P es el punto original y P es el punto transformado, y
T1 Tn son transformaciones (rotaciones, escalados, traslaciones) que se aplican al
[304] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS

punto P , podemos expresar la transformacin neta como:


P = Tn T2 T1 P

Este orden de multiplicacin de matrices es el habitual empleado en grcos por


computador, donde las transformaciones se premultiplican (la primera est ms cerca
del punto original P , ms a la derecha.
La matriz de transformacin neta MN se denira como MN = Tn T2 T1 ,
de tal forma que slo habra que calcularla una vez para todos los puntos del modelo
y aplicarla a todos vrtices en su posicin original para obtener su posicin nal. De
este modo, si un objeto poligonal est formado por V vrtices, habr que calcular la Figura 9.15: El formato de la ma-
matriz de transformacin neta MN y aplicarla una vez a cada vrtice del modelo. triz de transformacin neta permite
identicar la posicin nal del obje-
P = MN P to (traslacin) en la cuarta columna
Px Py Pz . La matriz 3 3 interior
combina las rotaciones y escalados
Otro aspecto a tener en cuenta es que la expresin de las transformaciones para que se aplicarn al objeto.
trabajar con coordenadas homogeneas, que se han comentado en las ecuaciones 9.9 y
9.10 se reeren al Sistema de Referencia Universal (SRU) o Sistema de Referencia
Global.
y
Conmutatividad. Recordemos que la multiplicacin de matrices es asociati-
va, pero no es conmutativa, por lo que el orden de aplicacin de las transfor-
maciones es importante (ver Figura 9.17). La propiedad asociativa se utiliza
para resumir en la Matriz de Transformacin Neta MN la secuencia de trans-
formaciones que se aplicarn sobre los modelos. De este modo, bastar con x
multiplicar una nica matriz a todos los vrtices.

Figura 9.16: Resultado de apli-


De este modo, si se quiere realizar una transformacin respecto de un punto dis- car directamente la rotacin Rz ()
tinto a ese origen del SRU, habr que hacer coincidir primero el punto con el origen respecto del SRU. Puede compro-
del sistema de referencia, aplicar la transformacin y devolver el objeto a su posicin barse el diferente resultado obteni-
do en comparacin con el de la -
original. As, en el ejemplo de la Figura 9.14 si queremos rotar el objeto respecto del
gura 9.14.
punto p es necesario, primero trasladar el objeto para que su origen quede alineado con
el origen del SRU, luego aplicar la rotacin, y nalmente aplicar la traslacin inversa.
As, tenemos MN = Tp Rz Tp .

9.3. Perspectiva: Representacin Matricial


Como vimos en la seccin 8.2.4, en la proyeccin en perspectiva se tomaban como
entrada las coordenadas de visualizacin, generando la proyeccin de los objetos sobre
el plano de imagen.
Vimos en la Figura 8.9 que, empleando tringulos semejantes, obtenamos las si-
guientes coordenadas:
px d d px
= px = (9.12)
px pz pz
9.3. Perspectiva: Representacin Matricial [305]

y y y
Rz(/4) Sx(2)

x x x

C9
y y y
Sx(2) Rz(/4)

x x x

Figura 9.17: La multiplicacin de matrices no es conmutativa, por lo que el orden de aplicacin de las
transformaciones es relevante para el resultado nal. Por ejemplo, la gura de arriba primero aplica una
rotacin y luego el escalado, mientras que la secuencia inferior aplica las transformaciones en orden inverso.

De igual forma obtenemos la coordenada py = d py /pz , y pz = d. Estas


ecuaciones se pueden expresar fcilmente de forma matricial (siendo Mp la matriz de
proyeccin en perspectiva):


1 0 0 0 px px d px /pz
0 1 0 0 py py d py /pz
p = Mp p = 0 0 1 0 pz = pz = d (9.13)
0 0 1/d 0 1 pz /d 1

Denicin near y far El ltimo paso de la ecuacin 9.13 corresponde con la normalizacin de los com-
ponentes dividiendo por el parmetro homogneo h = pz /d. De esta forma tenemos
Un error comn suele ser que la de- la matriz de proyeccin que nos aplasta los vrtices de la geometra sobre el plano de
nicin correcta de los planos near
y far nicamente sirve para limitar proyeccin. Desafortunadamente esta operacin no puede deshacerse (no tiene inver-
la geometra que ser representada sa). La geometra una vez aplastada ha perdido la informacin sobre su componente
en el Frustum. La denicin correc- de profundidad. Es interesante obtener una trasnformacin en perspectiva que proyec-
ta de estos parmetros es impres- te los vrtices sobre el cubo unitario descrito previamente (y que s puede deshacerse).
cindible para evitar errores de pre-
cisin en el Z-Buffer. De esta forma, denimos la pirmide de visualizacin o frustum, como la pirmide
truncada por un plano paralelo a la base que dene los objetos de la escena que sern
representados. Esta pirmide de visualizacin queda denida por cuatro vrtices que
denen el plano de proyeccin (left l, right r, top t y bottom b), y dos distancias a los
planos de recorte (near n y far f ), como se representa en la Figura 9.18. El ngulo de
visin de la cmara viene determinado por el ngulo que forman l y r (en horizontal)
y entre t y b (en vertical).
[306] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS

Y Y
top
left

right
botto Mp
m

near
far
X Z X
Z

Figura 9.18: La matriz Mp se encarga de transformar la pirmide de visualizacin en el cubo unitario.

La matriz que transforma el frustum en el cubo unitario viene dada por la expresin
de la ecuacin 9.14.
2n r+l
rl 0 rl 0
0 2n t+b
tb 0
Mp =
0
tb
f +n n
(9.14)
0 f n f2fn
0 0 1 0

Si se usa este tipo de proyeccin, el valor de profundidad normalizado no cambia


linealmente con la entrada, sino que se va perdiendo precisin. Es recomendable situar
los planos de recorte cercano y lejano (distancias n y f en la matriz) lo ms juntos
posibles para evitar errores de precisin en el Z-Buffer en distancias grandes.

9.4. Cuaternios
Los cuaternios (quaternion) fueron propuestos en 1843 por William R. Hamilton
como extensin de los nmeros complejos. Los cuaternios se representan mediante
una 4-tupla q = [qx , qy , qz , qw ]. El trmino qw puede verse como un trmino escalar
que se aade a los tres trminos que denen un vector (qx , qy , qz ). As, es comn
representar el cuaternio como q = [qv , qs ] siendo qv = (qx , qy , qz ) y qs = qw en la
tupla de cuatro elementos inicial.
Los cuaternios unitarios, que cumplen la restriccin de que (qx2 +qy2 +qz2 +qw
2
) = 1,
se emplean ampliamente para representar rotaciones en el espacio 3D. El conjunto de
todos los cuaternios unitarios denen la hiperesfera unitaria en R4 .
Si denimos como a al vector unitario que dene el eje de rotacin, como el
ngulo de rotacin sobre ese eje empleando la regla de la mano derecha (si el pulgar
apunta en la direccin de a, las rotaciones positivas se denen siguiendo la direccin
Figura 9.19: Representacin de un
de los dedos curvados de la mano), podemos describir el cuaternio como (ver Figu-
cuaternio unitario.
ra 9.19):
Teorema de Euler
q = [qv , qs ] = [a sin(/2), cos(/2)]
Segn el Teorema de Rotacin de
Leonhard Euler (1776), cualquier
De forma que la parte vectorial se calcula escalando el vector de direccin uni- composicin de rotaciones sobre un
tario por el seno de /2, y la parte escalar como el coseno de /2. Obviamente, esta slido rgido es equivalente a una
multiplicacin en la parte vectorial se realiza componente a componente. sola rotacin sobre un eje, llamado
Polo de Euler.
9.4. Cuaternios [307]

z z z z
a) b) c) d)

x x x x
y y y y

Figura 9.20: El problema del bloqueo de ejes (Gimbal Lock). En a) se muestra el convenio de sistema de

C9
coordenadas que utilizaremos, junto con las elipses de rotacin asociadas a cada eje. En b) aplicamos una
rotacin respecto de x, y se muestra el resultado en el objeto. El resultado de aplicar una rotacin de 90o
en y se muestra en c). En esta nueva posicin cabe destacar que, por el orden elegido de aplicacin de las
rotaciones, ahora el eje x y el eje z estn alineados, perdiendo un grado de libertad en la rotacin. En d) se
muestra cmo una rotacin en z tiene el mismo efecto que una rotacin en x, debido al efecto de Gimbal
Lock.

Como vimos en la seccin 9.2.1, se puede usar una matriz para resumir cualquier
conjunto de rotaciones en 3D. Sin embargo, las matrices no son la mejor forma de
representar rotaciones, debido a:

1. Almacenamiento. La representacin mediante matrices require nueve valores


de punto otante para almacenar una rotacin. Segn el Teorema de Rotacin
de Euler es suciente con establecer la rotacin frente a un nico eje (el Polo
de Euler).
2. Tiempo de Cmputo. En la composicin de la matriz con un vector, se necesi-
tan calcular nueve multiplicaciones y seis sumas en punto otante.
3. Interpolacin. En muchas ocasiones es necesario calcular valores intermedios
entre dos puntos conocidos. En el caso de rotaciones, es necesario calcular los
valores de rotacin intermedios entre dos rotaciones clave (en la animacin de la
rotacin de una cmara virtual, o en las articulaciones de un personaje animado).
La interpolacin empleando matrices es muy complicada.
4. Bloque de Ejes. El problema del bloqueo de ejes (denominado Gimbal Lock)
ocurre cuando se trabaja con ngulos de Euler. Como hemos visto, cualquier ro-
Figura 9.21: Los cuaternios unita- tacin se puede descomponer en una secuencia de tres rotaciones bsicas sobre
rios pueden ser representados co- cada uno de los ejes. Como hemos visto, el orden de aplicacin de las rotaciones
mo puntos sobre la esfera unitaria.
importa (no es conmutativo), por lo que tendremos que decidir uno. Por ejem-
La interpolacin esfrica lineal en-
tre dos cuaternios p y q se puede
plo, podemos aplicar primero la rotacin en x, a continuacin en y y nalmente
igualmente representar como un ar- en z. El orden de las rotaciones es relevante porque rotaciones posteriores tienen
co sobre la esfera.. inuencia jerrquica sobre las siguientes. El problema de Gimbal Lock ocurre
cuando, por accidente uno de los ejes queda alineado con otro, reduciendo los
grados de libertad del objeto. En el ejemplo de la Figura 9.20, tras aplicar la
rotacin de 90o sobre el eje y en c), el eje x sufre la misma transformacin (por
arrastrar la rotacin en y por jerarqua), de modo que queda alineado con Z.
Ahora, la rotacin en z equivale a la rotacin en x, por lo que hemos perdido un
grado de libertad.

De este modo, es interesante trabajar con cuaternios para la especicacin de rota-


ciones. Veamos a continuacin algunas de las operaciones ms comunmente utilizadas
con esta potente herramienta matemtica.
[308] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS

9.4.1. Suma y Multiplicacin


La suma de dos cuaternios se realiza sumando la parte escalar y vectorial por
separado:
p + q = [(pv + qv ), (ps + qs )]

La multiplicacin de dos cuaternios representa su composicin (es decir, el resul-


tado de aplicar la rotacin de uno a continuacin del otro). Existen diferentes formas
de multiplicar cuaternios. A continuacin deniremos la ms ampliamente utilizada
en grcos por computador, que es la multiplicacin de Grassman:
pq = [(ps qv + qs pv + pv qv ), (ps qs pv qv )]

Como vemos, este tipo de multiplicacin utiliza el producto vectorial y la suma de


vectores en la parte vectorial de cuaternio, y el producto escalar en la parte escalar del
cuaternio.
Inversin de cuaternios
9.4.2. Inversa La inversa de un cuaternio es mu-
cho ms rpida que el clculo de la
inversa de una matriz cuadrada. s-
En el caso de cuaternios unitarios, la inversa del cuaternio q 1 es igual al conju- ta es otra razn ms por la que elegir
gado q . El conjugado se calcula simplemente negando la parte vectorial, por lo que, cuaternios para especicar rotacio-
nes.
trabajando con cuaternios unitarios, podemos calcularlos simplemente como:

q 1 = q = [qv , qs ]

La multiplicacin de un cuaternio por su inversa obtiene como resultado el escalar


1 (es decir, rotacin 0). De este modo, q q 1 = [0 0 0 1].

9.4.3. Rotacin empleando Cuaternios


Para aplicar una rotacin empleando cuaternios, primero convertimos el punto o el
vector que rotaremos a cuaternio. En ambos casos, se le aplica 0 como trmino de la
parte escalar. As, dado el vector v, el cuaternio correspondiente a v se calcula como
v = [v 0] = [vx vy vz 0].
Una vez que tenemos expresado en formato de cuaternio el vector, para aplicar Asociatividad
la rotacin empleando el cuaternio, primero lo pre-multiplicamos por q y posterior-
La concatenacin de cuaternios
mente lo post-multiplicamos por el inverso (o el conjugado, en el caso de trabajar con cumple la propiedad asociativa, por
cuaternios unitarios). As, el resultado de la rotacin v puede expresarse como: lo que es posible obtener la expre-
sin del cuaternio neto y aplicarlo a
v = qvq 1 varios objetos de la escena.

La concatenacin de cuaternios funciona exactamente igual que la concatenacin


de matrices. Basta con multiplicar los cuaternios entre s. Si quisiramos aplicar las
rotacioens de los cuaternios q1 y q2 sobre el vector v (especicado en formato de
cuaternio), bastar con realizar la operacin:

v = q2 q1 v q11 q21
9.5. Interpolacin Lineal y Esfrica [309]

Es habitual realizar conversiones entre matrices y cuaternios. OGRE incorpo-


ra llamadas a su biblioteca (ver seccin 9.6) para realizar esta operacin. Se
recomienda el artculo de Gamasutra de Nick Bobic Rotating Objects Using
Quaternions para profundizar en su uso.

9.5. Interpolacin Lineal y Esfrica


Una de las operaciones ms empleadas en grcos por computador es la inter-

C9
polacin. Un claro ejemplo de su uso es para calcular la posicin intermedia en una
animacin de un objeto que se traslada desde un punto A, hasta un punto B.
6 B La interpolacin lineal es un mtodo para calcular valores intermedios mediante
5 polinomios lineales. Es una de las formas ms simples de interpolacin. Habitual-
4 v mente se emplea el acrnimo LERP para referirse a este tipo de interpolacin. Para
3 A obtener valores intermedios entre dos puntos A y B, habitualmente se calcula el vec-
2 tor v = B A con origen en A y destino en B.
1
1 2 3 4 5 6 7 z SLERP (p,q, t=0.5) cuaternio q
(0.816, 0, 0.408, 0.408) (0.707, 0, 0.707, 0)
Figura 9.22: Ejemplo de interpola-
cin lineal entre dos puntos. Se han
z
marcado sobre el vector v los pun- z
tos correspondientes a t = 0, 25, y
t = 0,5 y t = 0,75.
y
y
x
cuaternio p x x
(0.707, 0, 0, 0.707)

Figura 9.23: Ejemplo de aplicacin de interpolacin esfrica SLERP en la rotacin de dos cuaternios p y q
sobre un objeto.

Para calcular valores intermedios, se utiliza una representacin paramtrica, de


forma que, modicando el valor de t (0, 1), obtenemos puntos intermedios:
LERP (A, B, u) = A + vt

Como podemos ver en el ejemplo de la Figura 9.22, para t = 0,25 tenemos que
v = (6, 4), por lo que v 0,25 = (1,5, 1). As, LERP (A, B, 0,25) = (1, 2) +
(1,5, 1) = (2,5, 3).
Como se enunci en la seccin 9.4, una de las ventajas del uso de cuaternios es
la relativa a la facilidad de realizar interpolacin entre ellos. La Interpolacin Lineal
Esfrica (tambin llamada slerp de Spherical Linear Interpolation) es una operacin
que, dados dos cuaternios y un parmetro t (0, 1), calcula un cuaternio interpolado
entre ambos, mediante la siguiente expresin1 :
sin((1 t)) sin(t)
SLERP (p, q, t) = p+ q
sin() sin()
1 Siendo el ngulo que forman los dos cuaternios unitarios.
[310] CAPTULO 9. MATEMTICAS PARA VIDEOJUEGOS

9.6. El Mdulo Math en OGRE Documentacin...


Recordemos que en distribuciones
La biblioteca de clases de OGRE proporciona una amplia gama de funciones mate- de GNU/Linux basadas en Debian,
mticas para trabajar con matrices, vectores, cuaternios y otras entidades matemticas puedes encontrar la documen-
tacin de OGRE en local en
como planos, rayos, esferas, etc... /usr/share/doc/ogre-
-doc.
La documentacin relativa a todas estas clases se encuentra en el Mdulo Math,
que forma parte del ncleo (Core) de OGRE. A continuacin se muestra un sencillo
ejemplo que utiliza algunos operadores de la clase Vector3 y Quaternion.

Listado 9.1: Ejemplo de algunas clases de Math


1 cout << " Ejemplo de algunas clases de Math en OGRE " << endl;
2 cout << "-------------------------------------------" << endl;
3
4 Vector3 v1(1.0, 0.0, 0.0);
5 Vector3 v2(0.0, 2.0, 0.0);
6 Quaternion p(0.707107, 0.0, 0.0, 0.707107);
7 Quaternion q(Degree(90), Vector3(0.0, 1.0, 0.0));
8
9 cout << " Vector V1 = " << v1 << endl;
10 cout << " Vector V2 = " << v2 << endl;
11 cout << " Cuaternio P = " << p << endl;
12 cout << " Cuaternio Q = " << q << endl;
13
14 cout << "--- Algunos operadores de Vectores ------" << endl;
15 cout << " Suma: V1 + V2 = " << v1 + v2 << endl;
16 cout << " Producto por escalar: V1 * 7.0 = " << v1*7.0 << endl;
17 cout << " P. escalar: V1 * V2 = " << v1.dotProduct(v2) << endl;
18 cout << " P. vectorial: V1 x V2 =" << v1.crossProduct(v2) << endl;
19 cout << " Modulo: |V1| = " << v1.length() << endl;
20 cout << " Normalizar: V2n = " << v2.normalisedCopy() << endl;
21 cout << " Angulo (V1,V2)= " << v1.angleBetween(v2).valueDegrees()
<< endl;
22 cout << "--- Algunos operadores de Cuaternios ----" << endl;
23 cout << " Suma: P + Q = " << p + q << endl;
24 cout << " Producto: P * Q = " << p * q << endl;
25 cout << " Producto escalar: P * Q = " << p.Dot(q) << endl;
26 cout << " SLERP(p,q,0.5)= "<< Quaternion::Slerp(0.5, p, q) << endl;

de estos objetos proporcionando diversos constructores.


Ogre facilita la creacin
Por ejemplo, en la lnea 7 se emplea un constructor de cuaternio que permite especi-
car por separado el ngulo de rotacin y el vector.
Algunas clases, como la clase Quaternion incorpora funciones auxiliares como el Operadores de alto nivel
mtodo de interpolacin lineal esfrico SLERP (spherical linear interpolation). En la
lnea 26 se emplea para obtener el cuaternio interpolado para t = 0,5. La Figura 9.23 Como puede verse en el listado
anterior, OGRE incorpora multitud
muestra el resultado de este caso de aplicacin concreto. El cuaternio denido en de operadores de alto nivel para
p se corresponde con una rotacin de 90o en Z (del SRU (Sistema de Referencia sumar, restar, multiplicar y volcar
Universal)), mientras que el cuaternio denido en q equivale a una rotacin de 90o en por pantalla los objetos de la clase
Y (del SRU). A continuacin se muestra el resultado de ejecutar el cdigo anterior: Vector y Quaternion.

Ejemplo de algunas clases de Math en OGRE


-------------------------------------------
Vector V1 = Vector3(1, 0, 0)
Vector V2 = Vector3(0, 2, 0)
Cuaternio P = Quaternion(0.707107, 0, 0, 0.707107)
Cuaternio Q = Quaternion(0.707107, 0, 0.707107, 0)
--- Algunos operadores de Vectores ------
Suma: V1 + V2 = Vector3(1, 2, 0)
Producto por escalar: V1 * 7.0 = Vector3(7, 0, 0)
Producto escalar: V1 * V2 = 0
Producto vectorial: V1 x V2 = Vector3(0, 0, 2)
9.7. Ejercicios Propuestos [311]

Modulo: |V1| = 1
Normalizar: V2n = Vector3(0, 1, 0)
Angulo entre V1 y V2 = 90
--- Algunos operadores de Cuaternios ----
Suma: P + Q = Quaternion(1.41421, 0, 0.707107, 0.707107)
Producto: P * Q = Quaternion(0.5, -0.5, 0.5, 0.5)
Producto escalar: P * Q = 0.5
SLERP(p,q,0.5) = Quaternion(0.816497, 0, 0.408248, 0.408248)

9.7. Ejercicios Propuestos


1. Dado el polgono ABC, con A=(1,1,3), B=(3,1,3) y C=(1,1,1), calcular el restul-
B' tado de aplicar una traslacin de -2 unidades en el eje X, y 1 unidad en el eje Z,

C9
A' y despus una rotacin de /2 radianes respecto del eje Z. Las transformaciones
C' se realizan en ese orden y sobre el SRU.
B 2. Dado el cuadrado denido por los puntos A=(1,0,3), B=(3,0,3), C=(3,0,1) y
A D=(1,0,1), realizar un escalado del doble en el eje X respecto a su centro geo-
mtrico. Compruebe que el resultado no es el mismo que si realiza el escalado
C directamente con respecto del eje X del SRU.
Figura 9.24: En el ejercicio 4, tras 3. Cules seran las expresiones matriciales correspondientes a la operacin de
aplicar el desplazamiento de 2 uni- reexin (espejo) sobre los planos ortogonales x=0, y=0 y z=0? Calcule una
dades en la direccin del vector nor- matriz por cada plano.
mal sobre los vrtices originales del
tringulo, obtenemos A B C . 4. Dado el tringulo ABC, siendo A=(1, -1, 1), B=(-1, -1, -1) y C=(1, 1, -1), despla-
ce la cara poligonal 2 unidades en la direccin de su vector normal (ver Figura
9.24). Suponga que el vector normal sale segn la regla de la mano derecha.
Captulo

Grafos de Escena
10
Carlos Gonzlez Morcillo

C
omo vimos en el captulo 8, uno de los pilares clave de cualquier motor grco
es la organizacin de los elementos que se representarn en la escena. Esta
gestin se realiza mediante el denominado Grafo de Escena que debe permitir
inserciones, bsquedas y mtodos de ordenacin ecientes. OGRE proporciona una
potente aproximacin a la gestin de los Grafos de Escena. En este captulo trabaja-
remos con los aspectos fundamentales de esta estructura de datos jerrquica.

10.1. Justicacin
Como seala D. Eberly [30], el motor de despliegue grco debe dar soporte a
cuatro caractersticas fundamentales, que se apoyan en el Grafo de Escena:
Test de Visibilidad
1. Gestin Datos Eciente. Es necesario eliminar toda la geometra posible antes
La gestin de la visibilidad de los
elementos de la escena es una de las de enviarla a la GPU. Aunque la denicin del Frustum y la etapa de recor-
tareas tpicas que se encarga de rea- te eliminan la geometra que no es visible, ambas etapas requieren tiempo de
lizar el motor grco empleando el procesamiento. Es posible utilizar el conocimiento sobre el tipo de escena a re-
Grafo de Escena. presentar para optimizar el envo de estas entidades a la GPU. Las relaciones
entre los objetos y sus atributos se modelan empleando el Grafo de Escena.
2. Interfaz de Alto Nivel. El Grafo de Escena puede igualmente verse como un
interfaz de alto nivel que se encarga de alimentar al motor grco de bajo nivel
empleando alguna API especica. El diseo de este interfaz de alto nivel per-
mite abstraernos de futuros cambios necesarios en las capas dependientes del
dispositivo de visualizacin.

313
[314] CAPTULO 10. GRAFOS DE ESCENA

3. Facilidad de Uso. Esta caracterstica est directamente relacionada con la an-


terior. El Grafo de Escena forma parte de la especicacin del middleware de
despliegue grco que facilita el uso al programador nal del videojuego. De
este modo, el programador se centra en la semntica asociada a la utilizacin
del Grafo, dejando de lado los detalles de representacin de bajo nivel.
4. Extensibilidad. En gran cantidad de videojuegos comerciales, los equipos de
trabajo de programacin frecuentemente se quejan del cambio en la especica-
cin de requisitos iniciales por parte del equipo artstico. Esto implica aadir
soporte a nuevos tipos de geometra, efectos visuales, etc. El motor debe es-
tar preparado para aadir los nuevos tipos sin necesidad de cambios profundos
en la arquitectura. El diseo correcto del Grafo de Escena mitiga los proble-
mas asociados con la extensibilidad y la adaptacin al cambio en los requisitos
iniciales.

Las estructuras de datos de Grafos de Escena suelen ser grafos que representen
las relaciones jerrquicas en el despliegue de objetos compuestos. Esta dependencia
espacial se modela empleando nodos que representan agregaciones de elementos (2D
o 3D), as como transformaciones, procesos y otras entidades representables (sonidos,
vdeos, etc...).
El Grafo de Escena puede denirse como un grafo dirigido sin ciclos. Los arcos
del grafo denen dependencias del nodo hijo respecto del padre, de modo que la apli-
cacin de una transformacin en un nodo padre hace que se aplique a todos los nodos
hijo del grafo.
El nodo raz habitualmente es un nodo abstracto que proporciona un punto de
inicio conocido para acceder a la escena. Gracias al grafo, es posible especicar f-
cilmente el movimiento de escenas complejas de forma relativa a los elementos padre Cabina
de la jerarqua. En la Figura 10.1, el movimento que se aplique a la Tierra (rotacin, Base Llantas
traslacin, escalado) se aplicar a todos los objetos que dependan de ella (como por
ejemplo a las Tiritas, o las Llantas de la exacavadora). A su vez, las modicaciones Codo
aplicadas sobre la Cabina se aplicarn a todos los nodos que herenden de la jerarqua,
pero no al nodo Llantas o al nodo Tierra.
Pala
Tierra
10.1.1. Operaciones a Nivel de Nodo
Como seala Theoharis et al. [98], en trminos funcionales, la principal ventaja
asociada a la construccin de un Grafo de Escena es que cada operacin se propaga de
forma jerrquica al resto de entidades mediante el recorrido del grafo. Las principales Escena
operaciones que se realizan en cada nodo son la inicializacin, simulacin, culling y
dibujado. A continuacin estudiaremos qu realiza cada operacin.
Tierra Cielo
Inicializacin. Este operador establece los valores iniciales asociados a cada
entidad de la jerarqua. Es habitual que existan diferentes instancias referencia- Tiritas Llantas
das de las entidades asociadas a un nodo. De este modo se separan los datos
estticos asociados a las entidades (denicin de la geometra, por ejemplo) y
las transformaciones aplicados sobre ellos que se incluyen a nivel de nodo. Cabina

Simulacin. En esta operacin se determinan los parmetros y las variables


asociadas a los tipos de datos existentes en el nodo. En el caso de animaciones, Base
se actualizan los controladores asociados para que reejen el instante de tiempo
actual. Figura 10.1: Ejemplo de grafo aso-
ciado a la escena de la excavado-
ra. Escena diseada por Manu Jr-
vinen.
10.2. El Gestor de Escenas de OGRE [315]

C10
Figura 10.2: Ejemplo de estructura de datos para la gestin del culling jerrquico. En el ejemplo, el nodo
referente a las Llantas (ver Figura 10.1) debe mantener la informacin de la caja lmite (Bounding Box) de
todos los hijos de la jerarqua, remarcada en la imagen de la derecha.

Culling. En esta operacin se estudia la visibilidad de los elementos conteni-


dos en los nodos. Gracias a la especicacin jerrquica del grafo, es posible
podar ramas completas de la jerarqua. Para realizar esta operacin, se utilizan
estructuras de datos adicionales que mantienen informacin sobre las coorde-
nadas lmite (mximo y mnimo) asociadas a cada objeto. As, consultando es-
tos lmites, si un nodo queda totalmente fuera de la pirmide de visualizacin
(Frustum), implicar que todos sus nodos hijo estn igualmente fuera y no ser
necesario su posterior dibujado (ver Figura 10.2).
Dibujado. En esta operacin se aplican los algoritmos de rendering a cada nodo
de la jerarqua (comenzando por la raz, bajando hasta las hojas). Si un nodo
contiene rdenes que cambien el modo de dibujado, se aplicarn igualmente a
todos los nodos hijo de la jerarqua.

A continuacin estudiaremos el interfaz de alto nivel que proporciona OGRE para


la gestin de Grafos de Escena. Veremos las facilidades de gestin orientada a obje-
tos, as como la abstraccin relativa a los tipos de datos encapsulados en cada nodo.
Tareas del Gestor
En este captulo nos centraremos en
la parte especca de aplicacin de 10.2. El Gestor de Escenas de OGRE
transformaciones a los objetos. A
lo largo del mdulo estudiaremos
otros aspectos relacionados con el Como hemos visto en la seccin anterior, el Gestor de Escenas, apoyado en el
gestor, como el tratamiento del cu- Grafo de Escena, permite optimizar los datos que se representarn nalmente, po-
lling o la gestin de sombras din- dando parte de la geometra que forma la escena. El Gestor de Escenas en OGRE se
micas.
encarga de las siguientes tareas:

Gestin, creacin y acceso eciente a objetos mviles, luces y cmaras.


Carga y ensamblado de la geometra (esttica) del mundo 3D.
Implementacin de la operacin de Culling para eliminacin de supercies no
visibles.
Dibujado de todos los elementos que forman la escena.
[316] CAPTULO 10. GRAFOS DE ESCENA

Gestin y representacin de sombras dinmicas.


Interfaz para realizar consultas a la escena. Estas consultas son del tipo: Qu
objetos estn contenidos en una determinada regin del espacio 3D?

Visibilidad
10.2.1. Creacin de Objetos Si quieres que un nodo no se dibuje,
simplemente ejecutas la operacin
de detach y no ser renderizado.
La tarea ms ampliamente utilizada del Gestor de Escenas es la creacin de los
objetos que formarn la escena: luces, cmaras, sistemas de partculas, etc. Cualquier Destruccin de nodos
elemento que forme parte de la escena ser gestionado por el Gestor de Escenas, y
La destruccin de un nodo de la es-
formar parte del Grafo de Escena. Esta gestin est directamente relacionada con el cena no implica la liberacin de la
ciclo de vida completa de los objetos, desde su creacin hasta su destruccin. memoria asignada a los objetos ad-
juntos al nodo. Es responsabilidad
Los Nodos del Grafo de Escena se crean empleando el Gestor de Escena. Los del programador liberar la memoria
nodos del grafo en OGRE tienen asignado un nico nodo padre. Cada nodo padre de esos objetos.
puede tener cero o ms hijos. Los nodos pueden adjuntarse (attach) o separarse (de-
tach) del grafo en tiempo de ejecucin. El nodo no se destruir hasta que se le indique
explcitamente al Gestor de Escenas.
En la inicializacin, el Gestor de Escena se encarga de crear al menos un nodo:
el Root Node. Este nodo no tiene padre, y es el padre de toda la jerarqua. Aunque
es posible aplicar transformaciones a cualquier nodo de la escena (como veremos en
la seccin 10.2.2), al nodo Root no se le suele aplicar ninguna transformacin y es
un buen punto en el que adjuntar toda la geometra esttica de la escena. Dado el
objeto SceneManager, una llamada al mtodo getRootSceneNode() nos devuelve
un puntero al Root SceneNode. Este nodo es, en realidad, una variable miembro de la
clase SceneManager.
Los objetos de la escena se pueden adjuntar a cualquier nodo de la misma. En
un nodo pueden existir varios objetos. Sin embargo, no es posible adjuntar la misma
instancia de un objeto a varios nodos de escena al mismo tiempo. Para realizar es-
ta operacin primero hay que separar (detach) el objeto del nodo, y posteriormente
adjuntarlo (attach) a otro nodo distinto.
Para crear un nodo en OGRE, se utiliza el mtodo createSceneNode, que puede
recibir como parmetro el nombre del nodo. Si no se especica, OGRE internamente
le asigna un nombre nico que podr ser accedido posteriormente.
Root
MovableObject

Hijo1 Hijo2
SimpleRenderable Entity Light AnimableObject ParticleSystem

Hijo11 Hijo12
Rectangle2D WireBoundingBox Frustum ManualObject
Hijo121
Camera
Figura 10.3: Ejemplo de grafo de
Figura 10.4: Algunos de los tipos de objetos que pueden ser aadidos a un nodo en OGRE. La clase escena vlido en OGRE. Todos los
abstracta MovableObject dene pequeos objetos que sern aadidos (attach) al nodo de la escena. nodos (salvo el Root) tienen un no-
do padre. Cada nodo padre tiene ce-
ro o ms hijos.
10.2. El Gestor de Escenas de OGRE [317]

La operacin de aadir un nodo hijo se realiza mediante la llamada al mtodo


addChild(Node *child) que aade el nodo hijo previamente creado al nodo exis-
tente. La clase SceneNode es una subclase de la clase abstracta Node, denida para
contener informacin sobre las transformaciones que se aplicarn a los nodos hijo. De
este modo, la transformacin neta que se aplica a cada hijo es el resultado de componer
las de sus padres con las suyas propias.
Como hemos visto antes, los nodos de la escena contienen objetos. OGRE dene
una clase abstracta llamada MovableObject para denir todos los tipos de objetos
que pueden ser aadidos a los nodos de la escena. En la Figura 10.4 se denen algunos
de las principales subclases de esta clase abstracta. Para aadir un objeto a un nodo de
la escena, se emplea la llamada al mtodo attachObject(MovableObject *obj).
La clase Entity se emplea para la gestin de pequeos objetos mviles basa-
dos en mallas poligonales. Para la denicin del escenario (habitualmente con gran
complejidad poligonal e inmvil) se emplea la clase StaticGeometry. La creacin

C10
de entidades se realiza empleando el mtodo createEntity del Gestor de Escena,
indicando el nombre del modelo y el nombre que quiere asignarse a la entidad.
Mundo
El cdigo del siguiente listado (modicacin del Hola del captulo 8) crea

Root

un nodo hijo myNode de RootSceneNode en las lneas , y aade la entidad myEnt
al nodo myChild en la lnea 6 (ver Figura 10.5). A partir de ese punto del cdi-
3-4

myNode go, cualquier transformacin que se aplique sobre el nodo myNode se aplicarn a la
entidad myEnt.

myEnt
Listado 10.1: Creacin de nodos
1 class SimpleExample : public ExampleApplication {
Figura 10.5: Jerarqua obtenida en 2 public : void createScene() {
el grafo de escena asociado al lista- 3 SceneNode* node = mSceneMgr->createSceneNode("myNode");
do de ejemplo. 4 mSceneMgr->getRootSceneNode()->addChild(node);
5 Entity *myEnt = mSceneMgr->createEntity("cuboejes", "cuboejes.
mesh");
6 node->attachObject(myEnt);
7 }
8 };

10.2.2. Transformaciones 3D
Las transformaciones 3D se realizan a nivel de nodo de escena, no a nivel de
objetos. En realidad en OGRE, los elementos que se mueven son los nodos, no los
objetos individuales que han sido aadidos a los nodos.
Como vimos en la seccin 9.1, OGRE utiliza el convenio de la mano derecha para
especicar su sistema de coordenadas. Las rotaciones positivas se realizan en contra
del sentido de giro de las agujas del reloj (ver Figura 9.12).
Las transformaciones en el Grafo de Escena de OGRE siguen el convenio general
explicado anteriormente en el captulo, de forma que se especican de forma relativa
al nodo padre.
Traslacin La traslacin absoluta se realiza mediante la llamada al mtodo setPosition,
La traslacin de un nodo que ya
que admite un Vector3 o tres argumentos de tipo Real. Esta traslacin se realiza de
ha sido posicionado anteriormen- forma relativa al nodo padre de la jerarqua. Para recuperar la traslacin relativa de un
te se realiza mediante la llamada a nodo con respecto a su nodo padre se puede emplear la llamada getPosition.
translate.
[318] CAPTULO 10. GRAFOS DE ESCENA

y y
y Root
x
z x x node1 node2
node2 z node1
z node3 ent1 ent2
y
node3 node4

ent3 ent4
x nod
a) e4
b) c)

Figura 10.6: Ejemplo de transformaciones empleando el Grafo de Escena de OGRE. a) Resultado de eje-
cucin del ejemplo. b) Representacin de los nodos asociados a la escena. c) Jerarqua del grafo de escena.

La rotacin puede realizarse mediante diversas llamadas a mtodos. Uno de los


ms sencillos es empleando las llamadas a pitch, yaw y roll que permiten especi-
car la rotacin (en radianes) respecto del eje X, Y y Z respectivamente. La forma ms
general de aplicar una rotacin es mediante la llamada a rotate que require un eje
de rotacin y un ngulo o un cuaternio como parmetro. De igual modo, la llamada a
getOrientation nos devuelve la rotacin actual del nodo.
El escalado anlogamente se realiza mediante la llamada al mtodo setScale.
Mediante el mtodo getScale obtenemos el factor de escala aplicado al nodo.
A continuacin veremos un cdigo que utiliza algunas de las llamadas a mto-
dos estudiadas anteriormente para aplicar transformaciones en 3D. El resultado de la
ejecucin de dicho cdigo se muestra en la Figura 10.6.

Listado 10.2: Ejemplo de uso del Grafo de Escena


1 class SimpleExample : public ExampleApplication {
2 public : void createScene() {
3 SceneNode* node1 = mSceneMgr->createSceneNode("Node1");
4 Entity *ent1 = mSceneMgr->createEntity("ent1", "cuboejes.mesh");
5 node1->attachObject(ent1);
6 mSceneMgr->getRootSceneNode()->addChild(node1);
7 node1->setPosition(0,0,480);
8 node1->yaw(Degree(-45));
9 node1->pitch(Radian(Math::PI/4.0));
10
11 SceneNode* node2 = mSceneMgr->createSceneNode("Node2");
12 Entity *ent2 = mSceneMgr->createEntity("ent2", "cuboejes.mesh");
13 node2->attachObject(ent2);
14 mSceneMgr->getRootSceneNode()->addChild(node2);
15 node2->setPosition(-10,0,470);
16
17 SceneNode* node3 = mSceneMgr->createSceneNode("Node3");
18 Entity *ent3 = mSceneMgr->createEntity("ent3", "cuboejes.mesh");
19 node3->attachObject(ent3);
20 node1->addChild(node3);
21 node3->setPosition(5,0,0);
22
23 SceneNode* node4 = mSceneMgr->createSceneNode("Node4");
24 Entity *ent4 = mSceneMgr->createEntity("ent4", "cuboejes.mesh");
25 node4->attachObject(ent4);
26 node1->addChild(node4);
27 node4->setPosition(0,0,5);
28 node4->yaw(Degree(-90));
29 }
30 };
10.2. El Gestor de Escenas de OGRE [319]

a radianes
Enellistado anterior se utilizan las funciones de utilidad para convertir
(lnea 7 ), as como algunas constantes matemticas (como en la lnea 8 ).
Posicin de Root Como se observa en la Figura 10.6, se han creado 4 nodos. La transformacin in-
cial aplicada al node1 es heredada por los nodos 3 y 4. De este modo, basta con aplicar
Aunque es posible aplicar transfor-
maciones al nodo Root, se desacon- una traslacin de 5 unidades en el eje x al nodo 3 para obtener el resultado mostrado
seja su uso. El nodo Root debe man- en la Figura 10.6.b). Esta traslacin es relativa al sistema de referencia transformado
tenerse esttico, como punto de re- del nodo padre. No ocurre lo mismo con el nodo 2, cuya posicin se especica de
ferencia del SRU. nuevo desde el origen del sistema de referencia universal (del nodo Root).

Los mtodos de trasnformacin de 3D, as como los relativos a la gestin


de nodos cuentan con mltiples versiones sobrecargadas. Es conveniente es-
tudiar la documentacin de la API de OGRE para emplear la versin ms
interesante en cada momento.

C10
10.2.3. Espacios de transformacin
Las transformaciones estudiadas anteriormente pueden denirse relativas a dife-
rentes espacios de transformacin. Muchos de los mtodos explicados en la seccin
anterior admiten un parmetro opcional de tipo TransformSpace que indica el es-
pacio de transformacin relativo de la operacin. OGRE dene tres espacios de trasn-
formacin como un tipo enumerado Node::TransformSpace:

TS_LOCAL. Sistema de coordenadas local del nodo.


TS_PARENT. Sistema de coordenadas del nodo padre.
TS_WORLD. Sistema de coordenadas universal del mundo.

El valor por defecto de este espacio de transformacin depende de la operacin a


realizar. Por ejemplo, la rotacin (ya sea mediante la llamada a rotate o a pitch, yaw o
roll) se realiza por defecto con respecto al sistema de local, mientras que la traslacin
se realiza por defecto relativa al padre.
Veamos en el siguiente listado un ejemplo que ilustre estos conceptos. En el listado
se denen tres nodos, en el que node1 es el padre de node2 y node3. A node 2 se
le aplica una rotacin respecto del eje y en la lnea 15 . Como hemos comentado
anteriormente, por defecto (si no se le indica ningn parmetro adicional) se realiza
sobre TS_LOCAL1 Posteriormente se aplica una traslacin en la lnea 16 , que por
defecto se realiza relativa al sistema de coordenadas del nodo padre. Al node3 se el

aplican exactamente la misma rotacin y traslacin, pero sta ltima se aplica respecto
del sistema de referencia local (lnea 23 ). Como puede verse en la Figura 10.7, la
entidad adjunta al node3 se representa trasladada 5 unidades respecto de su sistema de
coordenadas local.

Listado 10.3: Uso de diferentes espacios de transformacin


1 class SimpleExample : public ExampleApplication {
2 public : void createScene() {
1 Especicar el valor por defecto del espacio de transformacin tiene el mismo efecto que no

especicarlo. Por ejemplo, cambiar la lnea 15 del ejemplo por node2->yaw(Degree(-90),


Node::TS_LOCAL); no tendra ningn efecto diferente sobre el resultado nal.
[320] CAPTULO 10. GRAFOS DE ESCENA

a) b) y y
x

z
node1 x node2
y

x no d
e3

Figura 10.7: Ejemplo de trabajo con diferentes espacios de transformacin. a) Resultado de ejecucin del
ejemplo. b) Distribucin de los nodos del Grafo de Escena.

3 SceneNode* node1 = mSceneMgr->createSceneNode("Node1");


4 Entity *ent1 = mSceneMgr->createEntity("ent1", "cuboejes.mesh");
5 node1->attachObject(ent1);
6 mSceneMgr->getRootSceneNode()->addChild(node1);
7 node1->setPosition(0,0,480);
8 node1->yaw(Degree(-45)); // Por defecto es Node::TS_LOCAL
9 node1->pitch(Degree(45)); // Por defecto es Node::TS_LOCAL
10
11 SceneNode* node2 = mSceneMgr->createSceneNode("Node2");
12 Entity *ent2 = mSceneMgr->createEntity("ent2", "cuboejes.mesh");
13 node2->attachObject(ent2);
14 node1->addChild(node2);
15 node2->yaw(Degree(-90)); // Por defecto es Node::TS_LOCAL
16 node2->translate(5,0,0); // Por defecto es Node::TS_PARENT
17
18 SceneNode* node3 = mSceneMgr->createSceneNode("Node3");
19 Entity *ent3 = mSceneMgr->createEntity("ent3", "cuboejes.mesh");
20 node3->attachObject(ent3);
21 node1->addChild(node3);
22 node3->yaw(Degree(-90)); // Por defecto es Node::TS_LOCAL
23 node3->translate(5,0,0, Node::TS_LOCAL); // Cambiamos a LOCAL!
24 }
25 };

El orden de las operaciones resulta especialmente relevante cuando se trabaja con


diferentes espacios de transformacin.
Qu ocurrira por ejemplo si invertimos el y y
orde de las lneas 21 y 22 del cdigo anterior?. En ese caso, las entidades relativas x
al node2 y al node3 aparecern desplegadas exactamente en el mismo lugar, como se
muestra en la Figura 10.8. z
no de 1 x node2
node3
Figura 10.8: Tras aplicar la trasla-
cin de la lnea 23, se aplica la rota-
cin local al node3. Ahora el objeto
del node2 y node3 aparecen alinea-
dos exactamente en la misma posi-
cin del espacio.
Recursos Grcos y Sistema de
Captulo 11
Archivos
Javier Alonso Albusac Jimnez
Carlos Gonzlez Morcillo

E
n este captulo se realizar un anlisis de los recursos que son necesarios en
los grcos 3D, centrndose sobre todo en los formatos de especicacin y
requisitos de almacenamiento. Adems, se analizarn diversos casos de estudio
de formatos populares como MD2, MD3, MD5, Collada y se har especial hincapi
en el formato de Ogre 3D.

11.1. Formatos de Especicacin

11.1.1. Introduccin
En la actualidad, el desarrollo de videojuegos de ltima generacin implica la ma-
nipulacin simultnea de mltiples cheros de diversa naturaleza, como son imgenes
estticas (BMP (BitMaP), JPG, TGA (Truevision Graphics Adapter), PNG, ...), che-
ros de sonido (WAV (WAVeform), OGG, MP3 (MPEG-2 Audio Layer III)), mallas que
representan la geometra de los objetos virtuales, o secuencias de vdeo (AVI (Audio
Video Interleave), BIK (BINK Video), etc). Una cuestin relevante es cmo se cargan
estos cheros y qu recursos son necesarios para su reproduccin, sobre todo teniendo
en cuenta que la reproduccin debe realizarse en tiempo real, sin producir ningn tipo
de demora que acabara con la paciencia del usuario.

321
[322] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

A la hora de disear un videojuego es muy importante tener en mente que los


recursos hardware no son ilimitados. Las plataformas empleadas para su ejecucin
poseen diferentes prestaciones de memoria, potencia de procesamiento, tarjeta gr-
ca, etc. No todos los usuarios que adquieren un juego estn dispuestos a mejorar las
prestaciones de su equipo para poder ejecutarlo con ciertas garantas de calidad [62].
Por todo ello, es fundamental elegir los formatos adecuados para cada tipo de archivo,
de tal forma que se optimice en la mayor medida de lo posible los recursos disponibles.
El formato empleado para cada tipo de contenido determinar el tamao y este
factor afecta directamente a uno de los recursos ms importantes en la ejecucin de
un videojuego: la memoria [62]. Independientemente del tamao que pueda tener la
memoria de la plataforma donde se ejecuta el videojuego, sta suele estar completa
durante la ejecucin. Durante la ejecucin se producen diversos cambios de contexto,
en los que se elimina el contenido actual de la memoria para incluir nuevos datos. Por
ejemplo, cuando se maneja un personaje en una determinada escena y ste entra en
una nueva habitacin o nivel, los datos de las escena anterior son eliminados tempo-
ralmente de la memoria para cargar los nuevos datos correspondientes al nuevo lugar
en el que se encuentra el personaje protagonista. Es entonces cuando entran en juego
los mecanismos que realizan el intercambio de datos y gestionan la memoria. Lgi-
camente, cuanto menor sea el espacio que ocupan estos datos ms sencillo y ptimo
ser el intercambio en memoria. A la hora de elegir formato para cada tipo de chero
siempre hay que tratar de encontrar un equilibrio entre calidad y tamao.
En la mayora de videojuegos, los bits seleccionados se empaquetan y comprimen
para ser ejecutados en el momento actual; a estos archivos se les conoce como cheros
de recursos, los cuales contienen una amplia variedad de datos multimedia (imgenes,
sonidos, mallas, mapas de nivel, vdeos, etc).

Figura 11.1: Flujo de datos desde los archivos de recursos hasta los subsistemas que se encargan de la
reproduccin de los contenidos [62].
11.1. Formatos de Especicacin [323]

Normalmente estos archivos estn asociados a un nivel del juego. En cada uno de
estos niveles se denen una serie de entornos, objetos, personajes, objetivos, eventos,
etc. Al cambiar de nivel, suele aparecer en la pantalla un mensaje de carga y el usuario
debe esperar; el sistema lo que est haciendo en realidad es cargar el contenido de
estos cheros que empaquetan diversos recursos.
Cada uno de los archivos empaquetados se debe convertir en un formato adecuado
que ocupe el menor nmero de recursos posible. Estas conversiones dependen en la
mayora de los casos de la plataforma hardware en la que se ejecuta el juego. Por
ejemplo, las plataformas PS3 y Xbox360 presentan formatos distintos para el sonido
y las texturas de los objetos 3D.
En la siguiente seccin se ver con mayor grado de detalle los recursos grcos 3D
que son necesarios, centrndonos en los formatos y los requisitos de almacenamiento.

11.1.2. Recursos de grcos 3D: formatos y requerimientos de al-


macenamiento

C11
Los juegos actuales con al menos una complejidad media-alta suelen ocupar varios
GigaBytes de memoria. La mayora de estos juegos constan de un conjunto de cheros
cerrados con formato privado que encapsulan mltiples contenidos, los cuales estn
distribuidos en varios DVDs (4.7 GB por DVD) o en un simple Blue-Ray ( 25GB)
como es en el caso de los juegos vendidos para la plataforma de Sony - PS3. Si tene-
mos algn juego instalado en un PC, tenemos acceso a los directorios de instalacin y
podemos hacernos una idea del gran tamao que ocupan una vez que los juegos han
sido instalados.
Lo que vamos a intentar en las siguientes secciones es hacernos una idea de cmo
estos datos se almacenan, qu formatos utilizan y cmo se pueden comprimir los datos
para obtener el producto nal. Por tanto, lo primero que debemos distinguir son los
tipos de cheros de datos que normalmente se emplean. La siguiente clasicacin,
ofrece una visin general [62]:

Objetos y mallas 3D para el modelado de entornos virtuales: para el almace-


namiento de este tipo de datos, normalmente son necesarias unas pocas decenas
de megabytes para llevar a cabo dicha tarea. En este tipo de cheros se almacena
toda la geometra asociada al videojuego.
Mallas 3D y datos de animacin: estos datos en realidad no ocupan demasiado
espacio pero suele suponer un nmero elevado de cheros, la suma de todos
ellos puede ocupar varias decenas de MB tambin.
Mapa/ Datos de nivel: en este tipo de archivos se almacenan disparadores de
eventos (eventos que se pueden producir en un determinado escenario y aso-
ciacin de acciones a realizar una vez que se producen), Tipos de objetos del
entorno, scripts, etc. No ocupan demasiado espacio al igual que el caso ante-
rior, y suele ser bastante sencillo compactarlos o comprimirlos.
Sprites (personajes) y texturas asociadas a los materiales: suele haber bas-
tante informacin asociada a estos tipos de datos. En un juego medianamente
complejo, los cheros de este tipo comienzan enseguida a ocupar bastante es-
pacio, hablamos de cientos de megas.
Sonido, msica y dilogos: suelen ser los datos que ocupan mas espacio de
todo el juego, sobre todo cuando los juegos relatan una profunda y larga historia.
Vdeo y escenas pre-grabadas: Cuando se usa este tipo de recursos suele ocu-
par la mayora de espacio, por eso se usan con moderacin. Suelen ser la com-
binacin de personajes animados con archivos de sonido.
[324] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

En las siguientes secciones se describir con mayor detalle cada uno de los puntos
anteriores.

Objetos y Mallas 3D

Al contrario de lo que normalmente se puede pensar, la geometra de los elementos


tridimensionales que se emplean en un videojuego no ocupan demasiado espacio en
comparacin con otro tipo de contenidos [62]. Como se coment en las secciones
anteriores, los principales consumidores de espacio son los cheros de audio y vdeo.
Una malla 3D, independientemente de que corresponda con un personaje, objeto
o entorno, es una coleccin de puntos situados en el espacio, con una serie de datos
asociados que describen cmo estos puntos estn organizados y forman un conjunto
de polgonos y cmo stos deben ser renderizados.
Por otro lado, a los puntos situados en el espacio se les llama vrtices y se repre-
sentan mediante tres puntos pertenecientes a las tres coordenadas espaciales (X,Y,Z),
tomando como referencia el punto de origen situado en (0,0,0). Cualquier elemento
virtual se modela mediante tringulos que forman la malla, y cada tringulo se dene
por medio de tres o mas ndices en una lista de puntos. Aqu se puede mostrar un
ejemplo de una malla que representa un cubo.
Para representar los tringulos del cubo siguiente, necesitaramos tres vrtices por
tringulo y tres coordenadas por cada uno de esos vrtices. Existen diferentes formas
de ahorrar espacio y optimizar la representacin de tringulos. Si se tiene en cuenta
que varios tringulos tienen vrtices en comn, no es necesario representar esos vr-
tices ms de una vez. El mtodo consiste en representar nicamente los tres ndices
del primer tringulo y, para el resto de tringulos, nicamente se aade el vrtice adi-
cional. Esta tcnica sera similar a dibujar el cubo con un lpiz sin separar en ningn
momento la punta del lpiz del papel.

Listado 11.1: Representacin de los vrtices de un cubo con respecto al origen de coordenadas
1 Vec3 TestObject::g_SquashedCubeVerts[] =
2 {
3 Vec3( 0.5,0.5,-0.25), // Vertex 0.
4 Vec3(-0.5,0.5,-0.25), // Vertex 1.
5 Vec3(-0.5,0.5,0.5), // And so on.
6 Vec3(0.75,0.5,0.5),
7 Vec3(0.75,-0.5,-0.5),
8 Vec3(-0.5,-0.5,-0.5),
9 Vec3(-0.5,-0.3,0.5),
10 Vec3(0.5,-0.3,0.5)
11 };

Listado 11.2: Representacin de los tringulos de un cubo mediante asociacin de ndices


1 WORD TestObject::g_TestObjectIndices[][3] =
2 {
3 { 0,1,2 }, { 0,2,3 }, { 0,4,5 },
4 { 0,5,1 }, { 1,5,6 }, { 1,6,2 },
5 { 2,6,7 }, { 2,7,3 }, { 3,7,4 },
6 { 3,4,0 }, { 4,7,6 }, { 4,6,5 }
7 };

Con un simple cubo es complicado apreciar los benecios que se pueden obtener
con esta tcnica, pero si nos paramos a pensar que un modelo 3D puede tener miles
y miles de tringulos, la optimizacin es clara. De esta forma es posible almacenar n
tringulos con n+2 ndices, en lugar de n*3 vrtices como sucede en el primer caso.
11.1. Formatos de Especicacin [325]

Adems de la informacin relativa a la geometra del objeto, la mayora de forma-


tos soporta la asociacin de informacin adicional sobre el material y textura de cada
uno de los polgonos que forman el objeto. El motor de rendering asumir, a menos
que se indique lo contrario, que cada grupo de tringulos tiene asociado el mismo ma-
terial y las mismas texturas. El material dene el color de un objeto y como se reeja
la luz en l. El tamao destinado al almacenamiento de la informacin relativa al ma-
terial puede variar dependiendo del motor de rendering empleado. Si al objeto no le
afecta la luz directamente y tiene un color slido, tan slo sern necesarios unos pocos
de bytes extra. Pero, por el contrario, si al objeto le afecta directamente la luz y ste
tiene una textura asociada, podra suponer casi 100 bytes ms por cada vrtice.
De todo esto debemos aprender que la geometra de un objeto puede ocupar mu-
cho menos espacio si se elige un formato adecuado para representar los tringulos
que forman el objeto. De cualquier forma, los requisitos de memoria se incrementan
notablemente cuando se emplean materiales complejos y se asocian texturas al objeto.

Datos de Animacin

C11
Una animacin es en realidad la variacin de la posicin y la orientacin de los
vrtices que forman un objeto a lo largo del tiempo. Como se coment anteriormente,
una forma de representar una posicin o vrtice en el espacio es mediante tres va-
lores reales asociados a las tres coordenadas espaciales X, Y y Z. Estos nmeros se
representan siguiendo el estndar IEEE (Institute of Electrical and Electronics Engi-
neers)-754 para la representacin de nmeros reales en coma otante mediante 32 bits
(4 bytes). Por tanto, para representar una posicin 3D ser necesario emplear 12 bytes.
Adems de la posicin es necesario guardar informacin relativa a la orientacin,
y el tamao de las estructuras de datos empleadas para ello suele variar entre 12 y 16
bytes, dependiendo del motor de rendering. Existen diferentes formas de representar la
orientacin, el mtodo elegido inuir directamente en la cantidad de bytes necesarios.
Dos de los mtodos ms comunes en el desarrollo de videojuegos son los ngulos de
Euler y el uso de cuaterniones o tambin llamados cuaternios.
Para hacernos una idea del tamao que sera necesario en una sencilla animacin,
supongamos que la frecuencia de reproduccin de frames por segundo es de 25. Por
otro lado, si tenemos en cuenta que son necesarios 12 bytes por vrtice ms otros
12 (como mnimo) para almacenar la orientacin de cada vrtice, necesitaramos por
cada vrtice 12 + 12 = 24 bytes por cada frame. Si en un segundo se reproducen 25
frames, 25 x 24 = 600 bytes por cada vrtice y cada segundo. Ahora supongamos
que un objeto consta de 40 partes movibles (normalmente las partes movibles de un
personaje las determina el esqueleto y los huesos que lo forman). Si cada parte movible
necesita 600 bytes y existen 40 de ellas, se necesitara por cada segundo un total de
24.000 bytes.
Naturalmente 24.000 bytes puede ser un tamao excesivo para un solo segundo
y existen diferentes formas de optimizar el almacenamiento de los datos de anima-
cin, sobre todo teniendo en cuenta que no todas las partes del objeto se mueven en
cada momento y, por tanto, no sera necesario almacenarlas de nuevo. Tampoco es
necesario almacenar la posicin de cada parte movible en cada uno de los 25 frames
que se reproducen en un segundo. Una solucin elegante es establecer frames claves
y calcular las posiciones intermedias de un vrtice desde un frame clave al siguiente
mediante interpolacin lineal. Es decir, no es necesario almacenar todas las posiciones
(nicamente la de los frames claves) ya que el resto pueden ser calculadas en tiempo
de ejecucin.
[326] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

Otra posible mejora se podra obtener empleando un menor nmero de bytes para
representar la posicin de un vrtice. En muchas ocasiones, el desplazamiento o el
cambio de orientacin no son exageradamente elevados y no es necesario emplear 12
bytes. Si los valores no son excesivamente elevados se pueden emplear, por ejemplo,
nmeros enteros de 2 bytes. Estas tcnicas de compresin pueden reducir considera-
blemente el tamao en memoria necesario para almacenar los datos de animacin.

Mapas/Datos de Nivel

Cada uno de los niveles que forman parte de un juego tiene asociado diferentes
elementos como objetos estticos 3D, sonido de ambientacin, dilogos, entornos,
etc. Normalmente, todos estos contenidos son empaquetados en un chero binario
que suele ser de formato propietario. Una vez que el juego es instalado en nuestro
equipo es complicado acceder a estos contenidos de manera individual. En cambio,
durante el proceso de desarrollo estos datos se suelen almacenar en otros formatos
que s son accesibles por los miembros del equipo como, por ejemplo, en formato
XML. El formato XML es un formato accesible, interpretable y permite a personas que
trabajan con diferentes herramientas y en diferentes mbitos, disponer de un medio
para intercambiar informacin de forma sencilla.

Texturas

Hasta el momento, los datos descritos para el almacenamiento de la geometra de


los objetos, animacin y datos de nivel no suponen un porcentaje alto de la capacidad
de almacenamiento requerida. Las texturas (en tercer lugar), los cheros de audio y
vdeo son los que implican un mayor coste en trminos de memoria.
La textura es en realidad una imagen esttica que se utiliza como piel para cubrir
la supercie de un objeto virtual. Existen multitud de formatos que permiten obtener
imgenes de mayor o menor calidad y, en funcin de esta calidad, de un mayor o menor
tamao. Para obtener la mxima calidad, los diseadores grcos preeren formatos
no comprimidos de 32 bits como TIF (TIFF) o TGA. Por contra, el tamao de estas
imgenes podra ser excesivo e inuir en el tiempo de ejecucin de un videojuego. Por
ejemplo, una imagen RAW (imagen sin modicaciones, sin comprimir) de 32 bits con
una resolucin de 1024 x 768 pxeles podra alcanzar el tamao de 3MB. Por tanto, una
vez ms es necesario encontrar un equilibrio entre calidad y tamao. Cuando se disea
un videojuego, una de las principales dicultades que se plantean es la eleccin de un
formato adecuado para cada uno de los recursos, tal que se satisfagan las expectativas
de calidad y eciencia en tiempo de ejecucin.
Uno de los parmetros caractersticos de cualquier formato de imagen es la profun-
didad del color [62]. La profundidad del color determina la cantidad de bits empleados
para representar el color de un pxel en una imagen digital. Si con n bits se pueden
representar 2n valores, el uso de n bits por pxel ofrecer la posibilidad de representar
2n colores distintos, es decir, cuando mayor sea el nmero n de bits empleados, mayor
ser la paleta de colores disponibles.

32-bits (8888 RGBA (Red Green Blue Alpha)). Las imgenes se representan
mediante cuatro canales, R(red), G(green), B(blue), A(alpha), y por cada uno de
estos canales se emplean 8 bits. Es la forma menos compacta de representar un
mapa de bits y la que proporciona un mayor abanico de colores. Las imgenes
representadas de esta forma poseen una calidad alta, pero en muchas ocasiones
es innecesaria y otros formatos podran ser ms apropiados considerando que Figura 11.2: Ejemplo de una ima-
hay que ahorrar el mayor nmero de recursos posibles. gen RGBA con porciones transpa-
rentes (canal alpha).
11.1. Formatos de Especicacin [327]

24-bits (888 RGB (Red Green Blue)). Este formato es similar al anterior pero
sin canal alpha, de esta forma se ahorran 8 bits y los 24 restantes se dividen en
partes iguales para los canales R(red), G(green) y B(blue). Este formato se suele
emplear en imgenes de fondo que contienen una gran cantidad de colores que
no podran ser representados con 16 u 8 bits.
24-bits (565 RGB, 8A). Este formato busca un equilibrio entre los dos anterio-
res. Permite almacenar imgenes con una profundidad de color aceptable y un
rango amplio de colores y, adems, proporciona un canal alfa que permite in-
cluir porciones de imgenes traslcidas. El canal para el color verde tiene un bit
extra debido a que el ojo humano es ms sensible a los cambios con este color.
16-bits (565 RGB). Se trata de un formato compacto que permite almacenar
imgenes con diferentes variedades de colores sin canal alfa. El canal para el
color verde tambin emplea un bit extra al igual que en el formato anterior.

Figura 11.3: Modelo aditivo de co-


16-bits (555 RGB, 1 A). Similar al formato anterior, excepto el bit extra del
canal verde que, ahora, es destinado al canal alfa.

C11
lores rojo, verde y azul.
8-bits indexado. Este formato se suele emplear para representar iconos o im-
genes que no necesitan alta calidad, debido a que la paleta o el rango de colores
empleado no es demasiado amplio. Sin embargo, son imgenes muy compactas
que ahorran mucho espacio en memoria y se transmiten o procesan rpidamente.
En este caso, al emplearse 8 bits, la paleta sera de 256 colores y se represen-
ta de forma matricial, donde cada color tiene asociado un ndice. Precisamente
los ndices son los elementos que permiten asociar el color de cada pxel en la
imagen a los colores representados en la paleta de colores.
Figura 11.4: Paleta de 256 colores
con 8 bits
11.1.3. Casos de Estudio
Formato MD2/MD3

El formato MD2 es uno de los ms populares por su simplicidad, sencillez de uso


y versatilidad para la creacin de modelos (personajes, entornos, armas, efectos, etc).
Fue creado por la compaa id Software y empleado por primera vez en el videojuego
Quake II (ver Figura 11.5).
El formato MD2 representa la geometra de los objetos, texturas e informacin
sobre la animacin de los personajes en un orden muy concreto, tal como muestra la
Figura 11.6 [72]. El primer elemento en la mayora de formatos 3D es la cabecera. La
cabecera se sita al comienzo del chero y es realmente til ya que contiene informa-
cin relevante sobre el propio chero sin la necesidad de tener que buscarla a travs
de la gran cantidad de datos que vienen a continuacin.
Cabecera
La cabecera del formato MD2 contiene los siguientes campos:

Listado 11.3: Cabecera del formato MD2


1 struct SMD2Header {
2 int m_iMagicNum;
3 int m_iVersion;
4 int m_iSkinWidthPx;
5 int m_iSkinHeightPx;
6 int m_iFrameSize;
7 int m_iNumSkins;
8 int m_iNumVertices;
9 int m_iNumTexCoords;
[328] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

Figura 11.5: Captura del vdeojuego Quake II, desarrollado por la empresa id Software, donde se emple
por primera vez el formato MD2

10 int m_iNumTriangles;
11 int m_iNumGLCommands;
12 int m_iOffsetSkins;
13 int m_iOffsetTexCoords;
14 int m_iOffsetTriangles;
15 int m_iOffsetFrames;
16 int m_iOffsetGlCommands;
17 int m_iFileSize;
18 int m_iNumFrames;
19
20 };

El tamao total es de 68 bytes, debido a que cada uno de los enteros se repre-
senta haciendo uso de 4 bytes. A continuacin, en el siguiente listado se describir
brevemente el signicado de cada uno de estos campos [72].

En primer lugar podemos apreciar un campo que se reere al "nmero mgico".


Este nmero sirve para identicar el tipo de archivo y comprobar que en efecto
se trata de un chero con formato MD2.
Versin del chero. De esta forma se lleva a cabo un control de versiones y se
evita el uso de versiones obsoletas del chero.
La siguiente variable hace referencia a la textura que se utiliza para cubrir la
supercie del modelo. En este formato, cada modelo MD2 puede utilizar un
piel o textura en un instante concreto de tiempo, aunque se podran cargar varias
texturas e ir alternndolas a lo largo del tiempo.
m_iSkinWidthPx y m_iSkinHeightPx representan la anchura y altura de la tex-
tura en pxeles.
11.1. Formatos de Especicacin [329]

C11
Figura 11.6: Jerarqua de capas en un chero con formato MD2

m_iFrameSize es un entero que representa el tamao en bytes de cada frame


clave.

Las seis variables siguientes se reeren a cantidades [72]:

m_iNumSkins es el nmero de texturas denidas en el chero. Como se comen-


t anteriormente, el formato no soporta la aplicacin simultnea de mltiples
texturas, pero s cargar varias texturas y ser aplicadas en diferentes instantes.
m_iNumVertices es un entero que representa el nmero de vrtices por frame.
m_iNumTexCoords representa el nmero de coordenadas de la textura. El n-
mero no tiene que coincidir con el nmero de vrtices y se emplea el mismo
nmero de coordenadas para todos los frames.
m_iNumTriangles es el nmero de tringulos que compone el modelo. En el
formato MD2, cualquier objeto est formado mediante la composicin de trin-
gulos, no existe otro tipo de primitiva como, por ejemplo, cuadrilteros.
m_iNumGLCommands especica el nmero de comandos especiales para opti-
mizar el renderizado de la malla del objeto. Los comandos GL no tienen por qu
cargar el modelo, pero s proporcionan una forma alternativa de renderizarlo.
m_iNumFrames representa el nmero de frames en el chero MD2 le. Cada
uno de los frames posee informacin completa sobre las posiciones de los vr-
tices para el proceso de animacin.
[330] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

Las cinco variables siguientes se emplean para denir el offset o direccin relativa
(el nmero de posiciones de memoria que se suman a una direccin base para obtener
la direccin absoluta) en bytes de cada una de las partes del chero. Esta informacin
es fundamental para facilitar los saltos en las distintas partes o secciones del chero
durante el proceso de carga. Por ltimo, la variable m_iFileSize representa el tamao
en bytes desde el inicio de la cabecera hasta el nal del chero.
Frames y Vrtices
A continuacin se muestra la estructura que representa la informacin que se ma-
neja por frame en el formato MD2 [72]:

Listado 11.4: Informacin empleada en cada frame en el formato MD2


1 struct SMD2Frame {
2 float m_fScale[3];
3 float m_fTrans[3];
4 char m_caName[16];
5 SMD2Vert * m_pVertss
6
7 SMD2Frame()
8 {
9 m_pVerts = 0;
10 }
11 ~SMD2Frame()
12 {
13 if(m_pVerts) delete [] m_pVerts;
14 }
15 };

Como se puede apreciar en la estructura anterior, cada frame comienza con seis
nmeros representados en punto otante que representan la escala y traslacin de los
vrtices en los ejes X, Y y Z. Posteriormente, se dene una cadena de 16 caracteres
que determinan el nombre del frame, que puede resultar de gran utilidad si se quiere
hacer referencia a ste en algn momento. Por ltimo se indica la posicin de todos los
vrtices en el frame (necesario para animar el modelo 3D). En cada uno de los frames
habr tantos vrtices como se indique en la variable de la cabecera m_iNumVerts. En la
ltima parte de la estructura se puede diferenciar un constructor y destructor, que son
necesarios para reservar y liberar memoria una vez que se ha reproducido el frame, ya
que el nmero mximo de vrtices que permite almacenar el formato MD2 por frame
es 2048.
Si se renderizaran nicamente los vrtices de un objeto, en pantalla tan slo se
veran un conjunto de puntos distribuidos en el espacio. El siguiente paso consistir
en unir estos puntos para denir los tringulos y dar forma al modelo.
Triangularizacin
En el formato MD2 los tringulos se representan siguiendo la tcnica descrita en
la Seccin 11.1.2. Cada tringulo tiene tres vrtices y varios tringulos tienen ndices
en comn [72]. No es necesario representar un vrtice ms de una vez si se representa
la relacin que existe entre ellos mediante el uso de ndices en una matriz.
Adems, cada tringulo debe tener asociado una textura, o sera ms correcto de-
cir, una parte o fragmento de una imagen que representa una textura. Por tanto, es Figura 11.7: Modelado de un ob-
jeto tridimensional mediante el uso
necesario indicar de algn modo las coordenadas de la textura que corresponden con de tringulos.
cada tringulo. Para asociar las coordenadas de una textura a cada tringulo se em-
plean el mismo nmero de ndices, es decir, cada ndice de un vrtice en el tringulo
tiene asociado un ndice en el array de coordenadas de una textura. De esta forma se
puede texturizar cualquier objeto fcilmente.
11.1. Formatos de Especicacin [331]

A continuacin se muestra la estructura de datos que se poda emplear para re-


presentar los tringulos mediante asociacin de ndices en un array y la asociacin de
coordenadas de la textura para cada vrtice:

Listado 11.5: Estructura de datos para representar los tringulos y las coordenadas de la tex-
tura
1 struct SMD2Tri {
2 unsigned short m_sVertIndices[3];
3 unsigned short m_sTexIndices[3];
4 };

Inclusin de Texturas
En el formato MD2 existen dos formas de asociar texturas a los objetos. La prime-
ra de ellas consiste en incluir el nombre de las texturas embebidos en el chero MD2.
El nmero de cheros de texturas y la localizacin, se encuentran en las variables de
la cabecera m_iNumSkins y m_iOffsetSkins. Cada nombre de textura ocupa 64 carac-

C11
teres alfanumricos como mximo. A continuacin se muestra la estructura de datos
empleada para denir una textura [72]:

Listado 11.6: Estructura de datos para denir una textura embebida en un chero MD2
1 struct SMD2Skin
2 {
3 char m_caSkin[64];
4 CImage m_Image;
5 };

Tal como se puede apreciar en la estructura anterior, existe una instancia de la


clase CImage. Esta clase posee varias funciones para cargar y asociar varias clases de
texturas. Existe una textura diferente por cada nombre de chero que aparece en la
seccin de texturas dentro del chero MD2.
Una segunda alternativa es utilizar la clase CImage y el mtodo SetSkin para cargar
una textura y asociarla a un objeto [72]. Si existe una correspondencia por coordenadas
entre la textura y el objeto, ser necesario cargar las coordenadas antes de asociar la
textura al objeto. El nmero de coordenadas de la textura se puede encontrar en la
variable de la cabecera m_iNumTexCoords. La estructura de datos que se utiliza para
denir las coordenadas de una textura es la siguiente:
Figura 11.8: Ejemplo de textura en
la que se establece una correspon-
Listado 11.7: Estructura de datos para representar las coordenadas de una textura
dencia entre los vrtices del objeto
y coordenadas de la imagen. 1 struct SMD2TexCoord {
2 float m_fTex[2];
3 };

Cada coordenada de una textura consiste en un par de nmeros de tipo oat de 2


bytes. La primera coordenada de la textura comienza en el cero y naliza en el valor
equivalente a la anchura de la imagen; la segunda desde el cero hasta el valor de la
altura. En el resto de coordenadas se establecen las correspondencias entre zonas de
la imagen de la textura y las supercies del objeto. Finalmente, una vez que se han
denido las coordenadas ya se puede asociar la textura al objeto, mediante la funcin
Bind de la clase CImage.
[332] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

Principales diferencias entre el formato MD2 y MD3

El formato MD3 fue el formato que se emple en el desarrollo del videojuego


Quake III y sus derivados (Q3 mods, Return to Castle Wolfenstein, Jedi Knights 2,
etc.). MD3 se basa en su predecesor pero aporta mejoras notables en dos aspectos
claves:

Animacin de personajes. La frecuencia de reproduccin de frames en el for-


mato MD2 est limitada a 10 fps. En el caso del formato MD3 la frecuencia es
variable, permitiendo animaciones de vrtices ms complejas.
Modelado de objetos 3D. Otra diferencia signicativa entre los formatos MD2
y MD3 es que en este ltimo los objetos se dividen en tres bloques diferencia-
dos, normalmente, cabeza, torso y piernas. Cada uno de estos bloques se tratan
de manera independiente y esto implica que cada parte tenga su propio conjunto Figura 11.9: Captura del vdeojue-
de texturas y sean renderizados y animados por separado. go Quake III.

Formato MD5

El formato MD5 se emple en el desarrollo de los videojuegos Doom III y Quake


IV. El formato presenta mejoras signicativas en la animacin de personajes. En el
caso de los formatos MD2 y MD3 los movimientos de cada personaje se realizaban
mediante animacin de vrtices, es decir, se almacenaba la posicin de los vrtices
de un personaje animado en cada frame clave. Tratar de forma individualizada cada
vrtice es realmente complejo y animar un personaje siguiendo esta metodologa no
es una tarea sencilla.
En el formato MD5 es posible denir un esqueleto formado por un conjunto de
huesos y asociarlo a un personaje. Cada uno de los huesos tiene a su vez asociado un
conjunto de vrtices. La animacin en el formato MD5 se basa en la animacin de los
huesos que forman el esqueleto; el grupo de vrtices asociado a un hueso variar su
posicin en base al movimiento de ste. Una de las grandes ventajas de la animacin
mediante movimiento de huesos de un esqueleto es que sta se puede almacenar y
reutilizar para personajes que tengan un esqueleto similar.

COLLADA

El formato COLLADA (COLLAborative Design Activity) [43] surge ante la ne-


cesidad de proponer un formato estndar de cdigo abierto que sirva como medio de
intercambio en la distribucin de contenidos. La mayora de empresas utilizan su pro-
pio formato de cdigo cerrado y en forma binaria, lo que diculta el acceso a los datos
y la reutilizacin de contenidos por medio de otras herramientas que permiten su edi-
cin. Con la elaboracin de COLLADA se pretenden alcanzar una serie de objetivos
bsicos [68]:

COLLADA no es un formato para motores de videojuegos. En realidad, CO-


LLADA benecia directamente a los usuarios de herramientas de creacin y
distribucin de contenidos. Es decir, COLLADA es un formato que se utiliza
en el proceso de produccin como mecanismo de intercambio de informacin
entre los miembros del equipo de desarrollo y no como mecanismo nal de
produccin.
El formato COLLADA debe ser independiente de cualquier plataforma o tec-
nologa de desarrollo (sistemas operativos, lenguajes de programacin, etc).
11.1. Formatos de Especicacin [333]

Ser un formato de cdigo libre, representado en XML para liberar los recursos
digitales de formatos binarios propietarios.
Proporcionar un formato estndar que pueda ser utilizado por una amplia ma-
yora de herramientas de edicin de modelos tridimensionales.
Intentar que sea adoptado por una amplia comunidad de usuarios de contenidos
digitales.
Proveer un mecanismo sencillo de integracin, tal que toda la informacin po-
sible se encuentre disponible en este formato.
Ser la base comn de todas las transferencias de datos entre aplicaciones 3D.

Cualquier chero XML representado en el formato COLLADA est dividido en


tres partes principales [68]:

C11
COLLADA Core Elements. Donde se dene la geometra de los objetos, in-
formacin sobre la animacin, cmaras, luces, etc.
COLLADA Physics. En este apartado es posible asociar propiedades fsicas
a los objetos, con el objetivo de reproducir comportamientos lo ms realistas
posibles.
COLLADA FX. Se establecen las propiedades de los materiales asociados a
los objetos e informacin valiosa para el renderizado de la escena.

No es nuestro objetivo ver de forma detallada cada uno de estos tres bloques, pero
s se realizar una breve descripcin de los principales elementos del ncleo ya que
stos componen los elementos bsicos de una escena, los cuales son comunes en la
mayora de formatos.
COLLADA Core Elements
En este bloque se dene la escena (<scene>) donde transcurre una historia y los
objetos que participan en ella, mediante la denicin de la geometra y la animacin
de los mismos. Adems, se incluye informacin sobre las cmaras empleadas en la
escena y la iluminacin. Un documento con formato COLLADA slo puede contener
un nodo <scene>, por tanto, ser necesario elaborar varios documentos COLLADA
para denir escenas diferentes. La informacin de la escena se representa mediante un
grafo acclico y debe estar estructurado de la mejor manera posible para que el pro-
cesamiento sea ptimo. A continuacin se muestran los nodos/etiquetas relacionados
con la denicin de escenas [68].

Listado 11.8: Nodos empleados en COLLADA para la denicin de escenas


1 <instance_node>
2 <instance_visual_scene>
3 <library_nodes>
4 <library_visual_scenes>
5 <node>
6 <scene>
7 <visual_scene>
[334] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

Para facilitar el manejo de una escena, COLLADA ofrece la posibilidad de agrupar


los elementos en libreras. Un ejemplo sencillo de denicin de una escena podra ser
el siguiente:
Al igual que en el caso anterior, la denicin de la geometra de un objeto tambin
puede ser estructurada y dividida en libreras, muy til sobre todo cuando la geometra
de un objeto posee una complejidad considerable. De esta forma los nodos geometry se
pueden encapsular dentro de una librera de objetos denominada library_geometrics.

Listado 11.9: Ejemplo de denicin de una escena en el formato COLLADA


1 <COLLADA>
2 <library_nodes id="comp">
3 <node name="earth">
4 </node>
5 <node name="sky">
6 </node>
7 </library_nodes>
8
9 <library_visual_scenes>
10 <visual_scene id="world">
11 <instance_library_nodes url="#comp">
12 </visual_scene>
13 </library_visual_scenes>
14
15 <scene>
16 <instance_visual_scene url="#world"/>
17 </scene>
18 </COLLADA>

El elemento <geometry> permite denir la geometra de un objeto. En el caso de


COLLADA (y en la mayora de formatos) la geometra se dene mediante la de-
nicin de una malla que incluye informacin del tipo: cantidad de puntos, posicin,
informacin de color, coordenadas de la textura aplicada al objeto, lneas que los co-
nectan, ngulos, supercies, etc. Los nodos que se emplean para denir la geometra
de un objeto en COLLADA son los siguientes [68]:

Listado 11.10: Nodos utilizados para denir la geometra de un objeto en el formato COLLADA
1 <control_vertices>
2 <geometry>
3 <instance_geometry>
4 <library_geometries>
5 <lines>
6 <linestrips>
7 <mesh>
8 <polygons>
9 <polylist>
10 <spline>
11 <triangles>
12 <trifans>

Un ejemplo de denicin de una malla en el formato COLLADA podra ser el que


se muestra en el siguiente listado [68].
Para completar la informacin de la geometra de un objeto es necesario incluir
datos sobre la transformacin de los vrtices. Algunas de las operaciones ms comunes
son la rotacin (<rotate>), escalado (<scale>) o traslaciones (<translate>).

Listado 11.11: Ejemplo de denicin de una malla en el formato COLLADA


1 <mesh>
11.1. Formatos de Especicacin [335]

2 <source id="position" />


3 <source id="normal" />
4 <vertices id="verts">
5 <input semantic="POSITION" source="#position"/>
6 </vertices>
7 <polygons count="1" material="Bricks">
8 <input semantic="VERTEX" source="#verts" offset="0"/>
9 <input semantic="NORMAL" source="#normal" offset="1"/>
10 <p>0 0 2 1 3 2 1 3</p>
11 </polygons>
12 </mesh>

En cuanto a la iluminacin, COLLADA soporta la denicin de las siguientes


fuentes de luces [68]:

Luces ambientales
Luces puntuales

C11
Luces direccionales
Puntos de luz

y se utilizan los siguientes nodos o etiquetas:

Listado 11.12: Nodos empleados en COLLADA para la denicin de fuentes de luz


1 <ambient>
2 <color>
3 <directional>
4 <instance_light>
5 <library_lights>
6 <Light>
7 <point>
8 <spot>

Por otro lado, COLLADA tambin permite la denicin de cmaras. Una cmara
declara una vista de la jerarqua del grafo de la escena y contiene informacin sobre la
ptica (perspectiva u ortogrca). Los nodos que se utilizan para denir una cmara
son los siguientes [68]:

Listado 11.13: Nodos empleados en COLLADA para denir cmaras


1 <camera>
2 <imager>
3 <instance_camera>
4 <library_cameras>
5 <optics>
6 <orthographic>
7 <Perspective>

Un ejemplo de denicin de una cmara podra ser el siguiente [68]:

Listado 11.14: Ejemplo de denicin de una cmara en el formato COLLADA


1 <camera name="eyepoint">
2 <optics>
3 <technique_common>
4 <perspective>
5 <yfov>45</yfov>
6 <aspect_ratio>1.33333
[336] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

7 </aspect_ratio>
8 <znear>1.0</znear>
9 <zfar>1000.0</zfar>
10 </perspective>
11 </technique_common>
12 </optics>
13 </camera>

En cuanto a la parte de animacin no entraremos en detalle en esta seccin ya


que se ver en captulos posteriores. Simplemente recalcar que COLLADA est pre-
parado para soportar los dos tipos de animacin principales: animacin de vrtices y
animacin de esqueletos asociado a objetos.

Formato para OGRE 3D

Los tres elementos esenciales en el formato de Ogre son los siguientes [45]:

Entity o entidad. Una entidad es cualquier elemento que se puede dibujar en


pantalla; por tanto, quedan excluidos de esta categora las fuentes de luz y las
cmaras. La posicin y la orientacin de una malla no se controla mediante este
tipo de objeto pero s el material y la textura asociada.
SceneNode o nodo de la escena. Un nodo se utiliza para manipular las propie-
dades o principales caractersticas de una entidad (tambin sirven para controlar
las propiedades de las fuentes de luz y cmaras). Los nodos de una escena estn
organizados de forma jerrquica y la posicin de cada uno de ellos siempre es
relativa a la de los nodos padre.
SceneManager o controlador de la escena. Es el nodo raz y de l derivan el
resto de nodos que se dibujan en la escena. A travs de este nodo es posible
acceder a cualquier otro nodo en la jerarqua.

Si analizamos cualquier chero OgreXML podemos apreciar que existe informa-


cin relativa a las mallas de los objetos. Cada malla puede estar formada a su vez por
una o varias submallas, que contienen la siguiente informacin:

Denicin de caras (<face>).


Denicin de vrtices (<vertex>) con su posicin, normal, color y coordenadas
UV.
Asignacin de vrtices a huesos (<vertexboneassignment>).
Enlace a un esqueleto (<skeletonlink>).

A su vez cada submalla contiene el nombre del material y el nmero de caras


asociada a la misma. Para cada una de las caras se almacenan los vrtices que la
componen:

Listado 11.15: Creacin de mallas y submallas con materiales asociados


1 <mesh>
2 <submeshes>
3 <submesh material="blanco_ojo" usesharedvertices="false">
4 <faces count="700">
5 <face v1="0" v2="1" v3="2"/>
6 .............................
7 </faces>
11.1. Formatos de Especicacin [337]

Adems es necesario saber la informacin geomtrica de cada uno de los vrti-


ces, es decir, posicin, normal, coordenada de textura y el nmero de vrtices de la
submalla

Listado 11.16: Geometra de un Objeto en OgreXML


1 <geometry vertexcount="3361">
2 <vertexbuffer positions="true" normals="true" texture_coords="1">
3 <vertex>
4 <position x="-0.000000" y="0.170180" z="-0.000000"/>
5 <normal x="0.000000" y="1.000000" z="0.000000"/>
6 <texcoord u="0.000000" v="1.000000"/>
7 </vertex>

Para la asignacin de los huesos se indican los huesos y sus pesos. Como se puede
observar, el ndice utilizado para los huesos empieza por el 0.

C11
Listado 11.17: Asignacin de huesos a una malla en OgreXML
1 <boneassignments>
2 <vertexboneassignment vertexindex="0" boneindex="26" weight="
1.000000"/>
3 .............................
4 </boneassignments>
5 </submesh>

Si la malla o mallas que hemos exportado tienen asociado un esqueleto se mostrar


con la etiqueta "skeletonlink". El campo name corresponde con el archivo xml que
contiene informacin sobre el esqueleto: <skeletonlink name=uerpo.skeleton/ >. En
este caso el archivo "name.skeleton.xml"dene el esqueleto exportado, es decir, el
conjunto de huesos que componen el esqueleto. El exportador asigna a cada hueso un
ndice empezando por el 0 junto con el nombre, la posicin y rotacin del mismo:

Listado 11.18: Denicin de un esqueleto en OgreXML


1 <skeleton>
2 <bones>
3 <bone id="0" name="cerrada">
4 <position x="5.395440" y="6.817142" z="-0.132860"/>
5 <rotation angle="0.000000">
6 <axis x="1.000000" y="0.000000" z="0.000000"/>
7 </rotation>
8 </bone>
9 .............................
10 </bones>

La denicin del esqueleto no estara completa si no se conoce la jerarqua de


los huesos, esta informacin se describe indicando cul es el padre de cada uno e los
huesos:

Listado 11.19: Denicin de la jerarqua de huesos en un esqueleto en el formato OgreXML


1 <bonehierarchy>
2 <boneparent bone="torso" parent="caderas" />
3 <boneparent bone="rota_ceja_izquierda" parent="ceja_izquierda"
/>
4 <boneparent bone="rota_ceja_derecha" parent="ceja_derecha" />
5 <boneparent bone="pecho" parent="torso" />
6 <boneparent bone="hombro.r" parent="pecho" />
7 .............................
8 </bonehierarchy>
[338] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

Por ltimo, si se han creado animaciones asociadas al esqueleto y han sido exporta-
das, se mostrar cada una de ellas identicndolas con el nombre denido previamente
en Blender y la longitud de la accin medida en segundos. Cada animacin contendr
informacin sobre los huesos que intervienen en el movimiento (traslacin, rotacin y
escalado) y el instante de tiempo en el que se ha denido el frame clave:

Listado 11.20: Animacin de los huesos de un esqueleto en OgreXML


1 <animations>
2 <animation name="ascensor" length="2.160000">
3 <track bone="d1root.l">
4 <keyframes>
5 <keyframe time="0.000000">
6 <translate x="-0.000000" y="0.000000" z="0.000000"/>
7 <rotate angle="0.000000">
8 <axis x="-0.412549" y="0.655310" z="0.632749"/>
9 </rotate>
10 <scale x="1.000000" y="1.000000" z="1.000000"/>
11 </keyframe>
12
13 .............................
14 <keyframe time="2.160000">
15 <translate x="0.000000" y="-0.000000" z="0.000000"/>
16 <rotate angle="0.000000">
17 <axis x="-0.891108" y="0.199133" z="0.407765"/>
18 </rotate>
19 <scale x="1.000000" y="1.000000" z="1.000000"/>
20 </keyframe>
21 </keyframes>
22 .............................
23 </track>

Otros Formatos

A continuacin se incluye una tabla con alguno de los formatos ms comunes para
la representacin de objetos 3D, su extensin, editores que lo utilizan y un enlace
donde se puede obtener ms informacin sobre el formato (ver Tabla 3.1) [72].

11.2. Exportacin y Adaptacin de Contenidos


Como se coment en secciones anteriores la mayora de herramientas de creacin
de contenidos digitales posee su propio formato privado. Sin embargo, la mayora de
estas herramientas permiten la exportacin a otros formatos para facilitar la distribu-
cin de contenidos. En esta seccin nos centraremos en la creacin de un modelo en
Blender, la aplicacin de texturas a dicho modelo mediante la tcnica de UV Mapping
y su exportacin a los formatos XML y binario de Ogre. Finalmente, cargaremos el
objeto exportado en una aplicacin de Ogre 3D.

11.2.1. Instalacin del exportador de Ogre en Blender


En primer lugar es necesario descargar la ltima versin del exportador desde
http://code.google.com/p/blender2ogre/. En la seccin downloads estn disponibles
las versiones del exportador asociadas a cada versin de blender. En funcin de la
versin actual de blender que tengamos instalada, elegimos una u otra.
11.2. Exportacin y Adaptacin de Contenidos [339]

EXT. EDITOR Link


3DMF 3D Meta File, QuickDraw3D http://www.apple.com
3DO Jedi Knight http://www.lucasarts.com
3DS 3D Studio Max, formato binario http://www.discreet.com
ACT Motor Genesis 3D http://www.genesis3D.com
ASE 3D Studio Max: versin de 3DS ba- http://www.discreet.com
sada en texto
ASC 3D Studio Max: mnima represen- http://www.discreet.com
tacin de datos representada en AS-
CII
B3D Bryce 3D http://www.corel.com
BDF Okino http://www.okino.com
BLEND Blender http://www.blender.com
CAR Carrara http://www.eovia.com/carrara
COB Calgari TrueSpace http://www.calgari.com

C11
DMO Duke Nukem 3D http://www.3drealms.com
DXF Autodesk Autocad http://www.autodesk.com
HRC Softimage 3D http://www.softimage.com
INC POV-RAY http://www.povray.org
KF2 Animaciones y poses en Max Payne http://www.maxpayne.com
KFS Max Payne: informacin de la ma- http://www.maxpayne.com
lla y los materiales
LWO Lightwave http://www.newtek.com
MB Maya http://www.aliaswavefront.com
MAX 3D Studio Max http://www.discreet.com
MS3D Milkshape 3D http://www.swissquake.ch
OBJ Alias|Wavefront http://aliaswavefront.com
PZ3 Poser http://www.curioslab.com
RAW Tringulos RAW http://
RDS Ray Dream Studio http://www.metacreations.com
RIB Renderman File http://www.renderman.com
VRLM Lenguaje para el modelado de reali- http://www.web3d.org
dad virtual
X Formato Microsoft Direct X http://www.microsoft.com
XGL Formato que utilizan varios progra- http://www.xglspec.com
mas CAD

Cuadro 11.1: Otros formatos para la representacin de objetos en 3D

Una vez descargado el archivo, lo descomprimimos y ejecutamos Blender. Pos-



teriormente nos dirigimos al men le >user preferences o bien pulsamos Ctrl +
ALT + U . En la ventana para la conguracin de las preferencias del usuario pulsa-
mos sobre el botn addons y en la parte inferior de la ventana pulsamos el botn install
addons. A continuacin el asistente nos ofrecer la posibilidad de elegir el script de
python con el exportador de Blender a Ogre. Finalmente, el exportador aparecer en
el listado de la derecha y marcaremos la casilla de vericacin.
Para comprobar que la instalacin es correcta, nos dirigimos al men File >Export
y comprobamos que entre las opciones aparece Ogre3D.
[340] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

11.2.2. Creacin de un modelo en Blender


El objetivo es modelar una caja o cubo y aplicar una textura a cada una de las seis
caras. Cuando ejecutamos Blender aparece en la escena un cubo creado por defecto,
por tanto, no ser necesario crear ningn modelo adicional para completar este ejerci-
cio. En el caso de que quisiramos aadir algn cubo ms sera sencillo, bastara con
dirigirnos al men Add >Mesh >Cube y aparecera un nuevo cubo en la escena.
Adems del cubo, Blender crea por defecto una fuente de luz y una cmara. Al
igual que sucede con los objetos, es posible aadir fuentes de luz adicionales y nuevas
cmaras. Para este ejercicio ser suciente con los elementos creados por defecto.

11.2.3. Aplicacin de texturas mediante UV Mapping


La tcnica de texturizado UV Mapping permite establecer una correspondencia
entre los vrtices de un objeto y las coordenadas de una textura. Mediante esta tcnica
no es necesario crear un chero o imagen para representar cada una de las texturas
que se aplicar a cada una de las supercies que forman un objeto. En otras palabras,
en un mismo chero se puede representar la piel que cubrir diferentes regiones de
una supercie tridimensional. En est seccin explicaremos como aplicar una textura
a cada una de las seis caras del cubo creado en la seccin anterior. La textura que se
aplica a cada una de las caras puede aparecer desglosada en una misma imagen tal
como muestra la Figura 11.10.

Figura 11.10: Orientacin de las texturas aplicadas a un cubo

Cada uno de los recuadros corresponde a la textura que se aplicar a una de las
caras del cubo en el eje indicado. En la Figura 11.11 se muestra la textura real que se
aplicar al modelo; tal como se puede apreciar est organizada de la misma forma que
se presenta en la Figura 11.10 (cargar la imagen llamada textura512.jpg que se aporta
con el material del curso). Todas las texturas se encuentran en una misma imagen
cuya resolucin es 512x512. Los mdulos de memoria de las tarjetas grcas estn
11.2. Exportacin y Adaptacin de Contenidos [341]

optimizados para trabajar con imgenes representadas en matrices cuadradas y con


una altura o anchura potencia de dos. Por este motivo, la resolucin de la imagen que
contiene las texturas es de 512x512 a pesar de que haya espacios en blanco y se pueda
compactar ms reduciendo la altura.

C11
Figura 11.11: Imagen con resolucin 512x512 que contiene las texturas que sern aplicadas a un cubo

Una vez conocida la textura a aplicar sobre el cubo y cmo est estructurada, se
describirn los pasos necesarios para aplicar las texturas en las caras del cubo mediante
la tcnica UV Mapping. En primer lugar ejecutamos Blender y dividimos la pantalla
en dos partes. Para ello situamos el puntero del ratn sobre el marco superior hasta
que el cursor cambie de forma a una echa doblemente punteada, pulsamos el botn
derecho y elegimos en el men otante la opcin Split Area.
En el marco de la derecha pulsamos el men desplegable situado en la esquina
inferior izquierda y elegimos la opcin UV/Image Editor. A continuacin cargamos la
imagen de la textura (textura512.jpg); para ello pulsamos en el men Image >Open
y elegimos la imagen que contiene las texturas. Despus de realizar estos datos la
apariencia del editor de Blender debe ser similar a la que aparece en la Figura 11.12
En el marco de la izquierda se pueden visualizar las lneas que delimitan el cubo,
coloreadas de color rosa, donde el modo de vista por defecto es Wireframe. Para vi-
sualizar por pantalla las texturas en todo momento elegimos el modo de vista Textured
en el men situado en el marco
inferior
Viewport Shading. Otra posibilidad consiste
en pulsar las teclas SHIFT + Z .
[342] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

Figura 11.12: Editor de Blender con una divisin en dos marcos. En la parte izquierda aparece la gu-
ra geomtrica a texturizar y en la parte derecha las texturas que sern aplicadas mediante la tcnica de
UV/Mapping

Por otro lado, el cubo situado en la escena tiene asociado un material por defecto,
pero ste no tiene asignada ninguna textura (en el caso de que no tenga un material
asociado, ser necesario crear uno). Para ello, nos dirigimos al men de materiales
situado en la barra de herramientas de la derecha (ver Figura 11.13).

Figura 11.13: Seleccin del material del objeto

En segundo lugar, ser necesario determinar la textura. Al panel de texturas se


accede pulsando el icono situado a la derecha de el de materiales (ver Figura 11.14).

Figura 11.14: Denicin de la textura del objeto

En el tipo de textura se elige Image y, ms abajo, en el panel de Image pulsamos


sobre el botn Open para abrir la imagen de la textura con formato jpg. Por otro lado,
en el panel Mapping, ser necesario elegir la opcin UV en el men Generate, para
que las coordenadas de la textura se asocien correctamente al objeto en el proceso de
renderizado.
El siguiente paso consiste en elegir los vrtices de cada una de las caras del cubo y

establecer una correspondencia con coordenadas de la textura. Para ello, pulsamos la
tecla TAB para visualizar los vrtices y nos aseguramos que todos estn seleccionados

(todos deben estar coloreados de amarillo). Para seleccionar o eliminar la seleccin
la tecla A . Con todos los vrtices del cubo
de todos los vrtices basta con pulsar
seleccionados, pulsamos la tecla U y en el men UV Mapping elegimos la opcin
Cube Projection. En el editor UV de la derecha deben aparecer tres recuadros nuevos
de color naranja.
11.2. Exportacin y Adaptacin de Contenidos [343]


Pulsamos A para que ninguno de los vrtices quede seleccionado y, de forma
manual, seleccionamos los vrtices de la cara superior (z+). Para seleccionar los vrti-
existen
ces dos alternativas; la primera es seleccionar uno a uno manteniendo la tecla
SHIFT pulsada y presionar el botn derecho del ratn en cada uno de los vrtices.

Una segunda opcin es dibujar un rea que encuadre los vrtices, para ello hay que
pulsar primero la tecla B (Pulsa el botn intermedio del ratn y muvelo para cambiar
la perspectiva y poder asegurar as que se han elegido los vrtices correctos).
En la parte derecha, en el editor UV se puede observar un cuadrado con los bordes
de color amarillo y la supercie morada. Este recuadro corresponde con la cara del
cubo seleccionada y con el que se pueden establecer correspondencias con coordena-
das de la textura. El siguiente paso consistir en adaptar la supercie del recuadro a la
imagen que debe aparecer en la parte superior del cubo (z+), tal como indica la Figu-
ra 11.10. Para adaptar la supercie
una de ellas es escalar
existen varias alternativas;
el cuadrado con la tecla S y para desplazarlo la tecla G (para hacer zoom sobre la
textura se gira la rueda central del ratn). Un segunda opcin es ajustar cada vrtice
para ello se selecciona un vrtice con el botn derecho del ra-
de forma individual,
tn, pulsamos G y desplazamos el vrtice a la posicin deseada. Las dos alternativas

C11
descritas anteriormente no son excluyentes y se pueden combinar, es decir, se podra
escalar el recuadro en primer lugar y luego ajustar los vrtices, e incluso escalar de
nuevo si fuera necesario.
Los pasos que se han realizo para establecer la textura de la parte superior del cubo

deben ser repetidos para el resto de caras del cubo. Si renderizamos la escena pulsando
F12 podemos apreciar el resultado nal.
Por ltimo vamos a empaquetar el archivo .blend para que sea autocontenido. La
textura es un recurso externo; si cargramos el chero .blend en otro ordenador posi-
blemente no se visualizara debido a que las rutas no coinciden. Si empaquetamos el
chero eliminamos este tipo de problemas. Para ello, seleccionamos File >External
Data >Pack into .blend le.

11.2.4. Exportacin del objeto en formato Ogre XML


Para exportar la escena de Blender al formato de Ogre seleccionamos File >Export
>Ogre 3D. Antes de exportar debemos asegurarnos de que nos encontramos en modo
objeto y no en modo de edicin, sino blender no nos permitir exportar el objeto. En
la parte izquierda aparecer un nuevo panel con las opciones del exportador tal como
muestra la Figura 11.15. Tal como se puede apreciar, el exportador dispone de mlti-
ples opciones encuadradas en dos categoras: exportacin de materiales y exportacin
de mallas.
Las opciones del exportador son las siguientes:

Posibilidad de intercambiar los ejes de coordenadas


Separate Materials: Exporta todos los materiales por separado, genera un ar-
chivo .material por cada uno de ellos, en lugar de aglomerar todos ellos en un
nico archivo.
Only Animated Bones: nicamente exporta aquellos huesos que han sido ani-
mados y forman parte de frames clave.
Export Scene: Exportacin de la escena actual.
Export Selected Only: Exporta nicamente los objetos seleccionados.
Force Camera: Exporta la cmara que est activa en el momento actual.
[344] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

Figura 11.15: Exportador de Ogre integrado en Blender

Force Lamps: Exportacin de todas las luces de la escena.


Export Meshes: Exportacin de las mallas de los objetos.
Export Meshes (overwrite): Exporta las mallas y las sobreescribe si se han
creado con anterioridad.
Armature Animation: Exportacin del esqueleto del objeto y la animacin de
los huesos.
Shape Animation: datos sobre la animacin de personajes. Ofrece la posibili-
dad de ignorar los huesos cuyo valor esta por debajo del umbral denido en el
parmetro Trim Weights.
Optimize Arrays: optimiza el array de modicadores como instancias.
Export Materials: Exportacin de los materiales y generacin de archivos .ma-
terial
Conversin de la imagen de la textura a otros formatos.
Nmero de Mip Maps
Nmero de niveles LOD en la malla
Valor de incremento para reducir LOD
Porcentaje de reduccin LOD
11.2. Exportacin y Adaptacin de Contenidos [345]

Generacin de la lista con las aristas de la geometra del objeto


Tangentes en la malla
Reorganizacin de los buffers de vrtices en la malla
Optimizacin de las animaciones de la malla

11.2.5. Carga del objeto en una aplicacin Ogre


Para cargar el cubo exportado en Ogre vamos a partir de ejemplo visto al comienzo
del curso Hello World. Recordemos que la estructura de directorios para la aplicacin
en Ogre era la siguiente:

Directorios:

C11
Include
Figura 11.16: Sistema de coorde-
nadas utilizado en Ogre. Media
Obj
Plugins
src
Ficheros en el directorio raz del proyecto Ogre:
ogre.cfg
plugins.cfg
resources.cfg

Figura 11.17: Sistema de coorde- Una vez que se ha exportado el cubo con las texturas asociadas y se han gene-
nadas utilizado en Blender. rado los cheros Cube.mesh, Cube.mesh.xml y Scene.material, los incluimos en el
directorio media (tambin incluimos la textura "textura512.jpg"). Los cheros deben
incluirse en este directorio porque as est congurado en el chero resources.cfg, si
deseramos incluir los recursos en otro directorio deberamos variar la conguracin
en dicho chero.
En segundo lugar, cambiamos el nombre del ejecutable; para ello, abrimos el ar-
chivo makele y en lugar de EXEC := helloWorld, ponemos EXEC:= cubo. Despus
es necesario cambiar el cdigo de ejemplo para que cargue nuestro modelo. Abrimos
el archivo /src/main.cpp que se encuentra dentro en el directorio /src. El cdigo es el
siguiente:

Listado 11.21: Cdigo incluido en el archivo main.cpp


1 #include <ExampleApplication.h>
2 class SimpleExample : public ExampleApplication {
3 public : void createScene() {
4 Ogre::Entity *ent = mSceneMgr->createEntity("Sinbad", "
Sinbad.mesh");
5 mSceneMgr->getRootSceneNode()->attachObject(ent);
6 }
7 };
8
9 int main(void) {
10 SimpleExample example;
11 example.go();
12 return 0;
13 }
[346] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

En nuestro caso la entidad a crear es aquella cuya malla se llama Cube.Mesh, sta
ya contiene informacin sobre la geometra de la malla y las coordenadas UV en el
caso de utilizacin de texturas.
Por lo tanto el cdigo tras la modicacin de la funcin createScene, sera el si-
guiente:

Listado 11.22: Modicacin del chero main.cpp para cargar el cubo modelado en Blender
1 public : void createScene() {
2 Ogre::Entity *ent = mSceneMgr->createEntity("Caja", "cubo.mesh"
);
3 mSceneMgr->getRootSceneNode()->attachObject(ent);
4 }

Al compilar (make) y ejecutar (./Cubo) aceptamos las opciones que vienen por
defecto y se puede observar el cubo pero muy lejano. A partir de ahora todas las
operaciones que queramos hacer (translacin, escalado, rotacin, ...) tendr que ser
a travs de cdigo. A continuacin se realiza una operacin de traslacin para acercar
el cubo a la cmara:

Listado 11.23: Traslacin del cubo para acercarlo hacia la cmara


1 Entity *ent1 = mSceneMgr->createEntity( "Cubo", "Cube.mesh" );
2 SceneNode *node1 = mSceneMgr->getRootSceneNode()->
createChildSceneNode( );
3 node1->attachObject( ent1 );
4 node1->translate(Vector3(0, 0, 490 ));

Ejercicio: intentar escalar el cubo y rotarlo en funcin de los ejes usando


Pitch, Yaw y Roll.

Listado 11.24: Ejemplo de escalado de un objeto


Figura 11.18: Operaciones de rota-
1 node1->scale( 3, 3, 3 ); cin.

Listado 11.25: Ejemplo de rotacin


1 node1->yaw(Ogre::Degree( -90 ) );
2 node1->pitch(Ogre::Degree( -90 ) );
3 node1->roll(Ogre::Degree( -90 ) );

11.3. Procesamiento de Recursos Grcos


En esta seccin estudiaremos cmo adaptar recursos 3D realizados con Blender
en Ogre. Tendremos en cuenta aspectos relativos a la escala, posicionamiento de los
objetos (respecto de su sistema de referencia local y global) y estudiaremos algunas
herramientas disponibles en Blender para el posicionamiento preciso de modelos 3D.
11.3. Procesamiento de Recursos Grcos [347]

Antes de continuar, el lector podra (opcionalmente) completar el estudio del cap-


tulo 13, ya que utilizaremos como base el cdigo fuente obtenido en el ltimo ejemplo
de dicho captulo, y estudiaremos algunas cuestiones referentes al gestor de recursos.
Blender, a diferencia de los sistemas de CAD (Computer Aided Design) (que em-
plean modelos de CSG (Constructive Solid Geometry), es una herramienta de mode-
lado de contorno B-R EP (Boundary Representation). Esto implica que, a diferencia
de los sistemas basados en CSG, trabaja con modelos huecos denidos por vrtices,
aristas y caras. Como hemos estudiado en el captulo de introduccin matemtica, las
coordenadas de estos modelos se especican de forma relativa a su centro, que dene
el origen de su sistema de referencia local.
En Blender el centro del objeto se representa mediante un punto de color rosa (ver
Figura 11.19). Si tenemos activos los manejadores en la cabecera de la ventana 3D
Figura 11.19: El centro del objeto , ser el punto de donde comienzan estos elementos de la interfaz.
dene el origen del sistema de refe- Este centro dene el sistema de referencia local, por lo que resulta crtico poder
rencia local. As, la posicin de los
vrtices del cubo se denen segn
cambiar el centro del objeto ya que, tras su exportacin, se dibujar a partir de esta

C11
este sistema local. posicin.
Con el objeto seleccionado en Modo de Objeto se puede indicar a Blender que
recalcule el centro geomtrico
del objeto en el panel de Object Tools (accesible con
la tecla T ), en el botn Origin (ver Figura 11.20). Una vez pulsado Origin, podemos
elegir entre Geometry to Origin, que desplaza los vrtices del objeto de modo que
se ajustan a la posicin jada del centro geomtrico, Origin to Geometry que realiza
la misma operacin de alineacin, pero desplazando el centro en lugar de mover los
vrtices, o Origin to 3D Cursor que cambia la posicin del centro del objeto a la
localizacin actual del cursor 3D.

En muchas ocasiones, es necesario situar el centro y los vrtices de un modelo
con absoluta precisin. Para realizar esta operacin, basta con pulsar la tecla N , y
Figura 11.20: Botn Origin para aparecer el panel de Transformacin Transform a la derecha de la vista 3D (ver Fi-
elegir el centro del objeto, en el pa- gura 11.21), en el que podremos especicar las coordenadas especcas del centro
de
nel Object Tools. del objeto. Si estamos en modo edicin, podremos indicar las coordenadas a nivel
vrtice. Es interesante que esas coordenadas se especiquen localmente (botn Local
activado), porque el exportador de Ogre trabajar con coordenadas locales relativas a
ese centro.
El centro del objeto puede igualmente situarse de forma precisa, empleando la po-
sicin del puntero 3D. El puntero 3D puedes situarse en cualquier posicin del espacio

con precisin. En el panel Transform comentado anteriormente (accesible mediante la
tecla T ) pueden especicarse numricamente
las coordenadas 3D (ver Figura 11.23).
Posteriormente, utilizando el botn Origin (ver Figura 11.20), y eligiendo la opcin
Figura 11.21: Propiedades del pa-
Origin to 3D Cursor podremos situar el centro del objeto en la posicin del puntero
nel Transform en modo edicin (te- 3D.
cla T). Permiten indicar las coorde- Es muy importante que las transformaciones de modelado se apliquen nalmente
nadas de cada vrtice con respecto
a las coordenadas locales del objeto, para que se apliquen de forma efectiva a las
de los vrtices. Es decir, si despus de trabajar con el modelo, pulsando
del Sistema de Referencia Local y
coordenadas
la tecla N en modo Objeto, el valor Scale es distinto de 1 en alguno de los ejes,
del Sistema de Referencia Univer-
sal (Global).
implica que esa transformacin se ha realizado en modo objeto, por lo que los vrtices
del mismo no tendrn esas coordenadas asociadas. De igual modo, tambin hay que
prestar atencin a la rotacin del objeto (estudiaremos cmo resolver este caso ms
adelante).
La Figura 11.22 muestra un ejemplo de este problema; el cubo de la izquierda se
ha escalado en modo edicin, mientras que el de la derecha se ha escalado en modo
objeto. El modelo de la izquierda se exportar correctamente, porque los vrtices tie-
Figura 11.23: Posicionamiento nu- nen asociadas sus coordenadas locales. El modelo de la derecha sin embargo aplica
mrico del cursor 3D. una transformacin a nivel de objeto, y ser exportado como un cubo.
[348] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

b)

a) c) d)

Figura 11.22: Aplicacin de transformaciones geomtricas en modo Edicin. a) Situacin inicial del mo-
delo al que le aplicaremos un escalado de 0.5 respecto del eje Z. b) El escalado se aplica en modo Edicin.
El campo Vertex Z muestra el valor correcto. c) Si el escalado se aplic en modo objeto, el campo Vertex Z
no reeja la geometra real del objeto. Esto puede comprobarse fcilmente si alguno de los campos Scale
no son igual a uno, como en d).

Blender dispone de una orden para aplicar las transformaciones realizadas en


modo Objeto al sistema de coordenadas local del objeto. Para ello, bastar con
pulsar Control A y elegir Apply/ Rotation & Scale, o bien desde la cabecera
de la ventana 3D en Object/ Apply/ Rotation & Scale.

Otra operacin muy interesante consiste en posicionar un objeto con total preci-
sin. Para realizar esta operacin puede ser suciente con situarlo numricamente,
comohemos
visto anteriormente, accediendo al panel de transformacin mediante la
tecla N . Sin embargo, otras veces necesitamos situarlo en una posicin relativa a otro
objeto; necesitaremos situarlo empleando el cursor 3D.
El centro de un objeto puede situarse en la posicin del puntero 3D, y el puntero
3D puede situarse en cualquier posicin del espacio. Podemos, por ejemplo, en modo
edicin situar el puntero 3D en la posicin de un vrtice de un modelo, y situar ah
el centro del objeto. Desde ese momento, los desplazamientos del modelo se harn

tomando como referencia ese punto.
Mediante el atajo Shift S Cursor to Selected podemos situar el puntero 3D en la
posicin de un elemento seleccionado (por ejemplo, un vrtice, oen elcentro
de otro
objeto que haya sido seleccionado en modo objeto) y mediante Shift S Selection to
Cursor moveremos el objeto seleccionado a la posicin del puntero 3D (ver Figura
11.24). Mediante este sencillo mecanismo de 2 pasos podemos situar los objetos con
precisin, y modicar el centro del objeto a cualquier punto de inters.

11.3.1. Ejemplo de uso


A continuacin veremos un ejemplo de utilizacin de los operadores comentados
anteriormente. Partiremos de un sencillo modelo (de 308 vrtices y 596 caras triangu- Figura 11.25: Resultado de expor-
lares) creado en Blender, que se muestra en la Figura 11.25. Al importar el modelo en tar directamente el modelo de la
Ogre, se ha creado un plano manualmente que sirve como base, y que est posicio- Master System desde Blender. La
nado en Y = 0 (con vector normal el Y unitario). El cdigo 11.26 muestra las lneas imagen superior muestra el mode-
ms relevantes relativas a la creacin de la escena. lo en Blender, y las inferior el re-
sultado de desplegar el modelo en
Ogre, con proporciones claramente
errneas.
11.3. Procesamiento de Recursos Grcos [349]

a) b) c) d)

C11
e) f)

Figura 11.24: Ejemplo de uso del operador Cursor to Selected y Selection to Cursor. a) Posicin inicial de
los dos modelos. Queremos llegar a la posicin f). En modo edicin, elegimos un vrtice superior del cubo
y posicionamos el puntero 3D ah (empleando Shift S Cursor to Selected). c) A continuacin modicamos
el valor de X e Y de la posicin del cursor numricamente, para que se site en el centro de la cara. d)
Elegimos Origin to 3D Cursor entre las opciones disponibles en el botn Origin del panel Object Tools.
Ahora el cubo tiene su centro en el punto medio de la cara superior. e) Elegimos el vrtice sealado en la
cabeza del mono y posicionamos ah el puntero 3D. Finalmente en f) cambiamos la posicin del cubo de
forma que interseca exactamente en ese vrtice (Shift S/ Selection to Cursor).


El problema viene asociado a que el modelo ha sido construido empleando trans-
formaciones a nivel de objeto. Si accedemos a las propiedades de transformacin N
para cada objeto, obtenemos la informacin mostrada en la Figura 11.26. Como ve-
mos, la escala a nivel de objeto de ambos modelos es distinta de 1.0 para alguno de
los ejes, lo que indica que la transformacin se realiz a nivel de objeto.
Aplicaremos la escala (y la rotacin, aunque en este caso el objeto no fue rotado)
realizada
a los vrtices del modelo, eliminando cualquier operacin en modo objeto.
Para ello, con cada objeto seleccionado, pulsaremos Control A Apply / Rotation &
Scale. Ahora la escala (ver Figura 11.26) debe mostrar un valor de 1.0 en cada eje.
Como hemos visto, la eleccin correcta del centro del objeto facilita el cdigo en
etapas posteriores. Si el modelo est normalizado (con escala 1.0 en todos los ejes),
las coordenadas del espacio 3D de Blender pueden ser fcilmente transformadas a
coordenadas de Ogre. En este caso, vamos a situar el centro de cada objeto de la
exactamente
escena de forma que est situado en el Z = 0. Para ello, con cada objeto
seleccionado, pulsaremos en Origin Origin to 3D Cursor (del panel Object Tools).

Figura 11.26: Propiedades de Ahora posicionaremos el cursor 3D en esa posicin Shift S Cursor to Selected
del cursor 3D, para que se site en
transformacin de los dos objetos y modicaremos numricamente la coordenada
del ejemplo.
Z = 0 (accediendo al panel de Propiedades N ). El resultado de posicionar el cursor
3D se muestra en la Figura 11.27.
[350] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

Listado 11.26: Fragmento de MyApp.c


1 int MyApp::start() {
2 // ... Carga de configuracion, creacion de window ...
3 Ogre::Camera* cam = _sceneManager->createCamera("MainCamera");
4 cam->setPosition(Ogre::Vector3(5,20,20));
5 cam->lookAt(Ogre::Vector3(0,0,0));
6 cam->setNearClipDistance(5);
7 cam->setFarClipDistance(100);
8 // ... Viewport, y createScene...
9 }
10
11 void MyApp::createScene() {
12 Ogre::Entity* ent1 = _sceneManager->createEntity("MS.mesh");
13 Ogre::SceneNode* node1 = _sceneManager->createSceneNode("MS");
14 node1->attachObject(ent1);
15 node1->translate(0,2,0);
16 _sceneManager->getRootSceneNode()->addChild(node1);
17
18 Ogre::Entity* ent2 = _sceneManager->createEntity("Mando.mesh");
19 Ogre::SceneNode* node2 = _sceneManager->createSceneNode("Mando");
20 node2->attachObject(ent2);
21 node2->translate(0,2,0);
22 node1->addChild(node2);
23 // ... creacion del plano, luces, etc...
24 }

Figura 11.27: Posicionamiento del puntero 3D en relacin al centro del objeto. En muchas situaciones
puede ser conveniente especicar que varios objetos tengan el centro en una misma posicin del espacio
(como veremos en la siguiente seccin). Esto puede facilitar la construccin de la aplicacin.

Sistemas de Coordenadas: Recordemos que los sistemas de coordenadas de


Ogre y Blender siguen convenios diferentes. Si utilizamos la opicin del ex-
portador de Ogre Swap axis por defecto (con valor xz-y), para obtener las
coordenadas equivalentes de Ogre desde Blender bastar con aplicar la mis-
ma coordenada X, la coordenada Z de Blender utilizarla en Y en Ogre, y la
coordenada Y de Blender aplicarla invertida en Z en Ogre.
11.4. Gestin de Recursos y Escena [351]

Ahora podemos exportar el modelo a Ogre, y las proporciones se mantendrn


correctamente. Sera conveniente posicionar exactamente el mando en relacin a la
consola, tal y como est en el modelo .blend. Para ello, podemos modicar y anotar
manualmente la posicin de objeto 3D (en relacin con el SRU). Como el centro de la
consola se ha posicionado en el origen del SRU, la posicin del centro del mando ser
relativa al centro de la consola. Bastar con consultar estos valores (Location X, Y, Z)
en el panel Transform (ver Figura 11.28) y utilizarlos en Ogre.
Figura 11.28: Posiconamiento del
objeto mando. En el ejemplo de la Figura 11.28, el objeto tiene asociadas en Blender las coorde-
nadas (-1.8, -2.8, 0). Al aplicar la equivalencia aplicada por el exportador (por defec-
to), obtendramos el equivalente en Ogre de (-1.8, 0, 2.8). De este modo, el siguiente
fragmento de cdigo muestra la creacin de la escena correcta en Ogre. En este caso
ya no es necesario aplicar ninguna traslacin al objeto MS (se posicionar en el origen
SRU). Por su parte, al objeto Mando se aplicar la traslacin indicada en la lnea
del
9 , que se corresponde con la obtenida de Blender.
Automatzate!!

C11
Obviamente, el proceso de exporta- Listado 11.27: Fragmento de MyApp.c
cin de los datos relativos al posi- 1 Ogre::Entity* ent1 = _sceneManager->createEntity("MS.mesh");
cionamiento de objetos en la esce- 2 Ogre::SceneNode* node1 = _sceneManager->createSceneNode("MS");
na (junto con otros datos de inte- 3 node1->attachObject(ent1);
rs) debern ser automatizados de- 4 _sceneManager->getRootSceneNode()->addChild(node1);
niendo un formato de escena pa- 5
ra Ogre. Este formato tendr la in- 6 Ogre::Entity* ent2 = _sceneManager->createEntity("Mando.mesh");
formacin necesaria para cada jue- 7 Ogre::SceneNode* node2 = _sceneManager->createSceneNode("Mando");
go particular. 8 node2->attachObject(ent2);
9 node2->translate(-1.8,0,2.8);
10 node1->addChild(node2);

Por ltimo, sera deseable poder exportar la posicin de las cmara para percibir
la escena con la misma conguracin que en Blender. Existen varias formas de con-
gurar el camera pose. Una de las ms cmodas para el diseador es trabajar con la
posicin de la cmara y el punto al que sta mira. Esta especicacin es equivalente a
yel punto look at (en el primer fragmento de cdigo de la seccin,
indicar la posicin
en las lneas 4 y 5 .
Para que la cmara apunte hacia un objeto de la escena, puede crearse una restric-
cin de tipo Track To. Para ello,
aadimos un objeto vaco a la escena (que no tiene
Shift A Add / Empty. Ahora seleccionamos primero la c-

representacin), mediante
mara, y luego con Shift pulsado seleccionamos el Empty y pulsamos Control T Track
To Constraint. De este modo, si desplazamos la cmara, obtendremos que siempre
est mirando hacia el objeto Empty creado (ver Figura 11.29).
Cuando tengamos la fotografa de la escena montada, bastar con consultar el
valor de posicin de la cmara y el Empty, y asignar esos valores al cdigo de posi-
cionamiento y look at de la misma (teniendo en cuenta las diferencias entre sistemas
de coordenadas de Blender y Ogre). El resultado se encuentra resumido en la Figura
11.30.

11.4. Gestin de Recursos y Escena


En esta seccin estudiaremos un ejemplo que cubre varios aspectos que no han si-
do tratados en el documento, relativos al uso del gestor de recursos y de escena. Desa-
Figura 11.29: Track de la cmara al rrollaremos un demostrador de 3D Picking que gestionar manualmente el puntero del
objeto Empty. Blender representa la ratn (mediante overlays), gestionar recursos empaquetados en archivos .zip (em-
relacin con una lnea punteada que pleando las facilidades que proporciona Ogre), cargar geometra esttica y utilizar
va de la cmara al objeto Empty. el potente sistema de queries del SceneManager con mscaras.
[352] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

cam->setPosition (Vector3(5.7,6.3,7.1));
cam->lookAt (Vector3(0.9,0.24,1.1));
cam->setFOVy (Ogre::Degree(52));

Figura 11.30: Resultado de aplicar la misma cmara en Blender y Ogre. El campo de visin de la lente
puede consultarse en las propiedades especcas de la cmara, indicando que el valor quiere representarse
en grados (ver lista desplegable de la derecha)

La Figura 11.33 muestra el interfaz del ejemplo desarrollado. Mediante la rueda


del ratn es posible desplazar la cmara. Si se pulsa la rueda, se aplicar un rotacin
sobre la misma. Si se pulsa el botn izquierdo del ratn sobre un objeto de colisin
del escenario (como el mostrado en la gura), se seleccionar mostrando su caja lmite
bounding box. Si se pulsa con el botn izquierdo sobre el suelo de la sala, se aadir
aleatoriamente una caja de tipo 1 o 2. Si pinchamos con el botn izquierdo sobre
alguna de las cajas creadas, se seleccionar. Mediante el botn derecho nicamente
podremos seleccionar cajas creadas (no se aadirn nunca cajas, aunque pulsemos

sobre el suelo de la sala).
Sobre una caja creada, podemos aplicar tres operaciones: con la tecla Supr elimi-

Con la tecla R rotaremos la caja respecto de su eje Y. Finalmente,
namos el objeto.
con la tecla S modicamos su escala. El operador de escala y de rotacin
permi-
ten invertir su comportamiento si pulsamos simultneamente la tecla Shift . Veamos a
continuacin algunos aspectos relevantes en la construccin de este ejemplo.

11.4.1. Recursos empaquetados


El gestor de recursos de Ogre permite utilizar cheros .zip deniendo en el ar-
chivo de conguracin de recursos que el tipo es Zip.
De este modo, el archivo de conguracin de recursos asociado a este ejemplo se
muestra en la Figura 11.31. La implementacin del cargador de recursos que estudia-
mos en el captulo 13 permite su utilizacin directamente.
Figura 11.31: Contenido del archi-
vo de conguracin de recursos re-
source.cfg.
11.4.2. Gestin del ratn
En el siguiente listado se resume el cdigo necesario para posicionar una imagen
usada como puntero del ratn en Ogre. La Figura 11.32 dene el material creado

(textura con transparencia) para cargar la imagen que emplearemos como puntero.
En la lnea 11 se posiciona el elemento del overlay
llamado cursor en la posicin
absoluta obtenida del ratn por OIS (lneas 1 y 2 ). En el constructor del FrameLis-
tener hay que indicar a OIS las dimensiones de la ventana, para que pueda posicionar
adecuadamente el ratn (de otra forma, trabajar en un cuadrado de 50 pxeles de
ancho y alto). Esta operacin se realiza como se indica en las lneas 14-15 .
Figura 11.32: Material asociado al
puntero del ratn.
11.4. Gestin de Recursos y Escena [353]

C11
Figura 11.33: Ejemplo de aplicacin que desarrollaremos en esta seccin. El interfaz permite seleccionar
objetos en el espacio 3D y modicar algunas de sus propiedades (escala y rotacin).


El desplazamiento de la rueda del ratn se obtiene en la coordenada Z con getMou-
seState (lnea 5 ). Utilizamos esta informacin para desplazar la cmara relativamente
en su eje local Z.

Listado 11.28: Fragmentos de MyFrameListener.cpp


1 int posx = _mouse->getMouseState().X.abs; // Posicion del puntero
2 int posy = _mouse->getMouseState().Y.abs; // en pixeles.
3
4 // Si usamos la rueda, desplazamos en Z la camara -----------
5 vt+= Vector3(0,0,-10)*deltaT * _mouse->getMouseState().Z.rel;
6 _camera->moveRelative(vt * deltaT * tSpeed);
7
8 // Gestion del overlay -----------------------------
9 OverlayElement *oe;
10 oe = _overlayManager->getOverlayElement("cursor");
11 oe->setLeft(posx); oe->setTop(posy);
12
13 // En el constructor de MyFrameListener...
14 _mouse->getMouseState().width = _win->getWidth();
15 _mouse->getMouseState().height = _win->getHeight();

11.4.3. Geometra Esttica


Como hemos estudiado anteriormente, el modelo abstracto de los MovableObject
abarca multitud de tipos de objetos en la escena (desde luces, cmaras, entidades,
etc...). Uno de los tipos ms empleados son las mallas poligionales, que tienen asocia-
da informacin geomtrica y datos especcos para el posterior uso de materiales.
La geometra esttica, como su nombre indica, est pensada para elementos gr-
cos que no modican su posicin durante la ejecucin de la aplicacin. Est pensada
para enviar pocos paquetes (lotes) grandes de geometra a la GPU en lugar de muchos
pequeos. Esto permite optimizar el rendimiento en su posterior despliegue. De este
modo, a menor nmero de paquetes enviados a la GPU tendremos mayor rendimiento
Sobre eciencia... en la aplicacin.
Realizar 100 llamadas de 10.000 El uso de la memoria empleando geometra esttica sin embargo es mayor. Mien-
polgonos a la GPU puede ser un
orden de magnitud menos eciente tras que los MovableObject comparten mallas poligonales (siempre que sean instan-
que realizar 10 llamadas de 100.000 cias del mismo objeto), empleando geometra esttica se copian los datos para cada
polgonos. instancia de la geometra.
[354] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

Existen algunas caractersticas principales que deben tenerse en cuenta a la hora


de trabajar con geometra esttica:

Construccin. La geometra esttica debe ser construda previamente a su uso.


Esta etapa debe realizarse una nica vez, por lo que puede generarse en la carga
del sistema y no supone una carga real en la tasa de refresco interactiva del
juego.
Gestin de materiales. El nmero de materiales diferentes asociados a la geo-
metra esttica delimita el nmero de lotes (paquetes) que se enviarn a la GPU.
De este modo, aunque un bloque de geometra esttica contenga gran canti-
dad de polgonos, se crearn tantos paquetes como diferentes materiales tenga
asociado el bloque. As, existe un compromiso de eciencia relacionado con el
nmero de materiales diferentes asociados a cada paquete de geometra esttica.
Rendering en grupo. Aunque nicamente una pequea parte de la geometra
esttica sea visible en el Frustum todo el paquete ser enviado a la GPU para su
representacin. De este modo, es conveniente separar la geometra esttica en
diferentes bloques para evitar el despliegue global de todos los elementos.

En el siguiente fragmento de cdigo relativo a MyApp.c se muestra las instruccio-


nes necesarias para denir un paquete de geometra esttica en Ogre. Basta con llamar

al SceneManager, e indicarle el nombre del bloque a denir (en este caso SG en
la lnea 1 ). A continuacin
se aade una entidad cargada desde un archivo .mesh.
Finalmente en la lnea 4 se ejecuta la operacin de construccin del bloque de geo-
metra.

Listado 11.29: Fragmento de MyApp.c


1 StaticGeometry* stage = _sceneManager->createStaticGeometry("SG"); Figura 11.34: El escenario del
2 Entity* ent1 = _sceneManager->createEntity("Escenario.mesh"); ejemplo est realizado en baja poli-
3 stage->addEntity(ent1, Vector3(0,0,0)); gonalizacin (1616 vrtices y 1003
4 stage->build(); // Operacion para construir la geometria caras). Aun as, el uso de geome-
tra esttica acelera su despliegue
en 3x!.
Otro aspecto importante a tener en cuenta a la hora de trabajar con geometra
esttica es que no es tenida en cuenta a la hora de realizar preguntas al gestor de
escena. En la siguiente seccin veremos cmo resolver este inconveniente.

11.4.4. Queries
El gestor de escena permite resolver preguntas relativas a la relacin espacial entre
los objetos de la escena. Estas preguntas pueden planterase relativas a los objetos
mviles MovableObject y a la geometra del mundo WorldFragment.
Ogre no gestiona las preguntas relativas a la geometra esttica. Una posible so-
lucin pasa por crear objetos mviles invisibles de baja poligonalizacin que servirn
para realizar estas preguntas. Estos objetos estn exportados conservando las mismas
dimensiones y rotaciones que el bloque de geometra esttica (ver Figura 11.35). Ade-
ms, para evitar tener que cargar manualmente los centros de cada objeto, todos los
bloques han redenido su centro en el origen del SRU (que coincide con el centro de
la geometra esttica). El siguiente listado muestra la carga de estos elementos en el
grafo de escena.

Listado 11.30: Fragmento de MyApp.cpp (Create Scene)


1 // Objeto movable "suelo"para consultar al SceneManager
11.4. Gestin de Recursos y Escena [355]

2 SceneNode *nodecol = _sceneManager->createSceneNode("Col_Suelo");


3 Entity *entcol = _sceneManager->createEntity
4 ("Col_Suelo", "Col_Suelo.mesh");
5 entcol->setQueryFlags(STAGE); // Usamos flags propios!
6 nodecol->attachObject(entcol);
7 nodecol->setVisible(false); // Objeto oculto
8 _sceneManager->getRootSceneNode()->addChild(nodecol);
9
10 // Cajas del escenario (baja poligonalizacion)
11 stringstream sauxnode, sauxmesh;
12 string s = "Col_Box";
13 for (int i=1; i<6; i++) {
14 sauxnode << s << i; sauxmesh << s << i << ".mesh";
15 SceneNode *nodebox = _sceneManager->createSceneNode
16 (sauxnode.str());
17 Entity *entboxcol = _sceneManager->createEntity
18 (sauxnode.str(), sauxmesh.str());
19 entboxcol->setQueryFlags(STAGE); // Escenario
20 nodebox->attachObject(entboxcol);
21 nodebox->setVisible(false);
22 nodecol->addChild(nodebox);

C11
23 sauxnode.str(""); sauxmesh.str(""); // Limpiamos el stream
24 }


Cabe destacar el establecimiento de ags propios (en las lneas 5 y 19 ) que

estudiaremos ascomo la propiedad de visibilidad del nodo (en
en la seccin 11.4.5,
7 y 21 ). El bucle denido en 13-23 sirve para cargar las cajas lmite mostradas en
la Figura 11.35.
Las preguntas que pueden realizarse al gestor de escena se dividen en cuatro cate-
goras principales: 1. Caja lmite, denida mediante dos vrtices opuestos, 2. Esfera,
denida por una posicin y un radio, 3. Volumen, denido por un conjunto de tres o
ms planos y 4. Rayo, denido por un punto (origen) y una direccin.
Otras intersecciones En este ejemplo utilizaremos las intersecciones Rayo-Plano. Una vez creado el
Ogre soporta igualmente realizar
objeto de tipo RaySceneQuery en el constructor (mediante una llamada a createRay-
consultas sobre cualquier tipo de in- Query del SceneManager), que posteriormente ser eliminado en el destructor (me-
tersecciones arbitrarias entre obje- diante una llamada a destroyQuery del SceneManager), podemos utilizar el objeto

tos de la escena. para realizar consultas a la escena.
En la lnea 20 se utiliza un mtodo auxiliar para crear la Query, indicando las
coordenadas del ratn. Empleando la funcin de utilidad de la lnea 2 , Ogre crea un
rayo con origen en la cmara y la direccin indicada por las dos coordendas X, Y

normalizadas (entre 0.0 y 1.0) que recibe como argumento (ver Figura 11.36). En la
lnea 4 se establece ese rayo para la consulta y se indica en la lnea 5 que ordene los
resultados por distancia de interseccin. La consulta de tipo rayo devuelve una lista
con todos los objetos que han intersecado con el rayo1 . Ordenando el resultado por
distancia, tendremos como primer elemento el primer objeto con el que ha chocado el
rayo. La ejecucin de la consulta se realiza en la lnea 21 .
Para recuperar los datos de la consulta, se emplea un iterador. En el caso de esta
aplicacin, slo nos interesa el primer elemento devuelto por la consulta (el primer
Primera
rayMouse
punto de interseccin), por lo que en el if de la lnea 25 preguntamos si hay algn
inter- elemento devuelto por la consulta.
seccin origen
1 El rayo se describe por un punto de origen y una direccin. Desde ese origen, y siguiendo esa direccin,

el rayo describe una lnea innita que podr intersecar con innitos objetos
Figura 11.36: Empleando la llama-
da a getCameraToViewportRay, el
usuario puede obtener un rayo con
origen en la posicin de la cma-
ra, y con la direccin denida por
la posicin del ratn.
[356] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

ox4
Col_B Col_
Box5

Col_ Col_
Box2 Box3
Box1 Col_

o
Suel
Col_

Figura 11.35: A la izquierda la geometra utilizada para las preguntas al gestor de escena. A la derecha la
geometra esttica. El centro de todas las entidades de la izquierda coinciden con el centro del modelo de
la derecha. Aunque en la imagen los modelos estn desplazados con respecto del eje X, en realidad ambos
estn exactamente en la misma posicin del mundo.

La creacin de las Queries son costosas computacionalmente (por lo que in-


teresa realizarlas al inicio; en los constructores). Sin embargo su ejecucin
puede realizarse sin problema en el bucle principal de dibujado. El rendi-
miento se ver notablemente afectado si creas y destruyes la query en cada
frame.

El iterador obtiene punteros a objetos de tipo RaySceneQueryResultEntry, que



cuentan con 3atributos pblicos. El primero llamado distance es de tipo Real (usa-
do en la lnea 35 ) nos indica la distancia de interseccin desde el origen del rayo. El
segundo atributo llamado movable contiene un puntero al MovableObject con el que
intersec (si existe). El tercer atributo worldFragment contiene un puntero al objeto
de ese tipo (en este ejemplo no utilizamos geometra de esta clase).
El test de interseccin rayo-objeto se realiza empleando cajas lmite. Esto resulta
muy eciente (ya que nicamente hay que comparar con una caja y no con todos
los polgonos que forman la entidad), pero tiene el inconveniente de la prdida de Figura 11.37: En el caso de obje-
precisin (Figura 11.37). tos mviles, Ogre emplea una ca-
ja lmite (denida por las coordena-
Finalmente, para aadir una caja en el punto de interseccin del rayo con el suelo, das mayor y menor de los vrtices
tenemos que obtener la posicin en el espacio 3D. Ogre proporciona una funcin de
que permite obtener el punto 3D, indicando una distan-
del modelo) para calcular la inter-
utilidad asociada a los rayos,
cia desde el origen (lnea 35 ). De esta forma, indicando la distancia de interseccin
seccin rayo-objeto y optimizar as
los clculos. Esto implica que, en el
(obtenida en el iterador) en el mtodo getPoint del rayo, obtenemos el Vector3 que caso del test de interseccin de la -
usamos directamente para posicionar el nuevo nodo en la escena (ver listado 11.31). gura dara que hay colisin entre el
rayo y el objeto. No obstante, es po-
sible realizar manualmente un test
de colisin con precisin (a nivel de
polgono) si el juego lo requiere.
11.4. Gestin de Recursos y Escena [357]

Listado 11.31: Fragmento de MyFrameListener.cpp


1 Ray MyFrameListener::setRayQuery(int posx, int posy, uint32 mask) {
2 Ray rayMouse = _camera->getCameraToViewportRay
3 (posx/float(_win->getWidth()), posy/float(_win->getHeight()));
4 _raySceneQuery->setRay(rayMouse);
5 _raySceneQuery->setSortByDistance(true);
6 _raySceneQuery->setQueryMask(mask);
7 return (rayMouse);
8 }
9
10 bool MyFrameListener::frameStarted(const FrameEvent& evt) {
11 // ... Codigo anterior eliminado...
12 if (mbleft || mbright) { // Boton izquierdo o derecho ------
13 if (mbleft) mask = STAGE | CUBE1 | CUBE2; // Todos
14 if (mbright) mask = ~STAGE; // Todo menos el escenario
15

C11
16 if (_selectedNode != NULL) { // Si hay alguno seleccionado...
17 _selectedNode->showBoundingBox(false); _selectedNode = NULL;
18 }
19
20 Ray r = setRayQuery(posx, posy, mask);
21 RaySceneQueryResult &result = _raySceneQuery->execute();
22 RaySceneQueryResult::iterator it;
23 it = result.begin();
24
25 if (it != result.end()) {
26 if (mbleft) {
27 if (it->movable->getParentSceneNode()->getName() ==
28 "Col_Suelo") {
29 SceneNode *nodeaux = _sceneManager->createSceneNode();
30 int i = rand() %2; stringstream saux;
31 saux << "Cube" << i+1 << ".mesh";
32 Entity *entaux=_sceneManager->createEntity(saux.str());
33 entaux->setQueryFlags(i?CUBE1:CUBE2);
34 nodeaux->attachObject(entaux);
35 nodeaux->translate(r.getPoint(it->distance));
36 _sceneManager->getRootSceneNode()->addChild(nodeaux);
37 }
38 }
39 _selectedNode = it->movable->getParentSceneNode();
40 _selectedNode->showBoundingBox(true);
41 }
42 }

11.4.5. Mscaras
Las mscaras permiten restringir el mbito de las consultas realizadas al gestor de
13 y 14 especicamos la mscara binaria
escena. En el listado anterior, en las lneas
que utilizaremos en la consulta (lnea 6 ). Para que una mscara sea vlida (y permita
realizar operaciones lgicas con ella), debe contener un nico uno.
As, la forma ms sencilla de denirlas es desplazando el 1 tantas posiciones como
se indica tras el operador << como se indica en la Figura 11.38. Como el campo
de mscara es de 32 bits, podemos denir 32 mscaras personalizadas para nuestra
aplicacin.
[358] CAPTULO 11. RECURSOS GRFICOS Y SISTEMA DE ARCHIVOS

la llamada a setQueryFlags
En el listado de la seccin 11.4.4, vimos que mediante
se podan asociar mscaras a entidades (lneas 5 y 19 ). Si no se especica ninguna
mscara en la creacin de la entidad, sta responder a todas las Queries, planteando
as un esquema bastante exible.
Los operadores lgicos pueden emplearse para combinar varias
mscaras, como el
uso del AND &&, el OR || o la negacin (ver lneas 13 y 14 del pasado cdigo
fuente). En este cdigo fuente, la consulta a STAGE sera equivalente a consultar por
CUBE1 | CUBE2. La consulta de la lnea 13 sera equivalente a preguntar por todos
los objetos de la escena (no especicar ninguna mscara).

Figura 11.38: Mscaras binarias


denidas en MyFrameListener.h pa-
ra el ejemplo.
APIS de Grcos 3D
Captulo 12
Carlos Gonzlez Morcillo

E
n este captulo se introducirn los aspectos ms relevantes de OpenGL. Como
hemos sealado en el captulo de introudccin, muchas bibliotecas de visuali-
zacin (como OGRE) nos abstraen de los detalles de bajo nivel. Sin embargo,
resulta interesante conocer el modelo de estados de este ipo de APIs, por compartir
multitud de convenios con las bibliotecas de alto nivel. Entre otros aspectos, en este
captulo se estudiar la gestin de pilas de matrices y los diferentes modos de trans-
formacin de la API.

12.1. Introduccin
Actualmente existen en el mercado dos alternativas principales como bibliotecas
de representacin 3D a bajo nivel: Direct3D y OpenGL. Estas dos bibliotecas son so-
portadas por la mayora de los dispositivos hardware de aceleracin grca. Direct3D
forma parte del framework DirectX de Microsoft. Es una biblioteca ampliamente ex-
tendida, y multitud de motores 3D comerciales estn basados en ella. La principal
desventaja del uso de DirectX es la asociacin exclusiva con el sistema operativo Mi-
crosoft Windows. Aunque existen formas de ejecutar un programa compilado para esta
plataforma en otros sistemas, no es posible crear aplicaciones nativas utilizando Di-
rectX en otros entornos. De este modo, en este primer estudio de las APIs de bajo nivel
nos centraremos en la API multiplataforma OpenGL.
OpenGL es, probablemente, la biblioteca de programacin grca ms utilizada
del mundo; desde videojuegos, simulacin, CAD, visualizacin cientca, y un largo
etectera de mbitos de aplicacin la conguran como la mejor alternativa en multitud
de ocasiones. En esta seccin se resumirn los aspectos bsicos ms relevantes para
comenzar a utilizar OpenGL.

359
[360] CAPTULO 12. APIS DE GRFICOS 3D

En este breve resumen de OpenGL se dejarn gran cantidad de aspectos sin men-
cionar. Se recomienda el estudio de la gua ocial de OpenGL [86] (tambin conocido
como El Libro Rojo de OpenGL para profundizar en esta potente biblioteca grca.
Otra fantstica fuente de informacin, con ediciones anteriores del Libro Rojo es la
pgina ocial de la biblioteca1 .
El propio nombre OpenGL indica que es una Biblioteca para Grcos Abierta2 .
Una de las caractersticas que ha hecho de OpenGL una biblioteca tan famosa es que es
independiente de la plataforma sobre la que se est ejecutando (en trminos software
y hardware). Esto implica que alguien tendr que encargarse de abrir una ventana
grca sobre la que OpenGL pueda dibujar los preciosos grcos 3D.
Para facilitar el desarrollo de aplicaciones con OpenGL sin preocuparse de los
detalles especcos de cada sistema operativo (tales como la creacin de ventanas,
gestin de eventos, etc...) M. Kilgard cre GLUT, una biblioteca independiente de
OpenGL (no forma parte de la distribucin ocial de la API) que facilita el desarro-
llo de pequeos prototipos. Esta biblioteca auxiliar es igualmente multiplataforma. En
este captulo trabajaremos con FreeGLUT, la alternativa con licencia GPL totalmente
compatible con GLUT. Si se desea mayor control sobre los eventos, y la posibilidad
de extender las capacidades de la aplicacin, se recomienda el estudio de otras APIs Programa de Usuario
multiplataforma compatibles con OpenGL, como por ejemplo SDL3 o la que emplea-
remos con OGRE (OIS). Existen alternativas especcas para sistemas de ventanas

GTKGLExt, Motif, etc...


concretos (ver Figura 12.2, como GLX para plataformas Unix, AGL para Macintosh o

GLUT (FreeGLUT)
GLX, AGL, WGL...
WGL para sistemas Windows.

Widget Opengl
De este modo, el ncleo principal de las biblioteca se encuentra en el mdulo GL
(ver Figura 12.2). En el mdulo GLU (OpenGL Utility) se encuentran funciones de
uso comn para el dibujo de diversos tipos de supercies (esferas, conos, cilindros,
curvas...). Este mdulo es parte ocial de la biblioteca.
Uno de los objetivos principales de OpenGL es la representacin de imgenes de
alta calidad a alta velocidad. OpenGL est diseado para la realizacin de aplicacio-
nes interactivas, como videojuegos. Gran cantidad de plataformas actuales se basan GLU
en esta especicacin para denir sus interfaces de dibujado en grcos 3D.
GL
Otras Bibliotecas
12.2. Modelo Conceptual Software

Figura 12.2: Relacin entre los


Las llamadas a funciones de OpenGL estn diseadas para aceptar diversos tipos mdulos principales de OpenGL.
de datos como entrada. El nombre de la funcin identica adems los argumentos que
recibir. Por ejemplo, en la Figura 12.4 se llama a una funcin para especicar un
nuevo vrtice en coordenadas homogneas (4 parmetros), con tipo de datos double Modelo Conceptual
y en formato vector. Se denen tipos enumerados como redenicin de tipos bsicos de OpenGL
(como GLoat, GLint, etc) para facilitar la compatibilidad con otras plataformas. Es
buena prctica utilizar estos tipos redenidos si se planea compilar la aplicacin en
otros sistemas.
El Modelo Conceptual General de OpenGL dene dos operaciones bsicas que Cambiar
el programador puede realizar en cada instante; 1) dibujar algn elemento o 2) cambiar Dibujar
Estado
el estado de cmo se dibujan los elementos. La primera operacin de dibujar algn
elemento tiene que ser a) una primitiva geomtrica (puntos, lneas o polgonos) o b)
una primitiva de imagen.
1 http://www.opengl.org/
Primitiva Primitiva
2 Las siglas de GL corresponden a Graphics Library Geomtrica Imagen
3 http://www.libsdl.org/

Figura 12.3: Modelo conceptual


general de OpenGL.
12.2. Modelo Conceptual [361]

glVertex4dv (v);

Componentes Tipo de Datos Vector (o escalar)

2 (x,y) b (byte) ub (unsigned byte) v implica vector. Si no


3 (x, y, z) s (short) us (unsig. short) aparece, los parmetros
4 (x, y, z, h) i (int) ui (unsigned int) son escalares.
f (float) d (double)

Figura 12.4: Prototipo general de llamada a funcin en OpenGL.

La Figura 12.3 resume el modelo conceptual de OpenGL. As, la aplicacin que


utilice OpenGL ser simplemente una coleccin de ciclos de cambio de estado y di-

C12
bujado de elementos.

12.2.1. Cambio de Estado

Cambio de Estado La operacin de Cambiar el Estado se encarga de inicializar las variables internas
de OpenGL que denen cmo se dibujarn las primitivas. Este cambio de estado puede
A modo de curiosidad: OpenGL
cuenta con ms de 400 llamadas a ser de mtiples tipos; desde cambiar el color de los vrtices de la primitiva, establecer
funcin que tienen que ver con el la posicin de las luces, etc. Por ejemplo, cuando queremos dibujar el vrtice de un
cambio del estado interno de la bi- polgono de color rojo, primero cambiamos el color del vrtice con glColor() y
blioteca. despus dibujamos la primitiva en ese nuevo estado con glVertex().
Algunas de las formas ms utilizadas para cambiar el estado de OpenGL son:

1. Gestin de Vrtices: Algunas llamadas muy utilizadas cuando se trabaja con


modelos poligonales son glColor() que utilizaremos en el primer ejemplo del
captulo para establecer el color con el que se dibujarn los vrtices4 , glNormal()
para especicar las normales que se utilizarn en la iluminacin, o glTexCoord()
para indicar coordendas de textura.
2. Activacin de Modos: Mediante las llamadas a glEnable y glDisable se
o desactivar caractersticas internas de OpenGL. Por ejemplo,
pueden activar
en la lnea 3 del primer ejemplo del captulo se activa el Test de Profundidad
que utiliza y actualiza el Z-Buffer. Este test se utilizar hasta que de nuevo se
cambia el interruptor desactivando esta funcionalidad en la lnea 17 .
3. Caractersticas Especiales: Existen multitud de caractersticas particulares de
los elementos con los que se est trabajando. OpenGL dene valores por defecto
para los elementos con los que se est trabajando, que pueden ser cambiadas
empleando llamadas a la API.
4 Como veremos a continuacin, los colores en OpenGL se especican en punto otante con valores

entre 0 y 1. Las primeras tres componentes se corresponden con los canales RGB, y la cuarta es el valor de
transparencia Alpha.
[362] CAPTULO 12. APIS DE GRFICOS 3D

12.2.2. Dibujar Primitivas


La operacin de Dibujar Primitivas requiere habitualmente que stas se de-
nan en coordenadas homogneas. Todas las primitivas geomtricas se especican con
vrtices. El tipo de primitiva determina cmo se combinarn los vrtices para for-
mar la superce poligional nal. La creacin de primitivas se realiza entre llamadas a
glBegin(PRIMITIVA) y glEnd(), siendo PRIMITIVA alguna de las 10 primitivas
bsicas soportadas por OpenGL5 . No obstante, el mdulo GLU permite dibujar otras
supercies ms complejas (como cilindros, esferas, discos...), y con GLUT es posible
dibujar algunos objetos simples. Como se puede ver, OpenGL no dispone de funcio-
nes para la carga de modelos poligonales creados con otras aplicaciones (como por
ejemplo, en formato OBJ o MD3). Es responsabilidad del programador realizar esta
carga y dibujarlos empleando las primitivas bsicas anteriores.
Una vez estudiados los cambios de estado y el dibujado de primitivas bsicas,
podemos especicar en la funcin display del siguiente listado para que dibuje un
cuadrado (primitiva GL_QUADS). Las lneas relativas al despliegue del cuadrado se
corresponden con el intervalo 7-17 . Especicamos la posicin de cada vrtice del
cuadrado modicando el color (el estado interno). OpenGL se encargar de calcular
la transicin de color entre cada punto intermedio del cuadrado.

Listado 12.1: Ejemplo de cambio de estado y dibujo de primitivas.


1 void display() {
2 glClear( GL_COLOR_BUFFER_BIT );
3 glEnable(GL_DEPTH_TEST); Figura 12.5: Salida por pantalla del
4 glLoadIdentity(); /* Cargamos la matriz identidad */
5 glTranslatef( 0.f, 0.f, -4.f ); ejemplo.
6
7 glBegin(GL_QUADS); /* Dibujamos un cuadrado */
8 glColor3f(1.0, 0.0, 0.0); /* de dos unidades de lado. */
9 glVertex3f(1.0, 1.0, 0.0); /* Especificamos la coorde- */
10 glColor3f(0.0, 1.0, 0.0); /* nada 3D de cada vertice */
11 glVertex3f(1.0, -1.0, 0.0); /* y su color asociado. */
12 glColor3f(0.0, 0.0, 1.0);
13 glVertex3f(-1.0, -1.0, 0.0);
14 glColor3f(1.0, 1.0, 1.0);
15 glVertex3f(-1.0, 1.0, 0.0);
16 glEnd();
17 glDisable(GL_DEPTH_TEST);
18
19 glutSwapBuffers();
20 }

12.3. Pipeline de OpenGL


Como vimos en el captulo 8.2, los elementos de la escena sufren diferentes trans-
formaciones en el pipeline de grcos 3D. Algunas de las principales etapas se corres-
ponden con las siguiente operaciones:

Transformacin de Modelado: En la que los modelos se posicionan en la es-


cena y se obtienen las Coordenadas Universales.
5 Las 10 primitivas bsicas de OpenGL son GL_POINTS, GL_LINE_STRIP, GL_LINES,

GL_LINE_LOOP, GL_POLYGON, GL_TRIANGLE_STRIP, GL_TRIANGLES,


GL_TRIANGLE_FAN, GL_QUADS y GL_QUAD_STRIP
12.3. Pipeline de OpenGL [363]

Transformacin de Visualizacin: Donde se especica la posicin de la cma-


ra y se mueven los objetos desde las coordenadas del mundo a las Coordenadas
de Visualizacin (o coordenadas de cmara).
Transformacin de Proyeccin: Obteniendo Coordenadas Normalizadas en el
cubo unitario.
Transformacin de Recorte y de Pantalla: Donde se obtienen, tras el recorte
(o clipping de la geometra), las coordenadas 2D de la ventana en pantalla.

+Y OpenGL combina la Transformacin de Modelado y la de Visualizacin en una


-Z
Transformacin llamada Modelview. De este modo OpenGL transforma directa-
+X mente las Coordenadas Universales a Coordenadas de Visualizacin empleando la
matriz Modelview. La posicin inicial de la cmara en OpenGL sigue el convenio
estudiado en el captulo 9 y que se resume en la Figura 12.6.
-X +Z

-Y
12.3.1. Transformacin de Visualizacin

C12
Figura 12.6: Descripcin del siste- La transformacin de Visualizacin debe especicarse antes que ninguna otra
ma de coordenadas de la cmara de- transformacin de Modelado. Esto es debido a que OpenGL aplica las transforma-
nida en OpenGL.
ciones en orden inverso. De este modo, aplicando en el cdigo las transformaciones
de Visualizacin antes que las de Modelado nos aseguramos que ocurrirn despus
que las de Modelado.
Para comenzar con la denicin de la Transformacin de Visualizacin, es nece-
sario limpiar la matriz de trabajo actual. OpenGL cuenta con una funcin que carga la
matriz identidad como matriz actual glLoadIdentity().
Una vez hecho esto, podemos posicionar la cmara virtual de varias formas:
Matriz Identidad
La Matriz Identidad es una matriz 1. glulookat. La primera opcin, y la ms comunmente utilizada es mediante la
4x4 con valor 1 en la diagonal prin-
cipal, y 0 en el resto de elementos. funcin gluLookAt. Esta funcin recibe como parmetros un punto (eye) y dos
La multiplicacin de esta matriz I vectores (center y up). El punto eye es el punto donde se encuentra la cmara
por una matriz M cualquiera siem- en el espacio y mediante los vectores libres center y up orientamos hacia dnde
pre obtiene como resultado M . Esto mira la cmara (ver Figura 12.7).
es necesario ya que OpenGL siem-
pre multiplica las matrices que se le 2. Traslacin y Rotacin. Otra opcin es especicar manualmente una secuencia
indican para modicar su estado in-
terno. de traslaciones y rotaciones para posicionar la cmara (mediante las funciones
glTraslate() y glRotate(), que sern estudiadas ms adelante.
3. Carga de Matriz. La ltima opcin, que es la utilizada en aplicaciones de
Realidad Aumentada, es la carga de la matriz de Visualizacin calculada exter-
namente. Esta opcin se emplea habitualmente cuando el programador quiere
calcular la posicin de la cmara virtual empleando mtodos externos (como
por ejemplo, mediante una biblioteca de tracking para aplicaciones de Realidad
Aumentada).

center up
(cx,cy,cz) (ux,uy,uz)
12.3.2. Transformacin de Modelado
Las transformaciones de modelado nos permiten modicar los objetos de la es-
cena. Existen tres operaciones bsicas de modelado que implementa OpenGL con
eye llamadas a funciones. No obstante, puede especicarse cualquier operacin aplicando
(ex,ey,ez) una matriz denida por el usuario.
Figura 12.7: Parmetros de la fun- Traslacin. El objeto se mueve a lo largo de un vector. Esta operacin se realiza
cin glulookat. mediante la llamada a glTranslate(x,y,z).
[364] CAPTULO 12. APIS DE GRFICOS 3D

Rotacin. El objeto se rota en el eje denido por un vector. Esta operacin


se realiza mediante la llamada a glRotate(,x,y,z), siendo el ngulo de
rotacin en grados sexagesimales (en sentido contrario a las agujas del reloj).
Escalado. El objeto se escala un determinado valor en cada eje. Se realiza me-
diante glScale(x,y,z).

12.3.3. Transformacin de Proyeccin


Como hemos visto, las transformaciones de proyeccin denen el volumen de
visualizacin y los planos de recorte. OpenGL soporta dos modelos bsicos de pro-
yeccin; la proyeccin ortogrca y la proyeccin en perspectiva.
El ncleo de OpenGL dene una funcin para denir la pirmide de visualiza-
cin (o frustum) mediante la llamada a glFrustum(). Esta funcin require los seis
parmetros (t, b, r, l, f y n) estudiados en la seccin 9.3.
Otro modo de especicar la transformacin es mediante la funcin de GLU glu-
Perspective(fov, aspect, near, far). En esta funcin, far y near son las
distancias de los planos de recorte (igual que los parmetros f y n del frustum. fov
especica en grados sexagesimales el ngulo en el eje Y de la escena que es visible
para el usuario, y aspect indica la relacin de aspecto de la pantalla (ancho/alto).
Sobre el uso de Matrices
12.3.4. Matrices Como el convenio en C y C++ de
denicin de matrices bidimensio-
nales es ordenados por las, sue-
OpenGL utiliza matrices 4x4 para representar todas sus transformaciones geom- le ser una fuente de errores ha-
tricas. Las matrices emplean coordenadas homogneas, como se estudi en la sec- bitual denir la matriz como un
cin 9.2.1. A diferencia de la notacin matemtica estndar, OpenGL especica por array bidimensional. As, para acce-
defecto las matrices por columnas, por lo que si queremos cargar nuestras propias der al elemento superior derecho de
la matriz, tendramos que acceder al
matrices de transformacin debemos tener en cuenta que el orden de elementos en matriz[3][0] segn la nota-
OpenGL es el siguiente: cin OpenGL. Para evitar errores,
se recomienda denir el array co-
m0 m4 m8 m12 mo unidimensional de 16 elementos
m1 m5 m9 m13 GLfloat matriz[16].
m m m m14 (12.1)
2 6 10
m3 m7 m11 m15

OpenGL internamente maneja pilas de matrices, de forma que nicamente la ma-


triz de la cima de cada pila es la que se est utilizando en un momento determinado.
Hay cuatro pilas de matrices en OpenGL:

1. Pila Modelview (GL_MODELVIEW). Esta pila contiene las matrices de Transfor-


macin de modelado y visualizacin. Tiene un tamao mnimo de 32 matrices
(aunque, dependiendo del sistema puede haber ms disponibles).
2. Pila Projection (GL_PROJECTION). Esta pila contiene las matrices de proyec-
cin. Tiene un tamao mnimo de 2 elementos.
3. Pila Color (GL_PROJECTION). Utilizada para modicar los colores.
4. Pila Texture (GL_TEXTURE). Estas matrices se emplean para transformar las
coordenadas de textura.

Es posible cambiar la pila sobre la que especicaremos las transformaciones em-


pleando la llamada a glMatrixMode(). Por ejemplo, para utilizar la pila de Model-
view utilizaramos glMatrixMode(GL_MODELVIEW).
12.3. Pipeline de OpenGL [365]

a) Visto como Sistema de Referencia Universal


Y'
X'
Y Y Y
Z'
Y

Posicin
final
Z Z Z
X X X
Posicin
inicial
b) Visto como Sistema de Referencia Local Y' X'
Sis
Re tema d Y Y Y
Unifverenciae Z'
ersal
Z
Y' X'
X Y'

Dos aproximaciones para ayudar a Z' X' Z'

C12
decidir el orden de las transfor- Z Z Z
maciones en OpenGL: SRU o SRL.
X X X

Figura 12.8: Cmo decidir el orden de las operaciones en OpenGL.

El uso de pilas de matrices nos facilita la construccin de modelos jerrquicos,


donde es posible utilizar modelos simples y ensamblarlos para construir modelos ms
complejos. Por ejemplo, si queremos dibujar un coche, tendramos que dibujar las 4
ruedas con su posicin relativa al chasis del vehculo. As, dibujaramos el chasis, y
luego desplazndonos cada vez desde el centro del coche dibujaramos cada una de
las ruedas. Esta operacin de volver al centro del coche se realiza fcilmente em-
pleando pilas de matrices. Este concepto est directamente relacionado con el Grafo
de Escena que estudiamos en el captulo 10.
Recordemos que nicamente trabajamos con la matriz que est en la cima de la
pila. La funcin glPushMatrix() aade una copia de la matriz de trabajo actual a la
parte superior de la pila. Por su parte, la funcin glPopMatrix() elimina la matriz
superior de la pila, descartando su informacin. La siguiente matriz de la pila pasar
a ser la matriz activa. El efecto de esta funcin es el de volver al ltimo punto que
guardamos en la pila.
Supongamos ahora que queremos rotar un objeto 45o respecto del eje Z y trasla-
darlo 5 unidades respecto del eje X, obteniendo una determinada posicin nal (ver
Figura 12.8 izquierda). El objeto inicialmente est en el origen del SRU. En qu or-
den debemos aplicar las transformaciones de OpenGL? Como la transformacin es de
modelo, tendremos que aplicarla sobre la pila de matrices Modelview, con el siguiente
cdigo resultante:

Listado 12.2: Ejemplo de transformaciones.


1 glMatrixMode(GL_MODELVIEW);
2 glLoadIdentity();
3 glRotatef(45,0,0,1);
4 glTraslatef(5,0,0);
5 dibujar_objeto();
[366] CAPTULO 12. APIS DE GRFICOS 3D


El cdigo anterior dibuja el objeto en la posicin deseada, pero cmo hemos

llegado a ese cdigo y no hemos intercambiado las instrucciones de las lneas 3 y
4 ?. Existen dos formas de imaginarnos cmo se realiza el dibujado que nos puede
ayudar a plantear el cdigo fuente. Ambas formas son nicamente aproximaciones
conceptuales, ya que el resultado en cdigo debe ser exactamente el mismo.

Idea de Sistema de Referencia Universal Fijo. La composicin de movimien-


tos aparece en orden inverso al que aparece en el cdigo fuente. Esta idea es
como ocurre realmente en OpenGL. Las transformaciones se aplican siempre
respecto del SRU, y en orden inverso a como se indica en el cdigo fuente. De
este modo, la primera transformacin que ocurrir ser la traslacin (lnea 4 )
la rotacin respecto del origen del sistema de referencia universal
y despus
(lnea 3 ). El resultado puede verse en la secuencia de la Figura 12.8 a).
Idea del Sistema de Referencia Local. Tambin podemos imaginar que cada
objeto tiene un sistema de referencia local interno al objeto que va cambiando.
La composicin se realiza en el mismo orden que aparece en el cdigo fuente,
y siempre respecto de ese sistema de referencia local. De esta forma, como se
muestra en la secuencia de la Figura 12.8 b), el objeto primero rota (lnea 3 ) por
lo que su sistema de referencia local queda rotado respecto del SRU, y respecto
de ese sistema de referencia local, posteriormente lo trasladamos 5 unidades
respecto del eje X (local).

Como hemos visto, es posible adems cargar matrices de transformacin denidas


por nuestros propios mtodos (podra ser interesante si empleramos, por ejemplo,
algn mtodo para calcular la proyeccin de sombras). Esto se realiza con la llamada
a funcin glLoadMatrix(). Cuando se llama a esta funcin se reemplaza la cima de
la pila de matrices activa con el contenido de la matriz que se pasa como argumento.
Si nos interesa es multiplicar una matriz denida en nuestros mtodos por el con-
tenido de la cima de la pila de matrices, podemos utilizar la funcin glMultMatrix
que postmultiplica la matriz que pasamos como argumento por la matriz de la cima de
la pila.
Ejemplo Planetario
12.3.5. Dos ejemplos de transformaciones jerrquicas Ejemplo sencillo para aanzar el or-
den de las transformaciones.
Veamos a continuacin un ejemplo sencillo que utiliza transformaciones jerrqui-
cas. Deniremos un sistema planetario que inicialmente estar formado por el Sol y la
Tierra.

Listado 12.3: Sistema Planetario


1 // ==== Definicion de constantes y variables globales =============
2 long hours = 0; // Horas transcurridas (para calculo rotaciones)
3 // ======== display ===============================================
4 void display()
5 {
6 float RotEarthDay=0.0; // Movimiento de rotacion de la tierra
7 float RotEarth=0.0; // Movimiento de traslacion de la tierra
8 glClear( GL_COLOR_BUFFER_BIT );
9 glPushMatrix();
10
11 RotEarthDay = (hours % 24) * (360/24.0);
12 RotEarth = (hours / 24.0) * (360 / 365.0) * 10; // x10 rapido!
13
14 glColor3ub (255, 186, 0);
15 glutWireSphere (1, 16, 16); // Sol (radio 1 y 16 div)
16 glRotatef (RotEarth, 0.0, 0.0, 1.0);
17 glTranslatef(3, 0.0, 0.0); // Distancia Sol, Tierra
12.3. Pipeline de OpenGL [367]

18 glRotatef (RotEarthDay, 0.0, 0.0, 1.0);


19 glColor3ub (0, 0, 255);
20 glutWireSphere (0.5, 8, 8); // Tierra (radio 0.5)
21 glutSwapBuffers();
22 glPopMatrix();
23 }

Como puede verse en el listado anterior, se ha incluido una variable global hours
que se incrementa cada vez que se llama a la funcin display. En este ejemplo, esta
funcin se llama cada vez que se pulsa cualquier tecla. Esa variable modela el paso
de
de las horas, de forma que la traslacin y rotacin la Tierra se calcular a partir del
nmero de horas que han pasado (lneas 11 y 12 ). En la simulacin se ha acelerado
10 veces el movimiento de traslacin para que se vea ms claramente.
Empleando la Idea de Sistema de Referencia Local podemos
pensar que despla-
zamos el sistema de referencia para dibujar el sol. En la lnea 15 , para dibujar una
esfera almbrica especicamos como primer argumento el radio, y a continuacin el
nmero de rebanadas (horizontales y verticales) en que se dibujar la esfera.
En ese punto dibujamos la primera esfera correspondiente al Sol. Hecho esto, ro-
(lnea 16 )

C12
tamos los grados correspondientes al movimiento de traslacin de la tierra
y nos desplazamos 3 unidades respecto del eje X local del objeto (lnea 17 ). Antes de
la Tierra tendremos que realizar el movimiento
dibujar de rotacin de la tierra (lnea
18 ). Finalmente dibujamos la esfera en la lnea 20 .
Veamos ahora un segundo ejemplo que utiliza glPushMatrix() y glPopMatrix()
Figura 12.9: Salida por pantalla del para dibujar un brazo robtico sencillo. El ejemplo simplemente emplea dos cubos
ejemplo del planetario. (convenientemente escalados) para dibujar la estructura jerrquica de la gura 12.10.

En este ejemplo, se asocian manejadores de callback para los eventos de teclado,
que se corresponden con la funcin keyboard (en 8-16 ). Esta funcin simplemente
Ejemplo Brazo Robot

de las
incrementa o decrementa los ngulos de rotacin dos articulaciones del robot,
que estn denidas como variables globales en 2 y 3 .
Un segundo ejemplo que utiliza las
funciones de aadir y quitar ele-


interesante
mentos de la pila de matrices.
La parte ms del ejemplo se encuentra denido en la funcin dibujar
en las lneas 19-42 .

Listado 12.4: Brazo Robtico


1 // ==== Definicion de constantes y variables globales =============
2 static int hombro = 15;
3 static int codo = 30;
4 GLfloat matVerde[] = {0.0, 1.0, 0.0, 1.0};
5 GLfloat matAzul[] = {0.0, 0.0, 1.0, 1.0};
6
Figura 12.10: Salida por pantalla 7 // ==== Funcion de callback del teclado ===========================
del ejemplo del robot. 8 void teclado(unsigned char key, int x, int y) {
9 switch (key) {
10 case q: hombro = (hombro++) % 360; break;
11 case w: hombro = (hombro--) % 360; break;
12 case a: codo = (codo++) % 360; break;
13 case s: codo = (codo--) % 360; break;
14 }
15 glutPostRedisplay();
16 }
17
18 // ==== Funcion de dibujado =======================================
19 void dibujar() {
20 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
21 glLoadIdentity();
22 gluLookAt(0.0, 1.0, 6.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0);
23
24 glRotatef(hombro, 0.0, 0.0, 1.0);
25 glTranslatef(1.0, 0.0, 0.0); // Nos posicionamos en la mitad
[368] CAPTULO 12. APIS DE GRFICOS 3D

26 glPushMatrix(); // Guardamos la posicion


27 glScalef (2.0, 0.7, 0.1); // Establecemos la escala
28 glMaterialfv(GL_FRONT, GL_DIFFUSE, matVerde);
29 glutSolidCube(1.0); // Dibujamos el ubo"
30 glPopMatrix(); // Recuperamos la posicion
31
32 glTranslatef(1.0,0.0,0.0); // Continuamos hasta el extremo
33 glRotatef(codo, 0.0, 0.0, 1.0);
34 glTranslatef(1.0,0.0,0.0); // Nos posicionamos en la mitad
35 glPushMatrix(); // Guardamos la posicion
36 glScalef (2.0, 0.7, 0.1); // Establecemos la .escala"
37 glMaterialfv(GL_FRONT, GL_DIFFUSE, matAzul);
38 glutSolidCube(1.0); // Dibujamos el ubo"
39 glPopMatrix();
40
41 glutSwapBuffers();
42 }

Aplicamos de nuevo la idea de trabajar en un sistema de referencia local que se


desplaza con los objetos segn los vamos dibujando. De este modo nos posicionare-
mos en la mitad del trayecto para dibujar el cubo escalado. El cubo siempre se dibuja
en el centro del sistema de referencia local (dejando mitad y mitad del cubo en el lado
positivo y negativo de cada eje). Por tanto, para que
el cubo rote respecto del extremo
tenemos que rotar primero
(como en la lnea ) y luego
desplazarnos hasta la mitad
del trayecto (lnea 25 ), dibujar y recorrer la otra mitad 32 antes de dibujar la segunda
24

parte del robot.

12.4. Ejercicios Propuestos


Se recomienda la realizacin de los ejercicios de esta seccin en orden, ya que
estn relacionados y su complejidad es ascendente.

1. Modique el ejemplo del listado del planetario para que dibuje una esfera de
color blanco que representar a la Luna, de radio 0.2 y separada 1 unidad del
centro de la Tierra (ver Figura 12.11). Este objeto tendr una rotacin completa
alrededor de la Tierra cada 2.7 das (10 veces ms rpido que la rotacin real
de la Luna sobre la Tierra. Supondremos que la luna no cuenta con rotacin
interna6 .
2. Modique el ejemplo del listado del brazo robtico para que una base (aadida Figura 12.11: Ejemplo de salida
con un toroide glutSolidTorus) permita rotar el brazo robtico respecto de del ejercicio propuesto.
la base (eje Y ) mediante las teclas 1 y 2. Adems, aada el cdigo para que el
extremo cuente con unas pinzas (creadas con glutSolidCone) que se abran y
cierren empleando las teclas z y x, tal y como se muestra en la Figura 12.12.

Figura 12.12: Ejemplo de salida


del ejercicio del robot.
6 Nota: Para la resolucin de este ejercicio es recomendable utilizar las funciones de
glPushMatrix() y glPopMatrix() para volver al punto donde se dibuj la Tierra antes
de aplicar su rotacin interna.
Gestin Manual OGRE 3D
Captulo 13
Carlos Gonzlez Morcillo

E
n las sesiones anteriores hemos delegado la gestin del arranque, incializacin y
parada de Ogre en una clase SimpleExample proporcionada junto con el motor
grco para facilitar el desarrollo de los primeros ejemplos. En este captulo
estudiaremos la gestin semi-automtica de la funcionalidad empleada en los ejemplos
anteriores, as como introduciremos nuevas caractersticas, como la creacin manual
de entidades, y el uso de Overlays, luces y sombras dinmicas.

13.1. Inicializacin Manual

Inicio Casi-Manual En esta seccin introduciremos un esqueleto de cdigo fuente que emplearemos
en el resto del captulo, modicndolo de manera incremental. Trabajaremos con
En este captulo no describiremos el
inicio totalmente manual de Ogre; dos clases; MyApp que proporciona el ncleo principal de la aplicacin, y la clase
emplearemos las llamadas de alto MyFrameListener que es una isntancia de clase que hereda de Ogre::FrameListener,
nivel para cargar plugins, inicializar basada en el patrn Observador.
ventana, etc...
Como puede verse en el primer listado, en el chero de cabecera de MyApp.h se
declara la clase que contiene tres miembros privados; la instancia de root (que hemos
estudiado en captulos anteriores), el gestor de escenas y un puntero a un objeto de la
clase propia MySceneManager que deniremos a continuacin.

Listado 13.1: MyApp.h


1 #include <Ogre.h>
2 #include "MyFrameListener.h"
3
4 class MyApp {
5 private:

369
[370] CAPTULO 13. GESTIN MANUAL OGRE 3D

6 Ogre::SceneManager* _sceneManager;
7 Ogre::Root* _root;
8 MyFrameListener* _framelistener;
9
10 public:
11 MyApp();
12 ~MyApp();
13 int start();
14 void loadResources();
15 void createScene();
16 };

El programa principal nicamente tendr que crear una instancia de la clase MyApp
y ejecutar su mtodo start. La denicin de este mtodo puede verse en el siguiente
listado, en las lneas 13-44 .

13.1.1. Inicializacin

En la lnea 14 se crea el objeto Root. Esta es la primera operacin que se debe
realizar antes de ejecutar cualquier otra operacin con Ogre. El constructor de Root
est sobrecargado, y permite que se le pase como parmetros el nombre del archivo
de conguracin de plugins (por defecto, buscar plugins.cfg en el mismo directorio
donde se encuentra el ejecutable), el de conguracin de vdeo (por defecto ogre.cfg),
y de log (por
defecto ogre.log). Si no le indicamos ningn parmetro (como en el caso
de la lnea 14 ), tratar de cargar los cheros con el nombre por defecto.

El comportamiento del constructor de Root es diferente entre no especicar


parmetro, o pasar la cadena vaca . En el primer caso se buscarn los
archivos con el nombre por defecto. En el segundo caso, indicamos a Ogre
que cargaremos manualmente los plugins y la conguracin de vdeo.


En la lnea 16 se intenta cargar la conguracin de vdeo existente en el archi-
vo ogre.cfg. Si el archivo no existe, o no es correcto, podemos
abrir el dilogo que
empleamos en los ejemplos de las sesiones anteriores (lnea 17 ), y guardar a con-
tinuacion los valores elegidos por el usuario (lnea 18 ). Con
el objeto Root creado,
podemos pasar a crear la ventana. Para ello, en la lnea 21 solicitamos al objeto Root
que nalice su inicializacin creando una ventana que utilice la conguracin que
eligi el usuario, con el ttulo especicado como segundo parmetro (MyApp en este
caso).
El primer parmetro booleano del mtodo indica a Ogre que se encarge de crear
automticamente la ventana. La llamada a este mtodo nos devuelve un puntero a un
objeto RenderWindow, que guardaremos en la variable window.
Lo que ser representado en la ventana vendr denido por el volumen de visua-
lizacin de la cmara virtual. Asociado a esta cmara, crearemos una supercie (algo
que conceptualmente puede verse como el lienzo, o el plano de imagen) sobre la que se
dibujarn los objetos 3D. Esta supercie se denomina viewport. El Gestor de Escena
admite diversos modos de gestin, empleando el patrn de Factora, que son cargados
como plugins (y especicados en el archivo plugins.cfg). El parmetro indicado en la
13.1. Inicializacin Manual [371]


llamada de 22 especica el tipo de gestor de escena que utilizaremos ST_GENERIC1 .
Esta gestin de escena mnima no est optimizada para ningn tipo de aplicacin en
particular, y resulta especialmente adecuada para pequeas aplicaciones, mens de
introduccin, etc.

Listado 13.2: MyApp.cpp


1 #include "MyApp.h"
2
3 MyApp::MyApp() {
4 _sceneManager = NULL;
5 _framelistener = NULL;
6 }
7
8 MyApp::~MyApp() {
9 delete _root;
10 delete _framelistener;
11 }
12
13 int MyApp::start() {
14 _root = new Ogre::Root(); // Creamos el objeto root
15
16 if(!_root->restoreConfig()) { // Si no se puede restaurar
17 _root->showConfigDialog(); // Abrimos ventana de config

C13
18 _root->saveConfig(); // Guardamos la configuracion
19 }
20
21 Ogre::RenderWindow* window = _root->initialise(true,"MyApp");
22 _sceneManager = _root->createSceneManager(Ogre::ST_GENERIC);
23
24 Ogre::Camera* cam = _sceneManager->createCamera("MainCamera");
25 cam->setPosition(Ogre::Vector3(5,20,20));
26 cam->lookAt(Ogre::Vector3(0,0,0));
27 cam->setNearClipDistance(5); // Establecemos distancia de
28 cam->setFarClipDistance(10000); // planos de recorte
29
30 Ogre::Viewport* viewport = window->addViewport(cam);
31 viewport->setBackgroundColour(Ogre::ColourValue(0.0,0.0,0.0));
32 double width = viewport->getActualWidth();
33 double height = viewport->getActualHeight();
34 cam->setAspectRatio(width / height);
35
36 loadResources(); // Metodo propio de carga de recursos
37 createScene(); // Metodo propio de creacion de la escena
38
39 _framelistener = new MyFrameListener(); // Clase propia
40 _root->addFrameListener(_framelistener); // Lo anadimos!
41
42 _root->startRendering(); // Gestion del bucle principal
43 return 0; // delegada en OGRE
44 }
45
46 void MyApp::loadResources() {
47 Ogre::ConfigFile cf;
48 cf.load("resources.cfg");
49
50 Ogre::ConfigFile::SectionIterator sI = cf.getSectionIterator();
51 Ogre::String sectionstr, typestr, datastr;
52 while (sI.hasMoreElements()) { // Mientras tenga elementos...
53 sectionstr = sI.peekNextKey();
54 Ogre::ConfigFile::SettingsMultiMap *settings = sI.getNext();
55 Ogre::ConfigFile::SettingsMultiMap::iterator i;
56 for (i = settings->begin(); i != settings->end(); ++i) {
57 typestr = i->first; datastr = i->second;
58 Ogre::ResourceGroupManager::getSingleton().
addResourceLocation(datastr, typestr, sectionstr);
1 Existen otros mtodos de gestin, como ST_INTERIOR, ST_EXTERIOR... Estudiaremos en
detalle las opciones de estos gestores de escena a lo largo de este mdulo.
[372] CAPTULO 13. GESTIN MANUAL OGRE 3D

59 }
60 }
61 Ogre::ResourceGroupManager::getSingleton().
initialiseAllResourceGroups();
62 }
63
64 void MyApp::createScene() {
65 Ogre::Entity* ent = _sceneManager->createEntity("Sinbad.mesh");
66 _sceneManager->getRootSceneNode()->attachObject(ent);
67 }

El gestor de escena es el encargado de crear las cmaras virtuales. En realidad el


gestor de escena trabaja como factora de diferentes tipos de objetos que creare-
una
mos en la escena. En la lnea 24 obtenemos un puntero a una cmara que llamaremos

MainCamera. de la cmara virtual
A continuacin establecemos la posicin (lnea
25 ), y el punto hacia el que mira del SRU (lnea 26 ). En las lneas 27-28 estable-
cemos ladistancia con los planos de recorte cercano y lejano (ver Seccin 9.3). En la
lnea 34 se establece la relacin de aspecto de la cmara. Esta relacin de aspecto se
calcula como el nmero de pxeles en horizontal con respecto del nmero de pxeles
en vertical. Resoluciones de 4/3 (como 800x600, o 1024x768) tendrn asociado un
valor de ratio de 1.3333, mientras que resoluciones panormicas de 16/9 tendrn un
valor de 1.77, etc.

Para calcular el aspect ratio de la cmara, creamos un viewport asociado a esa Viewports...
cmara y establecemos el color de fondo como negro (lneas 30-31 ).

A continuacin en las lneas 36-37 se ejecutan mtodos propios de la clase para
Una cmara puede tener cero o ms
Viewports. Un uso comn de los
manual la estudiaremos
cargar los recursos y crear la escena. La carga de recursos viewports es la generacin de im-
en la seccin 13.1.2. La creacin de la escena (lneas 64-67 ), se realiza aadiendo la genes dentro de otros viewports.
Veremos un uso de esta caracters-

entidad directamente al nodo Root. tica en prximos captulos del m-
Las lneas 39-40 crean un objeto de tipo FrameListener. Una clase FrameListener
dulo.

es cualquier clase que implemente el interfaz de FrameListener, permitiendo que Ogre


llame a ciertos mtodos al inicio y al nal del dibujado de cada frame empleando el
patrn Observer. Veremos en detalle cmo se puede implementar esta clase en la
seccin 13.1.3.
Al nalizar la inicializacin, llamamos al mtodo startRendering de Root (lnea
42 ), que inicia el bucle principal de rendering. Esto hace que Ogre entre en un bu-
cle innito, ejecutando los mtodos asociados a los FrameListener que hayan sido
aadidos anteriormente.

En muchas ocasiones, no es conveniente delegar en Ogre la gestin del bucle


principal de dibujado. Por ejemplo, si queremos atender peticiones de networ-
king, o actualizar estados internos del juego. Adems, si queremos integrar la
ventana de visualizacin de Ogre como un widget dentro de un entorno de
ventanas, es posible que ste no nos deje utilizar nuestro propio bucle prici-
pal de dibujado. En este caso, como veremos en sucesivos ejemplos, puede
ser conveniente emplear la llamada a renderOneFrame() y gestionar ma-
nualmente el bucle de rendering.
13.1. Inicializacin Manual [373]

13.1.2. Carga de Recursos



El chero de denicin de recursos se especica en la lnea 48 . Este archivo est
formado de diferentes secciones, que contienen pares de clave y valor. En el sencillo
ejemplo que vamos a denir en este captulo, tendremos el contenido denido en la
Figura 13.1.
Como puede verse, la nica seccin del archivo se denomina General. Creare-
mos un iterador que permita cargar cada una de las secciones y, dentro de cada una,

obtendremos los elementos que la denen.
La lnea 50 crea el SectionIterator asociado al chero de conguracin.
Mientras
Figura 13.1: Contenido del archivo

52-60 ), ob-
resources.cfg del ejemplo.
existan elementos que procesar por el iterador (bucle while de las lneas
tiene la clave del primer elemento de la coleccin (lnea 53 ) sin avanzar al siguiente
(en el caso del ejemplo, obtiene la seccin General). A continuacin, obtiene en set-

tings un puntero al valor del elemento actual de la coleccin, avanzando al siguiente.
Este valor en realidad otro mapa. Emplearemos un segundo iterador (lneas 56-69 )
para recuperar los nombres y valores de cada entrada de la seccin. En el chero de
ejemplo, nicamente iteraremos una vez dentro del segundo iterador, para obtener en

typestr la entrada a FileSystem, y en datastr el valor del directorio media.
Finalmente la llamada al ResourceGroupManager (lnea 61 ) solicita que se ini-
cialicen todos
los grupos de recursos que han sido aadidos en el iterador anterior, en

C13
la lnea 58 .

13.1.3. FrameListener
Como hemos comentado anteriormente, el uso de FrameListener se basa en el
patrn Observador. Aadimos instancias de esta clase al mtodo Root de forma que
ser noticada cuando ocurran ciertos eventos. Antes de representar un frame, Ogre
itera sobre todos los FrameListener aadidos, ejecutando el mtodo frameStarted de
cada uno de ellos. Cuando el frame ha sido dibujado, Ogre ejecutar igualmente el
mtodo frameEnded asociado a cada FrameListener. En el siguiente listado se declara
la clase MyFrameListener.
FrameStarted
Si delegamos la gestin de bucle de Listado 13.3: MyFrameListener.h
dibujado a Ogre, la gestin de los 1 #include <OgreFrameListener.h>
eventos de teclado, ratn y joystick 2
se realiza en FrameStarted, de mo- 3 class MyFrameListener : public Ogre::FrameListener {
do que se atienden las peticiones 4 public:
antes del dibujado del frame. 5 bool frameStarted(const Ogre::FrameEvent& evt);
6 bool frameEnded(const Ogre::FrameEvent& evt);
7 };

Vemos que en esta sencilla implementacin de la clase MyFrameListener no es


necesario tener ninguna variable miembro especca, ni denir ningn constructor o
destructor particular asociado a la clase. Veremos en las siguientes secciones cmo se
complica la implementacin de la misma segn necesitamos aadir nueva funcionali-
dad.
La denicin de la clase es igualmente sencilla. Los mtodos frameStarted o fra-
meEnded devolvern false cuando queramos nalizar la aplicacin. En este ejemplo,
tras llamar la primera vez a frameStarted, se enviar al terminal la cadena Frame
Started y Ogre nalizar. De este modo, no se llegar a imprimir la cadena asociada
[374] CAPTULO 13. GESTIN MANUAL OGRE 3D

a frameEnded (ni se representar el primer frame de la escena!). Si modicamos la


implementacin del mtodo frameStarted, devolviendo true, se dibujar el primer fra-
me y al ejecutar el mtodo frameEnded (devolviendo false), Ogre nalizar el bucle
principal de dibujado.
Habitualmente el mtodo frameEnded se dene pocas veces. nicamente si ne-
cesitamos liberar memoria, o actualizar algn valor tras dibujar el frame, se realizar
una implementacin especca de este mtodo. En los prximos ejemplos, nicamente
deniremos el mtodo frameStarted.

Listado 13.4: MyFrameListener.cpp


1 #include "MyFrameListener.h"
2
3 bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
4 std::cout << "Frame started" << std::endl;
5 return false;
6 }
7
8 bool MyFrameListener::frameEnded(const Ogre::FrameEvent& evt) {
9 std::cout << "Frame ended" << std::endl;
10 return false;
11 }

13.2. Uso de OIS


A continuacin utilizaremos OIS aadir un soporte de teclado mnimo, de
para
forma que cuando se pulse la tecla ESC , se cierre la aplicacin. Bastar con devolver
Sobre OIS

false en el mtodo frameStarted del FrameListener cuando esto ocurra. Como vemos
Recordemos que OIS no forma par-
en el siguiente listado, es necesario aadir al constructor de la clase (lnea 11 ) un
te de la distribucin de Ogre. Es
posible utilizar cualquier biblioteca
puntero a la RenderWindow. para la gestin de eventos.

Listado 13.5: MyFrameListener.h


1 #include <OgreFrameListener.h>
2 #include <OgreRenderWindow.h>
3 #include <OIS/OIS.h>
4
5 class MyFrameListener : public Ogre::FrameListener {
6 private:
7 OIS::InputManager* _inputManager;
8 OIS::Keyboard* _keyboard;
9
10 public:
11 MyFrameListener(Ogre::RenderWindow* win);
12 ~MyFrameListener();
13 bool frameStarted(const Ogre::FrameEvent& evt);
14 };

Esta ventana se necesita para obtener el manejador de la ventana. Este manejador


es un identicador nico que mantiene el sistema operativo para cada ventana. Cada
sistema operativo mantiene una lista de atributos diferente. Ogre abstrae del sistema
operativo subyacente empleando una funcin genrica, que admite como primer par-
metro el tipo de elemento que queremos obtener, y como segundo un puntero al tipo
de datos que vamos a recibir. En el caso de la llamada de la lnea 7 , obtenemos el ma-
nejador de la ventana (el tipo asociado al manejador se especica mediante la cadena
WINDOW).
13.2. Uso de OIS [375]


En las lneas 8-9 convertimos el manejador a cadena, y aadimos el par de objetos
(empleando la plantilla pair de std) como parmetros de OIS. Como puede verse el
convenio de Ogre y de OIS es similar a la hora de nombrar el manejador de la ventana
(salvo por el hecho de que OIS requiere que se especique como cadena, y Ogre lo

devuelve como un entero).
Con este parmetro, creamos un InputManager de OIS en 11 , que nos permitir
interfaces para trabajar con eventos de teclado, ratn, etc. As, en las
crear diferentes
lneas 12-13 creamos un objeto de tipo OIS::Keyboard, que nos permitir obtener
las pulsaciones de tecla del usuario. El segundo parmetro de la lnea 13 permite
indicarle a OIS si queremos que almacene en un buffer los eventos de ese objeto. En
este caso, la entrada por teclado se consume directamente sin almacenarla en un buffer
intermedio.
Tanto el InputManager como el ojbeto de teclado deben ser eliminados explci-
tamente en el destructor de nuestro FrameListener. Ogre se encarga de liberar los
recursos asociados a todos sus Gestores. Como Ogre es independiente de OIS, no tie-
ne constancia de su uso y es responsabilidad del programador liberar los objetos de
entrada de eventos, as como el propio gestor InputManager.

Listado 13.6: MyFrameListener.cpp

C13
1 #include "MyFrameListener.h"
2
3 MyFrameListener::MyFrameListener(Ogre::RenderWindow* win) {
4 OIS::ParamList param;
5 unsigned int windowHandle; std::ostringstream wHandleStr;
6
7 win->getCustomAttribute("WINDOW", &windowHandle);
8 wHandleStr << windowHandle;
9 param.insert(std::make_pair("WINDOW", wHandleStr.str()));
10
11 _inputManager = OIS::InputManager::createInputSystem(param);
12 _keyboard = static_cast<OIS::Keyboard*>
13 (_inputManager->createInputObject(OIS::OISKeyboard, false));
14 }
15
16 MyFrameListener::~MyFrameListener() {
17 _inputManager->destroyInputObject(_keyboard);
18 OIS::InputManager::destroyInputSystem(_inputManager);
19 }
20
21 bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
22 _keyboard->capture();
23 if(_keyboard->isKeyDown(OIS::KC_ESCAPE)) return false;
24 return true;
25 }


La implementacin del mtodo frameStarted es muy sencilla. En la lnea 22 se
obtiene si hubo alguna pulsacin de tecla. Si se presion la tecla Escape (lnea 23 ), se
devuelve false de modo que Ogre nalizar el bucle principal de dibujado y liberar
todos los recursos que se estn empleando.
muestra
Hasta ahora, la escena es totalmente esttica. La Figura 13.2 el resultado
de ejecutar el ejemplo (hasta que el usuario presiona la tecla ESC ). A continuacin
veremos cmo modicar la posicin de los elementos de la escena, deniendo un
interfaz para el manejo del ratn.

Figura 13.2: Resultado de ejecu-


cin del ejemplo bsico con OIS.
[376] CAPTULO 13. GESTIN MANUAL OGRE 3D

13.2.1. Uso de Teclado y Ratn


En el ejemplo de esta seccin desplazaremos la cmara y rotaremos el modelo
empleando el teclado y el ratn. Como en el diseo actual de nuestra apliacin el
despliegue se gestiona ntegramente en la clase MyFrameListener, ser necesario que

esta clase conozca los nuevos objetos sobre los que va a trabajar: elratn (lnea 8 ), la

cmara (declarada igualmente como variable miembro privada en 9), y el nodo (que
contendr las entidades que queremos mover en 10 ).
En estos primeros ejemplos nos centraremos en el aspecto funcional de los mis-
mos. En sucesivos captulos estudiaremos otras aproximaciones de diseo para la ges-
tin de eventos.

Listado 13.7: MyFrameListener.h


1 #include <Ogre.h>
2 #include <OIS/OIS.h>
3
4 class MyFrameListener : public Ogre::FrameListener {
5 private:
6 OIS::InputManager* _inputManager;
7 OIS::Keyboard* _keyboard;
8 OIS::Mouse* _mouse;
9 Ogre::Camera* _camera;
10 Ogre::SceneNode *_node;
11
12 public:
13 MyFrameListener(Ogre::RenderWindow* win, Ogre::Camera* cam,
14 Ogre::SceneNode* node);
15 ~MyFrameListener();
16 bool frameStarted(const Ogre::FrameEvent& evt);
17 };

Listado 13.8: MyFrameListener.cpp


1 #include "MyFrameListener.h"
2
3 MyFrameListener::MyFrameListener(Ogre::RenderWindow* win,
4 Ogre::Camera* cam, Ogre::SceneNode *node) {
5 OIS::ParamList param;
6 size_t windowHandle; std::ostringstream wHandleStr;
7
8 _camera = cam; _node = node;
9
10 win->getCustomAttribute("WINDOW", &windowHandle);
11 wHandleStr << windowHandle;
12 param.insert(std::make_pair("WINDOW", wHandleStr.str()));
13
14 _inputManager = OIS::InputManager::createInputSystem(param);
15 _keyboard = static_cast<OIS::Keyboard*>
16 (_inputManager->createInputObject(OIS::OISKeyboard, false));
17 _mouse = static_cast<OIS::Mouse*>
18 (_inputManager->createInputObject(OIS::OISMouse, false));
19 }
20
21 MyFrameListener::~MyFrameListener() {
22 _inputManager->destroyInputObject(_keyboard);
23 _inputManager->destroyInputObject(_mouse);
24 OIS::InputManager::destroyInputSystem(_inputManager);
25 }
26
27 bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
28 Ogre::Vector3 vt(0,0,0); Ogre::Real tSpeed = 20.0;
29 Ogre::Real r = 0;
30 Ogre::Real deltaT = evt.timeSinceLastFrame;
31
32 _keyboard->capture();
13.3. Creacin manual de Entidades [377]

33 if(_keyboard->isKeyDown(OIS::KC_ESCAPE)) return false;


34 if(_keyboard->isKeyDown(OIS::KC_UP)) vt+=Ogre::Vector3(0,0,-1);
35 if(_keyboard->isKeyDown(OIS::KC_DOWN)) vt+=Ogre::Vector3(0,0,1);
36 if(_keyboard->isKeyDown(OIS::KC_LEFT)) vt+=Ogre::Vector3(-1,0,0);
37 if(_keyboard->isKeyDown(OIS::KC_RIGHT))vt+=Ogre::Vector3(1,0,0);
38 _camera->moveRelative(vt * deltaT * tSpeed);
39
40 if(_keyboard->isKeyDown(OIS::KC_R)) r+=180;
41 _node->yaw(Ogre::Degree(r * deltaT));
42
Tiempo (Frames)

a) 43 _mouse->capture();
44 float rotx = _mouse->getMouseState().X.rel * deltaT * -1;
45 float roty = _mouse->getMouseState().Y.rel * deltaT * -1;
46 _camera->yaw(Ogre::Radian(rotx));
47 _camera->pitch(Ogre::Radian(roty));
48
49 return true;
50 }
Espacio
Tiempo (Frames)

b) La implementacin de la clase MyFrameListener requiere asignar en el constructor


punteros de la cmara y del nodo de escena a las variables miembro privadas(lnea
los
8 ). De forma anloga, crearemos un InputObject
para el ratn en las lneas 17-18
(que eliminaremos en el destructor en 23 ).
El mtodo frameStarted es algo ms complejo que el estudiado en el ejemplo

C13
anterior. En esta ocasin, se denen en las lneas 28-30 una serie de variables que
Espacio comentaremos a continuacin. En vt almacenaremos el vector de traslacin relativo
que aplicaremos a la cmara, dependiendo de la pulsacin de teclas del usuario. La
Tiempo (Segundos) Tiempo (Segundos)

c)
variable r almacenar la rotacin que aplicaremos al nodo de la escena (pasado como
tercer argumento al constructor). En deltaT guardaremos el nmero de segundos
transcurridos desde el despliegue del ltimo frame. Finalmente la variable tSpeed
servir para indicar la traslacin (distancia en unidades del mundo) que queremos
recorrer con la cmara en un segundo.
Espacio La necesidad de medir el tiempo transcurrido desde el despliegue del ltimo frame
es imprescindible si queremos que las unidades del espacio recorridas por el modelo
d) (o la cmara en este caso) sean dependientes del tiempo e independientes de las capa-
cidades de representacin grcas de la mquina sobre la que se estn ejecutando. Si
aplicamos directamente un incremento del espacio sin tener en cuenta el tiempo, como
se muestra en la Figura 13.3, el resultado del espacio recorrido depender de la velo-
cidad de despliegue (ordenadores ms potentes avanzarn ms espacio). La solucin
es aplicar incrementos en la distancia dependientes del tiempo.
Espacio De este modo, aplicamos un movimiento relativo a la cmara (en funcin de sus
ejes locales), multiplicando el vector de traslacin (que ha sido denido dependien-
do de la pulsacin de teclas del usuario en las lneas 34-37 ), por deltaT y por la
Figura 13.3: Comparacin entre
animacin basada en frames y ani-

macin basada en tiempo. El eje de velocidad de traslacin (de modo que avanzar 20 unidades por segundo).
abcisas representa el espacio reco- De forma similar, cuando el usuario pulse la tecla R , se rotar
el modelo respecto
rrido en una trayectoria, mientras
de su eje Y local a una velocidad de 180 por segundo (lneas 40-41 ).
o
que el eje de ordenadas representa
el tiempo. En las grcas a) y b) el Finalmente, se aplicar una rotacin a la cmara respecto de sus ejes Y y Z locales
el movimiento relativo del ratn. Para ello, se obtiene el estado del ratn
empelando
tiempo se especica en frames. En
las grcas c) y d) el tiempo se es- (lneas 44-45 ), obteniendo el incremento relativo en pxeles desde la ltima captura
pecica en segundos. Las grcas (mediante el atributo abs se puede obtener el valor absoluto del incremento).
a) y c) se corresponden con los rsul-
tados obtenidos en un computador
con bajo rendimiento grco (en el
mismo tiempo presenta una baja ta- 13.3. Creacin manual de Entidades
sa de frames por segundo). Las gr-
cas b) y d) se corresponden a un
computador con el doble de frames Ogre soporta la creacin manual de multitud de entidades y objetos. Veremos a
por segundo. continuacin cmo se pueden aadir planos y fuentes de luz a la escena.
[378] CAPTULO 13. GESTIN MANUAL OGRE 3D


manual del plano se realiza en las lneas 8-11 del siguiente listado.
La creacin
En la lnea 8 se especica que el plano tendr como vector normal, el vector unitario
en Y , y que estar situado a -5 unidades respecto del vector normal. Esta denicin
se corresponde con un plano innito (descripcin matemtica abstracta). Si queremos
representar el plano, tendremos que indicar a Ogre el tamao (nito) del mismo, as
como la resolucin que queremos aplicarle (nmero de divisiones
horizontales y ver-
ticales). Estas operaciones se indican en las lneas 9-11 .

Listado 13.9: Denicin de createScene (MyApp.cpp)


1 void MyApp::createScene() {
2 Ogre::Entity* ent1 = _sceneManager->createEntity("Sinbad.mesh");
3 Ogre::SceneNode* node1 =
4 _sceneManager->createSceneNode("SinbadNode");
5 node1->attachObject(ent1);
6 _sceneManager->getRootSceneNode()->addChild(node1);
7
8 Ogre::Plane pl1(Ogre::Vector3::UNIT_Y, -5);
9 Ogre::MeshManager::getSingleton().createPlane("pl1",
10 Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
11 pl1,200,200,1,1,true,1,20,20,Ogre::Vector3::UNIT_Z);
12
13 Ogre::SceneNode* node2 = _sceneManager->createSceneNode("node2");
14 Ogre::Entity* grEnt = _sceneManager->createEntity("pEnt", "pl1");
15 grEnt->setMaterialName("Ground");
16 node2->attachObject(grEnt);
17
18 _sceneManager->setShadowTechnique(Ogre::
SHADOWTYPE_STENCIL_ADDITIVE);
19 Ogre::Light* light = _sceneManager->createLight("Light1");
20 light->setType(Ogre::Light::LT_DIRECTIONAL);
21 light->setDirection(Ogre::Vector3(1,-1,0));
22 node2->attachObject(light);
23
24 _sceneManager->getRootSceneNode()->addChild(node2); Figura 13.4: Nmero de segmentos
25 } de denicin del plano. La imagen
superior implica un segmento en X
y un segmento en Y (valores por
El uso del MeshManager nos permite la creacin de planos, skyboxes, super- defecto). La imagen central se co-
cies de Bezier, y un largo etctera. En el caso de este ejemplo, se utiliza la llamada rresponde con una denicin de 2
a createPlane que recibe los siguientes parmetros indicados en orden: el primer segmentos en ambos planos, y la in-
parmetro es el nombre de la malla resultante, a continuacin el nombre del grupo de ferior de 4 segmentos.

mallas (emplearemos el grupo por defecto). El tercer parmetro es el objeto denicin
del plano innito (creado en la lnea 8 ). Los siguientes dos parmetros indican el an-
cho y alto del plano en coordenadas del mundo (200x200 en este caso). Los siguientes
dos parmetros se corresponden con el nmero de segmentos empleados para denir
el plano (1x1 en este caso), como se indica en la Figura 13.4. Estos parmetros sir-
ven para especicar la resolucin geomtrica del plano creado (por si posteriormente
queremos realizar operaciones de distorsin a nivel de vrtice, por ejemplo).
El siguiente parmetro booleano indica (si es cierto) que los vectores normales se
calcularn perpendiculares al plano. El siguiente parmetro (por defecto, 1) indica el
conjunto de coordenadas de textura que sern creadas. A continuacin se indica el n-
mero de repeticin (uTile y vTile) de la textura (ver Figura 13.6). El ltimo parmetro

indica la direccin del vector Up del plano.
A continuacin en la lnea 15 asignamos al plano el material Ground. Este
material est denido en un archivo en el directorio media que ha sido cargado em-
pleando el cargador de recursos explicado en la seccin 13.1.2. Este material permite
la recepcin de sombras, tiene componente de brillo difuso y especular, y una textura
de imagen basada en el archivo ground.jpg. Estudiaremos en detalle la asignacin de Figura 13.5: Fichero de deni-
materiales en el prximo captulo del documento. cin del material Ground.ma-
terial.
13.4. Uso de Overlays [379]


Finalmente las lneas 18-22 se encargan de denir una fuente de luz dinmica
direccional 2 y habilitar el clculo de sombras. Ogre soporta 3 tipos bsicos de fuentes
de luz y 11 modos de clculo de sombras dinmicas, que sern estudiados en detalle
en prximos captulos.

13.4. Uso de Overlays


En el ltimo ejemplo del captulo deniremos superposiciones (Overlays) para
desplegar en 2D elementos de informacin (tales como marcadores, botones, logoti-
pos, etc...).
La gestin de los Overlays en Ogre se realiza mediante el gestor de recursos llama-
do OverlayManager, que se encarga de cargar los scripts de denicin de los Overlays.
En un overlay pueden existir objetos de dos tipos: los contenedores y los elementos.
Los elementos pueden ser de tres tipos bsicos: TextArea empleado para incluir texto,
Panel empleado como elemento que puede agrupar otros elementos con un fondo jo
y BorderPanel que es igual que Panel pero permite que la textura de fondo se repi-
ta y denir un borde independiente del fondo. La denicin del Overlay del ejemplo
de esta seccin se muestra en la Figura 13.8. En el caso de utilizar un TextArea, ser

C13
necesario especicar la carga de una fuente de texto. Ogre soporta texturas basadas
en imagen o truetype. La denicin de las fuentes se realiza empleando un chero de
extensin .fontdef (ver Figura 13.7).
Figura 13.6: Valores de repeti- En el listado de la Figura 13.8 se muestra la denicin de los elementos y conten-
cin en la textura. La imagen supe- dores del Overlay, as como los cheros de material (extensin .material) asociados
rior se corresponde con valores de al mismo. En el siguiente captulo estudiaremos en detalle la denicin de materiales
uT ile = 1, vT ile = 1. La ima- en Ogre, aunque un simple vistazo al chero de materiales nos adelanta que estamos
gen inferior implica un valor de 2 creando dos materiales, uno llamado panelInfoM, y otro matUCLM. Ambos utilizan
en ambos parmetros (repitiendo la texturas de imagen. El primero de ellos utiliza una tcnica de mezclado que tiene en
textura 2x2 veces en el plano). cuenta la componente de opacidad de la pasada.
El primer contenedor de tipo Panel (llamado PanelInfo), contiene cuatro TextArea
en su interior. El posicionamiento de estos elementos se realiza de forma relativa al
posicionamiento del contenedor. De este modo, se establece una relacin de jerarqua
implcita entre el contenedor y los objetos contenidos. El segundo elemento contene-
dor (llamado logoUCLM) no tiene ningn elemento asociado en su interior.
La Tabla 13.1 describe los principales atributos de los contenedores y elementos
de los overlays. Estas propiedades pueden ser igualmente modicadas en tiempo de
Figura 13.7: Fichero de denicin ejecucin, por lo que son animables. Por ejemplo, es posible modicar la rotacin de
de la fuente Blue.fontdef. un elemento del Overlay consiguiendo bonitos efectos en los mens del juego.
El sistema de scripting de Overlays de Ogre permite denir plantillas de estilo
que pueden utilizar los elementos y contenedores. En este ejemplo se han denido
dos plantillas, una para texto genrico (de nombre MyTemplates/Text), y otra para tex-
to pequeo (MyTemplates/SmallText). Para aplicar estas plantillas a un elemento del
Overlay, basta con indicar seguido de dos puntos el nombre de la plantilla a continua-
cin del elemento (ver Figura 13.8).
En el listado del Overlay denido, se ha trabajado con el modo de especicacin
del tamao en pxeles. La alineacin de los elementos se realiza a nivel de pxel (en
lugar de ser relativo a la resolucin de la pantalla), obteniendo un resultado como se
muestra en la Figura 13.8. El uso de valores negativos en los campos de alineacin
permite posicionar con exactitud los paneles alineados a la derecha y en el borde infe-
2 Este tipo de fuentes de luz nicamente tienen direccin. Denen rayos de luz paralelos de una fuente

distante. Es el equivalente al tipo de fuente Sun de Blender.


[380] CAPTULO 13. GESTIN MANUAL OGRE 3D

Figura 13.8: Denicin de los Overlays empleando cheros externos. En este chero se describen dos
paneles contenedores; uno para la zona inferior izquierda que contiene cuatro reas de texto, y uno para la
zona superior derecha que representar el logotipo de la UCLM.

rior. Por ejemplo, en el caso del panel logoUCLM, de tamao (150x120), la alineacin
horizontal se realiza a la derecha. El valor de -180 en el campo left indica que que-
remos que quede un margen de 30 pxeles entre el lado derecho de la ventana y el
extremo del logo.
Los Overlays tienen asociada una profundidad, denida en el campo zorder (ver
Figura 13.8), en el rango de 0 a 650. Overlays con menor valor de este campo sern
dibujados encima del resto. Esto nos permite denir diferentes niveles de despliegue
de elementos 2D.
Para nalizar estudiaremos la modicacin en el cdigo de MyApp y MyFrame-

Listener.
En el siguiente listado se muestra que el constructor del FrameListener (lnea
7 ) necesita conocer el OverlayManager, cuya referencia se obtiene en la lnea 14 .
En realidad no sera necesario pasar el puntero al constructor, pero evitamos de esta
forma que el FrameListener tenga que solicitar la referencia.
13.4. Uso de Overlays [381]

Atributo Descripcin
metrics_mode Puede ser relative (valor entre 0.0 y 1.0), o pixels (valor en pxeles). Por defecto es relative. En
coordenadas relativas, la coordenada superior izquierda es la (0,0) y la inferior derecha la (1,1).
horz_align Puede ser left (por defecto), center o right.
vert_align Puede ser top (por defecto), center o bottom.
left Posicin con respecto al extremo izquierdo. Por defecto 0. Si el valor es negativo, se interpreta
como espacio desde el extremo derecho (con alineacin right).
top Posicin con respecto al extremo superior. Por defecto 0. Si el valor es negativo, se interpreta como
espacio desde el extremo inferior (con alineacin vertical bottom).
width Ancho. Por defecto 1 (en relative).
height Alto. Por defecto 1 (en relative).
material Nombre del material asociado (por defecto Ninguno).
caption Etiqueta de texto asociada al elemento (por defecto Ninguna).
rotation ngulo de rotacin del elemento (por defecto sin rotacin).

Cuadro 13.1: Atributos generales de Elementos y Contenedores de Overlays.

C13
1024x768

1280x800 800x600

Figura 13.9: Resultado de ejecucin del ejemplo de uso de Overlays, combinado con los resultados parcia-
les de las secciones anteriores. La imagen ha sido generada con 3 conguraciones de resolucin diferentes
(incluso con diferente relacin de aspecto): 1280x800, 1024x768 y 800x600.


En las lneas 15-16 , obtenemos el Overlay llamado Info (denido en el listado de

la Figura 13.8), y lo mostramos.
Para nalizar, el cdigo del FrameListener denido en las lneas 6-16 del siguien-
te listado parcial, modica el valor del texto asociado a los elementos de tipo TextArea.
Hacemos uso de la clase auxiliar Ogre::StringConverter que facilita la conversin a
String de ciertos tipos de datos (vectores, cuaternios...).

Listado 13.10: Modicacin en MyApp.cpp


1 int MyApp::start() {
2 ...
[382] CAPTULO 13. GESTIN MANUAL OGRE 3D

3 loadResources();
4 createScene();
5 createOverlay(); // Metodo propio para crear el overlay
6 ...
7 _framelistener = new MyFrameListener(window, cam, node,
8 _overlayManager);
9 _root->addFrameListener(_framelistener);
10 ...
11 }
12
13 void MyApp::createOverlay() {
14 _overlayManager = Ogre::OverlayManager::getSingletonPtr();
15 Ogre::Overlay *overlay = _overlayManager->getByName("Info");
16 overlay->show();
17 }

Listado 13.11: Modicacin en MyFrameListener.cpp


1 bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
2 ...
3 _camera->yaw(Ogre::Radian(rotx));
4 _camera->pitch(Ogre::Radian(roty));
5
6 Ogre::OverlayElement *oe;
7 oe = _overlayManager->getOverlayElement("fpsInfo");
8 oe->setCaption(Ogre::StringConverter::toString(fps));
9 oe = _overlayManager->getOverlayElement("camPosInfo");
10 oe->setCaption(Ogre::StringConverter::toString(_camera->
getPosition()));
11 oe = _overlayManager->getOverlayElement("camRotInfo");
12 oe->setCaption(Ogre::StringConverter::toString(_camera->
getDirection()));
13 oe = _overlayManager->getOverlayElement("modRotInfo");
14 Ogre::Quaternion q = _node->getOrientation();
15 oe->setCaption(Ogre::String("RotZ: ") +
16 Ogre::StringConverter::toString(q.getYaw()));
17
18 return true;
19 }
Captulo

Interaccin y Widgets
14
Csar Mora Castro

C
omo se ha visto a lo largo del curso, el desarrollo de un videojuego requiere
tener en cuenta una gran variedad de disciplinas y aspectos: grcos 3D o 2D,
msica, simulacin fsica, efectos de sonido, jugabilidad, eciencia, etc. Uno
de los aspectos a los que se les suele dar menos importancia, pero que juegan un papel
fundamental a la hora de que un juego tenga xito o fracase, es la interfaz de usuario.
Sin embargo, el mayor inconveniente es que la mayora de los motores grcos no dan
soporte para la gestin de Widgets, y realizarlos desde cero es un trabajo ms costoso
de lo que pueda parecer.
En este captulo se describe de forma general la estructura y las guas que hay que
tener en cuenta a la hora de disear y desarrollar una interfaz de usuario. Adems, se
explicar el uso de CEGUI, una biblioteca de gestin de Widgets para integrarlos en
motores grcos.
Eciencia y diseo
Para desarrollar un videojuego, tan
importante es cuidar la eciencia y
14.1. Interfaces de usuario en videojuegos
optimizarlo, como que tenga un di-
seo visualmente atractivo.
Las interfaces de usuario especcas de los videojuegos deben tener el objetivo
de crear una sensacin positiva, que consiga la mayor inmersin del usuario posible.
Estas interfaces deben tener lo que se denomina ow. El ow[50] es la capacidad de
atraer la atencin del usuario, manteniendo su concentracin, la inmersin dentro de
la trama del videojuego, y que consiga producir una experiencia satisfactoria.

383
[384] CAPTULO 14. INTERACCIN Y WIDGETS

Cada vez se realizan estudios ms serios sobre cmo desarrollar interfaces que
tengan ow, y sean capaces de brindar una mejor experiencia al usuario. La principal
diferencia con las interfaces de usuario de aplicaciones no orientadas al ocio, es que
estas centran su diseo en la usabilidad y la eciencia, no en el impacto sensorial.
Si un videojuego no consigue atraer y provocar sensaciones positivas al usuario, este
posiblemente no triunfar, por muy eciente que sea, o por muy original o interesante
que sea su trama.
A continuacin se detalla una lista de aspectos a tener en cuenta que son reco-
mendables para aumentar el ow de un videojuego, aunque tradicionalmente se han
considerado perjudiciales a la hora de disear interfaces tradicionales segn las reglas
de interaccin persona-computador:

Mostrar la menor cantidad de informacin posible: durante el transcurso del


juego, es mejor no sobrecargar la interfaz con una gran cantidad de informacin.
En la gran mayora de videojuegos, toda esta conguracin se establece antes
de comenzar el juego (por ejemplo, en el Men), por lo que la interfaz queda
menos sobrecargada y distrae menos al usuario. Incluso el usuario puede tener
la opcin de mostrar menos informacin an si lo desea.
Inconsistencia de acciones: en algunos casos, es posible que se den inconsis-
tencias en las acciones dependiendo del contexto del personaje. Por ejemplo, el
botn de saltar cuando el personaje est en tierra puede ser el de nadar si de
repente salta al agua.
Es importante mantener un nmero reducido de teclas (tanto si es por limita-
ciones de la plataforma, como una videoconsola, como para hacer la usabilidad
ms sencilla al usuario). Por lo tanto, hay que conseguir agrupar las acciones
en los botones segn su naturaleza. Por ejemplo, un botn lleva a cabo acciones
con objetos y peronajes (hablar, abrir una puerta), y otro movimientos de des-
plazamiento (saltar, escalar). Esto aumentar la intuitividad y la usabilidad del
videojuego, y por lo tanto, su ow.
Dicultar los objetivos al usuario: una de las reglas de oro de la interaccin
persona-computador es prevenir al usuario de cometer errores. Sin embargo, en
los videojuegos esta regla puede volverse contradictoria, pues en la mayora de
los casos el usuario busca en los videojuegos un sentimiento de satisfaccin que
se logra por medio de la superacin de obstculos y desafos.
Por lo tanto, es tambin de vital importancia conseguir un equilibrio en la di-
cultad del juego, que no sea tan difcil como para frustrar al usuario, pero no tan
fcil como para que resulte aburrido. En este aspecto la interfaz juega un papel
de mucho peso.

Se ha visto cmo el caso particular de las interfaces de los videojuegos puede con-
tradecir reglas que se aplican en el diseo de interfaces de usuario clsicas en el campo
de la interaccin persona-computador. Sin embargo, existen muchas recomendaciones
que son aplicables a ambos tipos de interfaces. A continuacin se explican algunas:

Mantener una organizacin intuitiva: es importante que el diseo de los me-


ns sean intuitivos. En muchos casos, esta falta de organizacin crea confusin
innecesaria al usuario.
Ofrecer una legibilidad adecuada: en algunos casos, darle demasiada impor-
tancia a la esttica puede implicar que la legibilidad del texto de las opciones o
botones se vea drsticamente reducida. Es importante mantener la funcionalidad
bsica.
14.1. Interfaces de usuario en videojuegos [385]

Esperas innecesarias: en multitud de videojuegos, el usuario se ve forzado a


esperar a que una determinada pelcula o animacin se reproduzca de forma
completa, sin poder omitirla. Incluso en el caso en que pueda omitirse, el usua-
rio ha debido esperar previamente a la carga del clip de vdeo para despus
poder omitirla. Este tipo de inconvenientes reduce notablemente el ow de la
aplicacin.
Existen multitud de formas en las que el usuario realiza esperas innecesarias, y
que aumenta su frustracin. Por ejemplo, si quiere volver a repetir una accin,
tiene que volver a conrmar uno por uno todos los parmetros, aunque estos
no cambien. Este es el caso de los juegos de carreras, en el que para volver
a repetir una carrera es necesario presionar multitud botones e incluso esperar
varios minutos.
Ayuda en lnea: muchas veces el usuario necesita consultar el manual durante
el juego para entender alguna caracterstica de l. Sin embargo, este manual
es documentacin extensa que no rompe con la inmersin del videojuego. Es
conveniente que se proporcione una versin suave, acorde con la esttica y que
muestre la informacin estrictamente necesaria.

En general, es importante tener en cuenta cuatro puntos importantes:

1. Intuitividad: cun fcil es aprender a usar una interfaz de un videojuego.

C14
2. Eciencia: cun rpido se puede realizar una tarea, sobretodo si es muy repeti-
tiva.
3. Simplicidad: mantener los controles y la informacin lo ms minimalista posi-
ble.
4. Esttica: cun sensorialmente atractiva es la interfaz.

Una vez vistas algunas guas para desarrollar interfaces de usuario de videojuegos
atractivas y con ow, se va a describir la estructura bsica que deben tener.
Existe una estructura bsica que un videojuego debe seguir. No es buena prctica
comenzar el juego directamente en el terreno de juego o campo de batalla. La
estructura tpica que debe seguir la interfaz de un videojuego es la siguiente:

En primer lugar, es buena prctica mostrar una splash screen. Este tipo de pantallas
se muestran al ejecutar el juego, o mientras se carga algn recurso que puede durar un
tiempo considerable, y pueden ser usadas para mostrar informacin sobre el juego o
sobre sus desarrolladores. Suelen mostrarse a pantalla completa, o de menor tamao
pero centradas. La Figura 14.1 muestra la splash screen del juego free orion.
Las otros dos elementos de la esctructura de un videojuego son el Men y el HUD.
Suponen una parte muy importante de la interfaz de un juego, y es muy comn utilizar
Widgets en ellos. A continuacion se analizarn ms en detalle y se mostrarn algunos
ejemplos.

Figura 14.1: Ejemplo de Splash


Screen durante la carga del juego
FreeOrion.
[386] CAPTULO 14. INTERACCIN Y WIDGETS

Figura 14.2: Extracto del men de conguracin de Nexuiz (izquierda), e interfaz de ScummVM (derecha).

14.1.1. Men
Todos los videojuegos deben tener un Men desde el cual poder elegir los modos
de juego, congurar opciones, mostrar informacin adicional y otras caractersticas.
Dentro de estos mens, es muy frecuente el uso de Widgets como botones, barras des-
lizantes (por ejemplo, para congurar la resolucin de pantalla), listas desplegables
(para elegir idioma), o check buttons (para activar o desactivar opciones). Por eso es
importante disponer de un buen repertorio de Widgets, y que sea altamente personali-
zable para poder adaptarlos al estilo visual del videojuego.
En la Figura 14.2 se puede apreciar ejemplos de interfaces de dos conocidos jue-
gos open-source. La interfaz de la izquierda, correspondiente al juego Nexuiz, muestra
un trozo de su dialogo de conguracin, mientras que la de la derecha corresponde a
la interfaz de ScummVM. En estos pequeos ejemplos se muestra un nmero conside-
rable de Widgets, cuyo uso es muy comn:

Pestaas: para dividir las opciones por categoras.


Barras de desplazamiento: para congurar opciones que pueden tomar valores
muy numerosos.
Radio buttons: parmetros que pueden tomar valores excluyentes entre ellos.
Check buttons: activan o desactivan una opcin.

14.1.2. HUD
En relacin a las interfaces de los videojuegos, concretamente se denomina HUD
(del ingls, Head-Up Display), a la informacin y elementos de interaccin mostrados
durante el propio transcurso de la partida. La informacin que suelen proporcionar son
la vida de los personajes, mapas, velocidad de los vehculos, etc.
En la Figura 14.3 se muestra una parte de la interfaz del juego de estrategia am-
bientada en el espacio FreeOrion. La interfaz utiliza elementos para mostrar los par-
metros del juego, y tambin utiliza Widgets para interactuar con l.
Como se puede intuir, el uso de estos Widgets es muy comn en cualquier vi-
deojuego, independientemente de su complejidad. Sin embargo, crear desde cero un
conjunto medianamente funcional de estos es una tarea nada trivial, y que roba mucho
tiempo de la lnea principal de trabajo, que es el desarrollo del propio videojuego.
14.2. Introduccin CEGUI [387]

Una vez que se ha dado unas guas de estilo y la estructura bsicas para cualquier
videojuego, y se ha mostrado la importancia de los Widgets en ejemplos reales, se va
a estudiar el uso de una potente biblioteca que proporciona estos mismos elementos
para distintos motores grcos, para que el desarrollo de Widgets para videojuegos sea
lo menos problemtico posible.

C14
Figura 14.3: Screenshot del HUD del juego FreeOrion.

CEGUIs mission
Como dice en su pgina web, CE-
GUI est dirigido a desarrolladores 14.2. Introduccin CEGUI
de videojuegos que deben invertir
su tiempo en desarrollar buenos jue-
gos, no creando subsistemas de in- CEGUI (Crazy Eddies GUI)[2] (Crazy Eddies GUI) es una biblioteca open sour-
terfaces de usuario. ce multiplataforma que proporciona entorno de ventanas y Widgets para motores gr-
cos, en los cuales no se da soporte nativo, o es muy deciente. Es orientada a objetos
y est escrita en C++.

CEGUI es una biblioteca muy potente en pleno desarrollo, por lo que est
sujeta a continuos cambios. Todas las caractersticas y ejemplos descritos a lo
largo de este captulo se han creado utilizando la versin actualmente estable,
la 0.7.x. No se asegura el correcto funcionamiento en versiones anteriores o
posteriores.

CEGUI y Ogre3D CEGUI es muy potente y exible. Es compatible con los motores grcos OpenGL,
Direct3D, Irrlicht y Ogre3D.
A lo largo de estos captulos,
se utilizar la frmula CEGUI/O-
gre3D/OIS para proveer interfaz
grca, motor de rendering y ges-
De la misma forma que Ogre3D es nicamente un motor de rendering, CEGUI
tin de eventos a los videojuegos, es slo un motor de de gestin de Widgets, por lo que el renderizado y la gestin de
aunque tambin es posible utilizarlo eventos de entrada deben ser realizadas por bibliotecas externas.
con otras bibliotecas (ver documen-
tacin de CEGUI).
En sucesivas secciones se explicar cmo integrar CEGUI con las aplicaciones de
este curso que hacen uso de Ogre3D y OIS. Adems se mostrarn ejemplos prcticos
de las caractersticas ms importantes que ofrece.
[388] CAPTULO 14. INTERACCIN Y WIDGETS

14.2.1. Instalacin
En las distribuciones actuales ms comunes de GNU/Linux (Debian, Ubuntu), est
disponible los paquetes de CEGUI para descargar, sin embargo estos dependen de la
versin 1.7 de Ogre, por lo que de momento no es posible utilizar la combinacin CE-
GUI+OGRE 1.8 desde los respositorios. Para ello, es necesario descargarse el cdigo
fuente de la ltima versin de CEGUI y compilarla. A continuacin se muestran los
pasos.

Descargar la ltima versin estable (0.7.7) desde la pgina web (www.cegui.org.uk),


ir a la seccin Downloads (columna de la izquierda). Pinchar sobre la versin 0.7.7,
y en la seccin CEGUI 0.7.7 Library Source Downloads descargar la versin para
GNU/Linux.

Una vez descomprimido el cdigo fuente, para compilar e instalar la biblioteca se


ejecutan los siguientes comandos:

./configure cegui_enable_ogre=yes
make && sudo make install

Despus de ejecutar el ./configure, es importante asegurarse de que en el resumen


se muestre, bajo la seccin Renderer Modules, la opcin Building Ogre Renderer: yes.

Estos pasos instalarn CEGUI bajo el directorio /usr/local/, por lo que es im-
portante indicar en el Makele que las cabeceras las busque en el directorio
/usr/local/include/CEGUI.
Adems, algunos sistemas operativos no buscan las bibliotecas de enlazado
dinmico en /usr/local/lib por defecto. Esto produce un error al ejecutar la
aplicacin indicando que no puede encontrar las bibliotecas libCEGUI*. Pa-
ra solucionarlo, se puede editar como superusuario el chero /etc/ld.so.conf,
y aadir la lnea include /usr/local/lib. Para que los cambios surtan efecto,
ejecutar sudo ldconfig.

14.2.2. Inicializacin
La arquitectura de CEGUI es muy parecida a la de Ogre3D, por lo que su uso es
similar. Est muy orientado al uso de scripts, y hace uso del patrn Singleton para
implementar los diferentes subsistemas. Los ms importantes son:

CEGUI::System: gestiona los parmetros y componentes ms importantes


de la biblioteca.
CEGUI::WindowManager: se encarga de la creacin y gestin de las win-
dows de CEGUI.
CEGUI::SchemeManager: gestiona los diferentes esquemas que utilizar la
interfaz grca.
CEGUI::FontManager: gestiona los distintos tipos de fuentes de la interfaz.

Estos subsistemas ofrecen funciones para poder gestionar los diferentes recursos
que utiliza CEGUI. A continuacin se listan estos tipos de recursos:
14.2. Introduccin CEGUI [389]

Schemes: tienen la extensin .scheme. Denen el repertorio (o esquema) de Wid-


gets que se utilizarn. Tambin indica qu scripts utilizar de otros tipos, como
por ejemplo el ImageSet, las Fonts o el LookNFeel.
Imageset: tienen la extensin .imageset. Dene cules sern la imgenes de los
elementos de la interfaz (punteros, barras, botones, etc).
LookNFeel: tienen la extensin .looknfeel. Dene el comportamiento visual de
cada uno de los Widgets para distintas acciones, por ejemplo, cmo se muestran
cuando se pasa el puntero del ratn o cuando se presionan,
Fonts: tienen la extensin .font. Cada uno de los scripts dene un tipo de fuente
junto con propiedades especcas como su tamao.
Layouts: tienen la extensin .layout. Cada script dene clases de ventanas con-
cretas, con cada uno de sus elementos. Por ejemplo, una ventana de chat o una
consola.

Segn se avance en el captulo se ir estudiando ms en profundidad cul es el


funcionamiento y la estructura de estos recursos.
En el siguiente cdigo se muestran los primeros pasos para poder integrar CEGUI
con Ogre3D, y de qu forma se inicializa.

C14
Listado 14.1: Inicializacn de CEGUI para su uso con Ogre3D
1 #include <CEGUI.h>
2 #include <RendererModules/Ogre/CEGUIOgreRenderer.h>
3
4 CEGUI::OgreRenderer* renderer = &CEGUI::OgreRenderer::
bootstrapSystem();
5
6 CEGUI::Scheme::setDefaultResourceGroup("Schemes");
7 CEGUI::Imageset::setDefaultResourceGroup("Imagesets");
8 CEGUI::Font::setDefaultResourceGroup("Fonts");
9 CEGUI::WindowManager::setDefaultResourceGroup("Layouts");
10 CEGUI::WidgetLookManager::setDefaultResourceGroup("LookNFeel");

Este mtodo de inicializar el renderer utilizando el bootstrapSystem fue in-


troducido a partir de la versin 0.7.1. Para inicializar CEGUI en versiones
anteriores, es necesario referirse a su documentacion.

En las lneas 1 y 2 se insertan las cabeceras necesarias. La primera incluye la


biblioteca general, y en la segunda se indica de forma concreta que se va a utilizar
el motor grco Ogre3D. En la lnea 4 se inicializa CEGUI para ser utilizado con
Ogre3D. Adems es necesario indicar dnde estarn los recursos que utilizar la in-
terfaz grca, tanto los scripts que utiliza, como las fuentes o las imgenes.
Dependiendo de la distribucin que se utilice, estos recursos pueden venir con el
paquete del repositorio o no. En este ejemplo vamos a considerar que debemos des-
cargarlos aparte. Se pueden conseguir directamente descargando el cdigo fuente de
CEGUI desde su pgina[2]. Las distribuciones que los proporcionan, suelen situarlos
en /usr/share/CEGUI/ o /usr/local/share/CEGUI/
Para que CEGUI pueda encontrar los recursos, es necesario aadir estos grupos al
chero resources.cfg de Ogre.
[390] CAPTULO 14. INTERACCIN Y WIDGETS

Listado 14.2: Contenido del chero resources.cfg


1 [General]
2 FileSystem=media
3 [Schemes]
4 FileSystem=media/schemes
5 [Imagesets]
6 FileSystem=media/imagesets
7 [Fonts]
8 FileSystem=media/fonts
9 [Layouts]
10 FileSystem=media/layouts
11 [LookNFeel]
12 FileSystem=media/looknfeel

Y las opciones que hay que aadir al Makele para compilar son:

Listado 14.3: Flags de compilacin y de enlazado de CEGUI.


1 #Flags de compilado
2 CXXFLAGS += pkg-config --cflags CEGUI-OGRE
3
4 #Flags de enlazado
5 LDFLAGS += pkg-config --libs-only-L CEGUI-OGRE
6 LDLIBS += pkg-config --libs-only-l CEGUI-OGRE

Es importante incluir los ags de compilado, en la lnea 2, para que encuentre las
cabeceras segn se han indicado en el ejemplo de inicializacin.

Esto es slo el cdigo que inicializa la biblioteca en la aplicacin para que pueda
comenzar a utilizar CEGUI como interfaz grca, todava no tiene ninguna funciona-
lidad. Pero antes de empezar a aadir Widgets, es necesario conocer otros conceptos
primordiales.

14.2.3. El Sistema de Dimensin Unicado


El posicionamiento y tamao de los distintos Widgets no es tan trivial como indicar
los valores absolutos. Puede ser deseable que un Widget se reposicione y redimensione
si el Widget al que pertenece se redimensiona, por ejemplo.
CEGUI utiliza lo que denomina el Sistema de Dimensin Unicado (Unied Di-
mension System). El elemento principal de este sistema es:

CEGUI::UDim(scale, offset)

Indica la posicin en una dimensin. El primer parmetro indica la posicin rela- (left, top)
tiva, que toma un valor entre 0 y 1, mientras que el segundo indica un desplazamiento
absoluto en pxeles.
Por ejemplo, supongamos que posicionamos un Widget con UDim 0.5,20 en la di- Widget
mensin x. Si el ancho de la pantalla fuese 640, la posicin sera 0.5*640+20 = 340,
mientras que si el ancho fuese 800, la posicin seri 0.5*800+20 = 420. En la Figu-
ra 14.5 se aprecian los dos ejemplos de forma grca. De esta forma, si la resolucin
de la pantalla cambia, por ejemplo, el Widget se reposicionar y se redimensionar de
forma automtica. (right, bottom)
Teniendo en cuenta cmo expresar el posicionamiento en una nica dimensin Figura 14.4: rea rectangular de-
utilizando UDim, se denen dos elementoss ms. nida por URect.
14.2. Introduccin CEGUI [391]

0.5 20 px
0.5 20 px

640x480 800x600

Coordenada x

Figura 14.5: Ejemplo del funcionamiento de UDim.

Para denir un punto o el tamao de un Widget se usa:

CEGUI::UVector2(UDim x, UDim y)

C14
que est compuesto por dos UDim, uno para la coordenada x, y otro para la y, o
para el ancho y el alto, dependiendo de su uso.
El segundo elemento, se utiliza para denir un rea rectangular:

CEGUI::URect(UDim left, UDim top, UDim right, UDIM bottom)

Como muestra la Figura 14.4, los dos primeros denen la esquina superior izquier-
da, y los dos ltimos la esquina inferior derecha.

14.2.4. Deteccin de eventos de entrada


Puesto que CEGUI es nicamente un motor de gestin de Widgets, tampoco incor-
pora la deteccin de eventos de entrada, por lo que es necesario inyectrselos desde
otra biblioteca. En este caso, se aprovechar la que ya se ha estudiado: OIS.
Considerando que se utiliza OIS mediante callbacks (en modo buffered), hay que
aadir las siguientes lneas para enviar a CEGUI la pulsacin y liberacin de teclas y
de los botones del ratn.

Listado 14.4: Inyeccin de eventos de pulsacin y liberacin de teclas a CEGUI.


1 bool MyFrameListener::keyPressed(const OIS::KeyEvent& evt)
2 {
3 CEGUI::System::getSingleton().injectKeyDown(evt.key);
4 CEGUI::System::getSingleton().injectChar(evt.text);
5
6 return true;
7 }
8
9 bool MyFrameListener::keyReleased(const OIS::KeyEvent& evt)
10 {
11 CEGUI::System::getSingleton().injectKeyUp(evt.key);
12
13 return true;
[392] CAPTULO 14. INTERACCIN Y WIDGETS

14 }
15
16 bool MyFrameListener::mousePressed(const OIS::MouseEvent& evt, OIS
::MouseButtonID id)
17 {
18 CEGUI::System::getSingleton().injectMouseButtonDown(
convertMouseButton(id));
19 return true;
20 }
21
22 bool MyFrameListener::mouseReleased(const OIS::MouseEvent& evt, OIS
::MouseButtonID id)
23 {
24 CEGUI::System::getSingleton().injectMouseButtonUp(
convertMouseButton(id));
25 return true;
26 }

Adems, es necesario convertir la forma en que identica OIS los botones del
ratn, a la que utiliza CEGUI, puesto que no es la misma, al contrario que sucede con
las teclas del teclado. Para ello se ha escrito la funcin convertMouseButton():

Listado 14.5: Funcin de conversin entre identicador de botones de ratn de OIS y CEGUI.
1 CEGUI::MouseButton MyFrameListener::convertMouseButton(OIS::
MouseButtonID id)
2 {
3 CEGUI::MouseButton ceguiId;
4 switch(id)
5 {
6 case OIS::MB_Left:
7 ceguiId = CEGUI::LeftButton;
8 break;
9 case OIS::MB_Right:
10 ceguiId = CEGUI::RightButton;
11 break;
12 case OIS::MB_Middle:
13 ceguiId = CEGUI::MiddleButton;
14 break;
15 default:
16 ceguiId = CEGUI::LeftButton;
17 }
18 return ceguiId;
19 }

Por otro lado, tambin es necesario decir a CEGUI cunto tiempo ha pasado desde
la deteccin del ltimo evento, por lo que hay que aadir la siguiente lnea a la funcin
frameStarted():

Listado 14.6: Orden que indica a CEGUI el tiempo transcurrido entre eventos.
1 CEGUI::System::getSingleton().injectTimePulse(evt.
timeSinceLastFrame)

Hasta ahora se ha visto el funcionamiento bsico de CEGUI, los tipos bsicos de


scripts que dene, la inicializacin, el sistema de posicionamiento y dimensionado que
utiliza e incluso como enviarle eventos de entrada. Una vez adquiridos estos conoci-
mientos, es momento de crear la primera aplicacin de Ogre que muestre un Widget
con funcionalidad, como se describir en la siguiente seccin.
14.3. Primera aplicacin [393]

14.3. Primera aplicacin


En esta seccin se van a poner en prctica los primeros conceptos descritos para
crear una primera aplicacin. Esta aplicacin tendr toda la funcionalidad de Ogre
(mostrando a Sinbad), y sobre l un botn para salir de la aplicacin.

Es importante tener en cuenta que en CEGUI, todos los elementos son Win-
dows. Cada uno de los Windows puede contener a su vez otros Windows. De
este modo, pueden darse situaciones raras como que un botn contenga a otro
botn, pero que en la prctica no suceden.

Listado 14.7: Cdigo de la funcin createGUI().


1 void MyApp::createGUI()
2 {
3 renderer = &CEGUI::OgreRenderer::bootstrapSystem();
4 CEGUI::Scheme::setDefaultResourceGroup("Schemes");
5 CEGUI::Imageset::setDefaultResourceGroup("Imagesets");
6 CEGUI::Font::setDefaultResourceGroup("Fonts");
7 CEGUI::WindowManager::setDefaultResourceGroup("Layouts");

C14
8 CEGUI::WidgetLookManager::setDefaultResourceGroup("LookNFeel");
9
10 CEGUI::SchemeManager::getSingleton().create("TaharezLook.scheme")
;
11 CEGUI::System::getSingleton().setDefaultFont("DejaVuSans-10");
12 CEGUI::System::getSingleton().setDefaultMouseCursor("TaharezLook"
,"MouseArrow");
13
14 //Creating GUI Sheet
15 CEGUI::Window* sheet = CEGUI::WindowManager::getSingleton().
createWindow("DefaultWindow","Ex1/Sheet");
16
17 //Creating quit button
18 CEGUI::Window* quitButton = CEGUI::WindowManager::getSingleton().
createWindow("TaharezLook/Button","Ex1/QuitButton");
19 quitButton->setText("Quit");
20 quitButton->setSize(CEGUI::UVector2(CEGUI::UDim(0.15,0),CEGUI::
UDim(0.05,0)));
21 quitButton->setPosition(CEGUI::UVector2(CEGUI::UDim(0.5-0.15/2,0)
,CEGUI::UDim(0.2,0)));
22 quitButton->subscribeEvent(CEGUI::PushButton::EventClicked,
23 CEGUI::Event::Subscriber(&MyFrameListener::quit,
24 _framelistener));
25 sheet->addChildWindow(quitButton);
26 CEGUI::System::getSingleton().setGUISheet(sheet);
27 }

La funcin createGUI() se encarga de la inicializacin de la interfaz grca y


de la creacin de los elementos que contendr. Como se explic en la Seccin 14.2.2,
de las lneas 3-8 se indica que se quiere utilizar Ogre3D como motor de rendering, y
se indica a CEGUI dnde estn los distintos recursos.
En la lnea 10 se crea el esquema que se va a utilizar. Se recuerda que un esquema
dena el conjunto de Widgets que se podrn utilizar en la aplicacin, junto a los ti-
pos de letras, apariencia o comportamiento visual. Es como la eleccin del tema de la
interfaz. El chero de script TaharezLook.scheme debe de encontrarse en algn lugar
del que CEGUI tenga constancia. Como se ha denido en el chero resources.cfg, los
[394] CAPTULO 14. INTERACCIN Y WIDGETS

esquemas (Schemes) deben estar en media/schemes. Desde su pgina web se pueden


descargar otros ejemplos, como el esquema Vanilla. Ms adelante se analizar breve-
mente el contenido de estos scripts, para poder ajustar la interfaz grca a la esttica
del videojuego.
En las lneas 11 y 12 se denen algunos parmetros por defecto. En el primer caso,
el tipo de letra predeterminada, y en el segundo el cursor, ambos elementos denidos
en TaharezLook.scheme.

Los Widgets de la interfaz grca se organizan de forma jerrquica, de forma Convenio de nombrado
anloga al grafo de escena Ogre. Cada Widget (o Window) debe pertenecer a otro CEGUI no exige que se siga ningn
que lo contenga. De este modo, debe de haber un Widget padre o raz. Este Wid- convenio de nombrado para sus ele-
get se conoce en CEGUI como Sheet (del ingls, hoja, rerindose a la hoja en blanco mentos. Sin embargo, es altamen-
te recomendable utilizar un conve-
que contiene todos los elementos). Esta se crea en la lnea 15. El primer parmetro nio jerrquico, utilizando la barra
indica el tipo del Window, que ser un tipo genrico, DefaultWindow. El segundo es / como separador.
el nombre que se le da a ese elemento. Con Ex1 se hace referencia a que es el primer
ejemplo (Example1), y el segundo es el nombre del elemento.

El siguiente paso es crear el botn. En la lnea 18 se llama al WindowManager


para crear un Window (hay que recordar que en CEGUI todo es un Window). El pri-
mer parmetro indica el tipo, denido en el esquema escogido, en este caso un botn.
El segundo es el nombre del elemento, para el cual se sigue el mismo convenio de
nombrado que para el Sheet.

Despus se indican algunos parmetros del botn. En la lnea 19 se indica el texto


del botn, utilizando la fuente predeterminada del sistema que se indic en la iniciali-
zacin.
En la lnea 20 se indica el tamao. Como se explic en la Seccin 14.2.3, para el
tamao se utiliza un UVector2, que contiene dos valores, ancho y alto. Por lo tanto, el
ancho de este botn siempre ser 0.15 del ancho del Sheet, y el alto ser 0.05.
Para indicar la posicin del Widget, se opta por centrarlo horizontalmente. Como
se puede ver en la lnea 21, para la posicin en la dimensin x se indica la mitad del
ancho del Sheet, 0.5, menos la mitad del ancho del Widget, es decir, 0.15/2 (el tamao
se acaba de indicar en la lnea 20). Esto se debe a que el posicionamiento toma como
punto de referencia la esquina superior izquierda del Widget. Para la posicin en el eje
y se ha optado por un 0.2 del alto del Sheet.

Hasta el momento se ha creado el Sheet que contendr todo el conjunto de Widgets,


un botn con el texto Quit, y con un tamao y apariencia determinado. Ahora se le va
a asociar un comportamiento para cuando se pulse. Para asociar comportamientos, es
necesario suscribir las funciones que implementan el comportamiento a los elementos.
En la lnea 22 se asocia ese comportamiento. El primer parmetro indica a qu tipo de
accin se asocia el comportamiento, y en el segundo qu funcin se ejecuta. Existen
una extensa lista de acciones a las que se pueden asociar comportamientos, como por
ejemplo:

MouseClicked
MouseEnters
MouseLeaves
EventActivated
Figura 14.6: Screenshot de la pri-
EventTextChanged mera aplicacin de ejemplo.
14.4. Tipos de Widgets [395]

EventAlphaChanged
EventSized

Como se ha estudiado anteriormente, al igual que pasa con el grafo de escena de


Ogre3D, cada Window de CEGUI debe tener un padre. En la lnea 25 se asocia el bo-
tn al Sheet, y por ltimo, en la lnea 26 se indica a CEGUI cul es el Sheet que debe
mostrar.

A continuacin se muestra la denicin de la funcin que implementa el com-


portamiento. Como se puede apreciar, simplemente cambia el valor de una variable
booleana que controla la salida de la aplicacin. Es importante tener en cuenta que no
todas las funciones pueden ser utilizadas para implementar el comportamiento de los
elementos. En su signatura, deben de tener como valor de retorno bool, y aceptar un
nico parmetro del tipo const CEGUI::EventArgs& e

Listado 14.8: Funcin que implementa el comportamiento del botn al ser pulsado.
1 bool MyFrameListener::quit(const CEGUI::EventArgs &e)
2 {
3 _quit = true;
4 return true;
5 }

C14
En la Figura 14.6 se puede ver una captura de esta primera aplicacin.

Ya que se ha visto cmo inicializar CEGUI y cul es su funcionamiento bsico, es


momento de comenzar a crear interfaces ms complejas, tiles, y atractivas.

14.4. Tipos de Widgets


Para comenzar a desarrollar una interfaz grca con CEGUI para un videojuego,
primero es necesario saber cul es exactamente el repertorio de Widgets disponible.
Como se ha estudiado en secciones anteriores, el repertorio como tal est denido
en el esquema escogido. Para los sucesivos ejemplos, vamos a utilizar el esquema
TaharezLook, utilizado tambin en la primera aplicacin de inicializacin. CEGUI
proporciona otros esquemas que ofrecen otros repertorios de Widgets, aunque los ms
comunes suelen estar implementados en todos ellos. Otros esquemas que proporciona
CEGUI son OgreTray, VanillaSkin y WindowsLook.
El script del esquema dene adems la apariencia y el comportamiento visual
de los Widgets. Para cambiar la apariencia visual, no es necesario crear un chero
esquema desde cero (lo que sera una ardua tarea). Basta con cambiar el script de los
ImageSet y de los Fonts. Esto se estudiar con ms profundidad en la Seccin 14.8.
A continuacin se muestra una pequea lista de los Widgets ms importantes de-
nidos en el esquema TaharezLook:

Button
Check Box
Combo Box
Frame Window
[396] CAPTULO 14. INTERACCIN Y WIDGETS

List Box
Progress Bar
Slider
Static Text
etc

El siguiente paso en el aprendizaje de CEGUI es crear una interfaz que bien podra
servir para un juego, aprovechando las ventajas que proporcionan los scripts.

14.5. Layouts
Los scripts de layouts especican qu Widgets habr y su organizacin para una
ventana especca. Por ejemplo, un layout llamado chatBox.layout puede contener la
estructura de una ventana con un editBox para insertar texto, un textBox para mostrar
la conversacin, y un button para enviar el mensaje. De cada layout se pueden crear
tantas instancias como se desee.

No hay que olvidar que estos cheros son xml, por lo que deben seguir su estruc-
tura. La siguiente es la organizacin genrica de un recurso layout:

Listado 14.9: Estructura de un script layout.


1 <?xml version="1.0" encoding="UTF-8"?>
2 <GUILayout>
3 <Window Type="WindowType" Name="Window1">
4 <Property Name="Property1" Value="Property1Value"/>
5 <Property Name="Property2" Value="Property2Value"/>
6 <!-- This is a comment -->
7 <Window Type="WindowType" Name="Window1/Window2">
8 <Property Name="Property1" Value="Property1Value"/>
9 <Property Name="Property2" Value="Property2Value"/>
10 </Window>
11 <!-- ... --!>
12 </Window>
13 </GUILayout>

En la lnea 1 se escribe la cabecera del archivo xml, lo cual no tiene nada que ver
con CEGUI. En la lnea 2 se abre la etiqueta GUILayout, para indicar el tipo de script
y se cierra en la lnea 13.
A partir de aqu, se denen los Windows que contendr la interfaz (en CEGUI todo
es un Window!). Para declarar uno, se indica el tipo de Window y el nombre, como se
puede ver en la lnea 3. Dentro de l, se especican sus propiedades (lneas 4 y 5).
Estas propiedades pueden indicar el tamao, la posicin, el texto, la transparencia,
etc. Los tipos de Widgets y sus propiedades se denan en el esquema escogido, por
lo que es necesario consultar su documentacin especca. En la Seccin 14.6 se ver
un ejemplo concreto.
En la lnea 6 se muestra un comentario en xml.
Despus de la denicin de las propiedades de un Window, se pueden denir ms
Windows que pertenecern al primero, ya que los Widgets siguen una estructura jerr-
quica.
14.6. Ejemplo de interfaz [397]

14.6. Ejemplo de interfaz


El siguiente es el cdigo de un script layout que dene una ventana de congu-
racin, con distintos Widgets para personalizar el volumen, la resolucin, o el puerto
para utilizar en modo multiplayer. Adems incorpora un botn para aplicar los cam-
bios y otro para salir de la aplicacin. En la Figura 14.7 se muestra el resultado nal.

Listado 14.10: Ejemplo de layout para crear una ventana de conguracin.


1 <?xml version="1.0" encoding="UTF-8"?>
2 <GUILayout >
3 <Window Type="TaharezLook/FrameWindow" Name="Cfg" >
4 <Property Name="Text" Value="Cfguration Window" />
5 <Property Name="TitlebarFont" Value="DejaVuSans-10" />
6 <Property Name="TitlebarEnabled" Value="True" />
7 <Property Name="UnifiedAreaRect" Value="
Figura 14.7: Resultado de la venta- {{0.133,0},{0.027,0},{0.320,300},{0.127,300}}" />
na de conguracin del ejemplo. 8 <!-- Sonud parameter -->
9 <Window Type="TaharezLook/StaticText" Name="Cfg/SndText" >
10 <Property Name="Text" Value="Sonud Volume" />
11 <Property Name="UnifiedAreaRect" Value="
{{0.385,0},{0.0316,0},{0.965,0},{0.174,0}}" />
12 </Window>
13 <Window Type="TaharezLook/Spinner" Name="Cfg/SndVolume" >
14 <Property Name="Text" Value="Sonud Volume" />
15 <Property Name="StepSize" Value="1" />

C14
16 <Property Name="CurrentValue" Value="75" />
17 <Property Name="MaximumValue" Value="100" />
18 <Property Name="MinimumValue" Value="0" />
19 <Property Name="UnifiedAreaRect" Value="
{{0.0598,0},{0.046,0},{0.355,0},{0.166,0}}" />
20 </Window>
21 <!-- Fullscreen parameter -->
22 <Window Type="TaharezLook/StaticText" Name="Cfg/FullScrText" >
23 <Property Name="Text" Value="Fullscreen" />
24 <Property Name="UnifiedAreaRect" Value="
{{0.385,0},{0.226,0},{0.965,0},{0.367,0}}" />
25 </Window>
26 <Window Type="TaharezLook/Checkbox" Name="Cfg/FullscrCheckbox"
>
27 <Property Name="UnifiedAreaRect" Value="
{{0.179,0},{0.244,0},{0.231,0},{0.370,0}}" />
28 </Window>
29 <!-- Port parameter -->
30 <Window Type="TaharezLook/StaticText" Name="Cfg/PortText" >
31 <Property Name="Text" Value="Port" />
32 <Property Name="UnifiedAreaRect" Value="
{{0.385,0},{0.420,0},{0.9656,0},{0.551,0}}" />
33 </Window>
34 <Window Type="TaharezLook/Editbox" Name="Cfg/PortEditbox" >
35 <Property Name="Text" Value="1234" />
36 <Property Name="MaxTextLength" Value="1073741823" />
37 <Property Name="UnifiedAreaRect" Value="
{{0.0541,0},{0.417,0},{0.341,0},{0.548,0}}" />
38 <Property Name="TextParsingEnabled" Value="False" />
39 </Window>
40 <!-- Resolution parameter -->
41 <Window Type="TaharezLook/StaticText" Name="Cfg/ResText" >
42 <Property Name="Text" Value="Resolution" />
43 <Property Name="UnifiedAreaRect" Value="
{{0.385,0},{0.60,0},{0.965,0},{0.750,0}}" />
44 </Window>
45 <Window Type="TaharezLook/ItemListbox" Name="Cfg/ResListbox" >
46 <Property Name="UnifiedAreaRect" Value="
{{0.0530,0},{0.613,0},{0.341,0},{0.7904,0}}" />
47 <Window Type="TaharezLook/ListboxItem" Name="Cfg/Res/Item1">
48 <Property Name="Text" Value="1024x768"/>
49 </Window>
[398] CAPTULO 14. INTERACCIN Y WIDGETS

50 <Window Type="TaharezLook/ListboxItem" Name="Cfg/Res/Item2">


51 <Property Name="Text" Value="800x600"/>
52 </Window>
53 </Window>
54 <!-- Exit button -->
55 <Window Type="TaharezLook/Button" Name="Cfg/ExitButton" >
56 <Property Name="Text" Value="Exit" />
57 <Property Name="UnifiedAreaRect" Value="
{{0.784,0},{0.825,0},{0.968,0},{0.966,0}}" />
58 </Window>
59 <!-- Apply button -->
60 <Window Type="TaharezLook/Button" Name="Cfg/ApplyButton" >
61 <Property Name="Text" Value="Apply" />
62 <Property Name="UnifiedAreaRect" Value="
{{0.583,0},{0.825,0},{0.768,0},{0.969,0}}" />
63 </Window>
64 </Window>
65 </GUILayout>

Para comenzar, la lnea 2 dene el tipo de script, como se ha explicado en la


Seccin anterior. El tipo de Window que contendr al resto es un TaharezLook/Fra-
meWindow, y se le ha puesto el nombre Cfg. Es importante que el tipo del Window
indique el esquema al que pertenece, por eso se antepone TaharezLook/. A partir de
aqu se aaden el resto de Widgets.
En total se han aadido 10 Widgets ms a la ventana Cfg:

Una etiqueta (StaticText) con el texto Sound Volume (llamada Cfg/SndText


- lnea 9) y un Spinner para indicar el valor (llamado Cfg/SndVolume - lnea
13).
Una etiqueta con el texto Fullscreen (lnea 22) y un Checkbox para activarlo
y desactivarlo (lnea 26).
Una etiqueta con el texto Port(lnea 30) y un EditBox para indicar el nmero
de puerto (lnea 34).
Una etiqueta con el texto Resolution (lnea 41) y un ItemListBox para elegir
entre varias opciones (45).
Un botn (Button) con el texto Exit para terminar la aplicacin (lnea 55).
Un botn con el texto Apply para aplicar los cambios (lnea 60).

Cada uno de los Windows tiene unas propiedades para personalizarlos. En la do-
cumentacin se explican todas y cada una de las opciones de los Window en funcin
del esquema que se utilice.

Para indicar el valor de una propiedad en un chero de script cualquiera de


CEGUI, se indica en el campo Value, y siempre entre comillas, ya sea un
nmero, una cadena o una palabra reservada.

Estas son algunas de las propiedades utilizadas:

Text: indica el texto del Widget. Por ejemplo, la etiqueta de un botn o el ttulo
de una ventana.
14.6. Ejemplo de interfaz [399]

Figura 14.8: Interfaz de CEGUI Layout Editor II.

UniedAreaRect: es uno de los ms importantes. Indica la posicin y el tamao,


mediante un objeto URect, de un Window relativo a su padre. Como se indic
en secciones anteriores, se trata de cuatro UDims (cada uno de ellos con un
factor de escala relativo y un offset), para indicar la esquina superior izquierda

C14
del rectngulo (los dos primeros UDims), y la inferior derecha.
Es importante tener en cuenta que si al Widget hijo se le indique que ocupe
todo el espacio (con el valor para la propiedad de {{0,0},{0,0},{1,0},{1,0}}),
ocupar todo el espacio del Window al que pertenezca, no necesariamente toda
la pantalla.
TitlebarFont: indica el tipo de fuente utilizado en el ttulo de la barra de una
FrameWindow.
TitlebarEnabled: activa o desactiva la barra de ttulo de una FrameWindow.
CurrentValue: valor actual de un Widget al que haya que indicrselo, como el
Spinner.
MaximumValue y MinimumValue: acota el rango de valores que puede tomar un
Widget.

Cada Widget tiene sus propiedades y uso especial. Por ejemplo, el Widget utilizado
para escoger la resolucin (un ItemListBox), contiene a su vez otros dos Window del
tipo ListBoxItem, que representan cada una de las opciones (lneas 47 y 50).
Para poder aadir funcionalidad a estos Widgets (ya sea asociar una accin o uti-
lizar valores, por ejemplo), se deben recuperar desde cdigo mediante el WindowMa-
nager, con el mismo nombre que se ha especicado en el layout. Este script por tanto
se utiliza nicamente para cambiar la organizacin y apariencia de la interfaz, pero no
su funcionalidad.
En este ejemplo concreto, slo se ha aadido una accin al botn con la etiqueta
Exit para cerrar la aplicacin.
[400] CAPTULO 14. INTERACCIN Y WIDGETS

14.7. Editores de layouts grcos


Este mtodo para disear interfaces de usuario directamente modicando los che-
ros xml .layout puede ser muy costoso. Existen un par de aplicaciones grcas para
el diseo de las interfaces. La primera CEGUI Layout Editor es muy avanzada, pero
se ha abandonado. Actualmente se est desarrollando un editor nuevo y actualizado,
CEGUI Unied Editor, pero todava no se considera estable.
Para compilar el editor, es necesario recompilar CEGUI con soporte para Python,
pues es necesaria la biblioteca PyCEGUI. Para compilarla en Ubuntu 11.04 y 11.10 se
puede seguir un tutorial disponible en la web de CEGUI[2], en el apartado HowTo, el
tutorial Build PyCEGUI from source for Linux.

Se puede encontrar un tutorial completo de cmo instalar el editor en:

www.cegui.org.uk/wiki/index.php/CEED

Al tratarse de una versin inestable, es mejor consultar este tutorial para mantener
los pasos actualizados de acuerdo a la ltima versin en desarrollo.

14.8. Scripts en detalle


En esta Seccin se va a ver la estructura del resto de scripts que se utilizan para
construir la interfaz. Es importante conocerlos para poder cambiar la apariencia y
poder adaptarlas a las necesidades artsticas del proyecto.

14.8.1. Scheme
Los esquemas contienen toda la informacin para ofrecer Widgets a una interfaz,
su apariencia, su comportamiento visual o su tipo de letra.

Listado 14.11: Estructura de un script scheme.


1 <?xml version="1.0" ?>
2 <GUIScheme Name="TaharezLook">
3 <Imageset Filename="TaharezLook.imageset" />
4 <Font Filename="DejaVuSans-10.font" />
5 <LookNFeel Filename="TaharezLook.looknfeel" />
6 <WindowRendererSet Filename="CEGUIFalagardWRBase" />
7 <FalagardMapping WindowType="TaharezLook/Button"
TargetType="CEGUI/PushButton" Renderer="Falagard/Button"
LookNFeel="TaharezLook/Button" />
8 <FalagardMapping WindowType="TaharezLook/Checkbox"
TargetType="CEGUI/Checkbox" Renderer="Falagard/
ToggleButton" LookNFeel="TaharezLook/Checkbox" />
9 </GUIScheme>

Al igual que suceda con los layout, comienzan con una etiqueta que identican el
tipo de script. Esta etiqueta es GUIScheme, en la lnea 2.
De las lneas 3-6 se indican los scripts que utilizar el esquema de los otros tipos.
Qu conjunto de imgenes para los botones, cursores y otro tipo de Widgets mediante
el Imageset (lnea 3), qu fuentes utilizar mediante un Font (lnea 4), y el comporta-
miento visual de los Widgets a travs del LookNFeel (lnea 5). Adems se indica qu
sistema de renderizado de skin utilizar (lnea 6). CEGUI utiliza Falagard.
14.8. Scripts en detalle [401]

El resto se dedica a declarar el conjunto de Widgets. Para ello realiza un mapeado


entre el Widget que habr disponible en el esquema (por ejemplo, TaharezLook/But-
ton, en la lnea 7), y los que ofrece CEGUI. Adems, por cada uno se indican varios
parmetros.
De este script usualmente se suele cambiar los Imageset y los Font para cambiar
la apariencia de la interfaz, y suministrar los desarrollados de forma propia.

14.8.2. Font
Describen los tipos de fuente que se utilizarn en la interfaz.

Listado 14.12: Estructura de un script font.


1 <?xml version="1.0" ?>
2 <Font Name="DejaVuSans-10" Filename="DejaVuSans.ttf" Type="FreeType
" Size="10" NativeHorzRes="800" NativeVertRes="600" AutoScaled=
"true"/>

En este ejemplo, slo se dene un tipo de fuente, en la lnea 2. Adems de asignarle


un nombre para poder usarla con CEGUI, se indica cul es el chero ttf que contiene
la fuente, y otras propiedades como el tamao o la resolucin. Dentro de este chero
se puede aadir ms de una fuente, aadiendo ms etiquetas del tipo Font.

C14
14.8.3. Imageset
Contiene las verdaderas imgenes que compondrn la interfaz. Este recurso es,
junto al de las fuentes, los que ms sujetos a cambio estn para poder adaptar la apa-
riencia de la interfaz a la del videojuego.

Listado 14.13: Estructura de un script imageset.


1 <?xml version="1.0" ?>
2 <Imageset Name="TaharezLook" Imagefile="TaharezLook.tga"
NativeHorzRes="800" NativeVertRes="600" AutoScaled="true">
3 <Image Name="MouseArrow" XPos="138" YPos="127" Width="31"
Height="25" XOffset="0" YOffset="0" />
4 </Imageset>

CEGUI almacena las imgenes que componen la interfaz como un conjunto de


imgenes. De este modo, CEGUI slo trabaja con un archivo de imagen, y dentro de
ella debe saber qu porcin corresponde a cada elemento. En la Figura 14.9 podemos
ver el archivo de imagen que contiene toda la apariencia del esquema TaharezLook y
OgreTray.
En la lnea 2 del script, se indica el nombre del conjunto de imgenes y cul es
el archivo de imagen que las contendr, en este caso, TaharezLook.tga. A partir de
ese archivo, se dene cada uno de los elementos indicando en qu regin de Taharez-
Look.tga se encuentra. En la lnea 3 se indica que el cursor del ratn (MouseArrow)
se encuentra en el rectngulo denido por la esquina superior izquierda en la posicin
(138, 127), y con unas dimensiones de 31x25.
De esta forma, se puede disear toda la interfaz con un programa de dibujo, unirlos
Figura 14.9: Matriz de imgenes todos en un archivo, e indicar en el script imageset dnde se encuentra cada una. Para
que componen la interfaz del esque-
ma TaharezLook (arriba), y Ogre-
ver a qu ms elementos se les puede aadir una imagen, consultar la referencia, o
Tray (abajo). estudiar los imageset proporcionados por CEGUI.
[402] CAPTULO 14. INTERACCIN Y WIDGETS

14.8.4. LookNFeel
Estos tipos de scripts son mucho ms complejos, y los que CEGUI proporciona
suelen ser ms que suciente para cualquier interfaz. An as, en la documentacin se
puede consultar su estructura y contenido.

14.9. Cmara de Ogre en un Window


Una caracterstica muy interesante que se puede realizar con CEGUI es mostrar lo
que capta una cmara de Ogre en un Widget. Puede ser interesante para mostrar mapas
o la vista trasera de un coche de carreras, por ejemplo.
Como se aprecia en la Figura 14.10, la aplicacin mostrar a Sinbad de la forma
habitual, pero adems habr una ventana que muestra otra vista distinta, y sta a su
vez contendr un botn para salir de la aplicacin.
A continuacin se muestra el chero del layout. Despus de haber visto en la
Seccin 14.5 cmo funcionan los layouts, este no tiene ninguna complicacin. Se
trata de una nica FrameWindow (lnea 3) llamada CamWin, con el ttulo Back
Camera y una posicin y tamao determinados. Esta a su vez contiene dos Windows
ms: uno del tipo StaticImage (lnea 8), que servir para mostrar la imagen de la
Figura 14.10: Screenshot de la
textura generada a partir de una cmara secundaria de Ogre, para tener ese segundo
aplicacin de ejemplo.
punto de vista; y uno del tipo Button (lnea 11), con el texto Exit.
El nico aspecto resaltable de este cdigo es que el Widget que muestra la imagen
(CamWin/RTTWindow) ocupa todo el area de su padre (su propiedad UniedArea-
Rect vale {{0,0},{0,0,},{1,0},{1,0}}. Esto quiere decir que ocupar toda la ventana
CamWin, que no toda la pantalla.

Listado 14.14: Layout del ejemplo.


1 <?xml version="1.0" encoding="UTF-8"?>
2 <GUILayout>
3 <Window Type="TaharezLook/FrameWindow" Name="CamWin" >
4 <Property Name="Text" Value="Back Camera" />
5 <Property Name="TitlebarFont" Value="DejaVuSans-10" />
6 <Property Name="TitlebarEnabled" Value="True" />
7 <Property Name="UnifiedAreaRect" Value="
{{0.6,0},{0.6,0},{0.99,0},{0.99,0}}" />
8 <Window Type="TaharezLook/StaticImage" Name="CamWin/RTTWindow"
>
9 <Property Name="UnifiedAreaRect" Value="
{{0,0},{0,0},{1,0},{1,0}}" />
10 </Window>
11 <Window Type="TaharezLook/Button" Name="ExitButton" >
12 <Property Name="Text" Value="Exit" />
13 <Property Name="UnifiedAreaRect" Value="
{{0.01,0},{0.01,0},{0.25,0},{0.15,0}}" />
14 </Window>
15 </Window>
16 </GUILayout>

Una vez tenemos la organizacin de la interfaz, es necesario dotarla de funciona-


lidad. A continuacin se muestran las modicaciones que hay que aadir para que se
pueda renderizar una cmara de Ogre en el Widget.
14.9. Cmara de Ogre en un Window [403]

Listado 14.15: Inicializacin de la textura y del Widget que la mostrar.


1 Ogre::Camera* _camBack = _sceneManager->createCamera("BackCamera");
2 _camBack->setPosition(Ogre::Vector3(-5,-20,20));
3 _camBack->lookAt(Ogre::Vector3(0,0,0));
4 _camBack->setNearClipDistance(5);
5 _camBack->setFarClipDistance(10000);
6 _camBack->setAspectRatio(width / height);
7
8 Ogre::TexturePtr tex = _root->getTextureManager()->createManual(
9 "RTT",
10 Ogre::ResourceGroupManager::
DEFAULT_RESOURCE_GROUP_NAME,
11 Ogre::TEX_TYPE_2D,
12 512,
13 512,
14 0,
15 Ogre::PF_R8G8B8,
16 Ogre::TU_RENDERTARGET);
17
18 Ogre::RenderTexture* rtex = tex->getBuffer()->getRenderTarget();
19
20 Ogre::Viewport* v = rtex->addViewport(_camBack);
21 v->setOverlaysEnabled(false);
22 v->setClearEveryFrame(true);
23 v->setBackgroundColour(Ogre::ColourValue::Black);
24
25 CEGUI::Texture& guiTex = renderer->createTexture(tex);
26

C14
27 CEGUI::Imageset& imageSet = CEGUI::ImagesetManager::getSingleton().
create("RTTImageset", guiTex);
28 imageSet.defineImage("RTTImage",
29 CEGUI::Point(0.0f,0.0f),
30 CEGUI::Size(guiTex.getSize().d_width, guiTex.getSize()
.d_height),
31 CEGUI::Point(0.0f,0.0f));
32
33 CEGUI::Window* ex1 = CEGUI::WindowManager::getSingleton().
loadWindowLayout("render.layout");
34
35 CEGUI::Window* RTTWindow = CEGUI::WindowManager::getSingleton().
getWindow("CamWin/RTTWindow");
36
37 RTTWindow->setProperty("Image",CEGUI::PropertyHelper::imageToString
(&imageSet.getImage("RTTImage")));
38
39 //Exit button
40 CEGUI::Window* exitButton = CEGUI::WindowManager::getSingleton().
getWindow("ExitButton");
41 exitButton->subscribeEvent(CEGUI::PushButton::EventClicked,
42 CEGUI::Event::Subscriber(&MyFrameListener::quit,
43 _framelistener));
44 //Attaching layout
45 sheet->addChildWindow(ex1);
46 CEGUI::System::getSingleton().setGUISheet(sheet);

Esta parte supone ms cdigo de Ogre que de CEGUI.


Puesto que el objetivo es mostrar en un Widget lo que est capturando una cmara,
el primer paso es crearla. De las lneas 1 a las 6 se crea una Ogre::Camera de forma
convencional. Se indica la posicin, hacia dnde mira, los planos de corte Near y Far,
y el aspect ratio.
Por otro lado, hay que crear la textura en la que se volcar la imagen de la cmara.
Para ello se utiliza la funcin createManual() de TexureManager, en las lneas
8 a la 16. La textura se llama RTT, tendr un tamao de 512x512, y ser del tipo
Ogre::TU_RENDERTARGET, para que pueda albergar la imagen de una cmara.
[404] CAPTULO 14. INTERACCIN Y WIDGETS

El siguiente paso es crear el ViewPort. En la lnea 18 se obtiene el objeto Ren-


derTexture a partir de la textura manual creada. En la lnea 20 se obtiene el objeto
ViewPort a partir del RenderTexture y utilizando la cmara que se quiere mostrar. En
este caso, _camBack. A este ViewPort se le indica que no dibuje los Overlays (lnea
21), aunque podra hacerlo sin problemas, y que se actualice en cada frame (lnea 22).
Adems se establece el color de fondo en negro (lnea 23).
Hasta ahora se ha creado la textura tex de Ogre que es capaz de actualizarse con
la imagen capturada por una cmara, tambin de Ogre, para tal efecto. El siguiente
paso es preparar CEGUI para mostrar imagen en uno de sus Widgets, y que adems
esa imagen la obtenga de la textura de Ogre.
En la lnea 25 se crea la textura guiTex de CEGUI. El objeto renderer especco
para Ogre proporciona la funcin createTexture(), que la crea a partir de una
de Ogre.
Como se vio en la Seccin 14.8.3, CEGUI est diseado para tratar las distintas
imgenes como porciones de un array de imgenes (ver Figura 14.9).
De este modo, primeramente hay que crear un Imageset a partir de la textura que
devuelve Ogre (ya en formato de CEGUI) en la lnea 27. A este conjunto de imgenes
se le ha llamado RTTImageset. Despus, hay que identicar qu porcin correspon-
de a la textura de la cmara de ese Imageset. En este caso, es la textura completa,
por lo que en la lnea 28 se dene la imagen con el nombre RTTImage. El primer
parmetro es el nombre, el segundo la esquina superior izquierda de la porcin que
dene la imagen (se indica el 0,0), el tercero el tamao de la porcin, que corresponde
al tamao de la textura, y el cuarto un offset.
Ya se ha conseguido obtener una imagen de CEGUI que se actualizar cada frame
con lo que capturar la cmara. Lo nico que falta es recuperar los Window denidos
en el layout e indicar que el Widget CamWin/RTTWindow muestre dicha textura.
En la lnea 33 se carga el layout como se hizo en anteriores ejemplos. Es importan-
te hacerlo antes de comenzar a recuperar los Windows denidos en el layout, porque
de lo contrario no los encontrar.
Se recupera el Window que mostrar la imagen (del tipo StaticImage), llamado
CamWin/RTTWindow, en la lnea 35. En la siguiente lnea, la 37, se indica en la
propiedad Image de dicho Window que utilice la imagen RTTImage del conjunto
de imagenes.
Con esto ya es suciente para mostrar en el Widget la imagen de la cmara. Por
ltimo se aade la funcionalidad al botn de salida, en las lneas 40 y 41, como en
anteriores ejemplos, y se aade el layout al Sheet (lnea 45) y se establece dicho Sheet
por defecto (lnea 46).

14.10. Formateo de texto


Una caracterstica muy verstil que ofrece CEGUI es la de proporcionar formato a
las cadenas de texto mediante el uso de tags (etiquetas). Este formato permite cambiar
el color, tamao, alineacin o incluso insertar imgenes. A continuacin se describe
el funcionamiento de estas etiquetas.

14.10.1. Introduccin
El formato de las etiquetas utilizadas son el siguiente:
14.10. Formateo de texto [405]

[tag-name=value]

Estas etiquetas se insertan directamente en las cadenas de texto, y funcionan como


estados. Es decir, si se activa una etiqueta con un determinado color, se aplicar a todo
el texto que le preceda a no ser que se cambie explcitamente con otra etiqueta.
A continuacin se muestran los diferentes aspectos que se pueden personalizar
con esta tcnica. Al nal se muestra una aplicacin de ejemplo que implementa su
funcionamiento.

Si se quiere mostrar como texto la cadena [Texto] sin que lo interprete como
una etiqueta, es necesario utilizar un carcter de escape. En el caso concreto
de C++ se debe anteponer \\ nicamente a la primera llave, de la forma
\\[Texto]

14.10.2. Color
Para cambiar el color del texto, se utiliza la etiqueta colour, usada de la siguiente
forma:

C14
[colour=FFFF0000]

Esta etiqueta colorea el texto que la siguiese de color rojo. El formato en el que
se expresa el color es ARGB de 8 bits, es decir AARRGGBB. El primer parmetro
expresa la componente de transparencia alpha, y el resto la componente RGB.

14.10.3. Formato
Para cambiar el formato de la fuente (tipo de letra, negrita o cursiva, por ejemplo)
es ms complejo ya que se necesitan los propios cheros que denan ese tipo de
fuente, y adems deben estar denidos en el script Font.
Estando seguro de tener los archivos .font y de tenerlos incluidos dentro del chero
del esquema, se puede cambiar el formato utilizando la etiqueta:

[font=Arial-Bold-10]

Esta en concreto corresponde al formato en negrita del tipo de letra Arial, de ta-
mao 10. El nombre que se le indica a la etiqueta es el que se especic en el .scheme.

14.10.4. Insertar imgenes


Insertar una imagen dentro de una cadena, al estilo de los emoticonos, es sencillo.
La estructura de la etiqueta es la siguiente:

[imageset=set:<imageset> image:<image>]

Una vez ms, CEGUI trata las imgenes individuales como parte de un Imageset,
por lo que hay que indicarle el conjunto, y la imagen.
[406] CAPTULO 14. INTERACCIN Y WIDGETS

Aprovechando el Imageset que utiliza la interfaz, TaharezLook, se va a mostrar


una de ellas, por ejemplo la equis para cerrar la ventana. Echando un vistazo al xml,
se puede identicar que la imagen que se corresponde con la equis se llama Close-
ButtonNormal. La etiqueta que habra que utilizar sera la siguiente:

[image=set:TaharezLook image=CloseButtonNormal]

Adems, existe otra etiqueta poder cambiar el tamao de las imgenes insertadas.
El formato de la etiqueta es el siguiente:

[image-size=w:<width_value> h:<height_value>]

El valor del ancho y del alto se da en pxeles, y debe ponerse antes de la etiqueta
que inserta la imagen. Para mostrar las imgenes en su tamao original, se deben poner
los valores de width y height a cero.

14.10.5. Alineamiento vertical


Cuando un texto contiene distintos tamaos de letra, es posible congurar el ali-
neamiento vertical de cada parte. El alto de una lnea concreta vendr denido por el
alto del texto con mayor tamao. El resto de texto, con menor tamao, podr alinearse
verticalmente dentro de ese espacio.
Los tipos de alineamiento vertical disponibles son:

top: lo alinea hacia arriba.


bottom: lo alinea hacia abajo.
center: lo centra verticalmente.
strecth: lo estira verticalmente para ocupar todo el alto.

El formato de la etiqueta es:

[vert-alignment=<tipo_de_alineamiento>]

14.10.6. Padding
El padding consiste en reservar un espacio alrededor del texto que se desee. Para
denirlo, se indican los pxeles para el padding izquierdo, derecho, superior e inferior.
As, adems de el espacio que ocupe una determinada cadena, se reservar como un
margen el espacio indicado en el padding. Viendo la aplicacin de ejemplo se puede
apreciar mejor este concepto.
El formato de la etiqueta es:

[padding=l:<left_padding> t:<top_padding> r:<right_padding> b:<


bottom_padding>]

Para eliminar el padding, utilizar la etiqueta con los valores a 0.


14.10. Formateo de texto [407]

Figura 14.11: Ejemplos de uso del formateo de cadenas.

C14
14.10.7. Ejemplo de texto formateado
El siguiente es un ejemplo que muestra algunas de las caractersticas que se han
descrito. En la Figura 14.11 se aprecia el acabado nal. El siguiente es el layout utili-
zado:

Listado 14.16: Layout de la aplicacin.


1 <?xml version="1.0" encoding="UTF-8"?>
2 <GUILayout>
3 <Window Type="TaharezLook/FrameWindow" Name="FormatWin">
4 <Property Name="Text" Value="Format String Window"/>
5 <Property Name="TitlebarEnabled" Value="True"/>
6 <Property Name="UnifiedAreaRect" Value="
{{0.05,0},{0.05,0},{0.95,0},{0.95,0}}"/>
7 <!-- Static Text -->
8 <Window Type="TaharezLook/StaticText" Name="FormatWin/Text1">
9 <Property Name="UnifiedAreaRect" Value="
{{0.05,0},{0.05,0},{0.95,0},{0.15,0}}"/>
10 </Window>
11 <!-- Other Static Text ... -->
12 <!-- Exit Button -->
13 <Window Type="TaharezLook/Button" Name="FormatWin/ExitButton">
14 <Property Name="Text" Value="Exit" />
15 <Property Name="UnifiedAreaRect" Value="
{{0,0},{0.95,0},{1,0},{1,0}}"/>
16 </Window>
17 </Window>
18 </GUILayout>

Y el siguiente listado muestra cada una de las cadenas que se han utilizado, junto
a las etiquetas:

Listado 14.17: Cdigo con los tags de formateo.


1 "Este color es [colour=FFFF0000] AZUL, mientras que [colour=
FF00FF00] este es ROJO [colour=FF0000FF] y este VERDE!"
[408] CAPTULO 14. INTERACCIN Y WIDGETS

2
3 "El tipo de letra puede [font=Batang-26]cambiar de un momento a
otro, [font=fkp-16]y sin previo aviso!"
4
5 "Si pulsas aqui [image-size=w:40 h:55][image=set:TaharezLook
image:CloseButtonNormal] no pasara nada :("
6
7 "[font=Batang-26] Soy GRANDE, [font=DejaVuSans-10][vert-
alignment=top] puedo ir arriba, [vert-alignment=bottom]o
abajo, [vert-alignment=centre]al centro..."
8
9 "En un lugar de la [padding=l:20 t:15 r:20 b:15]Mancha[padding=l
:0 t:0 r:0 b:0], de cuyo nombre no quiero acordarme, no ha
mucho..."

La primera cadena (lnea 1) utiliza las etiquetas del tipo colour para cambiar el
color del texto escrito a partir de ella. Se utilizan los colores rojo, verde y azul, en ese
orden.
La segunda (lnea 3) muestra cmo se pueden utilizar las etiquetas para cambiar
totalmente el tipo de fuente, siempre y cuando estn denidos los recursos .font y estos
estn reejados dentro el .scheme.
La tercera (lnea 5) muestra una imagen insertada en medio del texto, y adems
redimensionada. Para ello se utiliza la etiqueta de redimensionado para cambiar el ta-
mao a 40x55, y despus inserta la imagen CloseButtonNormal, del conjunto Taha-
rezLook
La cuarta (lnea 7) muestra una cadena con un texto (Soy GRANDE) de un tipo
de fuente con un tamao 30, y el resto con un tamao 10. Para el resto del texto, sobra
espacio vertical, por lo que se utiliza la etiqueta vertical-alignment para indicar dnde
posicionarlo.
Por ltimo, la quinta cadena (lnea 9), utiliza padding para la palabra Mancha. A
esta palabra se le reserva un margen izquierdo y derecho de 20 pxeles, y un superior
e inferior de 15.

14.11. Caractersticas avanzadas


CEGUI es una biblioteca muy potente y exible que puede ser utilizada junto a
muchas otras para crear efectos visualmente muy impactantes. Algunas caractersticas
avanzadas que se han implementado son efectos de ventanas, como transparencia y
aspecto gelatinoso, o incluso incrustar un navegador dentro de una ventana.
Para aprender estas caractersticas y ms, en su pgina existen muchos manuales,
y dispone de una muy buena documentacin [2].
Captulo

Materiales y Texturas
15
Carlos Gonzlez Morcillo

Iluminacin

E
Local n este captulo estudiaremos los conceptos fundamentales con la denicin de
materiales y texturas. Introduciremos la relacin entre los modos de sombrea-
L do y la interaccin con las fuentes de luz, describiendo los modelos bsicos
Punto
Vista
soportados en aplicaciones interactivas. Para nalizar, estudiaremos la potente apro-
ximacin de Ogre para la denicin de materiales, basndose en los conceptos de
tcnicas y pasadas.

15.1. Introduccin
Los materiales describen las propiedades fsicas de los objetos relativas a cmo
Iluminacin reejan la luz incidente. Obviamente, el aspecto nal obtenido ser dependiente tanto
Global de las propiedades del material, como de la propia denicin de las fuentes de luz. De
este modo, materiales e iluminacin estn ntimamente relacionados.
L
Desde los inicios del estudio de la ptica, investigadores del campo de la fsica
Punto
Vista han desarrollado modelos matemticos para estudiar la interaccin de la luz en las
supercies. Con la aparicin del microprocesador, los ordenadores tuvieron suciente
potencia como para poder simular estas complejas interacciones.
As, usando un ordenador y partiendo de las propiedades geomtricas y de mate-
riales especicadas numricamente es posible simular la reexin y propagacin de la
luz en una escena. A mayor precisin, mayor nivel de realismo en la imagen resultado.

Figura 15.1: Diferencias entre los


modelos de iluminacin local y glo-
bal.

409
[410] CAPTULO 15. MATERIALES Y TEXTURAS

a) b)

Figura 15.2: Ejemplo de resultado utilizando un modelo de iluminacin local y un modelo de iluminacin
global con la misma conguracin de fuentes de luz y propiedades de materiales. a) En el modelo de ilumi-
nacin local, si no existen fuentes de luz en el interior de la habitacin, los objetos aparecern totalmente a
oscuras, ya que no se calculan los rebotes de luz indirectos. b) Los modelos de iluminacin global tienen
en cuenta esas contribuciones relativas a los rebotes de luz indirectos.

Esta conexin entre la simulacin del comportamiento de la luz y el nivel de rea-


lismo queda patente en las aproximaciones existentes de diferentes mtodos de render.
Una ecuacin que modela el comportamiento fsico de la luz, ampliamente aceptada
por la comunidad, es la propuesta por Kajiya en 1986. De forma general podemos
decir que a mayor simplicacin en la resolucin de los trminos de esta ecuacin
tendremos mtodos menos realistas (y computacionalmente menos costosos).
A un alto nivel de abstraccin, podemos realizar una primera taxonoma de mto-
dos de render entre aquellos que realizan una simulacin de iluminacin local, tenien-
do en cuenta nicamente una interaccin de la luz con las supercies, o los mtodos
de iluminacin global que tratan de calcular todas 1 las interacciones de la luz con las
supercies de la escena. La Figura 15.2 muestra el resultado de renderizar la misma
escena con un mtodo de iluminacin local y uno global. Los modelos de iluminacin
global incorporan la iluminacin directa que proviene de la primera interaccin de las
supercies con las fuentes de luz, as como la iluminacin indirecta reejada por otras
supercies existentes en la escena.
La potencia de clculo actual hace inviable el uso de mtodos de iluminacin glo-
bal. Se emplean aproximaciones de preclculo de la iluminacin, que sern estudiadas
en el captulo de iluminacin. As, en los motores grcos actuales como Ogre, los
materiales denen cmo se reeja la luz en los objetos (pero no su contribucin con
otros objetos), empleando un esquema de iluminacin local.

15.2. Modelos de Sombreado L

Como hemos comentado anteriormente, es habitual en grcos por computador n


interactivos (y en el caso de videojuegos especialmente) emplear modelos de ilumi- l
nacin local. En cierto modo, el sombrado es equivalente a pintar con luz. En este
apartado estudiaremos los modelos de sombreado ms ampliamente utilizados en gr-
cos interactivos, que fueron inicialmente desarrollados en los aos 70. Figura 15.3: Modelo de sombreado
1 Debido a que es imposible calcular las innitas interacciones de los rayos de luz con todos los objetos de difuso bsico de Lambert en el que
el color c se dene como c n l.
la escena, las aproximaciones de iluminacin global se ocuparn de calcular algunas de estas interacciones,
Los vectores n y l deben estar nor-
tratando de minimizar el error de muestreo.
malizados.
15.2. Modelos de Sombreado [411]

+ + + =

Sombreado Sombreado Sombreado Sombreado Sombreado


Ambiental Difuso Especular de Emisin Final

Figura 15.4: Modos bsicos de sombreado de iluminacin local.

Sombreado difuso. Muchos objetos del mundo real tienen una acabado emi-
nentemente mate (sin brillo). Por ejemplo, el papel o una tiza pueden ser su-
percies con sombreado principalmente difuso. Este tipo de materiales reejan
la luz en todas las direcciones, debido principalmente a las rugosidades mi-

C15
croscpicas del material. Como efecto visual, el resultado de la iluminacin es
mayor cuando la luz incide perpendicularmente en la supercie. La intensidad
nal viene determinada por el ngulo que forma la luz y la supercie (es inde-
pendiente del punto de vista del observador). En su expresin ms simple, el
modelo de reexin difuso de Lambert dice que el color de una supercie es
proporcional al coseno del ngulo formado entre la normal de la supercie y el
vector de direccin de la fuente de luz (ver Figura 15.3).
Sombreado especular. Es el empleado para simular los brillos de algunas su-
percies, como materiales pulidos, pintura plstica, etc. Una caracterstica prin-
cipal del sombreado especular es que el brillo se mueve con el observador. Esto
implica que es necesario tener en cuenta el vector del observador. La dureza del
brillo (la cantidad de reejo del mismo) viene determinada por un parmetro h.
A mayor valor del parmetro h, ms concentrado ser el brillo. El comporta-
miento de este modelo de sombreado est representado en la Figura 15.5, donde
r es el vector reejado del l (forma el mismo ngulo con n) y e es el vector
que se dirige del punto de sombreado al observador. De esta forma, el color nal
de la supercie es proporcional al ngulo .

L
Sombreado ambiental. Esta componente bsica permite aadir una aproxima-
n cin a la iluminacin global de la escena. Simplemente aade un color base
r independiente de la posicin del observador y de la fuente de luz. De esta for-
ma se evitan los tonos absolutamente negros debidos a la falta de iluminacin
l e global, aadiendo este trmino constante. En la Figura 15.4 se puede ver la
componente ambiental de un objeto sencillo.
Sombreado de emisin. Finalmente este trmino permite aadir una simula-
Figura 15.5: En el modelo de som-
breado especular, el color c se ob-
cin de la iluminacin propia del objeto. No obstante, debido a la falta de inter-
tiene como c (r e)h . Los vecto- accin con otras supercies (incluso con las caras poligonales del propio obje-
res r y e deben estar normalizados. to), suele emplearse como una alternativa al sombreado ambiental a nivel local.
El efecto es como tener un objeto que emite luz pero cuyos rayos no interactan
con ninguna supercie de la escena (ver Figura 15.4).
[412] CAPTULO 15. MATERIALES Y TEXTURAS

El sombreado nal de la supercie se obtiene como combinacin de los cuatro


modos de sombreado anteriores (ver Figura 15.4). Esta aproximacin es una simpli-
cacin del modelo de reexin fsicamente correcto denido por la Funcin de Dis-
tribucin de Reactancia Bidireccional BRDF (Bidirectional Reactance Distribution
Function). Esta funcin dene cmo se reeja la luz en cualquier supercie opaca, y
es empleada en motores de rendering fotorrealistas.

15.3. Mapeado de Texturas


Los materiales denen propiedades que son constantes a lo largo de la supercie.
Hasta ahora, hemos hablado de materiales bsicos en las supercies, con propiedades
(como el color) constantes.
Las texturas permiten variar estas propiedades, determinando en cada punto cmo
cambian concretamente estas propiedades. Bsicamente podemos distinguir dos tipos
de texturas:

Texturas Procedurales. Su valor se determina mediante una ecuacin. Estas


texturas se calculan rpidamente y no tienen requisitos de espacio en disco, por
lo que son ampliamente utilizadas en sntesis de imagen realista para simular
ciertos patrones existentes en la naturaleza (madera, mrmol, nubes, etc).
La Figura 15.6 muestra un ejemplo de este tipo de texturas. En videojuegos
sin embargo, se emplean en menor medida, ya que resulta habitualmente ms
interesante emplear texturas de imagen con una resolucin controlada.
Texturas de Imagen. Almacenan los valores en una imagen, tpicamente bidi-
mensional.

Las texturas procedurales obtienen valores habitualmente en el espacio 3D, por lo


que no es necesaria ninguna funcin de proyeccin de estas texturas sobre el objeto.
Sin embargo, para utilizar las texturas de imagen es necesario indicar cmo queremos
aplicar esa textura (2D) a la geometra del modelo 3D. Es decir, debemos espeicifcar
cmo se recubrir el objeto con ese mapa de textura. Existen varias alternativas para
realizar este mapeado. Empleando proyecciones ortogonales es posible describir esta
correspondencia. Se emplean cuatro modos bsicos de proyeccin (ver Figura 15.8).
Estos modos de proyeccin utilizas las coordenadas del objeto 3D normalizadas en el
interior de una caja unitaria.

Una de las caractersticas interesantes de los modelos de mapeado de texturas Figura 15.6: Denicin de una sen-
(tanto ortogonales como mediante mapas paramtricos UV) es la indepen- cilla textura procedural que dene
dencia de la resolucin de la imagen. De este modo es posible tener textu- bandas de color dependiendo del
ras de diferentes tamaos y emplearlas aplicando tcnicas de nivel de detalle valor de coordenada Z del punto
(LOD). As, si un objeto se muestra a una gran distancia de la cmara es po- 3D.
sible cargar texturas de menor resolucin que requieran menor cantidad de
memoria de la GPU.

Como hemos visto en captulos anteriores, un mtodo de proyeccin de texturas de


imagen muy empleado en videojuegos es el mapeado paramtrico, tambin denomi-
nado mapeado UV. En este mtodo se denen dos coordenadas paramtricas (entre 0 y
1) para cada vrtice de cada cara del modelo (ver Figura 15.7). Estas coordenadas son
independientes de la resolucin de la imagen, por lo que permite cambiar en tiempo de
15.4. Materiales en Ogre [413]

u=0.35 Asociacin de
coordendas de textura
UV a cada vrtice de
cada cara del modelo.

v=0.71 Resultado final


del modelo
texturizado

Figura 15.7: Asignacin de coordenadas UV a un modelo poligonal. Esta operacin suele realizarse con el
soporte de alguna herramienta de edicin 3D.

C15
Proyeccin Proyeccin Proyeccin Proyeccin
Textura a Mapear Plana Cbica Cilndrica Esfrica

Figura 15.8: Mtodos bsicos de mapeado ortogonal sobre una esfera. Los bordes marcados con lneas de
colores en la textura a mapear en la izquierda se proyectan de forma distinta empleado diversos mtodos de
proyeccin.

ejecucin la textura teniendo en cuenta ciertos factores de distancia, importancia del


objeto, etc. El mapeado UV permite pegar la textura al modelo de una forma muy pre-
cisa. Incluso si se aplica sobre el modelo deformacin de vrtices (vertex blending),
el mapa seguir aplicndose correctamente. Por esta razn y su alta eciencia es una
tcnica ampliamente utilizada en grcos por computador.
Shading programable
En esta seccin estudiaremos nica-
mente lo que se conoce como sha-
15.4. Materiales en Ogre
ding jo (xed shading). En captu-
los posteriores del documento estu-
diaremos cmo aplicar shaders pro- El despliegue de entidades en Ogre se realiza en paquetes, de modo que existe
gramando la GPU (en pixel shading una relacin directa entre el nmero de materiales y el nmero de paquetes que Ogre
o fragment shading). enviar a la tarjeta grca. Por ejemplo, si 10 elementos comparten el mismo material,
podrn ser enviados en un nico paquete a la GPU (en lugar de en 10 paquetes por
separado), de modo que podrn compartir el mismo estado interno.
[414] CAPTULO 15. MATERIALES Y TEXTURAS

Material
Technique 1 Technique 2 Technique N

Pass 1 Pass 1 Pass 1

Pass 2 Pass 2
... Pass 2

... ... ...


Pass i Pass j Pass k

Figura 15.10: Descripcin de un material en base a tcnicas y pasadas. El nmero de tcnicas y pasadas
asociadas a cada tcnica puede ser diferente.

Con la idea de realizar estas optimizaciones, Ogre dene que por defecto los mate-
riales son compartidos entre objetos. Esto implica que el mismo puntero que referencia
a un material es compartido por todos los objetos que utilizan ese material. De este
modo, si queremos cambiar la propiedad de un material de modo que nicamente afec-
te a un objeto es necesario clonar este material para que los cambios no se propaguen
al resto de objetos.
Los materiales de Ogre se denen empleando tcnicas y esquemas. Una Tcnica
puede denirse como cada uno de los modos alternativos en los que puede renderi-
zarse un material. De este modo, es posible tener, por ejemplo, diferentes niveles de
detalle asociados a un material. Los esquemas agrupan tcnicas, permitiendo denir
nombres y grupos que identiquen esas tcnicas como alto nivel de detalle, medio
rendimiento, etc.

15.4.1. Composicin
Un material en Ogre est formado por una o varias tcnicas, que contienen a su
vez una o varias Pasadas (ver Figura 15.10). En cada momento slo puede existir
una tcnica activa, de modo que el resto de tcnicas no se emplearn en esa etapa de
render.
Una vez que Ogre ha decidido qu tcnica emplear, generar tantas pasadas (en el
mismo orden de denicin) como indique el material. Cada pasada dene una opera-
cin de despliegue en la GPU, por lo que si una tcnica tiene cuatro pasadas asociadas
a un material tendr que desplegar el objeto tres veces en cada frame.
Las Unidades de Textura (Texture Unit) contienen referencias a una nica textura.
Esta textura puede ser generada en cdigo (procedural), puede ser obtenida mediante
un archivo o mediante un ujo de vdeo. Cada pasada puede utilizar tantas unidades
de textura como sean necesarias. Ogre optimiza el envo de texturas a la GPU, de
modo que nicamente se descargar de la memoria de la tarjeta cuando no se vaya a
utilizar ms.

Figura 15.9: Un material en Ogre


se describe mediante sucesivas pa-
sadas que van congurando la apa-
riencia nal.
15.4. Materiales en Ogre [415]

Atributo Descripcin
lod_values Lista de distancias para aplicar diferentes niveles de detalle. Est relacionado
con el campo lod_strategy (ver API de Ogre).
receive_shadows Admite valores on (por defecto) y off. Indica si el objeto slido puede recibir
sombras. Los objetos transparentes nunca pueden recibir sombras (aunque s
arrojarlas, ver el siguiente campo).
transparency_casts_shadows Indica si el material transparente puede arrojar sombras. Admite valores on y
off (por defecto).
set_texture_alias Permite crear alias de un nombre de textura. Primero se indica el nombre del
alias y despus del nombre de la textura original.

Cuadro 15.1: Atributos generales de Materiales.

Atributo Formato Descripcin


ambient r g b [a] Valor de sombreado ambiente (por defecto 1.0 1.0 1.0 1.0).
diffuse r g b [a] Valor de sombreado difuso (por defecto 1.0 1.0 1.0 1.0).
specular r g b [a] h Valor de sombreado especular (por defecto 0.0 0.0 0.0 0.0). El valor de dureza
(shininess) puede ser cualquier valor >0.
emissive r g b [a] Valor de emisin (por defecto 0.0 0.0 0.0 0.0).
scene_blend (Ver valores) Tipo de mezclado de esta pasada con el resto de la escena. Por defecto no se
realiza mezclado. Si se especica, puede tomar valores entre add, modulate,
colour_blend y alpha_blend.

C15
depth_check on | off Por defecto on. Indica si se utilizar el depth-buffer para comprobar la profun-
didad.
lighting on | off Por defecto on. Indica si se emplear iluminacin dinmica en la pasada.
shading (Ver valores) Indica el tipo de mtodo de interpolacin de iluminacin a nivel de vrtice. Se
especican los valores de interpolacin de sombreado at o gouraud o phong.
Por defecto se emplea el mtodo de gouraud.
polygon_mode (Ver valores) Indica cmo se representarn los polgonos. Admite tres valores: solid (por
defecto), wireframe o points.

Cuadro 15.2: Atributos generales de las Pasadas. Ver API de Ogre para una descripcin completa de todos los atributos soportados.

15.4.2. Ejemplo de Materiales


A continuacin veremos un ejemplo sencillo de denicin de materiales en Ogre.
Como hemos visto, el material denido en el listado de la Figura 15.11 contiene una
tcnica y una nica pasada que dene un mtodo de sombreado difuso (especicando
el color base en RGB). El nombre asignado al material especicado a la derecha de la
etiqueta Material (en este caso Material1) debe ser nico a lo largo de la aplicacin.
El nombre de los elementos que denen el material (tcnicas, pasadas y unidades
de textura) es opcional. Si no se especica ninguno, Ogre comenzar a nombrarlas
comenzando en 0 segn el orden de especicacin del script. El nombre de estos
elementos puede repetirse en diferentes materiales.
Veamos a continuacin un ejemplo ms complejo de denicin de material. En
la pasada denida se utiliza una texture_unit que referencia a un archivo de mapa de
entorno esfrico.

Figura 15.12: Mapa de entorno es-


frico empleado para simular la re-
exin de un supuesto mundo.
[416] CAPTULO 15. MATERIALES Y TEXTURAS

Figura 15.11: Denicin y resultado de un sencillo material con sombreado difuso.

Figura 15.13: Un material ms complejo que incluye la denicin de una texture_unit con diversas com-
ponentes de sombreado.

Los mapas de entorno se utilizan para simular la reexin del mundo (representado
en el mapa) dependiendo de la relacin entre las normales de las caras poligionales y la
posicin del observador. En este caso, se indica a Ogre que el tipo de mapa de entorno
es esfrico (tipo ojo de pez, ver Figura 15.12). El operador de colour_op empleado
indica cmo se combinar el color de la textura con el color base del objeto. Existen
diversas alternativas; mediante modulate se indica a Ogre que multiplique el color base
(en este caso, un color amarillo indicado en la componente difusa del color) y el color
del mapa.
A continuacin se describen algunas de las propiedades generales de los mate-
riales. En la tabla 15.1 se resumen los atributos generales ms relevantes empleados
en la denicin de materiales, mientras que las tablas 15.2 y 15.3 resumen los atri-
butos globales ms utilizados en la denicin de las pasadas y unidades de textura
resepectivamente. Cada pasada tiene asociada cero o ms unidades de textura. En los
siguientes ejemplos veremos cmo combinar varias pasadas y unidades de textura para
denir materiales complejos en Ogre.
15.5. Mapeado UV en Blender [417]

Atributo Descripcin
texture Especica el nombre de la textura (esttica) para esta texture_unit. Permite especicar el tipo, y el
uso o no de canal alpha separado.
anim_texture Permite utilizar un conjunto de imgenes como textura animada. Se especica el nmero de frames
y el tiempo (en segundos).
ltering Filtro empleado para ampliar o reducir la textura. Admite valores entre none, bilinear (por defec-
to), trilinear o anisotropic.
colour_op Permite determinar cmo se mezcla la unidad de textura. Admite valores entre replace, add, mo-
dulate (por defecto) o alpha_blend.
colour_op_ex Versin extendida del atributo anterior, que permite especicar con mucho mayor detalle el tipo
de mezclado.
env_map Uso de mapa de entorno. Si se especica (por defecto est en off, puede tomar valores entre sphe-
rical, planar, cubic_reection y cubic_normal.
rotate Permite ajustar la rotacin de la textura. Requiere como parmetro el ngulo de rotacin (en contra
de las agujas del reloj).
scale Ajusta la escala de la textura, especicando dos factores (en X e Y).

Cuadro 15.3: Atributos generales de las Unidades de Textura. Ver API de Ogre para una descripcin completa de todos los atributos soportados

15.5. Mapeado UV en Blender

Mapas UV La forma ms exible para la proyeccin de texturas en aplicaciones interactivas

C15
es el mapeado paramtrico UV. Como hemos visto, a cada vrtice del modelo (con
El mapeado UV es el estndar en coordenadas X,Y,Z) se le asocian dos coordenadas paramtricas 2D (U,V).
desarrollo de videojuegos, por lo
que prestaremos especial atencin Los modelos de mapeado ortogonales no se ajustan bien en objetos complejos.
en este documento. Por ejemplo, la textura asociada a una cabeza de un personaje no se podra mapear
adecuadamente empleando modelos de proyeccin ortogonal. Para tener el realismo
necesario en modelos pintados manualmente (o proyectando texturas basadas en foto-
grafas) es necesario tener control sobre la posicin nal de cada pxel sobre la textura.
El mapa UV describe qu parte de la textura se asociar a cada polgono del modelo,
de forma que, mediante una operacin de despliegue (unwrap) del modelo obtenemos
la equivalencia de la supercie en el plano 2D.
En esta seccin estudiaremos con ms detalle las opciones de Blender para trabajar
con este tipo de coordenadas.
Como vimos en el captulo 11, para comenzar es necesario aplicar una operacin
de despliegue del modelo para obtener una o varias regiones en la ventana de UV/Ima-
ge Editor . Esta operacin de despliegue se realiza siempre en modo edicin. Con
el objeto en modo edicin,
en los botones de edicin , y tras aplicar el despliegue
(Unwrap) (tecla U ), aparece un nuevo
subpanel en el panel de herramientas (Tool
Shelf ), accesible mediante la tecla T (ver Figura 15.15) que controla el modo en el
que se realiza el despliegue del modelo. Este panel aparece mientras el objeto est en
Figura 15.14: El problema del des- modo de edicin, y es dependiente del modo de despliegue elegido (por ejemplo, el de
pliegue se ha afrontado desde los la Figura 15.15 contiene las opciones de Unwrap, pero otros modos de despliegue ten-
orgenes de la cartografa. En la drn otras opciones asociadas). A continuacin describimos las principales opciones
imagen, un mapa de 1482 muestra que pueden encontrarse en este tipo de subpaneles:
una proyeccin del mundo conoci-
do hasta el momento.
Angle Based | Conformal. Dene el mtodo de clculo de la proyeccin. El
Isla UV basado en ngulo crea una nueva isla cuando el ngulo entre caras vecinas sea
relevante. En el modo conformal se realiza dependiendo del mtodo de pro-
Se dene una isla en un mapa UV
como cada regin que contiene vr-
yeccin seleccionado. El mtodo basado en ngulo ofrece, en general, mejores
tices no conectados con el resto. resultados.
[418] CAPTULO 15. MATERIALES Y TEXTURAS

Fill Holes. Intenta ordenar todas las islas para evitar que queden huecos en la
textura sin ninguna cara UV.
Correct Aspect. Permite el escalado de los vrtices desplegados en UV para
ajustarse a la relacin de aspecto de la textura a utilizar.
Margin. Dene la separacin mnima entre islas.
Use Subsurf Data. Utiliza el nivel de subdivisin especicado en Subsurf Tar-
get para el clculo de las coordenadas UV en lugar de la malla original (Red de
Control).
Transform Correction. Corrige la distorsin del mapa UV mientras se est
editando.
Cube Size. Cuando se emplea una proyeccin cbica, este parmetro indica el
porcentaje de la imagen que se emlpear por el mapa UV (por defecto el 100 %).
Figura 15.15: Opciones del panel
Radius. Cuando se emplea el mtodo de proyeccin Cylinder projection, este Unwrap.
parmetro dene el radio del cilindro de proyeccin. A menores valores, las
coordenadas del objeto aparecern estiradas en el eje Y.
Align: Polar ZX | Polar ZY. Determina el plano frontal de proyeccin en los
mtodos cilndrico y esfrico.

El modo general de trabajo asociado al despliegue de una malla est compuesto


por la siguiente serie de pasos secuenciales:

1. Seleccionar el objeto que queremos desplegar.


2. Suele ser interesante ver los cambios realizados sobre la textura UV cambiando
el modo de dibujado del objeto en la ventana 3D a Texture .

3. Cambiar al modo edicin (mediante TAB ) y seleccionar
las caras que queremos Figura 15.16: Opciones del panel
desplegar. En algunos casos sern todas (tecla A ), o puede ser conveniente ele-
Mesh.

gir individualmente (en este caso es conveniente elegir el modo de seleccin de


caras en la cabecera de la ventana 3D).
4. Establecer los valores globales de despliegue en la pestaa asociada al mtodo
de despliegue elegido (ver Figura 15.15).
5. Vericar que existe al menos una capa de textura UV en el panel Mesh del grupo
de botones Object Data (ver Figura 15.16). Cada cara de la malla puede tener
varias texturas UV, pero cada textura nicamente puede tener una nica imagen Figura 15.17: Conguracin de las
asignada.

opciones de despliegue en Smart

6. Pulsando la tecla U accedemos al men de despliegue (UV Mapping) que nos


UV Project.

permite elegir el mtodo de despiegue que aplicaremos as la malla. Entre estos


mtodos podemos destacar los siguientes (ver Figura 15.19):
Unwrap. Esta opcin despliega las caras del objeto tratando de obtener
una conguracin que rellene de la mejor forma posible la textura, em-
pleando informacin topolgica de la malla (cmo estn conectadas las
caras de la malla). Si es posible, cada cara desplegada tendr su propia
regin de la imagen sin solapar con otras caras.
Figura 15.18: Ventana para la crea-
cin de una nueva textura para el
mapeado UV.
15.5. Mapeado UV en Blender [419]

a b c d

e f g h

Figura 15.19: Resultado obtenido tras aplicar diferentes mtodos de despliegue (unwrap) al modelo de
Suzanne con todas las caras seleccionadas: a) Unwrap, b) Cube, c) Cylinder, d) Sphere, e) Project from

C15
view, f) Reset, g) Lightmap y h) Smart UV.

Smart UV Projects. Este mtodo estudia la forma del objeto, estudiando


la relacin entre las caras seleccionadas y crea un mapa UV basada en las
opciones de conguracin que se muestran en la Figura 15.17. A menor
lmite de ngulo, mayor nmero de islas se crearn. El margen existente
entre islas se puede elegir en el parmetro Island Margin. El parmetro de
Area Weight permite dar mayor peso a las caras de mayor supercie. Este
mtodo de proyeccin automtico se ajusta perfectamente en multitud de
situaciones, siendo el que probablemente ofrezca una mejor conguracin
de partida para ajustar posteriormente los vrtices de forma manual.
Lightmap Pack. Despliega las caras de forma regular para ser utilizados
en el clculo de mapas de luz. Este tipo de despiegue ser utilizado en el
Captulo 16 para el preclculo de iluminacin.
Follow Active Quads. Este mtodo toma como entrada las caras seleccio-
nadas y las despliega siguiendo bucles de cara continuos. Este mtodo no
tiene en cuenta el tamao de la imagen.
Cube | Cylinder | Sphere Projection. Estos mtodos tratan de despegar el
objeto empleando estos modelos de proyeccin ortogonales. Como hemos
visto anteriormente, en estos mtodos resulta especialmente relevante la
denicin de las opciones del panel de la Figura 15.15.
Project from View | (Bounds). Emplea el punto de vista 3D para des-
plegar el objeto. Si se elige la opcin Bounds, el despliegue se realizar
ajustando a los lmites de la imagen.
Figura 15.20: Resultado de aplicar Reset. Mediante esta opcin todas las caras se despliegan ocupando el
la textura de prueba a la malla de 100 % de la imagen, con vrtices en las esquinas de la misma.
Suzanne con el modo de despliegue
bsico.
[420] CAPTULO 15. MATERIALES Y TEXTURAS

Figura 15.23: Utilizacin de costuras y despliegue del modelo utilizando diferentes tcnicas de desplie-
gue. En la imagen de la izquerda se marcan perfectamente los seams denidos para recortar el modelo
adecuadamente.

Una vez que la malla ha sido desplegada, podemos ayudarnos de la herramienta de


generacin de rejillas de prueba que trae integrado Blender para hacernos una idea del
resultado del despliegue. En la cabecera de la ventana del editor UV , accediendo
al men Image/ New Image la ventana de la Figura 15.18, donde podemos elegir la
resolucin en pxeles de la nueva imagen (por defecto 1024x1024 pxeles), el color de
fondo y el nivel de Alpha. Si activamos el botn UV Test Grid, se crear la imagen
con la rejilla de fondo que se muestra en la Figura 15.20. Esta imagen permite hacerse
una idea del resultado del despliegue tras aplicar la textura al objeto 3D.
Las coordenadas UV se almacenan en el modelo de Blender y son pasadas a Ogre
empleando el script de exportacin. Sin embargo, es comn utilizar un editor de im-
genes externo para asignar colores a la textura. Para guardar el estado del despliegue
(y utilizarlo como plantilla para el programa de edicin, como GIMP (GNU Image
Manipulation Program)), es posible emplear el script de la cabecera de la ventana del
UV/ Image Editor en UVs / Export UV Layout para generar una imagen PNG, EPS
o SVG.
En la cabecera de la ventana 3D puede elegirse el modo Texture Paint (ver Figu-
ra 15.21). Cuando este modo est activo, nos permite dibujar directamente sobre el

modelo 3D, accediendo a los controles que se encuentran en el subpanel de la vista
3D accesible medidante la tecla T ((ver Figura 15.22)).
Figura 15.21: Editando la textura
aplicada a Suzanne en el espacio 3D
mediante el modo Texture Paint.

15.5.1. Costuras
En el despliegue de mallas complejas, es necesario ayudar a los mtodos de des-
pliegue deniendo costuras (seams). Estas costuras servirn para denir las zonas de
recorte, guiando as al mtodo de desplegado.
Para denir una costura basta con elegir las aristas que la denen en modo edicin,
tal y como se muestra en la Figura 15.23 . La seleccin de bucles de aristas (como en
el caso del bote de spray del modelo) puede realizarse cmodamente seleccionando
una de las aristas del bucle y empleando la combinacin de teclas Control E
Edge Loop. Cuando tenemos la zona de recorte seleccionada, deniremos la costura
mediante la combinacin Control E Mark Seam. En este men aparece igualmente
la opcin para eliminar una costura Clear Seam.
15.6. Ejemplos de Materiales en Ogre [421]

las costuras, podemos emplear la seleccin de zonas enlazadas


Una vez denidas
(mediante la tecla L ) para elegir regiones conectadas. Podemos aplicar a cada gru-
po de caras un modo de despliegue distinto. Por ejemplo, en la Figura 15.23 se ha
utilizado una proyeccin cilndrica para el cuerpo central del Spray y para el difusor,
mientras que la base y la zona superior se han desplegado mediante el modo general
Unwrap.
Cada zona de despliegue en la ventana de UV Image Editor
puede
igualmen-
empleando los operadores habituales de traslacin G , rotacin R
te desplazarse
y escalado S . En esta ventana puede igualmente
emplearse el atajo de teclado pa-
ra seleccionar los vrtices conectados (link) L y seleccionar rpidamente regiones
(incrementalmente si pulsamos L mientras mantenemos pulsada la tecla
desplegadas
Shift ).

15.6. Ejemplos de Materiales en Ogre


En esta seccin estudiaremos algunos ejemplos de denicin de materiales em-
pleando diversas unidades de tetura en Ogre. Nos centraremos en el uso de algunos
operadores para la composicin de diferentes unidades de textura. Las Figuras 15.24
y 15.27 muestran el resultado de denicin de estos materiales y texturas. Veamos
algunas de las propiedades empleadas en su denicin.
En la denicin del Material3 se ha empleado el despliegue que se muestra en la
Figura 15.25. Este material simplemente utiliza las coordenadas UV del modelo para

C15
proyectar la misma textura que en el suelo de la escena.
En la denicin del Material4 se utiliza una textura de tipo cebra donde las bandas
Figura 15.22: Subpanel de opcio- negras son totalmente transparentes. En la pasada de este material se utiliza el atributo
nes de Texture Paint en el que puede scene_blend que permite especicar el tipo de mezclado de esta pasada con el resto de
elegirse el color de pintado, tamao contenido de la escena. Como veremos en el ejemplo del Material6, la pricipal dife-
del pincel, opacidad, etc... rencia entre las operaciones de mezclado a nivel de pasada o a nivel de textura es que
las primeras denen la mezcla con el contenido global de la escena, mientras que las
segundas estn limitadas entre capas de textura. Mediante el modicador alpha_blend
se indica que utilizaremos el valor alpha de la textura. A continuacin estudiaremos
las opciones permitidas por el atributo scene_blend en sus dos versiones.
El formato de scene_blend permite elegir cuatro parmetros en su forma bsica:

add. El color del resultado de la pasada se suma al resultado de la escena.


modulate. El resultado de la pasada se multiplica con el resultado de color de
la escena.
colour_blend. El color resultado de la pasada se mezcla empleando la compo-
Figura 15.25: Despliegue del mo-
nente de brillo de los colores.
delo de los ejemplos. Se ha emplea-
do una operacin de despliegue cin- alpha_blend. Este modo utiliza la informacin de transparencia del resultado.
lndrico para la mandbula inferior,
Smart Projections para los dientes
y el Unwrap bsico para el resto del El atributo scene_blend permite otro formato mucho ms general y exible, que
objeto. requiere dos parmetros: scene_blend src dest. En esta segunda versin, el color nal
se obtiene como: c = (texture * src) + (scene_pixel * dest). En esta segunda versin,
src y dest son factores de mezclado, entre 10 posibles valores:

one. Valor constante 1.0.


zero. Valor constante 0.0.
[422] CAPTULO 15. MATERIALES Y TEXTURAS

Figura 15.24: Ejemplos de denicin de algunos materiales empleando diversos modos de composicin en
Ogre.

dest_colour. Color del pxel en la escena.


src_colour. Color del txel (de la pasada).
one_minus_dest_colour. 1 - (dest_colour).
one_minus_src_colour. 1 - (src_colour).
dest_alpha. Valor alfa del pxel en la escena.
src_alpha. Valor alfa del txel (de la pasada).
one_minus_dest_alpha. 1 - (dest_alpha).
Figura 15.26: En los ejemplos se
one_minus_src_alpha. 1 - (src_alpha). han utilizado dos versiones de la
textura de cebra; una con transpa-
De este modo, el scene_blend por defecto que se aplica es el totalmente opaco, rencia total denida en las bandas
utilizando totalmente el valor del txel de la pasada y sin tener en cuenta el valor negras (en formato PNG), y otra en
del pxel existente en la escena (scene_blend one zero). Anlogamente, el equivalente JPG sin transparencia.
al modo bsico de alpha_blend estudiado anteriormente sera scene_blend src_alpha
one_minus_src_alpha, el modo bsico add se escribira como scene_blend one one,
etc...
15.6. Ejemplos de Materiales en Ogre [423]

Para nalizar la descripcin de los atributos utilizados en el Material4, mediante


Consulta el manual! depth_write off se fuerza a que la pasada no utilice el ZBuffer. Es habitual desactivar
el ZBuffer cuando se despliegan objetos transparentes sobre la escena, para que se
En el manual de Ogre pueden con- superpongan adecuadamente. El atributo lighting off indica que no se tenga en cuenta
sultarse todos los parmetros que
admiten todos los operadores dispo- la iluminacin dinmica en esta pasada. Cuando se desactiva la iluminacin dinmica,
nibles a nivel de textura y pasadas. las propiedades de sombreado difusas, ambientales, especulares y de emisin no se
tienen en cuenta, por lo que cualquier denicin en el material sera redundante.
El Material5 dene un color transparente deniendo una unidad de textura ma-
nualmente. Este material hace uso de las versiones que ms precisin ofrecen a la
hora de denir cmo se combina el color (y la opacidad) de una capa de textura con
las anteriores. As, la denicin del color manualmente de la fuente como verde, y
el nivel de transparencia como 0.25. En trminos generales, el atributo colour_op_ex
requiere como primer parmetro el identicador de una operacin operation, y luego
dos fuentes src1 src2. Las fuentes pueden ser una de las siguientes cinco opciones:

src_current. El color obtenido en capas anteriores.


src_texture. El color de la capa actual.
src_diffuse. El color difuso de los vrtices.
src_specular. El color especular de los vrtices.
src_manual. Denicin manual del color.

Como operacin admite 15 valores diferentes. Si se indica source1 o source2 se

C15
utilizar su valor directamente sin modicacin. La operacin blend_current_alpha
(como veremos en el siguiente ejemplo) utiliza la informacin de alpha de las capas
anteriores. La operacin modulate multiplica los valores de src1 y src2.
El Material6 dene tres capas de textura. La primera carga una textura de csped.
La segunda capa carga la textura de cebra con transparencia, e indica que la combinar
con la anterior empleando la informacin de alpha de esta segunda textura. Finalmen-
te la tercera capa utiliza el operador extendido de colour_op_ex, donde dene que
combinar el color de la capa actual (indicado como primer parmetro en src_texture
con el obtenido en las capas anteriores src_current. El parmetro blend_current_alpha
multiplica el alpha de src2 por (1-alpha(src1)).
Los ejemplos de la Figura 15.27 utilizan algunas de las opciones de animacin
existentes en las unidades de textura. El Material7 por ejemplo dene dos capas de
textura. La primera aplica la textura del suelo al objeto, utilizando el atributo rota-
te_anim. Este atributo requiere un parmetro que indica el nmero de revoluciones
por segundo (empleando velocidad constante) de la textura.
La composicin de la segunda capa de textura (que emplea un mapeado de en-
torno esfrico) se realiza utilizando el atributo colour_op_ex estudiado anteriormente.
La versin de la operacin modulate_x2 multiplica el resultado de modulate (multi-
plicacin) por dos, para obtener un resultado ms brillante.
En el ejemplo del Material8 se denen dos capas de textura, ambas con anima-
cin. La segunda capa utiliza una operacin de suma sobre la textura de cebra sin
opacidad, de modo que las bandas negras se ignoran. A esta textura se aplica una rota-
cin de 0.15 revoluciones por segundo. La primera capa utiliza el atributo scroll_anim
que permite denir un desplazamiento constante de la textura en X (primer parmetro)
y en Y (segundo parmetro). En este ejemplo la textura nicamente se desplaza en el
eje X. De igual modo, la primera capa emplea wave_xform para denir una animacin
basado en una funcin de onda. En este caso se utiliza una funcin senoidal indicando
los parmetros requeridos de base, frecuencia, fase y amplitud (ver el manual de Ogre
para ms detalles sobre el uso de la funcin).
[424] CAPTULO 15. MATERIALES Y TEXTURAS

Figura 15.27: Ejemplos de uso de animacin de texturas (rotacin y onda senoidal).

15.7. Render a Textura


En esta seccin estudiaremos un ejemplo en el que se denir una textura que
contendr el resultado de renderizar la escena empleando otra cmara auxiliar. Para
ello, estudiaremos la creacin manual de texturas, y el uso de un Listener particular
que ser ejecutado cada vez que se produzca una actualizacin en la textura.
El objeto sobre el que se proyectar la textura est denido en la Figura 15.28.a).
En este modelo se han denido tres materiales, cada uno con sus propias coordenadas
de despiegue. Las coordenadas ms importantes son las relativas a la pantalla de la
televisin, sobre la que desplegaremos la textura. Estas coordenadas se han denido de
modo que ocupan todo el rea de mapeado (como se muestra en la Figura 15.28.b). El
material asociado a la pantalla se ha nombrado como pantallaTV, y ser editado en
cdigo a nivel de SubEntity. Cuando un modelo cuenta con diversos materiales (como
es el caso), Ogre crea un objeto de la clase SubEntity para cada material, de modo que,
como veremos a continuacin, tendremos que acceder a todos los SubEntity del objeto
para elegir el que tiene el material de la pantalla y cambiarlo por el que calcularemos
en tiempo de ejecucin.
Hasta ahora hemos desplegado el resultado de la escena sobre la ventana princi- Render a Textura
pal de la aplicacin. Sin embargo, en multitud de aplicaciones es habitual renderizar Existen multitud de aplicaciones en
la escena total o parcialmente sobre una textura. Ogre facilita enormemente la cons- videojuegos para utilizar Render a
truccin de este tipo de texturas. Una vez que tenemos la textura, podemos aplicar Textura. Por ejemplo, los espejos de
cualquier operador de los estudiados en la seccin anterior para mezclarlas con otras cualquier simulador de conduccin
o cmaras de vigilancia dentro del
capas de textura. juego, as como multitud de efec-
El siguiente listado muestra el cdigo relevante para el ejemplo de Render a Tex- tos grcos (como espejos, motion
blur...) y de postproduccin que se
tura. realizan empleando esta tcnica.
El primerpaso para aplicar el Render a Textura es obtener un objeto de tipo Textu-
rePtr (lneas 1-3 ), que permite crear una textura manualmente, indicando el nombre
de la textura (RttT), y las propiedades de tamao (512x512 pxeles), as como el
formato de color (32Bits en RGB, sin canal alfa PF_R8G8B8). El ltimo parmetro
de TU_RENDERTARGET indica a Ogre el tipo de uso que haremos de la textura.
15.7. Render a Textura [425]

a)

b) c) d)

Figura 15.28: Construccin del ejemplo de RTT. a) Modelo empleado para desplegar la textura de RTT.
El modelo se ha denido con tres materiales; uno para la carcasa, otro para las letras del logotipo (ambos

C15
desplegados en c), y uno para la pantalla llamado pantallaTV, desplegado en b). En d) se muestra el
resultado de la ejecucin del programa de ejemplo.

Listado 15.1: Fragmento de CreateScene (MyApp.cpp).


1 TexturePtr rtt = TextureManager::getSingleton().createManual(
2 "RttT", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
3 TEX_TYPE_2D, 512, 512, 0, PF_R8G8B8, TU_RENDERTARGET);
4
5 RenderTexture *rtex = rtt->getBuffer()->getRenderTarget();
6
7 Camera *cam = _sceneManager->createCamera("SecondCamera");
8 cam->setPosition(Vector3(17,16,-4));
9 cam->lookAt(Vector3(-3,2.7,0));
10 cam->setNearClipDistance(5);
11 cam->setFOVy(Degree(38));
12
13 rtex->addViewport(cam);
14 rtex->getViewport(0)->setClearEveryFrame(true);
15 rtex->getViewport(0)->setBackgroundColour(ColourValue::Black);
16 rtex->getViewport(0)->setOverlaysEnabled(false);
17 rtex->setAutoUpdated(true);
18
19 MaterialPtr mPtr = MaterialManager::getSingleton().create(
20 "RttMat",Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
21 Technique* matTechnique = mPtr->createTechnique();
22 matTechnique->createPass();
23 mPtr->getTechnique(0)->getPass(0)->setLightingEnabled(true);
24 mPtr->getTechnique(0)->getPass(0)->setDiffuse(.9,.9,.9,1);
25 mPtr->getTechnique(0)->getPass(0)->setSelfIllumination(.4,.4,.4);
26
27 mPtr->getTechnique(0)->getPass(0)->createTextureUnitState("RttT");
28
29 for (unsigned int i=0; i<entTV->getNumSubEntities(); i++) {
30 SubEntity *aux = entTV->getSubEntity(i);
31 if (aux->getMaterialName() == "pantallaTV")
32 aux->setMaterialName("RttMat");
[426] CAPTULO 15. MATERIALES Y TEXTURAS

33 }


En la lnea 5 se obtiene un puntero a un RenderTexture que es una especializacin
de la clase RenderTarget especca para renderizar sobre una textura.
Puede haber varios RenderTargets que generen resultados sobre la misma textura.

A este objeto de tipo RenderTexture le asociamos una cmara en la lnea 13 (que ha
sido creada en las lneas 7-11 ), y conguramos las propiedades especdas del view-
port asociado (como que
se limpie en cada frame en la lnea 14 , y que no represente
los overlays en la lnea 16 ).

En la lnea 17 indicamos que la textura se actualice automticamente (gestionada
por el bucle principal de Ogre). En otro caso, ser necesario ejecutar manualmente el

mtodo update del RenderTarget.
Las lneas 19-25 declaran manualmente el material sobre el que emplearemos la
Texture Unit con la textura anteriormente denida.
En creamos un material

21 (lnea 22 ). Esa
19-20
llamado RttMat, con una nica tcnica (lnea ) y una pasada
pasada dene sus propiedades en las lneas 23-25 , y aade como TextureUnit a la
textura RttT.
El ltimo bucle recorre todas las SubEntities que forman a la entidad de la tele-
visin. En el caso de que el material asociado a la subentidad sea el llamado pan-

tallaTV (lnea ), le asignamos el material que hemos creado anteriormente (lnea
32 ).
31

15.7.1. Texture Listener


En el ejemplo anterior, la textura proyectada sobre la televisin tiene un efecto
recursivo debido a que aparece en el render de la cmara auxiliar. En muchos casos,
interesa ocultar uno o varios objetos de la escena para realizar el render a textura (por
ejemplo si queremos simular el punto de vista desde el interior de un objeto).
Al igual que ocurre a nivel de Frame, es posible aadir uno o varios objetos Lis-
tener para controlar la actualizacin de las texturas. De este modo, cada vez que se
renderiza un RenderTarget (en nuestro caso concreto una textura), Ogre invocar pre-
viamente el mtodo asociado al preRenderTargetUpdate. Cuando la textura se haya
actualizado, se ejecutar el mtodo llamado postRenderTargetUpdate.
El siguiente listado muestra el chero de cabecera de la declaracin de una clase
propia llamada MyTextureListener que implementa el interfaz denido en RenderTar-
getListener. Esta clase recibe en el constructor un parmetro con el puntero a un objeto
de tipo Entity. La clase se encargar de ocultar esa entidad antes de renderizar la es-
cena sobre la textura y de volver a mostrarlo tras el despliegue.

Listado 15.2: MyTextureListener.h


1 #include <Ogre.h>
2 using namespace std;
3 using namespace Ogre; Figura 15.29: Tras aplicar el textu-
4 re listener que oculta la entidad de
5 class MyTextureListener : public RenderTargetListener { la televisin, se consigue eliminar
6 private:
7 Entity* _ent; el efecto de despliegue recursivo.
8 public:
9 MyTextureListener(Entity *ent);
10 ~MyTextureListener();
11 virtual void preRenderTargetUpdate(const RenderTargetEvent& evt);
12 virtual void postRenderTargetUpdate(const RenderTargetEvent& evt);
13 };
15.7. Render a Textura [427]

A continuacin se muestra el listado que dene la clase MyTextureListener. La


funcionalidad ha sido comentada anteriormente.

Listado 15.3: MyTextureListener.cpp


1 #include "MyTextureListener.h"
2
3 MyTextureListener::MyTextureListener(Entity* ent){
4 _ent = ent;
5 }
6
7 MyTextureListener::~MyTextureListener(){ }
8
9void MyTextureListener::preRenderTargetUpdate(const
RenderTargetEvent& evt) {
10 cout << "preRenderTargetupdate" << endl;
11 _ent->setVisible(false);
12 }
13
14 void MyTextureListener::postRenderTargetUpdate(const
RenderTargetEvent& evt) {
15 cout << "postRenderTargetupdate" << endl;
16 _ent->setVisible(true);
17 }

sencillo. Basta con aadir el listener al objeto de tipo


El uso de la clase es muy
RenderTexture (ver lnea 2 del siguiente listado).

C15
Listado 15.4: Utilizacin en MyApp.cpp
1 _textureListener = new MyTextureListener(entTV);
2 rtex->addListener(_textureListener);

15.7.2. Espejo (Mirror)


La reexin en Espejo es otro de los efectos clsicos que se obtienen mediante
render a textura. El siguiente listado muestra el cdigo necesario para realizar este
ejemplo. Estudiaremos los aspectos que lo distinguen sobre el cdigo de la seccin

anterior.
En las lneas 8-12 se dene la cmara que utilizaremos para crear el efecto de
mirror. Esta cmara debe tener la misma posicin y orientacin que la cmara desde
donde percibimos la escena, pero debe ser independiente (ya que activaremos la ree-
xin sobre un plano, y modicaremos su plano de recorte cercano en las lneas 42-43).
Esta cmara para el efecto de espejo (llamada MirrorCamera en este ejemplo) debe
estar siempre correctamente alineada con la cmara principal. En este ejemplo, la c-
mara principal es esttica, por lo que tampoco modicaremos la posicin y orientacin
de la MirrorCamera. Queda como ejercicio propuesto para el lector aadir movimien-
to a la cmara principal, actualizando anlogamente en cada frame la posicin de la

cmara Mirror.
En las lneas 24-30 se denen las propiedades generales del material. La lnea 31
nos permite generar las coordenadas de textura segn la cmara que se le pasa como
argumento, de modo que da la impresin de que la textura ha sido proyectada sobre la
supercie. De esta forma, la textura que generamos ser posteriormente renderizada
Figura 15.30: Resultado de aplicar utilizando el modelo de proyeccin denido por la cmara de Mirror.
el material con Render a Textura pa-
ra simular espejo sobre el plano del
suelo.
[428] CAPTULO 15. MATERIALES Y TEXTURAS


Para concluir, en las lneas 42-43 se conguran los aspectos relativos a la cmara
Mirror en relacin al plano de reexin. La llamada a enableReection hace que se
modique el Frustum de la cmara de modo que renderice empleando la reexin con
respecto del plano que se le pasa como argumento.
Finalmente, la llamada a enableCustomNearClipPlane permite recortar la geome-
tra situada debajo del plano pasado como argumento, de modo que nicamente la
geometra que est situada sobre el plano ser nalmente desplegada en el reejo,
evitando as errores de visualizacin.

Listado 15.5: Denicin del Material tipo Espejo


1 TexturePtr rttM_texture = TextureManager::getSingleton()
2 .createManual("RttMTex", ResourceGroupManager::
3 DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, 512, 512, 0, PF_R8G8B8,
4 TU_RENDERTARGET);
5
6 RenderTexture *rMtex= rttM_texture->getBuffer()->getRenderTarget();
7
8 Camera *camM = _sceneManager->createCamera("MirrorCamera");
9 Camera *mainCam = _sceneManager->getCamera("MainCamera");
10 camM->setPosition(mainCam->getPosition());
11 camM->setOrientation(mainCam->getOrientation());
12 camM->setAspectRatio(mainCam->getAspectRatio());
13
14 rMtex->addViewport(camM);
15 rMtex->getViewport(0)->setClearEveryFrame(true);
16 rMtex->getViewport(0)->setBackgroundColour(ColourValue::Black);
17 rMtex->getViewport(0)->setOverlaysEnabled(false);
18 rMtex->setAutoUpdated(true);
19
20 MaterialPtr mMPtr=MaterialManager::getSingleton().create("RttMMat",
21 Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
22 Technique* matMTechnique = mMPtr->createTechnique();
23 matMTechnique->createPass();
24 TextureUnitState *t = mMPtr->getTechnique(0)->getPass(0)->
createTextureUnitState("grid.jpg");
25 t = mMPtr->getTechnique(0)->
26 getPass(0)->createTextureUnitState("RttMTex");
27 t->setColourOperationEx(LBX_BLEND_MANUAL, LBS_TEXTURE,
28 LBS_CURRENT, ColourValue::White, ColourValue::White, 0.5);
29
30 t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
31 t->setProjectiveTexturing(true, camM);
32
33 // Creacion del plano del suelo...
34 Plane plane1(Vector3::UNIT_Y, 0);
35 MeshManager::getSingleton().createPlane("plane1",
36 ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane1,
37 200,200,1,1,true,1,10,10,Vector3::UNIT_Z);
38
39 SceneNode* node3 = _sceneManager->createSceneNode("ground");
40 Entity* grEnt = _sceneManager->createEntity("planeEnt", "plane1");
41
42 camM->enableReflection(plane1);
43 camM->enableCustomNearClipPlane(plane1);
44
45 grEnt->setMaterialName("RttMMat");
Captulo 16
Iluminacin
Carlos Gonzlez Morcillo

E
n el captulo anterior hemos estudiado algunas de las caractersticas relativas a
la denicin de materiales bsicos. Estas propiedades estn directamente rela-
cionadas con el modelo de iluminacin y la simulacin de las fuentes de luz
que realicemos. Este captulo introduce algunos conceptos generales sobre ilumina-
cin en videojuegos, as como tcnicas ampliamente utilizadas para la simulacin de
la iluminacin global.

16.1. Introduccin
Una pequea parte de los rayos de luz son visibles al ojo humano. Aquellos rayos
que estn denidos por una onda con longitud de onda entre 700 y 400nm. Variando
las longitudes de onda obtenemos diversos colores.
En grcos por computador es habitual emplear los denominados colores-luz, don-
de el Rojo, Verde y Azul son los colores primarios y el resto se obtienen de su combi-
nacin. En los colores-luz el color blanco se obtiene como la suma de los tres colores
bsicos.
Figura 16.1: Gracias a la proyec- El RGB es un modelo clsico de este tipo. En el mundo fsico real se trabaja
cin de sombras es posible cono- con colores-pigmento, donde el Cyan, el Magenta y el Amarillo forman los colores
cer la posicin relativa entre obje- primerarios. La combinacin de igual cantidad de los tres colores primarios obtiene el
tos. En la imagen superior no es po- color negro1 . As, el CMYK (Cyan Magenta Yellow Key) empleado por las impresoras
sible determinar si el modelo de Su- es un clsico modelo de este tipo. De un modo simplicado, podemos denir los pasos
zanne reposa sobre algn escaln o
ms relevantes para realizar la representacin de una escena sinttica:
est otando en el aire. La imagen
inferior, gracias al uso de sombras 1 En realidad se obtiene un tono parduzco, por lo que habitualmente es necesario incorporar el negro
elimina esa ambigedad visual. como color primario.

429
[430] CAPTULO 16. ILUMINACIN

1. La luz es emitida por las fuentes de luz de la escena (como el sol, una lmpara de
luz situada encima de la mesa, o un panel luminoso en el techo de la habitacin).
2. Los rayos de luz interactan con los objetos de la escena. Dependiendo de las
propiedades del material de estos objetos, parte de la luz ser absorbida y otra
parte reejada y propagada en diversas direcciones. Todos los rayos que no son
totalmente absorbidos continuarn rebotando en el entorno.
3. Finalmente algunos rayos de luz sern capturados por un sensor (como un ojo
humano, el sensor CCD (Charge-Coupled Device) de una cmara digital o una
pelcula fotogrca).

La luz puede ser modelada empleando diversas aproximaciones, centrndose en Simplica!!


las propiedades direccionales (rayos puramente geomtricos), como ondas electro-
magnticas o como partculas cunticas (fotones). Dependiendo del mtodo de repre- Como ya comentamos en el Cap-
tulo 15, este modelo de iluminacin
sentacin, suele emplearse un modelo u otro. Independientemente del tratamiento que es una simplicacin del modelo f-
demos a la luz, sta debe ser simulada como energa que viaja en el espacio. Las sicamente correcto que se resuel-
fuentes de luz sern emisores de esta energa. ve con mejores aproximaciones de
la ecuacin de Rendering de James
Directamente asociado al concepto de iluminacin encontramos las sombras. Gra- Kajiya.
cias a la proyeccin de sombras, podemos establecer relaciones espaciales entre los
objetos de la escena. Por ejemplo, en la Figura 16.1, gracias al uso de sombras pode-
mos saber la posicin exacta de la esfera relativa a la escalera.
A continuacin estudiaremos los principales tipos de luz que suelen emplearse en
videojuegos, as como los modelos de sombreado estticos y dinmicos ms utiliza-
dos.

16.2. Tipos de Fuentes de Luz


Las fuentes de luz pueden representarse de diversas formas, dependiendo de las
caractersticas que queramos simular en la etapa de rendering. Fuente
Puntual
Para especicar la cantidad de energa emitida por una fuente de luz, la radiome-
tra (ciencia que se encarga de medir la luz) dene la irradiancia como la cantidad de
fotones que pasan por una supercie por segundo.
En videojuegos suelen permitirse tres tipos de fuentes de luz directamente sopor-
tadas por el hardware de aceleracin grco:

Las fuentes puntuales (point lights) irradian energa en todas las direcciones
a partir de un punto que dene su posicin en el espacio. Este tipo de fuentes Fuente
permite variar su posicin pero no su direccin. En realidad, las fuentes de luz Direccional
puntuales no existen como tal en el mundo fsico (cualquier fuente de luz tiene
asociada un rea, por lo que para realizar simulaciones realistas de la ilumina-
cin tendremos que trabajar con fuentes de rea. Ogre dene este tipo de fuente
como LT_POINT.
Uno de los tipos de fuentes ms sencillo de simular son las denominadas fuentes
direccionales (directional lights), que pueden considerarse fuentes situadas a
una distancia muy grande, por lo que los rayos viajan en una nica direccin
Fuente Foco
en la escena (son paralelos entre s). El sol podra ser modelado mediante una
fuente de luz direccional. De este modo, la direccin viene determinada por un
Figura 16.2: Tipos de fuentes de
vector l (especicado en coordenadas universales). Este tipo de fuentes de luz luz utilizadas en aplicaciones in-
no tienen por tanto una posicin asociada (nicamente direccin). Este tipo de teractivas. Ogre soporta nicamente
fuente est descrito como LT_DIRECTIONAL en Ogre. estos tres tipos de fuentes bsicos.
16.3. Sombras Estticas Vs Dinmicas [431]

Finalmente los focos (spot lights) son en cierto modo similares a las fuentes
de luz puntuales, pero aadiendo una direccin de emisin. Los focos arrojan
luz en forma cnica o piramidal en una direccin especca. De este modo,
requieren un parmetro de direccin, adems de dos ngulos para denir los
conos de emisin interno y externo. Ogre las dene como LT_SPOTLIGHT.

Las fuentes de luz permiten especicar multitud de parmetros y propiedades, co-


mo el color difuso y especular. Una fuente de luz puede denir un color de iluminacin
difuso (como si el cristal de la bombilla estuviera tintado), y un color diferente para el
brillo especular. Ambas propiedades estn directamente relacionadas con el modo en
el que reejarn la luz los materiales.
En aplicaciones de sntesis de imagen realista suelen denirse adems fuentes de
luz de rea. Este tipo de fuentes simulan el comportamiento de la luz de un modo
ms realista, donde potencialmente cada punto de la supercie se comporta como un
emisor de luz. En la seccin 16.7 estudiaremos el modelo de Radiosidad que permite
trabajar con fuentes de luz de rea.

16.3. Sombras Estticas Vs Dinmicas


La gestin de sombras es un aspecto crtico para dotar de realismo a las escenas
sintticas. Sin embargo, el clculo de sombras trae asociado un coste computacio-
nal importante. De esta forma, en multitud de ocasiones se emplean tcnicas de pre-
clculo de la iluminacin y las sombras. Estos mapas de iluminacin permiten generar
sombras suaves sin coste computacional adicional.

C16
Ogre soporta dos tcnicas bsicas de sombreado dinmico: sombreado empleando
el Stencil Buffer y mediante Mapas de Texturas (ver Figura 16.4). Ambas aproxima-
ciones admiten la especicacin como Additive o Modulative.

En muchas ocasiones se implementan, empleando el Pipeline en GPU pro-


gramable, algoritmos de sombreado avanzados como aproximaciones al Am-
bient Occlusion (que veremos en la seccin 16.6 o Radiosidad Instantnea
(ver seccin 16.7).

a Las tcnicas de tipo Modulative nicamente oscurecen las zonas que quedan en
sombra, sin importar el nmero de fuentes de luz de la escena. Por su parte, si la
tcnica se especica de tipo Additive es necesario realizar el clculo por cada fuente
de luz de modo acumulativo, obteniendo un resultado ms preciso (pero ms costoso
computacionalmente).
Cada tcnica tiene sus ventajas e inconvenientes (como por ejemplo, el relativo a
la resolucin en el caso de los Mapas de Textura, como se muestra en la Figura 16.3).
b No es posible utilizar varias tcnicas a la vez, por lo que deberemos elegir la tcnica
que mejor se ajuste a las caractersticas de la escena que queremos representar. En
Figura 16.3: Comparativa entre trminos generales, los mtodos basados en mapas de textura suelen ser ms precisos,
sombras calculadas por a) Mapas pero requieren el uso de tarjetas aceleradoras ms potentes. Veamos las caractersticas
de Texturas y b) Stencil Buffer. Las generales de las tcnicas de sombreado para estudiar a continuacin las caractersticas
basadas en mapas de texturas son
claramente dependientes de la re-
particulares de cada mtodo.
solucin del mapa, por lo que de-
ben evitarse con reas de proyec-
cin muy extensas.
[432] CAPTULO 16. ILUMINACIN

nicamente podemos utilizar una tcnica de sombreado en la escena. Esta tc-


nica debe ser especicada preferiblemente antes de especicar los objetos que
formen parte de la escena.
Las propiedades del material determinan si el objeto arrojar o recibir som-
bra. Por defecto los objetos arrojan sombra (salvo los objetos con materiales
transparentes, que no arrojarn sombra).
Es posible denir fuentes de luz que no arrojen sombras.
En ambos casos es conveniente evitar que las sombras se proyecten de forma
extrema (por ejemplo, simulando un amanacer). Este tipo de situaciones hace
que la calidad de la sombra se degrade enormemente.
Dado su coste computacional, por defecto las sombras estn desactivadas en
Ogre.
Para que un material reciba o arroje sombras, el parmetro lighting del material
debe estar en on (por defecto).

A continuacin estudiaremos los detalles de ambos tipos de sombras soportados


en Ogre.

16.3.1. Sombras basadas en Stencil Buffer


Empleando esta tcnica de sombreado, la forma de la sombra que ser proyectada
se obtiene proyectando la silueta del objeto calculada desde la perspectiva de la fuente
de luz.
El Stencil Buffer es un buffer extra disponible en las GPUs modernas que per-
mite almacenar un byte por pxel. Habitualmente se emplea para recortar el rea de
renderizado de una escena. En el clculo de sombras, se utiliza en combinacin con
el ZBuffer para recortar la zona de sombra relativa al punto de vista. El proceso de
clculo puede resumirse en los siguientes pasos (ver Figura 16.5):

1. Para cada fuente de luz, obtener la lista de aristas de cada objeto que comparten
polgonos cuyo vector normal apunta hacia la fuente de luz y las que estn
opuestas a la misma. Estas aristas denen la silueta del objeto desde el punto
de vista de la fuente de luz.
2. Proyectar estas aristas de silueta desde la fuente de luz hacia la escena. Obtener
el volumen de sombra proyectado sobre los objetos de la escena.
3. Finalmente, utilizar la informacin de profundidad de la escena (desde el punto
de vista de la cmara virtual) para denir en el Stencil Buffer la zona que debe
recortarse de la sombra (aquella cuya profundidad desde la cmara sea mayor
que la denida en el ZBuffer). La Figura 16.5 muestra cmo la proyeccin del
volumen de sombra es recortado para aquellos puntos cuya profundidad es me-
nor que la relativa a la proyeccin del volumen de sombra.

Esta tcnica de generacin de sombras, a diferencia de las basadas en Mapas de


Textura, utiliza ciclos de la CPU para el renderizado (las operaciones relativas al clcu-
lo de aristas y proyeccin sobre la escena 3D). A continuacin se describen algunas
de las caractersticas fundamentales de las sombras basadas en Stencil Buffer.
16.3. Sombras Estticas Vs Dinmicas [433]

a b c

d e f

Figura 16.4: Utilizacin de diversos modos de clculo de sombras y materiales asociados. a) Sombras
mediante Mapas de Texturas y Material simple. b) Sombras por Stencil Buffer y Material simple. c) Som-
bras por Stencil Buffer y Material con Ambient Occlusion precalculado. d) Sombras mediante Mapas de

C16
Texturas y Material basado en textura de mrmol procedural precalculada. e) Sombras por Stencil Buffer
y Material basado en textura de mrmol procedural precalculada. f) Sombras por Stencil Buffer y Material
combinado de c) + e) (Ambient Occlusion y Textura de Mrmol precalculados).

Fuente
Foco

ZBuffer (Mapa Profundidad) Imagen Resultado

+
Volumen de
Cmara sombra
proyectada

Volumen de Stencil
sombra buffer
proyectada

Figura 16.5: Proceso de utilizacin del Stencil Buffer para el recorte de la proyeccin del volumen de
sombra.
[434] CAPTULO 16. ILUMINACIN

Fuente Fuente Cmara


de luz de luz

b
a
Mapa Profundidad
desde la fuente de luz Pb

Pa
Resultado final

Figura 16.7: Pasos en el clculo de sombras mediante mapas de textura.

Empleando el Stencil Buffer, las sombras de objetos transparentes emitirn som-


bras totalmente slidas. Es posible desactivar totalmente las sombras de este
tipo de objetos, pero empleando Stencil Buffer no es posible obtener sombras
semitransparentes.
Empleando esta tcnica no es posible obtener sombras con aristas suaves. En el
caso de necesitar este tipo de sombras ser necesario emplear la tcnica basada
de Mapas de Textura (ver Seccin 16.3.2).
Esta tcnica permite que el objeto reciba su propia sombra, como se muestra en
la Figura 16.6.b.
Es necesario que Ogre conozca el conjunto de aristas que denen el mode-
lo. Exportando desde Blender, la utilidad OgreXMLConverter realiza el clculo a
automticamente. Sin embargo, si implementamos nuestro propio cargador, de-
beremos llamar a buildEdgeList de la clase Mesh para que construya la lista.

16.3.2. Sombras basadas en Texturas


El clculo de las sombras basadas en texturas se basa en un simple principio:
si observamos una escena desde el punto de vista de la fuente de luz, cualquier punto
b
situado detrs de lo que ve la fuente de luz estar en sombra. Esta idea puede realizarse
en dos pasos principales empleando texturas calculadas directamente en la GPU.
Figura 16.6: En el clculo de som-
En el primer paso se construye el mapa de profundidad desde el punto de vista. bras mediante Mapas de Textura (en
Este mapa simplemente codica la distancia menor de los objetos de la escena a la a)), el objeto emisor de sombras no
fuente de luz (como se muestra en la parte izquierda de la Figura 16.7). recibe su propia sombra proyectada.
Empleando el Stencil Buffer (b)) es
El segundo paso utiliza esta informacin para construir la sombra. Se calcula la posible recibir la sombra proyecta-
distancia del objeto a la cmara y compara esta distancia con la codicada en el mapa da.
de profundidad. Si la distancia entre cada punto y la fuente de luz es mayor que la
almacenada en dicho mapa, el punto est en sombra (Figura 16.7 derecha). Z-Fighting
Empleando sombras basadas en texturas el objeto debe ser denido como receptor La distincin en grupos de emiso-
o emisor de sombras. Un objeto no puede ser emisor y receptor de sombras a la vez res y receptores de sombras se crea
(por lo que un emisor no puede recibir sus propias sombras). para evitar problemas de Z-ghting
(cuando dos o ms elementos tie-
A continuacin enumeraremos algunas de las caractersticas fundamentales de este nen asociada la misma profundidad
tipo de tcnica. en el ZBuffer y su representacin es
incorrecta).
16.4. Ejemplo de uso [435]

Este tipo de sombras permiten el manejo correcto de la transparencia. Adems,


las sombras pueden tener un color propio.
Se basan principalmente en el uso de la GPU, por lo que descargan en gran
medida la CPU. Directamente relacionado con esta caracterstica, las sombras
basadas en mapas de textura permiten componer otros efectos en la GPU (como
deformaciones empleando un Vertex Shader).
Esta tcnica no permite que el objeto reciba sus propias sombras.

16.4. Ejemplo de uso


El siguiente ejemplo de uso construye una aplicacin que permite elegir entre el
uso de texturas basadas en Stencil Buffer o en Mapas de Textura.

Listado 16.1: Fragmento de MyApp.cpp


1 void MyApp::createScene() {
2 _sceneManager->setShadowTechnique(SHADOWTYPE_STENCIL_MODULATIVE);

3 _sceneManager->setShadowColour(ColourValue(0.5, 0.5, 0.5));


4 _sceneManager->setAmbientLight(ColourValue(0.9, 0.9, 0.9));
5
6 _sceneManager->setShadowTextureCount(2);
7 _sceneManager->setShadowTextureSize(512);
8
9 Light* light = _sceneManager->createLight("Light1");

C16
10 light->setPosition(-5,12,2);
11 light->setType(Light::LT_SPOTLIGHT);
12 light->setDirection(Vector3(1,-1,0));
13 light->setSpotlightInnerAngle(Degree(25.0f));
14 light->setSpotlightOuterAngle(Degree(60.0f));
15 light->setSpotlightFalloff(0.0f);
16 light->setCastShadows(true);
17
18 Light* light2 = _sceneManager->createLight("Light2");
19 light2->setPosition(3,12,3);
20 light2->setDiffuseColour(0.2,0.2,0.2);
21 light2->setType(Light::LT_SPOTLIGHT);
22 light2->setDirection(Vector3(-0.3,-1,0));
23 light2->setSpotlightInnerAngle(Degree(25.0f));
24 light2->setSpotlightOuterAngle(Degree(60.0f));
25 light2->setSpotlightFalloff(5.0f);
26 light2->setCastShadows(true);
27
28 Entity* ent1 = _sceneManager->createEntity("Neptuno.mesh");
29 SceneNode* node1 = _sceneManager->createSceneNode("Neptuno");
30 ent1->setCastShadows(true);
31 node1->attachObject(ent1);
32 _sceneManager->getRootSceneNode()->addChild(node1);
33
34 // ... Creamos plano1 manualmente (codigo eliminado)
35
36 SceneNode* node2 = _sceneManager->createSceneNode("ground");
37 Entity* groundEnt = _sceneManager->createEntity("p", "plane1");
38 groundEnt->setMaterialName("Ground");
39 groundEnt->setCastShadows(false);
40 node2->attachObject(groundEnt);
41 _sceneManager->getRootSceneNode()->addChild(node2);
42 }
[436] CAPTULO 16. ILUMINACIN


En la lnea 2 se dene la tcnica de clculo de sombras que se utilizar por de-
se cambiar en tiempo de ejecucin empleando las
en el FrameListener
fecto, aunque
teclas 1 y 2 . Las lneas 2-3 denen propiedades generales de la escena, como el
color con el que se representarn las sombras y el color de la luz ambiental (que en

este ejemplo, debido a la denicin de los materiales, tendr especial importancia).
Las lneas 6-9 nicamente tienen relevancia en el caso del uso del mtodo de
clculo basado en mapas de textura. Si el mtodo utilizado es el de Stencil Buffer
simplemente sern ignoradas. La lnea 6 congura el nmero de texturas que se em-
plearn para calcular las sombras. Si se dispone de ms de una fuente de luz (como en

este ejemplo), habr que especicar el nmero de texturas a utilizar.
El tamao de la textura (cuadrada) se indica en la lnea 7 . A mayor tamao,
mejor resolucin en la sombra (pero mayor cantidad de memoria gastada). El tamao

por defecto es 512, y debe ser potencia de dos.
A continuacin en las lneas 9-26 se denen dos fuente de luz de tipo SPOTLIGHT.

Cada luz dene su posicin y rotacin.
En el caso de la segunda fuente luz se dene
de
adems un color difuso (lnea 20 ). El ngulo interno y externo 13-14 dene el cono
de iluminacin de la fuente. En ambas fuentes se ha activado la propiedad de que la
fuente de luz permita el clculo de sombras.

El mtodo de setCastShadows pertenece a la clase MovableObject. En el ca-


so de entidades, si se especica como cierto, el objeto arrojar sombras sobre
otros objetos. En caso contrario, funcionar como un receptor de sombras.
El mismo mtodo se aplica sobre luces (que, es un tipo especco de Mova-
bleObject). En el caso de fuentes de luz, el mismo mtodo sirve para especi-
car si esa fuente de luz se utilizar en el clculo de sombras.

Como hemos comentado en la seccin 16.3.2, si utilizamos mapas de textura para


calcular sombras, un objeto puede funcionar nicamente como receptor o como emisor
En este ejemplo, el plano se congura como receptor de sombras
de sombras. (en la
lnea 39 ), mientras que el objeto Neptuno funciona como emisor (lnea 30 ).

Mediante las teclas 7 ... 0 es posible cambiar el material asociado al objeto

principal de la escena. En el caso del ltimo material denido para el objeto (tecla
0 ), se compone en dos capas de textura el color base (calculado mediante una textura
procedural) y una capa de iluminacin basada en Ambient Occlusion. La denicin
del material compuesto se muestra en la Figura 16.8. Gracias a la separacin de la
iluminacin y del color en diferentes mapas, es posible cambiar la resolucin indi-
vidualmente de cada uno de ellos, o reutilizar el mapa de iluminacin en diferentes
modelos que compartan el mismo mapa de iluminacin pero tengan diferente mapa de
color.
Multitud de juegos utilizan el preclculo de la iluminacin. Quake II y III utili-
zaron modelos de Radiosidad para crear mapas de iluminacin para simular de una
forma mucho ms realista el comportamiento fsico de la luz en la escena. Aunque
actualmente poco a poco se implantan las tcnicas de iluminacin dinmicas a nivel
de pxel, los mapas de iluminacin siguen siendo una opcin ampliamente utilizada.
Figura 16.8: Denicin del mate-
rial multicapa para componer una
textura con iluminacin de tipo Ab-
mientOcclusion con la textura de
color. Ambas texturas emplean un
despliegue automtico (tipo Light-
Map en Blender).
16.5. Mapas de Iluminacin [437]

Geometra base Mapa de color Mapa de Resultado Final


Iluminacin
(Ambient Occlusion)

Figura 16.9: Ejemplo de uso de un mapa de iluminacin para denir sombras suaves en un objeto. El mapa
de color y el mapa de iluminacin son texturas independiente que se combinan (multiplicando su valor) en
la etapa nal.

16.5. Mapas de Iluminacin


Como hemos visto anteriormente, los modelos de iluminacin local calculan en
cada vrtice del modelo la interaccin con la luz. El color nal de cada punto del mo-
delo se calcula mediante tcnicas de interpolacin. Las sombras obtenidas empleando
estos mtodos no son precisas (por ejemplo, no se pueden calcular sombras difusas).
En los ltimos aos las tarjetas grcas permiten el clculo de mtodos de ilu-

C16
minacin por pxel (per pixel lighting), de modo que por cada pxel de la escena se
calcula la contribucin real de la iluminacin. El clculo preciso de las sombras es
posible, aunque a da de hoy todava es muy costoso para videojuegos.
Los mapas de luz permiten precalcular la iluminacin por pxel de forma estti-
ca. Esta iluminacin precalculada puede ser combinada sin ningn problema con los
mtodos de iluminacin dinmicos estudiados hasta el momento. La calidad nal de
la iluminacin es nicamente dependiente del tiempo invertido en el preclculo de la
misma y la resolucin de las texturas.

En trminos generales, el pxel de una textura se denomina texel (de Texture


Element). De forma anloga, un pxel de un mapa de iluminacin se denomina
lumel (Lumination Element).

De este modo, para cada cara poligonal del modelo se denen una o varias capas de
textura. La capa del mapa de iluminacin es nalmente multiplicada con el resultado
de las capas anteriores (como se puede ver en la Figura 16.9).
A diferencia de las texturas de color donde cada vrtice del modelo puede com-
partir las mismas coordenadas UV con otros vrtices, en mapas de iluminacin cada
vrtice de cada cara debe tener una coordenada nica. Los mapas de iluminacin se
cargan de la misma forma que el resto de texturas de la escena. En la Figura 16.8
hemos estudiado un modo sencillo de composicin de estos mapas de iluminacin,
[438] CAPTULO 16. ILUMINACIN

aunque pueden denirse otros mtodos que utilicen varias pasadas y realicen otros
modos de composicin. A continuacin estudiaremos dos tcnicas ampliamente utili-
zadas en el preclculo de la iluminacin empleando mapas de iluminacin: Ambient
Occlusion y Radiosidad.

Aunque nos centremos en los mtodos de Ambient Occlusion y Radiosidad,


Blender permite precalcular (Render Baking) cualquier conguracin de es-
cena. De este modo, se puede precalcular cualquier conguracin de luces y
materiales de la escena.

Como hemos comentado antes, cualquiera de los mtodos de clculo de mapas de


iluminacin requiere que cada cara poligional del modelo tenga unas coordenadas UV
nicas en el modelo. Como estudiamos en la seccin 15.5, empleando el despliegue
de tipo Lightmap Pack conseguimos que Blender despliegue el modelo de esta forma,
asignando el rea de la imagen de forma proporcional al tamao de cada cara del
modelo. La Figura 16.10 muestra el despliegue realizado para el modelo de Neptuno
tras aplicar Ambient Occlusion. Figura 16.10: Despliegue del mo-
Una vez realizado el despliegue y con una imagen asociada al despiegue UV (pue- delo de Neptuno empleando Light-
map Pack. Cada cara poligonal tie-
de crearse una imagen nueva vaca de color slido desde la cabecera de la ventana UV ne asociado un espacio propio en la
Image Editor en el men Image/ New Image), el preclculo de la iluminacin se textura.
realiza en la pestaa Bake del grupo de botones de Render (ver Figura 16.11).
A continuacin se estudiarn las opciones ms relevantes en el mbito del pre-
clculo de la iluminacin.
Mediante la lista desplegable Bake Mode, se elige el tipo de preclculo que va a
realizarse. Con Full Render se realizar el clculo de todas las propiedades del ma-
terial, texturas e iluminacin sobre la textura (sin tener en cuenta el brillo especular
que es dependiente del punto de vista del observador). Si se elige la opcin Ambient
Occlusion (como se muestra en la gura 16.11), nicamente se tendr en cuenta la in-
formacin de AO ignorando el resto de fuentes de luz de la escena (ver seccin 16.6).
Mediante la opcin Textures se asignan los colores base de los materiales y texturas
(sin tener en cuenta el sombreado). Figura 16.11: Opciones de la pes-
El checkbox Clear sirve para borrar la textura antes de realizar el baking. Pue- taa Bake del grupo de botones de
de ser interesante desactivarlo si queremos aplicar una pasada de AO a una textura Render.
previamente asignada manualmente.
El botn Selected to Active permite asignar la informacin de otro objeto. Un uso
tpico de esta herramienta es disponer de un modelo en alta resolucin que dene una
geometra muy detallada, y mapear su informacin en otro modelo de baja resolucin.
En esta seccin utilizaremos esta funcionalidad para asociar la informacin de una
malla de radiosidad a la malla en baja resolucin de la escena.

Finalmente, cuando se han elegido los parmetros en la pestaa, pulsando el botn
Bake se inicia el proceso de clculo. En la ventana de UV Mapping deber verse la
actualizacin en tiempo real de la textura mapeada al modelo.
16.6. Ambient Occlusion [439]

16.6. Ambient Occlusion


El empleo del trmino de luz ambiente viene aplicndose desde el inicio de los
grcos por computador como un mtodo muy rpido de simular la contribucin de
luz ambiental que proviene de todas las direcciones. La tcnica de Ambient Occlusion
es un caso particular del uso de pruebas de oclusin en entornos con iluminacin local
para determinar los efectos difusos de iluminacin. Estas tcnicas fueron introducidas
inicialmente por Zhurov como alternativa a las tcnicas de radiosidad para aplicacio-
nes interactivas (videojuegos), por su bajo coste computacional.
En el esquema de la gura 16.12 podemos ver en qu se basan estas tcnicas.
Desde cada punto P de interseccin con cada supercie (obtenido mediante trazado
de rayos), calculamos el valor de ocultacin de ese punto que ser proporcional al
nmero de rayos que alcanzan el cielo (los que no intersectan con ningn objeto
a
dada una distancia mxima de interseccin). En el caso de la gura sern 4/7 de los
Punto rayos lanzados.
de Vista
Objeto Podemos denir la ocultacin de un punto de una supercie como:
1
W (P ) = (d(P, ))cosd (16.1)

Obteniendo un valor de ocultacin W (P ) entre 0 y 1, siendo d(P, ) la distancia entre


P y la primera interseccin con algn objeto en la direccin de . (d(P, )) es una
funcin con valores entre 0 y 1 que nos indica la magnitud de iluminacin ambiental
que viene en la direccin de , y es el ngulo formado entre la normal en el punto
P y la direccin de .

C16
Estas tcnicas de ocultacin (obscurances) se desacoplan totalmente de la fase de
iluminacin local, teniendo lugar en una segunda fase de iluminacin secundaria difu-
sa. Se emplea un valor de distancia para limitar la ocultacin nicamente a polgonos
b c cercanos a la zona a sombrear mediante una funcin. Si la funcin toma valor de ocul-
tacin igual a cero en aquellos puntos que no superan un umbral y un valor de uno si
Figura 16.12: Descripcin esque- estn por encima del umbral, la tcnica de ocultacin se denomina Ambient Occlusion.
mtica del clculo de Ambient Oc- Existen multitud de funciones exponenciales que se utilizan para lograr estos efectos.
clusion. a) Esquema de clculo del
valor de ocultacin W (P ). b) Ren- La principal ventaja de esta tcnica es que es bastante ms rpida que las tcnicas
der obtenido con iluminacin lobal que realizan un clculo correcto de la iluminacin indirecta. Adems, debido a la
(dos fuentes puntuales). c) La mis- sencillez de los clculos, pueden realizarse aproximaciones muy rpidas empleando
ma conguracin que en b) pero la GPU, pudiendo utilizarse en aplicaciones interactivas. El principal inconveniente
con Ambient Occlusion de 10 mue-
es que no es un mtodo de iluminacin global y no puede simular efectos complejos
tras por pxel.
como casticas o contribuciones de luz entre supercies con reexin difusa.
Para activar el uso de Ambient Occlusion (AO) en Blender, en el grupo de botones
del mundo , en la pestaa Ambient Occlusion activamos el checkbox (ver Figura
16.13). La pestaa Gather permite elegir el tipo de recoleccin de muestras entre
trazado de rayos Raytrace o Aproximada Approximate (ver Figura 16.13).
El clculo de AO mediante Trazado de Rayos ofrece resultados ms precisos a
costa de un tiempo de render mucho mayor. El efecto del ruido blanco debido al n-
mero de rayos por pxel puede disminuirse a costa de aumentar el tiempo de cmputo
aumentando el nmero de muestras (Samples).

Figura 16.13: Pestaa Amb Occ en


Blender.
[440] CAPTULO 16. ILUMINACIN

El mtodo AO Aproximado realiza una rpida aproximacin que, en muchos ca-


sos, puede ser suciente. No sufre de ruido de muestreo, por lo que es buena opcin
para ser utilizada en multitud de ocasiones. El parmetro Error dene la calidad de las
sombras calculadas (valores menores implican mejores resultados con mayor tiempo
de cmputo). El checkbox Pixel Cache si est activo hace que el valor de sombreado
se interpole entre pxeles vecinos, haciendo que el clculo sea an ms rpido (aunque
menos exacto). A continuacin estudiaremos algunas opciones relevantes del mtodo:

Falloff: Esta opcin controla el tamao de las sombras calculadas por AO. Si
est activa, aparece un nuevo control Strength que permite variar el factor de
atenuacin. Con valores mayores de Strength, la sombra aparece ms enfocada
(es ms pequea).
Add (Por defecto): El punto recibe luz segn los rayos que no se han chocado
con ningn objeto. La escena esta ms luminosa que la original sin AO.
Multiply: El punto recibe sombra segn los rayos que han chocado con algn
objeto. La escena es ms oscura que la original sin AO.

16.7. Radiosidad
En esta tcnica se calcula el intercambio de luz entre supercies. Esto se consigue
subdividiendo el modelo en pequeas unidades denominadas parches, que sern la
base de la distribucin de luz nal. Inicialmente los modelos de radiosidad calculaban
las interacciones de luz entre supercies difusas (aquellas que reejan la luz igual en
todas las direcciones), aunque existen modelos ms avanzados que tienen en cuenta
modelos de reexin ms complejos.
El modelo bsico de radiosidad calcula una solucin independiente del punto de
vista. Sin embargo, el clculo de la solucin es muy costoso en tiempo y en espacio
de almacenamiento. No obstante, cuando la iluminacin ha sido calculada, puede uti-
lizarse para renderizar la escena desde diferentes ngulos, lo que hace que este tipo
de soluciones se utilicen en visitas interactivas y videojuegos en primera persona ac-
tuales. En el ejemplo de la gura 16.15, en el techo de la habitacin se encuentran las
caras poligonales con propiedades de emisin de luz. Tras aplicar el proceso del clcu-
lo de radiosidad obtenemos la malla de radiosidad que contiene informacin sobre la
distribucin de iluminacin entre supercies difusas.
En el modelo de radiosidad, cada supercie tiene asociados dos valores: la intensi-
dad luminosa que recibe, y la cantidad de energa que emite (energa radiante). En este
algoritmo se calcula la interaccin de energa desde cada supercie hacia el resto. Si
Figura 16.14: El modelo de Radio-
tenemos n supercies, la complejidad del algoritmo ser O(n2 ). El valor matemtico sidad permite calcular el intercam-
que calcula la relacin geomtrica entre supercies se denomina Factor de Forma, y bio de luz entre supercies difusas,
se dene como: mostrando un resultado de render
correcto en el problema planteado
cosi cosj en la histrica Cornell Box.
Fij = Hij dAj (16.2)
r2
Siendo Fij el factor de forma de la supercie i a la supercie j, en el numerador de
la fraccin denimos el ngulo que forman las normales de las supercies, r2 mide
la distancia entre las supercies, Hij es el parmetro de visibilidad, que valdr uno si
la superce j es totalmente visible desde i, cero si no es visible y un valor entre uno
y cero segn el nivel de oclusin. Finalmente, dAj indica el rea de la superce j (no
tendremos el mismo resultado con una pequea supercie emisora de luz sobre una
superce grande que al contrario). Este factor de forma se suele calcular empleando
un hemi-cubo.
16.7. Radiosidad [441]

Figura 16.15: Ejemplo de aplicacin del mtodo de radiosidad sobre una escena simple. En el techo de
la habitacin se ha denido una fuente de luz. a) Malla original. b) Malla de radiosidad. c) Resultado del
proceso de renderizado.

C16
Ser necesario calcular n2 factores de forma (no cumple la propiedad conmutativa)
debido a que se tiene en cuenta la relacin de rea entre supercies. La matriz que
contiene los factores de forma relacionando todas las supercies se denomina Matriz
de Radiosidad. Cada elemento de esta matriz contiene un factor de forma para la
interaccin desde la supercie indexada por la columna hacia la supercie indexada
por la la (ver gura 16.16).

Para cada iteracin


seleccionar un parche i
calcular Fij para todas las superficies j
para cada superficie j hacer:
actualizar la radiosidad de la superficie j
actualizar la emisin de la superficie j
emision(i) = 0

En 1988, Cohen introdujo una variante de clculo basada en el renamiento pro-


gresivo que permite que la solucin de radiosidad encontrada en cada iteracin del
algoritmo sea mostrada al usuario. Este mtodo progresivo es un mtodo incremental
que requiere menos tiempo de cmputo y de almacenamiento; en cada iteracin se
calcula los factores de forma entre una supercie y el resto (en el artculo original se
requera el clculo de n2 factores de forma).
Este mtodo de renamiento progresivo nalmente obtiene la misma solucin que
el original, proporcionando resultados intermedios que van siendo renados.
Renamiento Progresivo En general, el clculo de la radiosidad es eciente para el clculo de distribuciones
Blender implementa el mtodo de de luz en modelos simples con materiales difusos, pero resulta muy costoso para mo-
Radiosidad basado en renamiento delos complejos (debido a que se calculan los valores de energa para cada parche del
progresivo modelo) o con materiales no difusos. Adems, la solucin del algoritmo se muestra
[442] CAPTULO 16. ILUMINACIN

como una nueva malla poligonal que tiende a desenfocar los lmites de las sombras.
Como en videojuegos suelen emplearse modelos en baja poligonalizacin, y es po-
sible mapear esta iluminacin precalculada en texturas, el modelo de Radiosidad se
ha empleado en diveros ttulos de gran xito comercial. Actualmente incluso existen
motores grcos que calculan soluciones de radiosidad en tiempo real.

2
1 4

Figura 16.16: Esquema de la matriz de radiosidad. En cada posicin de la matriz se calcula el Factor de
Forma. La diagonal principal de la matriz no se calcula.

Veamos a continuacin un ejemplo de utilizacin de Radiosidad en Blender. Como


hemos indicado anteriormente, el modelo de Radiosidad calcula la interaccin de ilu-
minacin entre supercies. As, la iluminacin de la escena vendr dada por las reas
de luz de las supercies superiores (planos) de la habitacin. De esta forma, necesi-
taremos crear planos a los que asignaremos un material que emita luz. La escena de
ejemplo (ver Figura 16.17) cuenta con planos con estas propiedades denidas.

Necesario Blender 2.49b. Aunque la radiosidad es un mtodo muy empleado Figura 16.17: Escena de ejemplo
en videojuegos, fue eliminado de la implementacin de Blender a partir de la para el clculo de la Radiosidad.
versin 2.5. Probablemente vuelva a incorporarse en futuras versiones, pero
de momento es necesario utilizar versiones anteriores del programa. La ltima
versin que la incorpor (Blender 2.49b) puede ser descargada de la pgina
ocial http://download.blender.org/release/.

Es muy importante la direccin del vector normal ya que Blender calcular la


interaccin de la luz en ese sentido. Por tanto, tendremos que asegurarnos que el vector
normal de cada foco apunta hacia el suelo, tal y como muestra la Figura 16.18.
Para comprobar que es as, seleccionamos cada emisor de luz, en modo de edicin de
vrtices activamos el botn Draw Normals de la pestaa Mesh Tools More. Podemos
ajustar el tamao de representacin del vector normal en Nsize. En caso de que la
normal est invertida, podemos ajustarla pulsando W Flip Normals.
Figura 16.18: Focos del techo con
el vector normal correctamente de-
nido.
16.7. Radiosidad [443]

Figura 16.19: Opciones generales de Radiosidad (Paneles Radio Render, Radio Tool y Calculation.

Los elementos que emiten luz tendrn el campo Emit del material con un valor
mayor que 0. Los emisores de este ejemplo tienen un valor de emisin de 0.4.
Accedemos al men de radiosidad
dentro de los botones de sombreado .
Seleccionamos todas las mallas que forman nuestra escena A y pinchamos en el botn

Collect Meshes (ver Figura 16.19). La escena aparecer con colores slidos.
Hecho esto, pinchamos en Go . De esta forma comienza el proceso de clculo de
la
solucin de radiosidad. Podemos parar el proceso en cualquier momento pulsando
Escape , quedndonos con la aproximacin que se ha conseguido hasta ese instante.
Si no estamos satisfechos con la solucin de Radiosidad calculada, podemos eli-
minarla pinchando en Free Radio Data.

C16
En la Figura 16.19 se muestran algunas opciones relativas al clculo de la solucin
de radiosidad. El tamao de los Parches (PaMax - PaMin) determina el detalle nal
(cuanto ms pequeo sea, ms detallado ser el resultado nal), pero incrementamos el
tiempo de clculo. De forma similar ocurre con el tamao de los Elementos2 (ElMax
- ElMin). En Max Iterations indicamos el nmero de pasadas que Blender har en
el bucle de Radiosidad. Un valor 0 indica que haga las que estime necesarias para
minimizar el error (lo que es conveniente si queremos generar la malla de radiosidad
nal). MaxEl indica el nmero mximo de elementos para la escena. Hemires es el
tamao del hemicubo para el clculo del factor de forma.

Una vez terminado el proceso de clculo (podemos pararlo cuando la calidad sea
aceptable con Esc ), podemos aadir la nueva malla calculada reemplazando las crea-
das anteriormente (Replace Meshes) o aadirla como nueva a la escena (Add new
Meshes). Elegiremos esta segunda opcin, para aplicar posteriormente el resultado a
informacin
la malla en baja poligonalizacin. Antes de deseleccionar la malla con la
de radiosidad calculada, la moveremos a otra capa (mediante la tecla M ) para tener
los objetos mejor organizados. Hecho esto, podemos liberar la memoria ocupada por
estos datos pinchando en Free Radio Data.
El resultado de la malla de radiosidad se muestra en la Figura 16.20. Como se pue-
de comprobar, es una malla extremadamente densa, que no es directamente utilizable
en aplicaciones de tiempo real. Vamos a utilizar el Baking de Blender para utilizar esta
informacin de texturas sobre la escena original en baja poligonalizacin.

Por simplicidad, uniremos todos dela escena original en una nica
los objetos
malla (seleccionndolos todos A y pulsando Control J Join Selected Meshes). A con-
tinuacin, desplegaremos el objeto empleando el modo de despliegue de Light Map, y
Figura 16.20: Resultado de la ma- crearemos una nueva imagen que recibir el resultado del render Baking.
lla de radiosidad.
2 En Blender, cada Parche est formado por un conjunto de Elementos, que describen supercies de

intercambio de mayor nivel de detalle.


[444] CAPTULO 16. ILUMINACIN

Figura 16.21: Resultado de aplicacin del mapa de radiosidad al modelo en baja resolucin.


Ahora seleccionamos primero la malla de radiosidad, y despus con Shift pulsado
to Active
seleccionamos la malla en baja resolucin. Pinchamos en el botn Selected
de la pestaa Bake, activamos Full Render y pinchamos en el botn Bake . Con esto
debemos haber asignado el mapa de iluminacin de la malla de radiosidad al objeto
en baja poligonalizacin (ver Figura 16.21).
Exportacin y Uso de Datos de
Captulo 17
Intercambio
Carlos Gonzlez Morcillo

H
asta ahora hemos empleado exportadores genricos de contenido de anima-
cin, mallas poligonales y denicin de texturas. Estas herramientas facilitan
enormemente la tarea de construccin de contenido genrico para nuestros
videojuegos. En este captulo veremos cmo crear nuestros propios scripts de expor-
tacin y su posterior utilizacin en una sencilla demo de ejemplo.

17.1. Introduccin
En captulos anteriores hemos utilizado exportadores genricos de contenido para
su posterior importacin. En multitud de ocasiones en necesario desarrollar herra-
mientas de exportacin de elementos desde las herramientas de diseo 3D a formatos
de intercambio.
La comunidad de Ogre ha desarrollado algunos exportadores de geometra y ani-
maciones para las principales suites de animacin y modelado 3D. Sin embargo, estos
elementos almacenan su posicin relativa a su sistema de coordenadas, y no tienen en
cuenta otros elementos (como cmaras, fuentes de luz, etc...).
En esta seccin utilizaremos las clases denidas en el Captulo 6 del Mdulo 1
para desarrollar un potencial juego llamado NoEscapeDemo. Estas clases importan
contenido denido en formato XML, que fue descrito en dicho captulo.
A continuacin enumeraremos las caractersticas esenciales que debe soportar el
Figura 17.1: El Wumpus ser el exportador a desarrollar:
personaje principal del videojuego
demostrador de este captulo NoEs-
cape Demo.

445
[446] CAPTULO 17. EXPORTACIN Y USO DE DATOS DE INTERCAMBIO

Mltiples nodos. El mapa del escenario (ver Figura 17.4) describe un grafo.
Utilizaremos un objeto de tipo malla poligonal para modelarlo en Blender. Cada
vrtice de la malla representar un nodo del grafo. Los nodos del grafo podrn
ser de tres tipos: Nodo productor de Wumpus (tipo spawn), Nodo destructor de
Wumpus (tipo drain) o un Nodo genrico del grafo (que servir para modelar
las intersecciones donde el Wumpus puede cambiar de direccin).
Mltiples aristas. Cada nodo del grafo estar conectado por uno o ms nodos,
deniendo mltiples aristas. Cada arista conectar una pareja de nodos. En este
grafo, las aristas no tienen asociada ninguna direccin.
Mltiples cmaras animadas. El exportador almacenar igualmente las ani-
maciones denidas en las cmaras de la escena. En el caso de querer almacenar
cmaras estticas, se exportar nicamente un fotograma de la animacin. Para
cada frame de la animacin de cada cmara, el exportador almacenar la posi-
cin y rotacin (mediante un cuaternio) de la cmara en el XML.

Existen algunas especicaciones en XML genricas para la denicin de


escenas (como el formato .scene) en Ogre, que est soportado por unas
clases adicionales disponibles en el Wiki (http://www.ogre3d.org/
tikiwiki/DotScene) del framework. Aunque este formato dene gran
cantidad de elementos de una escena, resulta crtico conocer los mecanismos
de exportacin para desarrollar los scripts especcos que necesitemos en ca-
da proyecto.

El mapa del escenario y los personajes principales se exportarn atendiendo a las


indicaciones estudiadas en el Captulo 11 (ver Figura 17.2). La exportacin del XML
se realizar empleando el script descrito a continuacin. Blender dene una API de
programacin muy completa en Python.
Python es un lenguaje ampliamente utilizado por su facilidad, versatilidad y po-
tencia 1 . Python es un lenguaje de muy alto nivel, interpretado (compilado a un cdigo
intermedio), orientado a objetos y libre bajo licencia GPL. Aunque en esta seccin nos
centraremos en su uso directo como lenguaje de acceso a la API de Blender, en el m-
dulo 3 estudiaremos cmo utilizar Python como lenguaje de script dentro de nuestra
apliacin en C++.

Figura 17.2: Como se estudi en el


Quin usa Python? Existen multitud de ttulos comerciales y videojuegos Captulo 11, es necesario denir el
de xito que han utilizado Python como lenguaje para su desarrollo. Por ejem- centro de los objetos especicando
plo, Civilization 4 utiliza python para la gestin de mltiples tareas, Vampire: adecuadamente su centro. En el ca-
The Masquerade lo emplea para la descripcin de Mods, o Battleeld 2 (de so del Wumpus, el centro se dene
Digital Illusions) para la denicin de todos los complementos del juego. en el centro de la base, escalndo-
lo adems al tamao existente entre
las paredes del escenario.

La versin de Blender 2.65 utiliza Python 3.3, por lo que ser necesaria su insta-
lacin para ejecutar los scripts desarrollados. En una ventana de tipo Text Editor
bastar con situar el puntero del ratn
podemos editar los scripts. Para su ejecucin,
dentro de la ventana de texto y pulsar Alt P .
1 Resulta especialmente interesante la comparativa emprica de Python con otros len-

guajes de programacin disponible en http://www.ipd.uka.de/~{}prechelt/


\discretionary{-}{}{}Biblio/jccpprtTR.pdf
17.1. Introduccin [447]

Aunque la versin 2.65 de Blender permite aadir nuevas propiedades a los obje-
tos de la escena, por mantener la compatibilidad con versiones anteriores lo haremos
codicndolo como parte del nombre. En la pestaa Object, es posible especicar el
nombre del objeto, como se muestra en la Figura 17.3. En nuestro ejemplo, se ha
utilizado el siguiente convenio de nombrado:

Cmaras. Las cmaras codican en el nombre (separadas por guin bajo), co-
mo primer parmetro el identicador de la misma (un ndice nico), y como
segundo parmetro el nmero de frames asociado a su animacin. De este mo-
do, la cmara llamada GameCamera_1_350 indica que es la primera cmara
Figura 17.3: Especicacin del
(que ser la utilizada en el estado de Juego), y que tiene una animacin denida
nombre del objeto en la pestaa Ob- de 350 frames.
ject.
Empty. Como no es posible asignar tipos a los vrtices de la malla poligonal
con la que se denir el grafo, se han aadido objetos de tipo Empty que ser-
virn para asociar los generadores (spawn) y destructores (drain) de Wumpus.
Estos objetos Empty tendrn como nombre el literal spawn o drain y a con-
tinuacin un nmero del 1 al 9.

Acontinuacin se muestra el cdigo fuente del script de exportacin. Las lneas


denen tres constantes que
pueden ser modicadas en su ejecucin: el nom-
bre del chero XML que se exportar, 9 el nombre del objeto donde sedene el grafo
8-10 8

(como hemos indicado anteriormente ser una malla poligonal), y 10 un valor que
utilizaremos como distancia mxima de separacin entre un vrtice del grafo y cada
Empty de la escena (para considerar que estn asociados).

Listado 17.1: Script de exportacin de NoEscapeDemo


1 # Exportador NoEscape 1.1 (Adaptado a Blender 2.65)

C17
2
3 import bpy, os, sys
4 import mathutils
5 from math import *
6 from bpy import *
7
8 FILENAME = "output.xml" # Archivo XML de salida
9 GRAPHNAME = "Graph" # Nombre del objeto Mesh del grafo
10 EPSILON = 0.01 # Valor de distancia Epsilon
11
12 # --- isclose --------------------------------------------------
13 # Decide si un empty coincide con un vertice del grafo
14 # --------------------------------------------------------------
15 def isclose(empty, coord):
16 xo, yo, zo = coord
17 xd, yd, zd = empty.location
18 v = mathutils.Vector((xo-xd, yo-yd, zo-zd))
19 if (v.length < EPSILON):
20 return True
21 else:
22 return False
23
24 # --- gettype --------------------------------------------------
25 # Devuelve una cadena con el tipo del nodo del grafo
26 # --------------------------------------------------------------
27 def gettype (dv, key):
28 obs = [ob for ob in bpy.data.objects if ob.type == EMPTY]
29 for empty in obs:
30 empName = empty.name
31 if ((empName.find("spawn") != -1) or
32 (empName.find("drain") != -1)):
33 if (isclose(empty, dv[key])):
34 return type ="+ empName[:-1] +"
35 return type=""
[448] CAPTULO 17. EXPORTACIN Y USO DE DATOS DE INTERCAMBIO

36
37 ID1 = *2 # Identadores para el xml
38 ID2 = *4 # Solo con proposito de obtener un xml "bonito"
39 ID3 = *6
40 ID4 = *8
41
42 graph = bpy.data.objects[GRAPHNAME]
43
44 dv = {} # Diccionario de vertices
45 for vertex in graph.data.vertices:
46 dv[vertex.index+1] = vertex.co
47
48 de = {} # Diccionario de aristas
49 for edge in graph.data.edges: # Diccionario de aristas
50 de[edge.index+1] = (edge.vertices[0], edge.vertices[1])
51
52 file = open(FILENAME, "w")
53 std=sys.stdout
54 sys.stdout=file
55
56 print ("<?xml version=1.0 encoding=UTF-8?>\n")
57 print ("<data>\n")
58
59 # --------- Exportacion del grafo ---------------------
60 print ("<graph>")
61 for key in dv.keys():
62 print (ID1 + <vertex index=" + str(key) + " +
63 gettype(dv,key) +>)
64 x,y,z = dv[key]
65 print (ID2 + <x> %f</x> <y> %f</y> <z> %f</z> % (x,y,z))
66 print (ID1 + </vertex>)
67 for key in de.keys():
68 print (ID1 + <edge>)
69 v1,v2 = de[key]
70 print (ID2 + <vertex> %i</vertex> <vertex> %i</vertex>
71 % (v1,v2))
72 print (ID1 + </edge>)
73 print ("</graph>\n")
74
75 # --------- Exportacion de la camara -------------------
76 obs = [ob for ob in bpy.data.objects if ob.type == CAMERA]
77 for camera in obs:
78 camId = camera.name
79 camName = camId.split("_")[0]
80 camIndex = int(camId.split("_")[1])
81 camFrames = int (camId.split("_")[2])
82 print (<camera index=" %i" fps=" %i"> %
83 (camIndex, bpy.data.scenes[Scene].render.fps))
84 print (ID1 + <path>)
85 for i in range (camFrames):
86 cFrame = bpy.data.scenes[Scene].frame_current
87 bpy.data.scenes[Scene].frame_set(cFrame+1)
88 x,y,z = camera.matrix_world.translation
89 qx,qy,qz,qw = camera.matrix_world.to_quaternion()
90 print (ID2 + <frame index=" %i"> % (i+1))
91 print (ID3 + <position>)
92 print (ID4 + <x> %f</x> <y> %f</y> <z> %f</z> % (x,y,z))
93 print (ID3 + </position>)
94 print (ID3 + <rotation>)
95 print (ID4 + <x> %f</x> <y> %f</y> <z> %f</z> <w> %f</w> %
96 (qx,qy,qz,qw))
97 print (ID3 + </rotation>)
98 print (ID2 + </frame>)
99 print (ID1 + </path>)
100 print (</camera>)
101
102 print ("</data>")
103
104 file.close()
105 sys.stdout = std
17.1. Introduccin [449]

El proceso de exportacin del XML se realiza directamente al vuelo, sin emplear


las facilidades (como el DOM o SAX de Python). La sencillez del XML descrito, as
como la independencia de una instalacin completa de Python hace que sta sea la
alternativa implementada en otros exportadores (como el exportador de Ogre, o el de

Collada).
En las lneas 44,48 se crean dos diccionarios donde se guardarn las listas re-
lativas a las coordenadas de los vrtices del grafo y las aristas. Blender numera los
vrtices y aristas asociados a una malla poligonal comenzando en 0. En la descripcin
del XML indicamos por convenio que todos los ndices comenzaran en 1, por lo que
es necesario sumar 1 a los ndices que nos devuelve Blender.

La malla asociada al objeto de nombre indicado
en GRAPHNAME se obtiene me-
diante la llamada a bpy.data.objects (lnea 42 ). La lnea 56 simplemente imprime la

cabecera del XML.
En el bloque descrito por 59-73 se generan las entradas relativas a la denicin del
grafo, empleando los diccionarios de vrtices dv y de aristas de creados anteriormente.
En la denicin del XML (ver Captulo 6 del Mdulo 1), un nodo puede tener
nodo utilizamos una funcin auxiliar
asociado un tipo. Para obtener el tipo de cada
llamada gettype (denida en las lneas 27-35 ).
Muy Cerca! En gettype se calcula la cadena correspondiente al tipo del nodo del grafo. Como
hemos indicado anteriormente, el tipo del nodo se calcula segn la distancia a los
Se emplea un valor Epsilon para objetos Empty de la escena. Si el vrtice est muy cerca de un Empty con subcadena
del grafo. En otro
calcular si un Empty ocupa la po-
drain o spawn en su nombre, le asignaremos ese tipo al vrtice
caso, el nodo del grafo ser genrico. De este modo, en la lnea 28 se obtiene en obs
sicin de un nodo del grafo porque
las comparaciones de igualdad con
valores de punto otante pueden dar la lista de todos los objetos de tipo Empty de la escena. Para cada uno de estos objetos,
cerca
resultados incorrectos debido a la si el nombre contiene alguna de las subcadenas indicadas y est sucientemente
34
precisin.
(empleando la funcin auxiliar isclose), entonces devolvemos ese tipo (lnea ) para
el nodo pasado como argumento de la funcin (lnea 27 ). En otro caso, devolvemos

cadena

C17
la vaca para tipo (que indica que no es un nodo especial del grafo) en la lnea
35 .

La funcin auxiliar isclose (denida en las lneas 15-22 devolver True si el empty
est a una distancia menor que respecto del nodo del grafo. En otro caso, devolver
False. Para realizar esta comparacin, simplemente creamos un vector restando las
coordenadas del vrtice con la posicin del Empty y utilizamos directamente el atri-
buto de longitud (lnea 19 ) de la clase Vector de la biblioteca mathutils.

La biblioteca Mathutils de Blender contiene una gran cantidad de funciones


para trabajar con Matrices, Vectores, Cuaternios y un largo etctera.


Trayectorias genricas La segunda parte del cdigo (denido en las lneas 76-100 ) se encarga de exportar

las animaciones asociadas a las cmaras. Segn el convenio explicado anteriormente,

el propio nombre de la cmara codica el ndice de la cmara (ver lnea 80 ) y el n-
Aunque en este ejemplo exportare-

mero de frames que contiene su animacin (ver lnea 81 ). En el caso de este ejemplo
mos trayectorias sencillas, el expor-
tar la posicin de la cmara en ca-
da frame nos permite tener un con- se han denido dos cmaras (ver Figura 17.4). La cmara del men inicial es esttica,
trol absoluto sobre la pose en cada por lo que dene en la parte relativa a los frames un valor de 1 (no hay animacin). La
instante. En la implementacin en
OGRE tendremos que utilizar algn cmara del juego describir rotaciones siguiendo una trayectoria circular.
mecanismo de interpolacin entre
cada pose clave.
[450] CAPTULO 17. EXPORTACIN Y USO DE DATOS DE INTERCAMBIO

spawn4

spawn1
drain2

spawn2
drain1

spawn3

Figura 17.4: Denicin del grafo asociado al juego NoEscapeDemo en Blender. En la imagen de la izquier-
da se han destacado la posicin de los Emptys auxiliares para describir el tipo especial de algunos nodos
(vrtices) del grafo. La imagen de la derecha muestra las dos cmaras de la escena y la ruta descrita para la
cmara principal del juego.

El nmero de frame se
modica directamente en el atributo frame_current del la
escena actual (ver lnea 86 ). Para cada frame se exporta la posicin de la cmara

(obtenida en la ltima columna de la matriz de transformacin del objeto, en la lnea
88 ), y el cuaternio de rotacin (mediante una conversin de la matriz en 89 ). La
Figura 17.5 muestra un fragmento del XML exportado relativo a la escena base del
ejemplo. Esta exportacin puede realizarse ejecutando el chero .py desde lnea de
rdenes, llamando a Blender con el parmetro -P <script.py> (siendo script.py el
nombre del script de exportacin).

Figura 17.5: Fragmento del resultado de la exportacin del XML del ejemplo.
Captulo 18
Animacin
Carlos Gonzlez Morcillo

E
n este captulo estudiaremos los fundamentos de la animacin por computador,
analizando los diversos mtodos y tcnicas de construccin, realizando ejem-
plos de composicin bsicos con Ogre. Se estudiar el concepto de Animation
State, exportando animaciones denidas previamente en Blender.

18.1. Introduccin

Animacin? En su forma ms simple, la animacin por computador consiste en generar un


conjunto de imgenes que, mostradas consecutivamente, producen sensacin de mo-
El trmino animacin proviene del
griego Anemos, que es la base de la vimiento. Debido al fenmeno de la Persistencia de la Visin, descubierto en 1824
palabra latina Animus que signica por Peter Mark Roget, el ojo humano retiene las imgenes una vez vistas unos 40 ms.
Dar aliento, dar vida. Siempre que mostremos imgenes a una frecuencia mayor, tendremos sensacin de
movimiento continuo1 . Cada una de las imgenes que forman la secuencia animada
recibe el nombre de frame2 .
La animacin por computador cuenta con una serie de ventajas que no se dan en
animacin tradicional; por ejemplo, la animacin puede producirse directamente des-
de modelos o conjuntos de ecuaciones que especican el comportamiento dinmico
de los objetos a animar.
1 A frecuencias menores de 20hz se percibir la discretizacin del movimiento, en un efecto de tipo

estroboscpico.
2 tambin, aunque menos extendido en el mbito de la animacin por computador se utiliza el trmino

cuadro o fotograma

451
[452] CAPTULO 18. ANIMACIN

Cuando hablamos de tcnicas de animacin por computador nos referimos a siste-


mas de control del movimiento. Existen multitud de elementos que hacen el problema
del control del movimiento complejo, y de igual forma, existen varias aproximacio-
nes a la resolucin de estas dicultades. La disparidad de aproximaciones y la falta
de unidad en los convenios de nombrado, hacen que la categorizacin de las tcnicas
de animacin por computador sea difcil. Una propuesta de clasicacin de se realiza
segn el nivel de abstraccin.
As, se distingue entre sistemas de animacin de alto nivel, que permiten al ani-
mador especicar el movimiento en trminos generales (denen el comportamiento en
trminos de eventos y relaciones), mientras que los sistemas de bajo nivel requieren
que el animador indique los parmetros de movimiento individualmente.
Si se desean animar objetos complejos (como por ejemplo la gura humana), es
necesario, al menos, un control de jerarqua para reducir el nmero de parmetros
que el animador debe especicar. Incluso en personajes digitales sencillos, como el
mostrado en la gura 18.1 el esqueleto interno necesario tiene un grado de compleji-
dad considerable. En concreto, este esqueleto tiene asociados 40 huesos con diversos
modicadores aplicados individualmente.
Al igual que en los lenguajes de programacin de alto nivel, las construcciones
deben nalmente compilarse a instrucciones de bajo nivel. Esto implica que cualquier
descripcin paramtrica de alto nivel debe transformarse en descripciones de salida
de bajo nivel. Este proceso proceso nalizar cuando dispongamos de todos los datos
necesarios para todos los frames de la animacin.
De esta forma, lo que se busca en el mbito de la animacin es ir subiendo de nivel,
obteniendo sistemas de mayor nivel de abstraccin. Las primeras investigaciones co-
menzaron estudiando las tcnicas de interpolacin de movimiento entre frames clave,
utilizando Splines. Mediante este tipo de curvas, los objetos podan moverse de forma
suave a lo largo de un camino en el espacio. Desde este punto, han aparecido multitud
de tcnicas de animacin de medio nivel (como la animacin basada en scripts, ani- Figura 18.1: Uno de los personajes
macin procedural, animacin jerrquica basada en cinemtica directa e inversa y los principales de YoFrankie, proyecto
estudios de sntesis animacin automtica). de videojuego libre de la Blender
Foundation.
A continuacin estudiaremos los principales niveles de abstraccin relativos a la
denicin de animacin por computador.
Tcnicas bsicas
18.1.1. Animacin Bsica Estas tcnicas forman la base de los
mtodos de animacin ms avan-
zados, por lo que es imprescindi-
Lo fundamental en este nivel es cmo parametrizar los caminos bsicos de movi- ble conocer su funcionamiento para
miento en el espacio. Hay diversas alternativas (no excluyentes) como los sistemas de emplear correctamente los mtodos
Script, sistemas de Frame Clave y animacin dirigida por Splines. de animacin basados en cinemti-
ca directa o inversa que estudiare-
mos ms adelante.
Sistemas de Script. Histricamente, los primeros sistemas de control del mo-
vimiento fueron los sistemas basados en scripts. Este tipo de sistemas requieren
que el usuario escriba un guin en un lenguaje especco para animacin y ade-
ms, presuponen una habilidad por parte del animador de expresar la animacin
con el lenguaje de script. Este tipo de aproximacin producen animaciones de
baja calidad, dada la complejidad en la especicacin de las acciones.
Sistema de Frame Clave. Los sistemas de Frame Clave toman su nombre de
la jerarqua de produccin tradicional de Walt Disney. En estos sistemas, los
animadores ms experimentados diseaban los fotogramas principales de cada
secuencia. La produccin se completaba con artistas jvenes que aadan los
frames intermedios3 .
3 Realizando la tcnica llamada n between"
18.1. Introduccin [453]

Dependiendo del tipo de mtodo que se utilice para el clculo de los fotogramas
intermedios estaremos ante una tcnica de interpolacin u otra. En general, sue-
len emplearse curvas de interpolacin del tipo Spline, que dependiendo de sus
propiedades de continuidad y el tipo de clculo de los ajustes de tangente obten-
dremos diferentes resultados. En el ejemplo de la Figura 18.2, se han aadido
claves en los frames 1 y 10 con respecto a la posicin (localizacin) y rotacin
en el eje Z del objeto. El ordenador calcula automticamente la posicin y rota-
cin de las posiciones intermedias. La gura muestra las posiciones intermedias
calculadas para los frames 4 y 7 de la animacin.
Animacin Procedural. En esta categora, el control sobre el movimiento se
realiza empleando procedimientos que denen explcitamente el movimiento
como funcin del tiempo. La generacin de la animacin se realiza mediante
descripciones fsicas, como por ejemplo en visualizaciones cientcas, simula-
ciones de uidos, tejidos, etc... Estas tcnicas de animacin procedural sern
igualmente estudiadas a continuacin este mdulo curso. La simulacin din-
mica, basada en descripciones matemticas y fsicas suele emplearse en anima-
cin de acciones secundarias. Es habitual contar con animaciones almacenadas
a nivel de vrtice simulando comportamiento fsico en videojuegos. Sera el
equivalente al Baking de animaciones complejas precalculadas.

Con base en estas tcnicas fundamentales se denen los mtodos de animacin de


alto nivel descritos a continuacin.

18.1.2. Animacin de Alto Nivel


Dentro de este grupo incluimos la animacin mediante tcnicas cinemticas je-
rrquicas (como cinemtica directa o inversa de guras articuladas), las modernas
aproximaciones de sntesis automtica de animacin o los sistemas de captura del mo-
vimiento. Estas tcnicas especcas sern estudiadas en detalle en el Mdulo 3 del
curso, por lo que nicamente realizaremos aqu una pequea descripcin introducto-

C18
ria.

Cinemtica Directa. Empleando cinemtica directa, el movimiento asociado a


las articulaciones debe ser especicado explcitamente por el animador. En el
ejemplo de la Figura 18.3, la animacin del efector nal vendra determinada in-
directamente por la composicin de transformaciones sobre la base, el hombro,
el codo y la mueca. Esto es, una estructura arbrea descendente. Esto es, dado
el conjunto de rotaciones , obtenemos la posicin del efector nal X como
X = f ().
Cinemtica Inversa. El animador dene nicamente la posicin del efector -
nal. Mediante cinemtica inversa se calcula la posicin y orientacin de todas
las articulaciones de la jerarqua que consiguen esa posicin particular del efec-
tor nal mediante = f (X).

Una vez realizada esta introduccin general a las tcnicas de animacin por compu-
tador, estudiaremos en la siguiente seccin los tipos de animacin y las caractersticas
generales de cada uno de ellos en el motor grco Ogre.
[454] CAPTULO 18. ANIMACIN

Posicin Clave Posiciones Interpoladas Posicin Clave


(Frame 1) (Frames 4 y 7) (Frame 10)
Rotacin Z

Posicin X
Posicin Y
Posicin Z

1 2 3 4 5 6 7 8 9 10 Frame

Figura 18.2: Ejemplo de uso de Frames Clave y Splines asociadas a la interpolacin realizada.

18.2. Animacin en Ogre


Ogre gestiona la posicin de los elementos de la escena en cada frame. Esto sig-
nica que la escena es redibujada en cada frame. Ogre no mantiene ningn tipo de
estructuras de datos con el estado anterior de los objetos de la escena. La animacin
se gestiona con dos aproximaciones; una basada en frames clave y otra mediante una
variable (habitualmente se emplear el tiempo) utilizada como controlador. El modo
ms sencillo de crear animaciones es mediante el uso de una herramienta externa y
reproducirlas posteriormente.
Como hemos sealado anteriormente, de modo general Ogre soporta dos modos de AnimationState
animacin: basada en Frames Clave (Keyframe Animation) y basada en Controladores. Cada animacin de un AnimationS-
tate tiene asociado un nombre nico
mediante el que puede ser accedi-
18.2.1. Animacin Keyframe do.

Ogre utiliza el trmino pista Track para referirse a un conjunto de datos alma-
cenados en funcin del tiempo. Cada muestra realizada en un determinado instante
de tiempo es un Keyframe. Dependiendo del tipo de Keyframes y el tipo de pista, se
denen diferentes tipos de animaciones.
Las animaciones asociadas a una Entidad se representan en un AnimationState.
Este objeto tiene una serie de propiedades que pueden ser consultadas:
18.2. Animacin en Ogre [455]

Hombro
Codo

Posicin
del Efector Base Mueca

Posicin
del Efector

Figura 18.3: Ejemplo de uso de Frames Clave y Splines asociadas a la interpolacin realizada.

Nombre. Mediante la llamada a getAnimationName se puede obtener el nombre


de la animacin que est siendo utilizada por el AnimationState.

C18
Activa. Es posible activar y consultar el estado de una animacin mediante las
llamadas a getEnabled y setEnabled.
Longitud. Obtiene en punto otante el nmero de segundos de la animacin,
mediante la llamada a getLength.
Posicin. Es posible obtener y establecer el punto de reproduccin actual de la
animacin mediante llamadas a getTimePosition y setTimePosition. El tiempo
se establece en punto otante en segundos.
Bucle. La animacin puede reproducirse en modo bucle (cuando llega al nal
continua reproduciendo el primer frame). Las llamadas a mtodos son getLoop
y setLoop.
Peso. Este parmetro controla la mezcla de animaciones, que ser descrita en la
seccin 18.4.

Tiempo El estado de la animacin requiere que se le especique el tiempo transcurrido


El tiempo transcurrido desde la lti- desde la ltima vez que se actualiz.
ma actualizacin puede especicar-
se con un valor o negativo (en el ca-
so de querer retroceder en la anima-
cin). Este valor de tiempo simple-
mente se aade a la posicin de re-
produccin de la animacin.
[456] CAPTULO 18. ANIMACIN

Figura 18.4: Principales ventanas de Blender empleadas en la animacin de objetos. En el centro se en-
cuentran las ventanas del NLA Editor y del Dope Sheet, que se utilizarn para denir acciones que sern
exportadas empleando el script de Exportacin a Ogre.

18.2.2. Controladores
La animacin basada en frames clave permite animar mallas poligionales. Me-
diante el uso de controladores se posicionan los nodos, deniendo una funcin. El
controlador permite modicar las propiedades de los nodos empleando un valor que
se calcula en tiempo de ejecucin.

18.3. Exportacin desde Blender


En esta seccin estudiaremos la exportacin de animaciones de cuerpos rgidos.
Aunque para almacenar los AnimationState es necesario asociar un esqueleto al ob-
jeto que se desea animar, no entraremos en detalles para la animacin de jerarquas
compuestas (personajes). Desde el punto de vista de la exportacin en Ogre es similar.
Para exportar diferentes animaciones, ser conveniente congurar el interfaz de
Blender como se muestra en la Figura 18.4. A continuacin describiremos brevemente
las dos ventanas principales que utilizaremos en la exportacin de animaciones:
El NLA Editor permite la edicin de animacin no lineal en Blender. Me-
diante esta tcnica de composicin, la animacin nal se crea mediante fragmentos de
animacin especcos (cada uno est especializado en un determinado movimiento)
llamados acciones. Estas acciones pueden duplicarse, repetirse, acelerarse o decele-
rarse en el tiempo para formar la animacin nal.
El Dope Sheet (en versiones anteriores de Blender se denominaba Action Edi- Acciones
tor) permite el control de mltiples acciones simultneamente, agrupndolas por tipo. Las accciones permiten trabajar a
Este editor est a un nivel de abstraccin medio, entre las curvas IPO y el NLA Editor un nivel de abstraccin mayor que
descrito anteriormente. Esta ventana permite trabajar muy rpidamente con los datos empleando curvas IPO (InterPOla-
de la animacin, desplazando la posicin de los frames clave (y modicando as el tion curve). Es conveniente emplear
esta aproximacin siempre que de-
Timing de cada accin). En el rea de trabajo principal del Dope Sheet se muestran los namos animaciones complejas.
canales de animacin asociados a cada objeto. Cada diamante de las barras asociadas
a cada canal se corresponden con un keyframe y pueden ser desplazadas individual-
mente o en grupos para modicar la temporizacin de cada animacin.
18.3. Exportacin desde Blender [457]

Las acciones aparecern en el interfaz del exportador de Ogre, siempre que hayan
sido correctamente creadas y estn asociadas a un hueso de un esqueleto. En la Figu-
ra 18.4 se han denido dos acciones llamadas Saltar y Rotar (la accin Rotar est
seleccionada y aparece resaltada en la gura). A continuacin estudiaremos el proceso
de exportacin de animaciones.
Como se ha comentado anteriormente, las animaciones deben exportarse emplean-
do animacin basada en esqueletos. En el caso ms sencillo de animaciones de cuerpo
rgido, bastar con crear un esqueleto de un nico hueso y emparentarlo con el objeto
que queremos animar.
un esqueleto con un nico hueso al objeto que queremos aadir median-
Aadimos
te Shift A Add/Armature/Single Bone. Ajustamos el extremo superior del hueso para
que tenga un tamao similar al objeto a animar (como se muestra en la Figura 18.5).
A continuacin crearemos una relacin de parentesco entre el objeto y el hueso del
esqueleto, de forma que el objeto sea hijo de el esqueleto.
Este parentesco debe denirse en modo Pose . Con el hueso del esqueleto selec-
cionado, elegiremos el modo
Pose en la cabecera de la ventana 3D (o bien empleando
el atajo de teclado Control TAB ). El hueso deber representarse en color azul en el inter-
faz de Blender. Con el hueso en modo pose, ahora seleccionamos primero al caballo,
y a continuacin con Shift pulsado seleccionamos el hueso (se deber elegir en color

azul), y pulsamos Control P , eligiendo Set Parent to ->Bone. Si ahora desplazamos el
hueso del esqueleto en modo pose, el objeto deber seguirle.
Antes de crear las animaciones, debemos aadir un Modicador de objeto sobre
Figura 18.5: Ajuste del tamao del la malla poligional. Con el objeto Horse seleccionado, en el panel Object Modiers
hueso para que sea similar al objeto aadimos un modicador de tipo Armature. Este modicador se utiliza para animar
a animar. las poses de personajes y objetos asociados a esqueletos. Especicando el esqueleto
que utilizaremos con cada objeto podremos incluso deformar los vrtices del propio
modelo.
Como se muestra en la Figura 18.6, en el campo Object especicaremos el nom-
bre del esqueleto que utilizaremos con el modicador (Armature en este caso). En
el mtodo de Bind to indicamos qu elementos se asociarn al esqueleto. Si quere-

C18
mos que los vrtices del modelo se deformen en funcin de los huesos del esqueleto,
activaremos Vertex Groups (en este ejemplo deber estar desactivado). Mediante Bo-
ne Envelopes podemos indicar a Blender que utilice los lmites de cada hueso para
denir la deformacin que se aplicar a cada hueso.
A continuacin creamos un par de animaciones asociadas a este hueso. Recorde-
Figura 18.6: Opciones del Modi-
cador Armature.
mos que debemos trabajar en modo Pose , por lo que siempre el hueso debe estar
seleccionado en color azul en el interfaz de Blender. Comenzaremos deniendo una
accin que se llamar Saltar. Aadiremos los frames clave de la animacin (en este
caso, hemos denido frames clave de LocRot en 1, 21 y 35).
Tras realizar esta operacin, las ventanas de DopeSheet y NLA Editor mostrarn
un aspecto como el de la Figura 18.7. La ventana DopeSheet permite modicar
fcilmente las posiciones de los frames clave asociados al hueso. Pinchando y des-
plazando los rombos asociados a cada clave, podemos modicar el timing de la ani-
Figura 18.7: Ventanas de DopeS- macin. En la ventana NLA podemos crear una accin (Action Strip), pinchando
heet y NLA Editor.
sobre el icono del copo de nieve . Tras esta operacin, se habr creado una accin
denida mediante una barra amarilla.

Es posible indicar el nombre de la accin en el campo Active Strip, accesible pul-
sando la tecla N . Aparecer un nuevo subpanel, como se muestra en la Figura 18.8,
en la zona derecha de la ventana, donde se podrn congurar los parmetros asociados
a la accin (nombre, frames, mtodo de mezclado con otras acciones, etc...).
Figura 18.8: Especicacin del
nombre de la accin en el Panel de
Propiedades.
[458] CAPTULO 18. ANIMACIN

Siguiendo el mismo procedimiento denimos otra accin llamada Rotar. Para


que no tengan inuencia las acciones previamente creadas, y no molesten a la hora
de denir nuevos comportamientos, es posible ocultarlas pinchando sobre el botn
situado a la derecha del nombre de la accin (ver Figura 18.8).

Es posible exportar diferentes animaciones asociadas a un objeto. El rango de


denicin de los frames puede ser diferente, como en el ejemplo estudiado en
esta seccin.


Mediante el atajo de teclado Alt A podemos reproducir la animacin relativa a la
accin que tenemos seleccionada. Resulta muy cmodo emplear una ventana de tipo
Timeline para denir el intervalo sobre el que se crearn las animaciones.
A la hora de exportar las animaciones, procedemos como vimos en el Captulo
11. En File/ Export/ Ogre3D, accederemos al exportador de Blender a Ogre3D. Es
interesante activar las siguientes opciones de exportacin:

Armature Animation. Crea el archivo con extensin .skeleton, que contiene


la denicin jerrquica del conjunto de huesos, junto con su posicin y orienta-
cin. Esta jerarqua incluye la inuencia que tiene cada hueso sobre los vrtices
del modelo poligonal.
Independent Animations. Exporta cada accin de forma independiente, de
modo que no tienen inuencia combinada.

El uso de las animaciones exportadas en Ogre es relativamente sencillo. En si- Huesos y vrtices
guiente cdigo muestra un ejemplode aplicacin
de las animaciones previamente ex-

portadas. Cuando se pulsa la tecla o se reproduce la animacin Saltar o Rotar En este captulo trabajaremos con
hasta que naliza. En la lnea 18 se actualiza el tiempo transcurrido desde la ltima
A Z
huesos que tienen una inuencia to-

teclas

tal sobre el objeto (es decir, inuen-
actualizacin. Cuando se pulsa alguna de las anteriores, la animacin seleccio-
nada se reproduce desde el principio (lnea 10 ).
cia de 1.0 sobre todos los vrtices
del modelo).

En el ejemplo siguiente puede ocurrir que se interrumpa una animacin por la


seleccin de otra antes de nalizar el estado. Esto poda dar lugar a posiciones nales

incorrectas. Por ejemplo, si durante la ejecucin de la accin Saltar se pulsaba la
tecla Z , el objeto se quedaba otando en el aire ejecutando la segunda animacin.

Listado 18.1: Fragmento de MyFrameListener.cpp.


1 if (_keyboard->isKeyDown(OIS::KC_A) ||
2 _keyboard->isKeyDown(OIS::KC_Z)) {
3 if (_keyboard->isKeyDown(OIS::KC_A))
4 _animState = _sceneManager->getEntity("Horse")->
5 getAnimationState("Saltar");
6 else _animState = _sceneManager->getEntity("Horse")->
7 getAnimationState("Rotar");
8 _animState->setEnabled(true);
9 _animState->setLoop(true);
10 _animState->setTimePosition(0.0);
11 }
12
13 if (_animState != NULL) {
14 if (_animState->hasEnded()) {
15 _animState->setTimePosition(0.0);
16 _animState->setEnabled(false);
17 }
18 else _animState->addTime(deltaT);
18.4. Mezclado de animaciones [459]

19 }

Es posible denir varias animaciones y gestionarlas mediante diferentes Anima-


tionState. En el siguienteejemplo
se cargan dos animaciones jas en el constructor
del FrameListener (lnea 3-8 ). Empleando las mismas teclas que en el ejemplo ante-
cada
rior, se resetean una de ellas, y se actualizan ambas de forma independiente (en
las lneas 26 y 28 ). Ogre se encarga de mezclar sus resultados
(incluso es posible
aplicar transformaciones a nivel de nodo, pulsando la tecla R mientras se reproducen
las animaciones del AnimationState).

Listado 18.2: Fragmento de MyFrameListener.cpp.


1 MyFrameListener::MyFrameListener() {
2 // ...
3 _animState = _sceneManager->getEntity("Horse")->
4 getAnimationState("Saltar");
5 _animState->setEnabled(false);
6 _animState2 = _sceneManager->getEntity("Horse")->
7 getAnimationState("Rotar");
8 _animState2->setEnabled(false);
9 }
10
11 bool MyFrameListener::frameStarted(const FrameEvent& evt) {
12 // ...
13 if (_keyboard->isKeyDown(OIS::KC_A)) {
14 _animState->setTimePosition(0.0);
15 _animState->setEnabled(true);
16 _animState->setLoop(false);
17 }
18
19 if (_keyboard->isKeyDown(OIS::KC_Z)) {
20 _animState2->setTimePosition(0.0);
21 _animState2->setEnabled(true);
22 _animState2->setLoop(false);
23 }
24

C18
25 if (_animState->getEnabled() && !_animState->hasEnded())
26 _animState->addTime(deltaT);
27 if (_animState2->getEnabled() && !_animState2->hasEnded())
28 _animState2->addTime(deltaT);
29 // ...

Obviamente, ser necesario contar con alguna clase de nivel superior que nos ges-
tione las animaciones, y se encarge de gestionar los AnimationState, actualizndolos
adecuadamente.

18.4. Mezclado de animaciones


En la seccin anterior hemos utilizado dos canales de animacin, con posibilidad
de reproduccin simultnea. En la mayora de las ocasiones es necesario contar con
mecanismos que permitan el mezclado controlado de animaciones (Animation Blen-
ding). Esta tcnica fundamental se emplea desde hace aos en desarrollo de videojue-
gos.
[460] CAPTULO 18. ANIMACIN

En la actualidad se emplean mdulos especcos para el mezclado de animaciones.


El uso de rboles de prioridad (Priority Blend Tree4 ) facilita al equipo artstico de una
produccin especicar con un alto nivel de detalle cmo se realizar la composicin
de las capas de animacin.
El mezclado de animaciones requiere un considerable tiempo de CPU. La inter-
polacin necesaria para mezclar los canales de animacin en cada frame hace que el
rendimiento del videojuego pueda verse afectado. Empleando interpoilacin esfri-
ca SLERP, para cada elemento del esqueleto es necesario calcular varias operaciones
costosas (cuatro senos, un arccos, y una raz cuadrada).
A un alto nivel de abstraccin, cada animacin en Ogre tiene asociado un peso.
Cuando se establece la posicin dentro de la animacin, puede igualmente modicarse
el peso del canal de animacin. Dependiendo del peso asignado a cada animacin,
Ogre se encargar de realizar la interpolacin de todas las animaciones activas para
obtener la posicin nal del objeto. A continuacin deniremos una clase llamada
AnimationBlender que se encargar de realizar la composicin bsica de capas de
animacin.
contienen punteros
Las variables miembro privadas de la clase AnimationBlender
a las animaciones de inicio y n del mezclado (lneas 10 y 11 ), el tipo de transicin
deseado (que es un tipo enumerado denido en las lneas 3-6 ), y un booleano que

indica si la animacin se reproducir en bucle.
Las variables pblicas (denidas en 16-18 ) contienen el tiempo restante de repro-
duccin de la pista actual, la duracin (en segundos) del mezclado entre pistas y un
valor booleano que indica si la reproduccin ha nalizado.
La clase incorpora, adems de los tres mtodos principales que estudiaremos a

continuacin, una serie de mtodos auxiliares que nos permiten obtener valores rela-
tivos a las variables privadas (lneas 24-27 ).
Figura 18.9: La clase de Anima-
tionBlender (Mezclado de Anima-
Listado 18.3: AminationBlender.h ciones) no tiene nada que ver con
el prestigioso paquete de animacin
1 class AnimationBlender { del mismo nombre.
2 public:
3 enum BlendingTransition {
4 Switch, // Parar fuente y reproduce destino
5 Blend // Cross fade (Mezclado suave)
6 };
7
8 private:
9 Entity *mEntity; // Entidad a animar
10 AnimationState *mSource; // Animacion inicio
11 AnimationState *mTarget; // Animacion destino
12 BlendingTransition mTransition; // Tipo de transicion
13 bool mLoop; // Animacion en bucle?
14
15 public:
16 Real mTimeleft; // Tiempo restante de la animacion (segundos)
17 Real mDuration; // Tiempo invertido en el mezclado (segundos)
18 bool mComplete; // Ha finalizado la animacion?
19
20 AnimationBlender( Entity *);
21 void blend(const String &anim, BlendingTransition transition,
22 Real duration, bool l=true);
23 void addTime(Real);
24 Real getProgress() { return mTimeleft/mDuration; }
25 AnimationState *getSource() { return mSource; }
26 AnimationState *getTarget() { return mTarget; }
27 bool getLoop() { return mLoop; }
28 };

4 Ms informacin sobre cmo se implement el motor de mezclado de animaciones del MechWarrior

en http://www.gamasutra.com/view/feature/3456/
18.4. Mezclado de animaciones [461]


Veamos a continuacin los principales mtodos de la clase, declarados en las lneas
20-23 del listado anterior.

Listado 18.4: AminationBlender.cpp (Constructor)


1 AnimationBlender::AnimationBlender(Entity *ent) : mEntity(ent) {
2 AnimationStateSet *set = mEntity->getAllAnimationStates();
3 AnimationStateIterator it = set->getAnimationStateIterator();
4 // Inicializamos los AnimationState de la entidad
5 while(it.hasMoreElements()) {
6 AnimationState *anim = it.getNext();
7 anim->setEnabled(false);
8 anim->setWeight(0);
9 anim->setTimePosition(0);
10 }
11 mSource = NULL; mTarget = NULL; mTimeleft = 0;
12 }


En el constructor de la clase inicializamos todas las animaciones asociadas a la en-

tidad (recibida como nico parmetro). Mediante un iterador (lnea 6
) desactivamos
todos los AnimationState asociados (lnea 7 ), y reseteamos la posicin
de reproduc-
cin y su peso asociado para su posterior composicin (lneas 8-9 ).
La clase AnimationBlender dispone de un mtodo principal para cargar animacio-
nes, indicando (como segundo parmetro) el tipo de mezcla que quiere realiarse con
la animacin que est reproducindose. La implementacin actual de la clase admite
dos modos de mezclado; un efecto de mezclado suave tipo cross fade y una transicin
bsica de intercambio de canales.
La transicin de tipo Blend implementa una transicin simple lineal de tipo Cross
Fade. En la Figura 18.10 representa este tipo de transicin, donde el primer canal de
animacin se mezclar de forma suave con el segundo, empleando una combinacin
lineal de pesos.

C18
Saltar
Listado 18.5: AminationBlender.cpp (Blend)

Rotar 1 void AnimationBlender::blend (const String &anim,


2 BlendingTransition transition, Real duration, bool l) {
3
Saltar
Saltar
Rotar 4 AnimationState *newTarget = mEntity->getAnimationState(anim);
Rotar + 5 newTarget->setLoop(l);
6 mTransition = transition;
Duracin de 7 mDuration = duration;
la transicin 8 mLoop = l;
9
Figura 18.10: Efecto de transicin 10 if ((mTimeleft<=0) || (transition == AnimationBlender::Switch)){
tipo Cross Fade. En el tiempo espe- 11 // No hay transicion (finalizo la anterior o Switch)
cicado en la duracin de la tran- 12 if (mSource != NULL) mSource->setEnabled(false);
sicin ambos canales de animacin 13 mSource = newTarget; // Establecemos la nueva
tienen inuencia en el resultado - 14 mSource->setEnabled(true);
nal. 15 mSource->setWeight(1); // Con maxima influencia
16 mSource->setTimePosition(0); // Reseteamos la posicion
17 mTimeleft = mSource->getLength(); // Duracion del AnimState
18 mTarget = NULL;
19 }
20 else { // Hay transicion suave
21 if (mSource != newTarget) {
22 mTarget = newTarget; // Nuevo destino
23 mTarget->setEnabled(true);
24 mTarget->setWeight(0); // Cambia peso en addTime
25 mTarget->setTimePosition(0);
26 }
[462] CAPTULO 18. ANIMACIN

27 }
28 }

Aunque la implementacin actual utiliza una simple combinacin lineal de pesos,


es posible denir cualquier funcin de mezcla. Bastar con modicar la implementa-
cin del mtodo addTime que veremos a continuacin.
Por su parte, el mtodo de mezclado de tipo Switch realiza un intercambio directo

Saltar
de los dos canales de animacin (ver Figura 18.11). Este mtodo es el utilizado igual-
mente si el canal que se est reproduciendo actualmente ha nalizado (ver lnea 10 Rotar
del listado anterior).
La implementacin del mtodo anterior tiene en cuenta el canal de animacin que Saltar Rotar

actualmente. Si el canal a mezclar es igual que el actual, se


se est reproduciendo
descarta (lnea 21 ). En otro caso, se aade
como objetivo, activando el nuevo canal
Figura 18.11: Transicin de tipo
pero estableciendo su peso a 0 (lnea 24 ). La mezcla efectiva de las animaciones (el Intercambio. El parmetro de dura-
cin, aunque se especique, no tie-
cambio de peso asociado a cada canal de animacin) se realiza en el mtodo addTime, ne ningn efecto.
cuyo listado se muestra a continuacin.

Listado 18.6: AminationBlender.cpp (addTime)


1 void AnimationBlender::addTime(Real time) {
2 if (mSource == NULL) return; // No hay fuente
3 mSource->addTime(time);
4 mTimeleft -= time;
5 mComplete = false;
6 if ((mTimeleft <= 0) && (mTarget == NULL)) mComplete = true;
7
8 if (mTarget != NULL) { // Si hay destino
9 if (mTimeleft <= 0) {
10 mSource->setEnabled(false);
11 mSource->setWeight(0);
12 mSource = mTarget;
13 mSource->setEnabled(true);
14 mSource->setWeight(1);
15 mTimeleft = mSource->getLength();
16 mTarget = NULL;
17 }
18 else { // Queda tiempo en Source... cambiar pesos
19 Real weight = mTimeleft / mDuration;
20 if (weight > 1) weight = 1.0;
21 mSource->setWeight(weight);
22 mTarget->setWeight(1.0 - weight);
23 if (mTransition == AnimationBlender::Blend)
24 mTarget->addTime(time);
25 }
26 }
27 if ((mTimeleft <= 0) && mLoop) mTimeleft = mSource->getLength();
28 }

Figura 18.12: Clculo del peso de


Al igual que en el uso de animaciones bsicas en Ogre, el mtodo addTime debe cada animacin basndose en la du-
ser llamado cada vez que se redibuje la escena, indicando el tiempo transcurrido desde racin de la transicin. En los ins-
la ltima actualizacin. La clase AnimationBlender se encargar asuvez de ejecutar tantes de tiempo a, b y c se muestra
el mtodo addTime de los canales de animacin activos (lneas 3 y 24 ). El mtodo el valor del peso asociado a cada ca-
nal de animacin.
addTime lleva internamente la cuenta del tiempo que le queda al canal de reproduccin
al canal de animacin. Cuando el tiempo es menor o igual que el tiempo empleado para
la transicin, se calcular el peso que se asignar a cada canal (lneas 19-22 ). El peso Cuntas animaciones?
se calcula empleando una sencilla combinacin lineal, de modo que el peso total para La cantidad de animaciones nece-
ambos canales en cualquier instante debe ser igual a uno (ver Figura 18.12). sarias para aplicar correctamente
las tcnicas de Animation Blending
No es necesario que el peso nal combinado de todas las animaciones sea igual a pueden ser muy elevadas. Por ejem-
plo, en MechWarrior de Microsoft
uno. No obstante, es conveniente que la suma de todos los canales de animacin estn cada robot tena asociadas ms de
normalizados y sean igual a 1. 150 animaciones.
18.4. Mezclado de animaciones [463]

En el caso de asignar pesos negativos, la animacin se reproducir empleando


curvas de animacin invertidas.
La composicin de animaciones suele ser una tarea compleja. En el ejemplo desa-
rrollado para ilustrar el uso de la clase AnimationBlender se han utilizado nicamente
dos animaciones, y se han utilizado tiempos de transicin jos en todos los casos.
En produccin es importante trabajar las transiciones y las curvas de animacin para
obtener resultados atractivos.

C18
Figura 18.13: Ejemplo de aplicacin que utiliza la clase AnimationBlender. Mediante las teclas indicadas
en el interfaz es posible componer las animaciones exportadas empleandos los dos modos de transicin
implementados.

El mezclado de animaciones que son muy diferentes provoca resultados extraos.


Es mejor combinar canales de animacin que son similares, de forma que la mezcla
funciona adecuadamente. En el ejemplo de esta seccin se mezclan animaciones muy
diferentes, para mostrar un caso extremo de uso de la clase. En entornos de produc-
cin, suelen denirse puntos de conexin entre animaciones, de modo que la clase
de Blending se espera hasta alcanzar uno de esos puntos para realizar la mezcla. De
este modo, se generan un alto nmero de animaciones para garantizar que las mezclas
funcionarn correctamente sin comportamientos extraos.

Es importante realizar pruebas de la correcta composicin y mezclado de ani-


maciones en las primeras etapas del desarrollo del juego. De otra forma, pode-
mos sufrir desagradables sorpresas cuando se acerca la deadline del proyecto.
Puede resultar complicado ajustar en cdigo la mezcla de animaciones, por
lo que suelen desarrollarse scripts de exportacin adicionales para indicar los
puntos adecuados en cada animacin donde puede componerse con el resto.
[464] CAPTULO 18. ANIMACIN

El siguiente listado muestra un ejemplo de uso de la clase. En el constructor del


FrameListener se crea una variable miembro de la clase que contendr un puntero al
Cada vez que se actualiza el frame, llamamos al mtodo add-
objeto AnimBlender.
Time (ver lnea 11 ) que actualizar los canales de animacin convenientes. En este
ejemplo se han utilizado las 8 teclas descritas en la Figura 18.13 para mezclar las dos
animaciones exportadas en Blender al inicio del captulo.

Listado 18.7: Uso de AnimationBlender


1 if (_keyboard->isKeyDown(OIS::KC_Q))
2 _animBlender->blend("Saltar",AnimationBlender::Blend,0.5, false);
3 if (_keyboard->isKeyDown(OIS::KC_R))
4 _animBlender->blend("Rotar", AnimationBlender::Blend, 0.5, true);
5
6 if (_keyboard->isKeyDown(OIS::KC_S))
7 _animBlender->blend("Saltar", AnimationBlender::Switch, 0, true);
8 if (_keyboard->isKeyDown(OIS::KC_D))
9 _animBlender->blend("Rotar", AnimationBlender::Switch, 0, false);
10
11 _animBlender->addTime(deltaT);

En este captulo hemos estudiado los usos fundamentales de la animacin de cuer-


po rgido. En el mdulo 3 del curso estudiaremos algunos aspectos avanzados, como
la animacin de personajes empleando esqueletos y aproximaciones de cinemtica
inversa y el uso de motores de simulacin fsica para obtener, de forma automtica,
animaciones realistas.
Captulo

Simulacin Fsica
19
Carlos Gonzlez Morcillo

E
n prcticamente cualquier videojuego (tanto 2D como 3D) es necesaria la de-
teccin de colisiones y, en muchos casos, la simulacin realista de dinmica
de cuerpo rgido. En este captulo estudiaremos la relacin existente entre sis-
temas de deteccin de colisiones y sistemas de simulacin fsica, y veremos algunos
ejemplos de uso del motor de simulacin fsica libre Bullet.

19.1. Introduccin
La mayora de los videojuegos requieren en mayor o menor medida el uso de tc-
nicas de deteccin de colisiones y simulacin fsica. Desde un videojuego clsico co-
mo Arkanoid, hasta modernos juegos automovilsticos como Gran Turismo requieren
denir la interaccin de los elementos en el mundo fsico.
El motor de simulacin fsica puede abarcar una amplia gama de caractersticas y
funcionalidades, aunque la mayor parte de las veces el trmino se reere a un tipo con-
creto de simulacin de la dinmica de cuerpos rgidos. Esta dinmica se encarga de
Figura 19.1: Anarkanoid, el ma-
chacaladrillos sin reglas es un jue-
determinar el movimiento de estos cuerpos rgidos y su interaccin ante la inuencia
go tipo Breakout donde la simula- de fuerzas.
cin fsica se reduce a una detec- En el mundo real, los objetos no pueden pasar a travs de otros objetos (salvo ca-
cin de colisiones 2D.
sos especcos convenientemente documentados en la revista Ms All). En nuestro
videojuego, a menos que tengamos en cuenta las colisiones de los cuerpos, tendre-
Cuerpo Rgido mos el mismo efecto. El sistema de deteccin de colisiones, que habitualmente es un
Denimos un cuerpo rgido como mdulo del motor de simulacin fsica, se encarga de calcular estas relaciones, deter-
un objeto slido ideal, innitamente minando la relacin espacial existente entre cuerpos rgidos.
duro y no deformable.

465
[466] CAPTULO 19. SIMULACIN FSICA

La mayor parte de los videojuegos actuales incorporan ciertos elementos de si-


mulacin fsica bsicos. Algunos ttulos se animan a incorporar ciertos elementos
complejos como simulacin de telas, cuerdas, pelo o uidos. Algunos elementos de
simulacin fsica son precalculados y almacenados como animaciones estticas, mien-
tras que otros necesitan ser calculados en tiempo real para conseguir una integracin
adecuada.
Como hemos indicado anteriormente, las tres tareas principales que deben estar
soportadas por un motor de simulacin fsica son la deteccin de colisiones, su re-
solucin (junto con otras restricciones de los objetos) y calcular la actualizacin del
mundo tras esas interacciones. De forma general, las caractersticas que suelen estar
presentes en motores de simulacin fsica son:

Deteccin de colisiones entre objetos dinmicos de la escena. Esta deteccin


podr ser utilizada posteriormente por el mdulo de simulacin dinmica.
Clculo de lineas de visin y tiro parablico, para la simulacin del lanzamiento
de proyectiles en el juego.
Denicin de geometra esttica de la escena (cuerpos de colisin) que formen
el escenario del videojuego. Este tipo de geometra puede ser ms compleja que
la geometra de cuerpos dinmicos.
Especicacin de fuerzas (tales como viento, rozamiento, gravedad, etc...), que
aadirn realismo al videjuego.
Simulacin de destruccin de objetos: paredes y objetos del escenario.
Denicin de diversos tipos de articulaciones, tanto en elementos del escenario
(bisagras en puertas, rales...) como en la descripcin de las articulaciones de
personajes.
Especicacin de diversos tipos de motores y elementos generadores de fuerzas,
as como simulacin de elementos de suspensin y muelles.
Figura 19.2: Tres instantes en la si-
Simulacin de uidos, telas y cuerpos blandos (ver Figura 19.2). mulacin fsica de una tela sobre un
cubo. Simulacin realizada con el
motor Bullet.
19.1.1. Algunos Motores de Simulacin
El desarrollo de un motor de simulacin fsica desde cero es una tarea compleja
y que requiere gran cantidad de tiempo. Afortunadamente existen gran variedad de
motores de simulacin fsica muy robustos, tanto basados en licencias libres como
comerciales. A continuacin se describirn brevemente algunas de las bibliotecas ms
utilizadas:

Bullet. Bullet es una biblioteca de simulacin fsica ampliamente utilizada tanto


en la industria del videojuego como en la sntesis de imagen realista (Blender,
Houdini, Cinema 4D y LightWave las utilizan internamente). Bullet es multi-
plataforma, y se distribuye bajo una licencia libre zlib compatible con GPL.
Estudiaremos con ms detalle este motor, junto con su uso en Ogre, en la Sec-
cin 19.5.
ODE. ODE (Open Dynamics Engine) www.ode.org es un motor de simulacin
fsica desarrollado en C++ bajo doble licencias BSD y LGPL. El desarrollo de
ODE comenz en el 2001, y ha sido utilizado como motor de simulacin fsica Figura 19.3: Ejemplo de simula-
en multitud de xitos mundiales, como el aclamado videojuego multiplataforma cin fsica con ODE (demo de la
World of Goo, BloodRayne 2 (PlayStation 2 y Xbox), y TitanQuest (Windows). distribucin ocial), que incorpora
Ogre cuenta igualmente con un wrapper para utilizar este motor de simulacin el uso de motores y diferentes geo-
fsica. metras de colisin.
19.1. Introduccin [467]

PhysX. Este motor privativo, es actualmente mantenido por NVidia con ace-
leracin basada en hardware (mediante unidades especcas de procesamiento
fsico PPUs Physics Processing Units o mediante ncleos CUDA. Las tarjetas
grcas con soporte de CUDA (siempre que tengan al menos 32 ncleos CU-
DA) pueden realizar la simulacin fsica en GPU. Este motor puede ejecutarse
en multitud de plataformas como PC (GNU/Linux, Windows y Mac), PlaySta-
tion 3, Xbox y Wii. El SDK es gratuito, tanto para proyectos comerciales como
no comerciales. Existen multitud de videojuegos comerciales que utilizan este
motor de simulacin. Gracias al wrapper NxOgre se puede utilizar este motor
en Ogre.
Figura 19.4: Logotipo del motor de
simulacin fsico estrella en el mun- Havok. El motor Havok se ha convertido en el estndar de facto en el mun-
do del software privativo. do del software privativo, con una amplia gama de caractersticas soportadas y
plataformas de publicacin (PC, Videoconsolas y Smartphones). Desde que en
Fsica 2007 Intel comprara la compaa que originalmente lo desarroll, Havok ha si-
do el sistema elegido por ms de 150 videojuegos comerciales de primera lnea.
Ttulos como Age of Empires, Killzone 2 & 3, Portal 2 o Uncharted 3 avalan la
calidad del motor.
... Grficos
Existen algunas bibliotecas especcas para el clculo de colisiones (la mayora
Lgica del distribuidas bajo licencias libres). Por ejemplo, I-Collide, desarrollada en la Universi-
Juego
dad de Carolina del Norte permite calcular intersecciones entre volmenes convexos.
Existen versiones menos ecientes para el tratamiento de formas no convexas, llama-
GUI Networking das V-Collide y RAPID. Estas bibliotecas pueden utilizarse como base para la cons-
truccin de nuestro propio conjunto de funciones de colisin para videojuegos que no
IA requieran funcionalidades fsicas complejas.

Figura 19.5: El motor de simula-


cin fsica est directamente rela- 19.1.2. Aspectos destacables
cionado con otros mdulos del vi-
deojuego. Esta dependencia conlle- El uso de un motor de simulacin fsica en el desarrollo de un videojuego conlleva
va una serie de dicultades que de-
una serie de aspectos que deben tenerse en cuenta relativos al diseo del juego, tanto
ben tenerse en cuenta.
a nivel de jugabilidad como de mdulos arquitectnicos:

C19
Predictibilidad. El uso de un motor de simulacin fsica afecta a la predictibili-
dad del comportamiento de sus elementos. Adems, el ajuste de los parmetros
relativos a la denicin de las caractersticas fsicas de los objetos (coecientes,
constantes, etc...) son difciles de visualizar.
Realizacin de pruebas. La propia naturaleza catica de las simulaciones (en
muchos casos no determinista) diculta la realizacin de pruebas en el video-
juego.
Integracin. La integracin con otros mdulos del juego puede ser compleja.
Por ejemplo, qu impacto tendr en la bsqueda de caminos el uso de simula-
ciones fsicas? cmo garantizar el determinismo en un videojuego multijuga-
dor?.
Realismo grco. El uso de un motor de simulacin puede dicultar el uso
Figura 19.7: La primera incursin de ciertas tcnicas de representacin realista (como por ejemplo el preclculo
de Steven Spielberg en el mundo de la iluminacin con objetos que pueden ser destruidos). Adems, el uso de
de los videojuegos fue en 2008 con cajas lmite puede producir ciertos resultados poco realistas en el clculo de
Boom Blox, un ttulo de Wii desa- colisiones.
rrollado por Electronic Arts con
una componente de simulacin f-
sica crucial para la experiencia del
jugador.
[468] CAPTULO 19. SIMULACIN FSICA

Figura 19.6: Gracias al uso de PhysX, las baldosas del suelo en Batman Arkham Asylum pueden ser des-
truidas (derecha). En la imagen de la izquierda, sin usar PhysX el suelo permanece inalterado, restando
realismo y espectacularidad a la dinmica del juego.

Exportacin. La denicin de objetos con propiedades fsicas aade nuevas va-


riables y constantes que deben ser tratadas por las herramientas de exportacin
de los datos del juego. En muchas ocasiones es necesario adems la exportacin
de diferentes versiones de un mismo objeto (una versin de alta poligonaliza-
cin, la versin de colisin, una versin destructible, etc).
Interfaz de Usuario. Es necesario disear interfaces de usuario adaptados a
las capacidades fsicas del motor (cmo se especica la fuerza y la direccin
de lanzamiento de una granada?, de qu forma se interacta con objetos que
pueden recogerse del suelo?).

19.1.3. Conceptos Bsicos


A principios del siglo XVII, Isaac Netwon public las tres leyes fundamentales
del movimiento. A partir de estos tres principios se explican la mayor parte de los
problemas de dinmica relativos al movimiento de cuerpos y forman la base de la
mecnica clsica. Las tres leyes pueden resumirse como:

1. Un cuerpo tiende a mantenerse en reposo o a continuar movindose en lnea


recta a una velocidad constante a menos que acte sobre l una fuerza externa.
Esta ley resume el concepto de inercia.
2. El cambio de movimiento es proporcional a la fuerza motriz aplicada y ocurre
segn la lnea recta a lo largo de la que se aplica dicha fuerza.
3. Para cada fuerza que acta sobre un cuerpo ocurre una reaccin igual y contra-
ria. De este modo, las acciones mutuas de dos cuerpos siempre son iguales y
dirigidas en sentido opuesto.

En el estudio de la dinmica resulta especialmente interesante la segunda ley de


Newton, que puede ser escrita como
F =ma (19.1)

donde F es la fuerza resultante que acta sobre un cuerpo de masa m, y con una
aceleracin lineal a aplicada sobre el centro de gravedad del cuerpo.
19.2. Sistema de Deteccin de Colisiones [469]

Desde el punto de vista de la mecnica en videojuegos, la masa puede verse como


una medida de la resistencia de un cuerpo al movimiento (o al cambio en su mo-
vimiento). A mayor masa, mayor resistencia para el cambio en el movimiento. Se-
gn la segunda ley de Newton que hemos visto anteriormente, podemos expresar que
a = F/m, lo que nos da una impresin de cmo la masa aplica resistencia al movi-
miento. As, si aplicamos una fuerza constante e incrementamos la masa, la acelera-
cin resultante ser cada vez menor.
El centro de masas (o de gravedad) de un cuerpo es el punto espacial donde, si
2
Modelo se aplica una fuerza, el cuerpo se desplazara sin aplicar ninguna rotacin.
(Ecuaciones) 1 Proceso
Fsico
Un sistema dinmico puede ser denido como cualquier coleccin de elemen-
3 tos que cambian sus propiedades a lo largo del tiempo. En el caso particular de las
Algoritmo
de Simulacin
4 simulaciones de cuerpo rgido nos centraremos en el cambio de posicin y rotacin.
Programa
(Implementacin)
As, nuestra simulacin consistir en la ejecucin de un modelo matemtico que
5
describe un sistema dinmico en un ordenador. Al utilizar modelos, se simplica el
Simulacin sistema real, por lo que la simulacin no describe con total exactitud el sistema simu-
(Ejecucin) lado.

Figura 19.8: Etapas en la construc-


cin de un motor de simulacin f- Habitualmente se emplean los trminos de interactividad y tiempo real de
sica. modo equivalente, aunque no lo son. Una simulacin interactiva es aquella
que consigue una tasa de actualizacin suciente para el control por medio de
una persona. Por su parte, una simulacin en tiepo real garantiza la actuali-
zacin del sistema a un nmero jo de frames por segundo. Habitualmente los
motores de simulacin fsica proporcionan tasas de frames para la simulacin
interactiva, pero no son capaces de garantizar Tiempo Real.

En multitud de ocasiones es necesario denir restricciones que denan lmites a


zi ciertos aspectos de la simulacin. Los controladores son elementos que generan en-
zi-1 tradas a la simulacin y que tratan de controlar el comportamiento de los objetos que
estn siendo simulados. Por ejemplo, un controlador puede intentar mantener cons-
tante el ngulo entre dos eslabones de una cadena robtica (ver Figura 19.9).

C19
e i-2
Enl
ac
i-1i 19.2. Sistema de Deteccin de Colisiones
xx
i-1i
La responsabilidad principal del Sistema de Deteccin de Colisiones (SDC) es
calcular cundo colisionan los objetos de la escena. Para calcular esta colisin, los
Figura 19.9: Un controlador puede objetos se representan internamente por una forma geomtrica sencilla (como esferas,
intentar que se mantenga constante cajas, cilindros...).
el ngulo i formado entre dos es-
labones de un brazo robtico. Adems de comprobar si hubo colisin entre los objetos, el SDC se encarga de
proporcionar informacin relevante al resto de mdulos del simulador fsico sobre las
Test de Interseccin propiedades de la colisin. Esta informacin se utiliza para evitar efectos indeseables,
como la penetracin de un objeto en otro, y consegir la estabilidad en la simulacin
En realidad, el Sistema de Detec-
cin de Colisiones puede entender- cuando el objeto llega a la posicin de equilibrio.
se como un mdulo para realizar
pruebas de interseccin complejas.
[470] CAPTULO 19. SIMULACIN FSICA

19.2.1. Formas de Colisin


Como hemos comentado anteriormente, para calcular la colisin entre objetos,
es necesario proporcionar una representacin geomtrica del cuerpo que se utilizar
para calcular la colisin. Esta representacin interna se calcular para determinar la
posicin y orientacin del objeto en el mundo. Estos datos, con una descripcin ma-
temtica mucho ms simple y eciente, son diferentes de los que se emplean en la
representacin visual del objeto (que cuentan con un mayor nivel de detalle).
Habitualmente se trata de simplicar al mximo la forma de colisin. Aunque
el SDC soporte objetos complejos, ser preferible emplear tipos de datos simples,
siempre que el resultado sea aceptable. La Figura 19.10 muestra algunos ejemplos de
aproximacin de formas de colisin para ciertos objetos del juego.
Multitud de motores de simulacin fsica separan la forma de colisin de la trans-
formacin interna que se aplica al objeto. De esta forma, como muchos de los ob-
jetos que intervienen en el juego son dinmicos, basta con aplicar la transformacin
a la forma de un modo computacionalmente muy poco costoso. Adems, separando
la transformacin de la forma de colisin es posible que varias entidades del juego
compartan la misma forma de colisin.

Algunos motores de simulacin fsica permiten compartir la misma descrip-


cin de la forma de colisin entre entidades. Esto resulta especialmente til
en juegos donde la forma de colisin es compleja, como en simuladores de
carreras de coches.

Como se muestra en la Figura 19.10, las entidades del juego pueden tener dife- (a)
rentes formas de colisin, o incluso pueden compartir varias primitivas bsicas (para
representar por ejemplo cada parte de la articulacin de un brazo robtico).
El Mundo Fsico sobre el que se ejecuta el SDC mantiene una lista de todas las
entidades que pueden colisionar empleando habitualmente una estructura global Sin-
gleton. Este Mundo Fsico es una representacin del mundo del juego que mantiene
la informacin necesaria para la deteccin de las colisiones. Esta separacin evita que (b)
el SDC tenga que acceder a estructuras de datos que no son necesarias para el clculo
de la colisin.
Los SDC mantienen estructuras de datos especcas para manejar las colisiones,
proporcionando informacin sobre la naturaleza del contacto, que contiene la lista de
las formas que estn intersectando, su velocidad, etc...
Para gestionar de un modo ms eciente las colisiones, las formas que suelen uti- (c)
lizarse con convexas. Una forma convexa es aquella en la que un rayo que surja desde
su interior atravesar la superce una nica vez. Las supercies convexas son mucho
Figura 19.10: Diferentes formas de
ms simples y requieren menor capacidad computacional para calcular colisiones que
colisin para el objeto de la imagen.
las formas cncavas. Algunas de las primitivas soportadas habitualmente en SDC son: (a) Aproximacin mediante una ca-
ja. (b) Aproximacin mediante un
Esferas. Son las primitivas ms simples y ecientes; basta con denir su centro volumen convexo. (c) Aproxima-
y radio (uso de un vector de 4 elementos). cin basada en la combinacin de
varias primitivas de tipo cilndrico.
Cajas. Por cuestiones de eciencia, se suelen emplear cajas lmite alineadas con
los ejes del sistema de coordenadas (AABB o Axis Aligned Bounding Box). Las Formas de colisin
cajas AABB se denen mediante las coordenadas de dos extremos opuestos. El A cada cuerpo dinmico se le aso-
principal problema de las cajas AABB es que, para resultar ecientes, requie- cia habitualmente una nica forma
ren estar alineadas con los ejes del sistema de coordenas global. Esto implica de colisin en el SDC.
19.2. Sistema de Deteccin de Colisiones [471]

Figura 19.11: Gestin de la forma de un objeto empleando cajas lmite alineadas con el sistema de referen-
cia universal AABBs. Como se muestra en la gura, la caja central realiza una aproximacin de la forma
del objeto muy pobre.

que si el objeto rota, como se muestra en la Figura 19.11, la aproximacin de


forma puede resultar de baja calidad. Por su eciencia, este tipo de cajas suelen
emplearse para realizar una primera aproximacin a la interseccin de objetos
para, posteriormente, emplear formas ms precisas en el clculo de la colisin.
Por su parte, las cajas OBB (Oriented Bounding Box) denen una rotacin
relativa al sistema de coordenadas. Su descripcin es muy simple y permiten
calcular la colisin entre primitivas de una forma muy eciente.
Cilindros. Los cilindros son ampliamente utilizados. Se denen mediante dos
puntos y un radio. Una extensin de esta forma bsica es la cpsula, que es un
cuerpo compuesto por un cilindro y dos semiesferas (ver Figura 19.12). Pue-
de igualmente verse como el volumen resultante de desplazar una esfera entre
dos puntos. El clculo de la interseccin con cpsulas es ms eciente que con
esferas o cajas, por lo que se emplean para el modelo de formas de colisin en

C19
formas que son aproximadamente cilndricas (como las extremidades del cuerpo
humano).
Volmenes convexos. La mayora de los SDC permiten trabajar con volmenes
convexos (ver Figura 19.10). La forma del objeto suele representarse interna-
mente mediante un conjunto de n planos. Aunque este tipo de formas es menos
eciente que las primitivas estudiadas anteriormente, existen ciertos algoritmos
como el GJK que permiten optimizar los clculos en este tipo de formas.
Malla poligional. En ciertas ocasiones puede ser interesante utilizar mallas ar-
bitrarias. Este tipo de supercies pueden ser abiertas (no es necesario que de-
nan un volumen), y se construyen como mallas de tringulos. Las mallas poli-
gonales se suelen emplear en elementos de geometra esttica, como elementos
del escenario, terrenos, etc. (ver Figura 19.13) Este tipo de formas de colisin
son las ms complejas computacionalmente, ya que el SDC debe probar con ca-
da tringulo. As, muchos juegos tratan de limitar el uso de este tipo de formas
de colisin para evitar que el rendimiento se desplome.
Formas compuestas. Este tipo de formas se utilizan cuando la descripcin de
Figura 19.12: Algunas primitivas un objeto se aproxima ms convenientemente con una coleccin de formas. Este
de colisin soportadas en ODE: Ca- tipo de formas es la aproximacin deseable en el caso de que tengamos que uti-
jas, cilindros y cpsulas. La imagen lizar objetos cncavos, que no se adaptan adecuadamente a volmenes convexos
inferior muestra los AABBs asocia-
dos a los objetos.
(ver Figura 19.14).
[472] CAPTULO 19. SIMULACIN FSICA

19.2.2. Optimizaciones
La deteccin de colisiones es, en general, una tarea que requiere el uso intensivo de
la CPU. Por un lado, los clculos necesarios para determianr si dos formas intersecan
no son triviales. Por otro lado, muchos juegos requiren un alto nmero de objetos en
la escena, de modo que el nmero de test de interseccin a calcular rpidamente crece.
En el caso de n objetos, si empleamos un algoritmo de fuerza bruta tendramos una
complejidad O(n2 ). Es posible utilizar ciertos tipos de optimizaciones que mejoran
esta complejidad inicial:

Coherencia Temporal. Este tipo de tcnica de optimizacin (tambin llamada


coherencia entre frames), evita recalcular cierto tipo de informacin en cada
frame, ya que entre pequeos intervalos de tiempo los objetos mantienen las
posiciones y orientaciones en valores muy similares. Figura 19.13: Bullet soporta la de-
nicin de mallas poligonales ani-
Particionamiento Espacial. El uso de estructuras de datos de particionamiento madas. En este ejemplo de las de-
especial permite comprobar rpidamente si dos objetos podran estar intersecan- mos ociales, el suelo est anima-
do si comparten la misma celda de la estructura de datos. Algunos esquemas de do y los objetos convexos colisio-
particionamiento jerrquico, como rboles octales, BSPs o rboles-kd permiten nan respondiendo a su movimiento.
optimizar la deteccin de colisiones en el espacio. Estos esquemas tienen en co-
mn que el esquema de particionamiento comienza realizando una subdivisin
general en la raz, llegando a divisiones ms nas y especcas en las hojas. Los
objetos que se encuentran en una determinada rama de la estructura no pueden
estar colisionando con los objetos que se encuentran en otra rama distinta.
Barrido y Poda (SAP). En la mayora de los motores de simulacin fsica se
emplea un algoritmo Barrido y Poda (Sweep and Prune). Esta tcnica ordena
las cajas AABBs de los objetos de la escena y comprueba si hay intersecciones
entre ellos. El algoritmo Sweep and Prune hace uso de la Coherencia temporal
frame a frame para reducir la etapa de ordenacin de O(n log(n)) a O(n).

En muchos motores, como en Bullet, se utilizan varias capas o pasadas para de-
tectar las colisiones. Primero suelen emplearse cajas AABB para comprobar si los
objetos pueden estar potencialmente en colisin (deteccin de la colisin amplia). A
continuacin, en una segunda capa se hacen pruebas con volmenes generales que
engloban los objetos (por ejemplo, en un objeto compuesto por varios subobjetos, se a) b)
calcula una esfera que agrupe a todos los subobjetos). Si esta segunda capa de colisin
da un resultado positivo, en una tercera pasada se calculan las colisiones empleando
las formas nales.

19.2.3. Preguntando al sistema...


Figura 19.14: El modelo de una si-
En el mdulo 2 ya estudiamos algunas de las funcionalidades que se pueden en- lla es un objeto que no se adapta
contrar en sistemas de deteccin de colisiones. El objetivo es poder obtener resultados bien a un volumen convexo (a). En
a ciertas consultas sobre el primer objeto que intersecar con un determinado rayo, si (b) se ha utilizado una forma com-
hay objetos situados en el interior de un determinado volumen, etc. puesta deniendo dos cajas.
A continuacin veremos dos de las principales collision queries que pueden en-
contrarse habitualmente en un SDC:

Ray Casting. Este tipo de query requiere que se especique un rayo, y un ori-
gen. Si el rayo interseca con algn objeto, se devolver un punto o una lista de
puntos. Como vimos, el rayo se especica habitualmente mediante una ecua-
cin paramtrica de modo que p(t) = po + td, siendo t el parmetro que toma
19.3. Dinmica del Cuerpo Rgido [473]

d d
d d
Po
Po Po Po

a) b) c) d)

Figura 19.15: Clculo de los puntos de colisin empleando Shape Casting. a) La forma inicialmente se
encuentra colisionando con algn objeto de la escena. b) El SDC obtiene un punto de colisin. c) La forma
interseca con dos objetos a la vez. d) En este caso el sistema calcula todos los puntos de colisin a lo largo
de la trayectoria en la direccin de d.

valores entre 0 y 1. d nos dene el vector direccin del rayo, que determinar la
Uso de Rayos distancia mxima de clculo de la colisin. Po nos dene el punto de origen del
rayo. Este valor de t que nos devuelve la query puede ser fcilmente convertido
El Ray Casting se utiliza amplia- al punto de colisin en el espacio 3D.
mente en videojuegos. Por ejemplo,
para comprobar si un personaje es- Shape Casting. El Shape Casting permite determinar los puntos de colisin de
t dentro del rea de visin de otro una forma que viaja en la direccin de un vector determinado. Es similar al Ray
personaje, para detectar si un dispa-
ro alcanza a un enemigo, para que Casting, pero en este caso es necesario tener en cuenta dos posibles situaciones
los vehculos permanezcan en con- que pueden ocurrir:
tacto con el suelo, etc.
1. La forma sobre la que aplicamos Shape Casting est inicialmente interse-
Uso de Shape Casting cando con al menos un objeto que evita que se desplace desde su posicin
Un uso habitual de estas queries inicial. En este caso el SDC devolver los puntos de contacto que pueden
permite determinar si la cmara en- estar situados sobre la supercie o en el interior del volumen.
tra en colisin con los objetos de la
escena, para ajustar la posicin de 2. La forma no interseca sobre ningn objeto, por lo que puede desplazarse
los personajes en terrenos irregula- libremente por la escena. En este caso, el resultado de la colisin suele ser
res, etc. un punto de colisin situado a una determinada distancia del origen, aun-
que puede darse el caso de varias colisiones simultneas (como se muestra
Lista de colisiones en la Figura 19.15). En muchos casos, los SDC nicamente devuelven el

C19
Para ciertas aplicaciones puede ser resultado de la primera colisin (una lista de estructuras que contienen el
igualmente conveniente obtener la valor de t, el identicador del objeto con el que han colisionado, el punto
lista de todos los objetos con los que de contacto, el vector normal de la supercie en ese punto de contacto, y
se interseca a lo largo de la trayec-
toria, como se muestra en la Figu- algunos campos extra de informacin adicional).
ra 19.15.d).

19.3. Dinmica del Cuerpo Rgido


La simulacin del movimiento de los objetos del juego forma parte del estudio
de cmo las fuerzas afectan al comportamiento de los objetos. El mdulo del motor
de simulacin que se encarga de la dinmica de los objetos estudia cmo cambian
su posicin en el tiempo. Hasta hace pocos aos, los motores de simulacin fsica se
centraban en estudiar exclusivamente la dinmica de cuerpos rgidos1 , que permite
simplicar el clculo mediante dos suposiciones:

Los objetos en la simulacin obedecen las leyes del movimiento de Newton


(estudiadas en la Seccin 19.1.3). As, no se tienen en cuenta ningn tipo de
efecto cuntico ni relativista.
1 Aunque en la actualidad se encuentran soportadas de una forma muy eciente otras tcnicas, como se

muestran en la Figura 19.16


[474] CAPTULO 19. SIMULACIN FSICA

a b c

Figura 19.16: Ejemplo de simulacin de cuerpos blandos con Bullet. a) Uso de softbodies con formas
convexas. b) Simulacin de una tela sostenida por cuatro puntos. c) Simulacin de cuerdas.

Todos los objetos que intervienen en la simulacin son perfectamente slidos y


no se deforman. Esto equivale a armar que su forma es totalmente constante.

En el clculo de la variacin de la posicin de los objetos con el tiempo, el motor


de simulacin necesita resolver ecuaciones diferenciales, que cuentan como variable
independiente el tiempo. La resolucin de estas ecuaciones habitualmente no puede
realizarse de forma analtica (es imposible encontrar expresiones simples que relacio-
nen las posiciones y velocidades de los objetos en funcin del tiempo), por lo que
deben usarse mtodos de integracin numrica.
Gracias a los mtodos de integracin numrica, es posible resolver las ecuaciones
diferenciales en pasos de tiempo, de modo que la solucin en un instante de tiempo
sirve como entrada para el siguiente paso de integracin. La duracin de cada paso de
integracin suele mantenerse constante t.
Uno de los mtodos ms sencillos que se pueden emplear es el de Euler, supo-
niendo que la velocidad del cuerpo es constante durante el incremento de tiempo. El
mtodo tambin presenta buenos resultados cuando t es sucientemente pequeo.
En trminos generales, este mtodo no es sucientemente preciso, y tiene problemas
de convergencia y de estabilidad (el sistema se vuelve inestable, no converge y hace
que la simulacin explote). La alternativa ms utilizada en la actualidad es el mtodo
de integracin de Verlet, por su bajo error y su eciencia computacional en la evalua-
cin de las expresiones.

19.4. Restricciones
Las restricciones sirven para limitar el movimiento de un objeto. Un objeto sin
ninguna restriccin tiene 6 grados de libertad. Las restricciones se usan en multitud de
situaciones en desarrollo de videojuegos, como en puertas, suspensiones de vehculos,
cadenas, cuerdas, etc. A continuacin enumeraremos brevemente los principales tipos
de restricciones soportadas por los motores de simulacin fsica.

Punto a punto. Este es el tipo de restricciones ms sencillas; los objetos estn


conectados por un punto. Los objetos tienen libertad de movimiento salvo por
el hecho de que tienen que mantenerse conectados por ese punto.
19.5. Introduccin a Bullet [475]

Restriccin Restriccin
Punto a Muelle
Punto Rgido

Articulacin Articulacin
Bola Articulacin Prismtica
Bisagra Pistn

Figura 19.17: Representacin de algunas de las principales restricciones que pueden encontrarse en los
motores de simulacin fsica.

Muelle rgido. Un muelle rgido (Stiff Spring) funciona como una restriccin
de tipo punto a punto salvo por el hecho de que los objetos estn separados por
una determinada distancia. As, puede verse como unidos por una barra rgida
que los separa una distancia ja.
Bisagras. Este tipo de restriccin limitan el movimiento de rotacin en un deter-
minado eje (ver Figura 19.17). Este tipo de restricciones pueden denir adems
un lmite de rotacin angular permitido entre los objetos (para evitar, por ejem-
plo, que el codo de un brazo robtico adopte una posicin imposible).
Pistones. Este tipo de restricciones, denominadas en general restricciones pris-
mticas, permiten limitar el movimiento de traslacin a un nico eje. Estas res-
tricciones podran permitir opcionalmente rotacin sobre ese eje.
Bolas. Estas restricciones permiten denir lmites de rotacin exibles, esta-
bleciendo puntos de anclaje. Sirven por ejemplo para modelar la rotacin que

C19
ocurre en el hombro de un personaje.
Otras restricciones. Cada motor permite una serie de restricciones especcas,
como planares (que restringen el movimiento en un plano 2D), cuerdas, cadenas
de objetos, rag dolls (denidas como una coleccin de cuerpos rgidos conecta-
dos utilizando una estructura jerrquica), etc...

19.5. Introduccin a Bullet


Como se ha comentado al inicio del captulo, Bullet es una biblioteca de simula-
cin fsica muy utilizada, tanto en la industria del videojuego, como en la sntesis de
imagen realista. Algunas de sus caractersticas principales son:

Est desarrollada ntegramente en C++, y ha sido diseada de modo que tenga


el menor nmero de dependencias externas posibles.
Se distribuye bajo licencia Zlib (licencia libre compatible con GPL), y ha sido
utilizada en proyectos profesionales en multitud de plataformas, entre las que
destacan PlayStation 3, XBox 360, Wii, Linux, Windows, MacOSX, iPhone y
Android.
[476] CAPTULO 19. SIMULACIN FSICA

Cuenta con un integrador muy estable y rpido. Permite el clculo de dinmi-


ca de cuerpo rgido, dinmicas de vehculos y diversos tipos de restricciones
(bisagras, pistones, bolas, etc...).
Permite dinmica de SoftBodies, como telas, cuerdas y deformacin de volme-
nes arbitrarios.
Las ltimas versiones permiten descargar algunos clculos a la GPU, empleando
OpenCL. La utilizacin de esta funcionalidad se encuentra en fase experimental,
y requiere la instalacin de versiones recientes de los drivers de la tarjeta grca.
Est integrado en multitud de paquetes de sntesis de imagen realista (bien de
forma interna o mediante plugins), como en Blender, Maya, Softimage, Houdi-
ni, Cinema4D, etc...
Permite importar y exportar archivos Collada.

Existen multitud de videojuegos comerciales que han utilizado Bullet como motor
de simulacin fsica. Entre otros se pueden destacar Grand Theft Auto IV (de Red
Dead Redemption), Free Realms (de Sony), HotWheels (de BattleForce), Blood Drive
(de Activision) o Toy Story 3 (The Game) (de Disney).
De igual forma, el motor se ha utilizado en pelculas profesionales, como Hancock
(de Sony Pictures), Bolt de Walt Disney, Sherlock Holmes (de Framestore) o Shrek 4
(de DreamWorks).
Muchos motores grcos y de videojuegos permiten utilizar Bullet. La comunidad
ha desarrollado multitud de wrappers para facilitar la integracin en Ogre, Crystal
Space, Irrlich y Blitz3D entre otros.
En el diseo de Bullet se prest especial atencin en conseguir un motor fcilmente
adaptable y modular. Tal y como se comenta en su manual de usuario, el desarrollador
puede utilizar nicamente aquellos mdulos que necesite (ver Figura 19.19):

Utilizacin exclusiva del componente de deteccin de colisiones.


Utilizacin del componente de dinmicas de cuerpo rgido sin emplear los com-
ponentes de SoftBody.
Utilizacin de pequeos fragmentos de cdigo de la biblioteca.
Extensin de la biblioteca para las necesidades especcas de un proyecto.
Eleccin entre utilizar precisin simple o doble, etc...
Figura 19.18: Multitud de video-
juegos AAA utilizan Bullet como
19.5.1. Pipeline de Fsicas de Cuerpo Rgido motor de simulacin fsica.

Dinmica Bullet
.dea, .bsp, .obj...

Bullet dene el pipeline de procesamiento fsico como se muestra en la Figu-


Plugins

SoftBody MultiHilo
ra 19.20. El pipeline se ejecuta desde la izquierda a la derecha, comenzando por la
etapa de clculo de la gravedad, y nalizando con la integracin de las posiciones Dinmica
(actualizando la transformacin del mundo). Cada vez que se calcula un paso de la Cuerpo Rgido
simulacin stepSimulation en el mundo, se ejecutan las 7 etapas denidas en la
imagen anterior. Deteccin de Colisiones
Contenedores, Gestin de
El Pipeline comienza aplicando la fuerza gravedad a los objetos de la escena. Memoria, Bib. Matemtica...
Posteriormente se realiza una prediccin de las transformaciones calculando la po-
sicin actual de los objetos. Hecho esto se calculan las cajas AABBs, que darn una Figura 19.19: Esquema de los prin-
estimacin rpida de la posicin de los objetos. Con estas cajas se determinan los cipales mdulos funcionales de Bu-
pares, que consiste en calcular si las cajas AABBs se solapan en algn eje. Si no hay llet.
19.5. Introduccin a Bullet [477]

Inicio Time Step Time Step Time Step Time Step Time Step Fin

Dinmica Deteccin de Colisiones Dinmica


Aplicar Predecir Calcular Detectar Puntos Definir Resolver Integrar
Gravedad Transf. AABBs Pares Contacto Restricciones Restricciones Posicin

Formas AABBs Pares Puntos Velocidades Masa Restricciones


Colisin Objetos Superpuestos Contacto Transformadas Inercia Articulac.

Datos de Colisin Datos de Dinmicas

Figura 19.20: Pipeline de Bullet. En la parte superior de la imagen se describen las principales etapas
computacionales, y en la parte inferior las estructuras de datos ms importantes.

ningn solape, podemos armar que no hay colisin. De otra forma, hay que realizar
un estudio ms detallado del caso. Si hay posibilidad de contacto, se pasa a la siguien-
te etapa de calcular los contactos, que calcula utilizando la forma de colisin real del
objeto el punto de contacto exacto. Estos puntos de contacto se pasan a la ltima etapa
de clculo de la dinmica, que comienza con la resolucin de las restricciones, don-
de se determina la respuesta a la colisin empleando las restricciones de movimientos
que se han denido en la escena. Finalmente se integran las posiciones de los objetos,
obteniendo las posiciones y orientaciones nuevas para este paso de simulacin.
Tipos bsicos Las estructuras de datos bsicas que dene Bullet se dividen en dos grupos: los da-
tos de colisin, que dependen nicamente de la forma del objeto (no se tiene en cuenta
Bullet cuenta con un subconjunto las propiedades fscas, como la masa o la velocidad asociada al objeto), y los datos
de utilidades matemticas bsicas
con tipos de datos y operadores de- de propiedades dinmicas que almacenan las propiedades fsicas como la masa, la

C19
nidos como btScalar (escalar en inercia, las restricciones y las articulaciones.
punto otante), btVector3 (vector en
el espacio 3D), btTransform (trans- En los datos de colisin se distinguen las formas de colisin, las cajas AABBs, los
formacin afn 3D), btQuaternion, pares superpuestos que mantiene una lista de parejas de cajas AABB que se solapan
btMatrix3x3... en algn eje, y los puntos de contacto que han sido calculados como resultado de la
colisin.

19.5.2. Hola Mundo en Bullet


Para comenzar, estudiaremos el Hola Mundo en Bullet, que denir una esfera
que cae sobre un plano. Primero instalaremos las bibliotecas, que pueden descargarse
de la pgina web del proyecto2 . Desde el directorio donde tengamos el cdigo des-
comprimido, ejecutamos:

cmake . -G "Unix Makefiles" -DINSTALL_LIBS=ON


make
sudo make install

Obviamente, es necesario tener instalado cmake para compilar la biblioteca. A


continuacin estudiaremos el cdigo de la simulacin.
2 http://code.google.com/p/bullet/
[478] CAPTULO 19. SIMULACIN FSICA

Listado 19.1: Hello World en Bullet.


1 #include <iostream>
2 #include <btBulletDynamicsCommon.h>
3
4 int main (void) {
5 btBroadphaseInterface* broadphase = new btDbvtBroadphase();
6 btDefaultCollisionConfiguration* collisionConfiguration =
7 new btDefaultCollisionConfiguration();
8 btCollisionDispatcher* dispatcher = new btCollisionDispatcher(
collisionConfiguration);
9 btSequentialImpulseConstraintSolver* solver = new
btSequentialImpulseConstraintSolver;
10 btDiscreteDynamicsWorld* dynamicsWorld = new
btDiscreteDynamicsWorld(dispatcher,broadphase,solver,
collisionConfiguration);
11
12 // Definicion de las propiedades del mundo ---------------------
13 dynamicsWorld->setGravity(btVector3(0,-10,0));
14
15 // Creacion de las formas de colision --------------------------
16 btCollisionShape* groundShape =
17 new btStaticPlaneShape(btVector3(0,1,0),1);
18 btCollisionShape* fallShape = new btSphereShape(1);
19
20 // Definicion de los cuerpos rigidos en la escena --------------
21 btDefaultMotionState* groundMotionState = new
btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),
btVector3(0,-1,0)));
22 btRigidBody::btRigidBodyConstructionInfo
23 groundRigidBodyCI(0,groundMotionState,groundShape,btVector3
(0,0,0));
24 btRigidBody* gRigidBody = new btRigidBody(groundRigidBodyCI);
25 dynamicsWorld->addRigidBody(gRigidBody);
26
27 btDefaultMotionState* fallMotionState =
28 new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),
btVector3(0,50,0)));
29 btScalar mass = 1;
30 btVector3 fallInertia(0,0,0);
31 fallShape->calculateLocalInertia(mass,fallInertia);
32 btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(mass,
fallMotionState,fallShape,fallInertia);
33 btRigidBody* fallRigidBody = new btRigidBody(fallRigidBodyCI);
34 dynamicsWorld->addRigidBody(fallRigidBody);
35
36 // Bucle principal de la simulacion ----------------------------
37 for (int i=0 ; i<300 ; i++) {
38 dynamicsWorld->stepSimulation(1/60.f,10); btTransform trans;
39 fallRigidBody->getMotionState()->getWorldTransform(trans);
40 std::cout << "Altura: " << trans.getOrigin().getY() << std::
endl;
41 }
42
43 // Finalizacion (limpieza) ----------------------------------
44 dynamicsWorld->removeRigidBody(fallRigidBody);
45 delete fallRigidBody->getMotionState(); delete fallRigidBody;
46 dynamicsWorld->removeRigidBody(gRigidBody);
47 delete gRigidBody->getMotionState(); delete gRigidBody;
48 delete fallShape; delete groundShape;
49 delete dynamicsWorld; delete solver;
50 delete collisionConfiguration;
51 delete dispatcher; delete broadphase;
52
53 return 0;
54 }

El ejemplo anterior podra compilarse con un sencillo makele como se muestra a


continuacin:
19.5. Introduccin a Bullet [479]

Listado 19.2: Makele para hello world.


1 CXXFLAGS := pkg-config --cflags bullet
2 LDLIBS := pkg-config --libs-only-l bullet
3
4 all: HelloWorldBullet
5
6 clean:
7 rm HelloWorldBullet *~

Altura: 49.9972 Una vez compilado el programa de ejemplo, al ejecutarlo obtenemos un resultado
Altura: 49.9917 puramente textual, como el mostrado en la Figura 19.21. Como hemos comentado
Altura: 49.9833 anteriormente, los Bullet est diseado para permitir un uso modular. En este primer
Altura: 49.9722 ejemplo no se ha hecho uso de ninguna biblioteca para la representacin grca de la
Altura: 49.9583
Altura: 49.9417 escena.
Altura: 49.9222 Si representamos los 300 valores de la altura de la esfera, obtenemos una grca
Altura: 49.9 como muestra la Figura 19.22. Como vemos, cuando la esfera (de 1 metro de radio)
Altura: 49.875 llega a un metro del suelo (medido desde el centro), rebota levemente y a partir del

Altura: 49.8472 frame 230 aproximadamente se estabiliza.
El primer include denido en la lnea 2 del programa anterior se encarga de
Altura: 49.8167
Altura: 49.7833
Altura: 49.7472 incluir todos los archivos de cabecera necesarios para crear una aplicacin que haga
Altura: 49.7083 uso del mdulo de dinmicas (cuerpo rgido, restricciones, etc...). Necesitaremos ins-
Altura: 49.6667 tanciar un mundo sobre el que realizar la simulacin. En nuestro caso crearemos un
... mundo discreto, que es el adecuado salvo que tengamos objetos de movimiento muy
Figura 19.21: Salida por pantalla rpido sobre el que tengamos que hacer una deteccin de su movimiento (en cuyo caso
de la ejecucin del Hola Mundo en podramos utilizar la clase an experimental btContinuousDynamicsWorld).
Bullet.
60
50
Creacin del mundo
40
30 Como vimos en la seccin 19.2.2, los motores de simulacin fsica utilizan varias
20 capas para detectar las colisiones entre los objetos del mundo. Bullet permite utilizar
10 algoritmos en una primera etapa (broadphase) que utilizan las cajas lmite de los ob-
0 jetos del mundo para obtener una lista de pares de cajas que pueden estar en colisin.
50 100 150 200 250
Esta lista es una lista exhaustiva de todas las cajas lmite que intersecan en alguno

C19
Figura 19.22: Representacin de de los ejes (aunque, en etapas posteriores lleguen a descartarse porque en realidad no
los valores obtenidos en el Hola colisionan).
Mundo.
Muchos sistemas de simulacin fsica se basan en el Teorema de los Ejes Separa-
dos. Este teorema dice que si existe un eje sobre el que la proyeccin de dos formas
convexas no se solapan, entonces podemos asegurar que las dos formas no colisio-
nan. Si no existe dicho eje y las dos formas son convexas, podemos asegurar que las
formas colisionan. Si las formas son cncavas, podra ocurrir que no colisionaran (de-
pendiendo de la suerte que tengamos con la forma de los objetos). Este teorema se
puede visualizar fcilmente en 2 dimensiones, como se muestra en la Figura 19.23.a.
El mismo teorema puede aplicarse para cajas AABB. Adems, el hecho de que las
cajas AABB estn perfectamente alineadas con los ejes del sistema de referencia, hace
que el clculo de la proyeccin de estas cajas sea muy rpida (simplemente podemos
utilizar las coordenadas mnimas y mximas que denen las cajas).
La Figura 19.23 representa la aplicacin de este teorema sobre cajas AABB. En
el caso c) de dicha gura puede comprobarse que la proyeccin de las cajas se solapa
en todos los ejes, por lo que tendramos un caso potencial de colisin. En realidad,
como vemos en la gura las formas que contienen dichas cajas AABB no colisionan,
pero este caso deber ser resuelto por algoritmos de deteccin de colisin de menor
granularidad.
[480] CAPTULO 19. SIMULACIN FSICA

Y Y Y
a) b) c)

B B B
Pr. B

A A A
Pr. A

Proyeccin Proyeccin
de A en X de B en X X Ejemplo de AABBs que X Ejemplo de AABBs que X
no intersecan intersecan

Figura 19.23: Aplicacin del Teorema de los Ejes Separados y uso con cajas AABB. a) La proyeccin
de las formas convexas de los objetos A y B estn separadas en el eje X, pero no en el eje Y. Como
podemos encontrar un eje sobre el que ambas proyecciones no intersecan, podemos asegurar que las formas
no colisionan. b) El mismo principio aplicados sobre cajas AABB denidas en objetos cncavos, que no
intersecan. c) La proyeccin de las cajas AABB se solapa en todos los ejes. Hay un posible caso de colisin
entre formas.


En la lnea 5 creamos un objeto que implementa un algoritmo de optimizacin
en una primera etapa broadphase. En posteriores etapas Bullet calcular las colisiones
exactas. Existen dos algoritmos bsicos que implementa Bullet para mejorar al apro-
ximacin a ciegas de complejidad O(n2 ) que comprobara toda la lista de pares. Estos
algoritmos aaden nuevas parejas de cajas que en realidad no colisionan, aunque en
general mejoran el tiempo de ejecucin.

rbol AABB Dinmico. Este algoritmo est implementado en la clase btDbvt-


Broadphase. Se construye un rbol AABB de propsito general que se utiliza
tanto en la primera etapa de optimizacin broadphase como en la deteccin de
colisiones entre softbodies. Este tipo de arbol se adapta automticamente a las
dimensiones del mundo, y la insercin y eliminacin de objetos es ms rpido
que en SAP.
Barrido y Poda (SAP). La implementacin de Sweep and Prune de Bullet re-
quiere que el tamao del mundo sea conocido de previamente. Este mtodo es
el que tiene mejor comportamiento en mundos dinmicos donde la mayora de
los objetos tienen poco movimiento. Se implementa en el conjunto de clases
AxisSweep (con versiones de diverso nivel de precisin).

gran cantidad de objetos que no coli-


Tras esta primera poda, hemos eliminado
sionan. A continuacin, en las lneas 6-8 se crea un objeto de conguracin de la
colisin, que nos permitir adaptar los parmetros de los algoritmos utilizados en pos-
teriores fases para comprobar la colisin. El btCollisionDispatcher es una clase que
permite aadir funciones de callback para ciertos tipos de eventos (como por ejemplo, Figura 19.24: El objeto solver se


cuando los objetos se encuentren cerca). encargar de resolver la interaccin
El objeto solver (lnea 9 ) se encarga de que los objetos interacten adecuada-
entre objetos.

mente, teniendo en cuenta la gravedad, las fuerzas, colisiones y restricciones. En este


ejemplo se ha utilizado la versin secuencial (que implementa el mtodo de Gauss Reutiliza!!
Seidel proyectado (PGS), para resolver problemas lineales), aunque existen versiones Es buena prctica reutilizar formas


que hacen uso de paralelismo empleando hilos. de colisin. Si varios objetos de la

En la lnea 10 se instancia el mundo. Este objeto nos permitir aadir los objetos
escena pueden compartir la misma
forma de colisin (por ejemplo, to-

aplicar gravedad, y avanzar el paso de la simulacin. En concreto, en la


del mundo, dos los enemigos pueden gestionar-
lnea 12 se establece una de las propiedades del mundo, la gravedad, asignando un
se con una misma esfera de un de-
terminado radio), es buena prctica
valor de 10m/s en el eje Y, por lo que se aplicar sobre ese eje la fuerza de gravedad. compartir esa forma de colisin en-
tre todos ellos.
19.5. Introduccin a Bullet [481]

la memoria que ha utilizado ex-


Al nalizar, Bullet requiere que el usuario libere
plcitamente. De esta forma, a partir de la lnea 42 se eliminan todos los elementos
que han sido creados a lo largo de la simulacin.
Hasta aqu hemos denido lo que puede ser el esqueleto bsico de una aplicacin
mnima de Bullet. Vamos a denir a continuacin los objetos que forman nuestra
escena y el bucle de simulacin.

Formas de Colisin

Como hemos comentado al inicio de la seccin, crearemos un objeto plano que


servir como suelo sobre el que dejaremos caer una esfera. Cada uno de estos cuerpos
necesita una forma de colisin, que internamente nicamente se utiliza para calcular
la colisin (no tiene propiedades de masa, inercia, etc...).
Las formas de colisin no tienen una posicin en el mundo; se adjuntan a los
cuerpos rgidos. La eleccin de la forma de colisin adecuada, adems de mejorar el
rendimiento de la simulacin, ayuda a conseguir una simulacin de calidad. Bullet
permite el uso de primitivas (que implementan algoritmos de deteccin de colisiones
muy optimizados) o mallas poligonales. Las primitivas soportadas por Bullet son:

btSphereShape. Esfera; la primitiva ms simple y rpida.


btBoxShape. La caja puede tener cualquier relacin de aspecto.
btCylinderShape. Cilindro con cualquier relacin de aspecto.
btCapsuleShape. Cpsula con cualquier relacin de aspecto.
btConeShape. Los conos se denen con el vrtice en el (0,0,0).
btMultiSphereShape. Forma convexa especial denida como combinacin de
esferas.
btCompoundShape. No es una primitiva bsica en s, sino que permite combi-

C19
nar formas de cualquier tipo (tanto primitivas como formas de colisin de tipo
malla que veremos a continuacin). Permite obtener formas compuestas, como
la que se estudi en la Figura 19.14.

Las formas de colisin de tipo malla soportadas son:

btConvexHull. Este es el tipo de forma de tipo malla ms rpido. Se dene co-


mo una nube de vrtices que forman la forma convexa ms pequea posible. El
nmero de vrtices debe ser pequeo para que la forma funcione adecuadamen-
te. El nmero de vrtices puede reducirse empleando la utilidad proporcionada
por la clase btShapeHull. Existe una versin similar a este tipo llamado btCon-
vexTriangleMeshShape, que est formado por caras triangulares, aunque es
deseable utilizar btConvexHull porque es mucho ms eciente.
btBvhTriangleMeshShape. Malla triangular esttica. Puede tener un nmero
considerable de polgonos, ya que utiliza una jerarqua interna para calcular la
colisin. Como la construccin de esta estructura de datos puede llevar tiempo,
se recomienda serializar el rbol para cargarlo rpidamente. Bullet incorpora
utilidades para la serializacin y carga del rbol BVH.
btHeighteldTerrainShape. Malla poligonal esttica optimizada descrita por
un mapa de alturas.
[482] CAPTULO 19. SIMULACIN FSICA

btStaticPlaneShape. Plano innito esttico. Se especica mediante un vector


de direccin y una distancia respecto del origen del sistema de coordenadas.

Algunos consejos sobre el uso de formas de colisin en Bullet:


Trata de utilizar las formas de colisin ms ecientes: esferas, cajas,
cilindros y ConvexHull.
Los objetos dinmicos deben tener una forma cerrada y denida por
un volumen nito. Algunas formas de colisin como los planos o las
triangleMesh no tienen un volumen nito, por lo que nicamente pue-
den ser usados como cuerpos estticos.
Reutiliza siempre que sea posible las formas de colisin.


En la lnea 16-17 creamos una forma de colisin de tipo plano, pasando como
parmetro el vector normal del plano (vector unitario en Y), y una distancia respecto
del origen. As, el plano de colisin queda denido por la ecuacin y = 1.
De igual modo, la forma de colisin del cuerpo que dejaremos caer sobre el suelo
ser una esfera de radio 1 metro (lnea 18 ).
Una vez denidas las formas de colisin, las posicionaremos asocindolas a ins-
tancias de cuerpos rgidos. En la siguiente subseccin aadiremos los cuerpos rgidos
al mundo.

Cuerpos Rgidos

Para aadir cuerpos rgidos, necesitamos primero denir el concepto de MotionS-


tate en Bullet. Un MotionState es una abstraccin proporcionada por Bullet para ac-
tualizar la posicin de los objetos que sern dibujados en el game loop. Empleando
MotionStates, Bullet se encargar de actualizar los objetos que sern representados por
el motor grco. En la siguiente seccin estudiaremos cmo trabajar con MotionStates
en Ogre.
Gracias al uso de MotionStates, nicamente se actualiza la posicin de los objetos MotionState propio
que se han movido. Bullet se encarga adems de la interpolacin de movimientos,
aislando al programador de esta tarea. Cuando se consulte la posicin de un objeto, Para implementar nuestro propio
MotionState basta con here-
por defecto se devolver la correspondiente al ltimo paso de simulacin calculado. dar de btMotionState
Sin embargo, cada vez que el motor grco necesite redibujar la escena, Bullet se y sobreescribir los mtodos
encargar de devolver la transformacin interpolada. getWorldTransform y
setWorldTransform.
Los MotionStates deben utilizarse en dos situaciones:

1. Cuando se crea un cuerpo. Bullet determina la posicin inicial del cuerpo en el


momento de su creacin, y requiere una llamada al MotionState.
2. Cuando se quiera actualizar la posicin del objeto.

Bullet proporciona un MotionState por


defecto que podemos utilizar para instan-
ciar cuerpos rgidos. As, en la lnea 21 se utiliza el MotionState por defecto especi-
cando como rotacin la identidad, y trasladando el origen -1 unidad en Y 3 .
3 Esta traslacin se realiza a modo de ejemplo para compensar la traslacin de 1 unidad cuando se cre

la forma de colisin del plano. El resultado sera el mismo si en ambos parmetros se hubiera puesto 0.
19.5. Introduccin a Bullet [483]


En las lneas 22-23 se emplea la estructura btRigidBodyConstructionInfo para
establecer la informacin para crear un cuerpo rgido.

Los componentes de la estructura btRigidBodyConstructionInfo se copian a


la informacin del cuerpo cuando se llama al constructor. Si queremos crear
un grupo de objetos con las mismas propiedades, puede crearse una nica
estructura de este tipo y pasarla al constructor de todos los cuerpos.

El primer parmetro es la masa del objeto. Estableciendo una masa igual a cero
(primer parmetro), se crea un objeto esttico (equivale a establecer una masa innita,
de modo que el objeto no se puede mover). El ltimo parmetro es la inercia del suelo

(que se establece igualmente a 0, por ser un objeto esttico).
En la lnea 24 creamos el objeto rgido a partir de la informacin
almacenada en
la estructura anterior, y lo aadimos al mundo en la lnea 25 .

La creacin de la esfera sigue un patrn de cdigo similar. En la lnea 27 se crea
el MotionState
para el objeto que dejaremos caer, situado a 50 metros del suelo (lnea


28 ).
En las lneas 29-31 se establecen las propieades del cuerpo; una masa de 1Kg y
se llama a un mtodo de btCollisionShape que nos calcula la inercia de una esfera a
partir de su masa.

Bucle Principal

Para nalizar, el bucle principal se ejecuta en las lneas 36-41 . El bucle se ejecuta
300 veces, llamando al paso de simulacin con un intervalo de 60hz. En cada paso de
la simulacin se imprime la altura de la esfera sobre el suelo.
Como puede verse, la posicin y la orientacin del objeto dinmico se encapsulan
en un objeto de tipo btTransform. Como se coment anteriormente, esta informacin

C19
puede obtenerse a partir del MotionState asociado al btRigidBody a travs de la es-

tructura de inicializacin btRigidBodyConstructInfo.
Tiempos! El mtodo para avanzar un paso en la simulacin (lnea 38 ) requiere dos parme-
tros. El primero describe la cantidad de tiempo que queremos avanzar la simulacin.
Cuidado, ya que las funciones de Bullet tiene un reloj interno que permite mantener constante esta actualizacin, de for-
clculo de tiempo habitualmente
devuelven los resultados en milise- ma que sea independiente de la tasa de frames de la aplicacin. El segundo parmetro
gundos. Bullet trabaja en segundos, es el nmero de subpasos que debe realizar bullet cada vez que se llama stepSimula-
por lo que sta es una fuente habi- tion. Los tiempos se miden en segundos.
tual de errores.
El primer parmetro debe ser siempre menor que el nmero de subpasos multipli-
cado por el tiempo jo de cada paso tStep < maxSubStep tF ixedStep .
No olvides... Decrementando el tamao de cada paso de simulacin se est aumentado la reso-
lucin de la simulacin fsica. De este modo, si en el juego hay objetos que atravie-
Cuando cambies el valor de los
tiempos de simulacin, recuerda san objetos (como paredes), es posible decrementar el xedTimeStep para aumentar
calcular el nmero de subpasos de la resolucin. Obviamente, cuando se aumenta la resolucin al doble, se necesitar
simulacin para que la ecuacin si- aumentar el nmero de maxSubSteps al doble, lo que requerir aproximadamente el
ga siendo correcta. doble de tiempo de CPU para el mismo tiempo de simulacin fsica.
[484] CAPTULO 19. SIMULACIN FSICA

Supongamos que queremos un tiempo jo de simulacin en cada paso de


60hz. En el mejor de los casos, nuestro videojuego tendr una tasa de 120fps
(120hz), y en el peor de los casos de 12fps. As, tStep en el primer caso ser
1/120 = 0,0083, y en el segundo tStep = 1/12 = 0,083. Por su parte,
el tiempo del paso jo para la simulacin sera 1/60 = 0,017. Para que la
expresin anterior se cumpla, en el primer caso el nmero de subpasos basta
con 1 0,0083 < 1 0,017. En el peor de los casos, necesitaremos que el
nmero de pasos sea al menos de 5 para que se cumpla la expresin 0,083 <
5 0,017. Con estas condiciones tendramos que establecer el nmero de
subpasos a 5 para no perder tiempo de simulacin

Cuando se especica un valor de maxSubSteps > 1, Bullet interpolar el movi-


miento (y evitar al programador tener que realizar los clculos). Si maxSubSteps ==
1, no realizar interpolacin.

19.6. Integracin manual en Ogre


Como se ha estudiado en la seccin anterior, los MotionStates se denen en Bullet
para abstraer la representacin de los rigidBody en el motor de dibujado. A conti-
nuacin deniremos manualmente una clase MyMotionState que se encargar de la
actualizacin de las entidades en Ogre.
La implementacin de un MotionState propio debe heredar de la clase btMotionS-
tate de bullet, y sobreescribir los mtodos getWorldTransform y setWorldTransform
(por lo que se denen como virtuales). Ambos mtodos toman como parmetro un
objeto de la clase btTransform, que se utiliza para la representacin interna de trans-
formaciones de cuerpo rgido.
El siguiente listado muestra la declaracin de la clase, que tiene dos variables btTransform
miembro; el nodo asociado a ese MotionState (que tendremos que actualizar en set-

Las transformaciones de cuerpo r-
WorldTransform), y la propia transformacin que devolveremos en getWorldTrans-
form (lnea 7 ).
gido estn formadas nicamente
por traslaciones y rotaciones (sin
escalado). As, esta clase utiliza
internamente un btVector3 para la
Listado 19.3: MyMotionState.h traslacin y una matriz 3x3 para al-
macenar la rotacin.
1 #include <Ogre.h>
2 #include <btBulletDynamicsCommon.h>
3
4 class MyMotionState : public btMotionState {
5 protected:
6 Ogre::SceneNode* _visibleobj;
7 btTransform _pos;
8
9 public:
10 MyMotionState(const btTransform &initialpos,
11 Ogre::SceneNode* node);
12 virtual ~MyMotionState();
13 void setNode(Ogre::SceneNode* node);
14 virtual void getWorldTransform(btTransform &worldTr) const;
15 virtual void setWorldTransform(const btTransform &worldTr);
16 };

La denicin de la clase es directa. El siguiente listado muestra los mtodos ms


importantes en su implementacin (el destructor no tiene que eliminar el nodo; se
encargar Ogre al liberar los recursos).
19.6. Integracin manual en Ogre [485]


En las lneas 15-19 se dene el mtodo principal, que actualiza la posicin y
rotacin del SceneNode en Ogre. Dado que Bullet y Ogre denen clases distintas
para trabajar con Vectores y Cuaternios, es necesario obtener la rotacin y posicin
y asignarlo
del objeto por separado al nodo mediante las llamadas a setOrientation y
setPosition (lneas 17 y 19 ).

La llamada a setWorldTransform puede retornar en la lnea 15 si no se ha estable-
cido nodo en el constructor. Se habilita un mtodo especco para establecer el nodo
ms adelante. Esto es interesante si se quieren aadir objetos a la simulacin que no
tengan representacin grca.

Listado 19.4: MyMotionState.cpp


1 #include "MyMotionState.h"
2
3 MyMotionState::MyMotionState(const btTransform &initialpos,
4 Ogre::SceneNode *node) {
5 _visibleobj = node; _pos = initialpos;
6 }
7
8 void MyMotionState::setNode(Ogre::SceneNode *node)
9 { _visibleobj = node; }
10
11 void MyMotionState::getWorldTransform (btTransform &worldTr) const
12 { worldTr = _pos; }
13
14 void MyMotionState::setWorldTransform(const btTransform &worldTr){
15 if(NULL == _visibleobj) return; // Si no hay nodo, return
16 btQuaternion rot = worldTr.getRotation();
17 _visibleobj->setOrientation(rot.w(), rot.x(), rot.y(), rot.z());
18 btVector3 pos = worldTr.getOrigin();
19 _visibleobj->setPosition(pos.x(), pos.y(), pos.z());
20 }

Una vez creada la clase que utilizaremos para denir el MotionState, la utilizare-
mos en el Hola Mundo construido en la seccin anterior para representar la simula-
cin con el plano y la esfera. El resultado que tendremos se muestra en la Figura 19.25.
Para la construccin del ejemplo emplearemos como esqueleto base el FrameListener

C19
del Mdulo 2 del curso.

Listado 19.5: MyFrameListener.cpp


1 #include "MyFrameListener.h"
2 #include "MyMotionState.h"
3
4 MyFrameListener::MyFrameListener(RenderWindow* win,
5 Camera* cam, OverlayManager *om, SceneManager *sm) {
6 // .... Omitida parte de la inicializacion
7 _broadphase = new btDbvtBroadphase();
8 _collisionConf = new btDefaultCollisionConfiguration();
9 _dispatcher = new btCollisionDispatcher(_collisionConf);
10 _solver = new btSequentialImpulseConstraintSolver;
11 _world = new btDiscreteDynamicsWorld(_dispatcher,_broadphase,
12 _solver,_collisionConf);
13 _world->setGravity(btVector3(0,-10,0));
14 CreateInitialWorld();
15 }
16
17 MyFrameListener::~MyFrameListener() {
Figura 19.25: Fragmento del resul- 18 _world->removeRigidBody(_fallRigidBody);
tado de integracin del Hola Mun- 19 delete _fallRigidBody->getMotionState();
20 delete _fallRigidBody;
do de Bullet en Ogre, empleando
21 // ... Omitida la eliminacion de los objetos
la clase de MyMotionState denida 22 }
anteriormente. 23
[486] CAPTULO 19. SIMULACIN FSICA

24 void MyFrameListener::CreateInitialWorld() {
25 // Creacion de la entidad y del SceneNode -----------------------
26 Plane plane1(Vector3::Vector3(0,1,0), 0);
27 MeshManager::getSingleton().createPlane("p1",
28 ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane1,
29 200, 200, 1, 1, true, 1, 20, 20, Vector3::UNIT_Z);
30 SceneNode* node = _sceneManager->createSceneNode("ground");
31 Entity* groundEnt = _sceneManager->createEntity("planeEnt","p1");
32 groundEnt->setMaterialName("Ground");
33 node->attachObject(groundEnt);
34 _sceneManager->getRootSceneNode()->addChild(node);
35
36 // Creamos las formas de colision -------------------------------
37 _groundShape = new btStaticPlaneShape(btVector3(0,1,0),1);
38 _fallShape = new btSphereShape(1);
39
40 // Creamos el plano ---------------------------------------------
41 MyMotionState* groundMotionState = new MyMotionState(
42 btTransform(btQuaternion(0,0,0,1),btVector3(0,-1,0)), node);
43 btRigidBody::btRigidBodyConstructionInfo groundRigidBodyCI
44 (0,groundMotionState,_groundShape,btVector3(0,0,0));
45 _groundRigidBody = new btRigidBody(groundRigidBodyCI);
46 _world->addRigidBody(_groundRigidBody);
47
48 // Creamos la esfera --------------------------------------------
49 Entity *entity2= _sceneManager->createEntity("ball","ball.mesh");
50 SceneNode *node2= _sceneManager->getRootSceneNode()->
createChildSceneNode();
51 node2->attachObject(entity2);
52 MyMotionState* fallMotionState = new MyMotionState(
53 btTransform(btQuaternion(0,0,0,1),btVector3(0,50,0)), node2);
54 btScalar mass = 1; btVector3 fallInertia(0,0,0);
55 _fallShape->calculateLocalInertia(mass,fallInertia);
56 btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(
57 mass,fallMotionState,_fallShape,fallInertia);
58 _fallRigidBody = new btRigidBody(fallRigidBodyCI);
59 _world->addRigidBody(_fallRigidBody);
60 }
61
62 bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
63 Real deltaT = evt.timeSinceLastFrame;
64 int fps = 1.0 / deltaT;
65
66 _world->stepSimulation(deltaT, 5); // Actualizar fisica
67
68 _keyboard->capture();
69 if (_keyboard->isKeyDown(OIS::KC_ESCAPE)) return false;
70
71 btVector3 impulse;
72 if (_keyboard->isKeyDown(OIS::KC_I)) impulse=btVector3(0,0,-.1);
73 if (_keyboard->isKeyDown(OIS::KC_J)) impulse=btVector3(-.1,0,0);
74 if (_keyboard->isKeyDown(OIS::KC_K)) impulse=btVector3(0,0,.1);
75 if (_keyboard->isKeyDown(OIS::KC_L)) impulse=btVector3(.1,0,0);
76 _fallRigidBody->applyCentralImpulse(impulse);
77
78 // Omitida parte del codigo fuente (manejo del raton, etc...)
79 return true;
80 }
81
82 bool MyFrameListener::frameEnded(const Ogre::FrameEvent& evt) {
83 Real deltaT = evt.timeSinceLastFrame;
84 _world->stepSimulation(deltaT, 5); // Actualizar fisica
85 return true;
86 }
19.7. Hola Mundo en OgreBullet [487]

En la implementacin del FrameListener es necesario mantener como variables


miembro el conjunto de objetos necesarios en la simulacin de Bullet. As, la im-
plementacin del constructor (lneas 4-15 dene
los objetos necesarios para crear
el mundo de simulacin de Bullet (lnea 11-12 ). Estos objetos sern liberados en el
destructor de la clase (ver lneas 17-22 ). De igual modo, los dos cuerpos rgidos que

intervienen en la simulacin y sus formas asociadas son variables miembro de la clase.
Variables miembro El mtodo CreateInitialWorld (denido en las lneas 24-60 ) se realiza como lti-
Mantener los objetos como varia-
mo paso en el constructor. En este mtodo se aaden a la escena de Ogre y al mundo
bles miembro de la clase no deja de Bullet los elementos que intervendrn en la simulacin (en este caso la esfera y el

de ser una mala decisin de diseo. plano).
La creacin de las entidades y los nodos para el plano y la esfera (lneas 26-34 y
En la seccin 19.7 veremos cmo se

49-51 respectivamente) ya han sido estudiadas en elMdulo 2 del curso. La creacin


gestionan listas dinmicas con los
objetos y las formas de colisin.
de las formas para el plano y la esfera (lneas 37-38 ) fueron descritas en el cdigo de
la seccin anterior. Cabe destacar que la malla exportada en ball.mesh (lnea 49 )
debe tener un radio de 1 unidad, para que la forma de colisin denida en la lnea 38
se adapte bien a su representacin grca.
Cada objeto tendr asociado un MotionState de la clase denida anteriormente,
que recibir la rotacin y traslacin inicial, y el puntero al nodoque guarda la entidad

a representar. En el caso del plano, se dene en las lneas 41-42 , y la esfera en 52-53 .
Por ltimo, tendremos que aadir cdigo especco en los mtodos de retrollama-

da de actualizacin frame. En el listado
del anterior se muestra el cdigo de frameS-
tarted (lneas 62-86 ). En la lnea 66 se actualiza el paso de simulacin de Bullet,
transcurrido
empleando el tiempo desdela ltima actualizacin. Adems, si el usuario
pulsa las teclas I , J , K o L (lneas 71-76 ), se aplicar una fuerza sobre la esfera.
Veremos ms detalles sobre la aplicacin de impulsos a los objetos en el ejemplo de
la seccin 19.8.
Actualizacin del mundo Para nalizar, se muestran los ags del Makele necesarios para integrar Bullet en
En el ejemplo anterior, se actuali- los ejemplos anteriores.
za el paso de simulacin igualmente
en el mtodo frameEnded. Bullet se
encarga de interpolar las posiciones Listado 19.6: Fragmento de Makele
de dibujado de los objetos. Si se eli-

C19
mina la llamada a stepSimulation, el 1 # Flags de compilacion -----------------------------
resultado de la simulacin es mucho 2 CXXFLAGS := -I $(DIRHEA) -Wall pkg-config --cflags OGRE pkg-
ms brusco. config --cflags bullet
3
4 # Flags del linker -------------------------------
5 LDFLAGS := pkg-config --libs-only-L OGRE pkg-config --libs-only-
l bullet
6 LDLIBS := pkg-config --libs-only-l OGRE pkg-config --libs-only-l
bullet -lOIS -lGL -lstdc++

19.7. Hola Mundo en OgreBullet


El desarrollo de un wrapper completo del motor Bullet puede ser una tarea costosa.
Afortunadamente existen algunas alternativas que facilitan la integracin del motor en
Ogre, como el proyecto OgreBullet. OgreBullet se distribuye bajo una licencia MIT
libre, y es multiplataforma.
[488] CAPTULO 19. SIMULACIN FSICA

Segn el autor, OgreBullet puede ser considerado un wrapper en versin estable.


La noticacin de bugs, peticin de nuevos requisitos y ejemplos se mantiene en un
apartado especco de los foros de Ogre 4 . Uno de los principales problemas relativos
al uso de este wrapper es la falta de documentacin, por lo que en algunos casos la
nica alternativa es la consulta de los archivos de cabecera de la distribucin.

OgreBullet puede descargarse de la pgina de complementos ocial de


Ogre en: http://ogreaddons.svn.sourceforge.net/viewvc/
ogreaddons/trunk/ogrebullet/?view=tar

En el siguiente ejemplo crearemos una escena donde se aadirn de forma din-


mica cuerpos rgidos. Adems de un puntero al DynamicsWorld (variable miembro
_world), el FrameListener mantiene un puntero a un objeto _debugDrawer (lnea 7 ),
que nos permite representar cierta informacin visual que facilita el depurado de la
En este primer ejemplo se activa el dibujado de las formas de colisin
aplicacin.
(lnea 8 ), tal y como se muestra en la Figura 19.26.
Este objeto permite aadir otros elementos que faciliten la depuracin, como l-
neas, puntos de contacto, cajas AABBs, etc. Los mtodos relativos a la representacin
de texto 3D en modo depuracin estn previstos pero an no se encuentran desarro-

llados. Este objetode depuracin debe ser aadido igualmente al grafo de escena de
Ogre (lneas 9-11 del siguiente listado).
La denicin del mundo en OgreBullet
requiere que se especiquen los lmites de
simulacin. En las lneas 14-15 se crea una caja AABB descrita por los vrtices de
sus esquinas que dene el volumen en el que se realizar la simulacin fsica.Este
lmite, junto con el vector de gravedad, permitirn crear el mundo (lneas 18-19 ).

Listado 19.7: Constructor


1 MyFrameListener::MyFrameListener(RenderWindow* win,
2 Camera* cam, OverlayManager *om, SceneManager *sm) {
3 _numEntities = 0; // Numero de Formas instanciadas
4 _timeLastObject = 0; // Tiempo desde ultimo objeto anadido
5 Figura 19.26: Salida del primer
6 // Creacion del modulo de debug visual de Bullet --------------- ejemplo con OgreBullet. El objeto
7 _debugDrawer = new OgreBulletCollisions::DebugDrawer(); _debugDrawer muestra las formas
8 _debugDrawer->setDrawWireframe(true); de colisin asociadas a las entida-
9 SceneNode *node = _sceneManager->getRootSceneNode()-> des de Ogre.
10 createChildSceneNode("debugNode", Vector3::ZERO);
11 node->attachObject(static_cast<SimpleRenderable*>(_debugDrawer));
12 // Creacion del mundo (definicion de los limites y la gravedad)
13 AxisAlignedBox worldBounds = AxisAlignedBox
14 (Vector3(-10000,-10000,-10000), Vector3(10000,10000,10000));
15 Vector3 gravity = Vector3(0, -9.8, 0);
16 _world = new OgreBulletDynamics::DynamicsWorld(_sceneManager,
17 worldBounds, gravity);
18 _world->setDebugDrawer (_debugDrawer);
19 _world->setShowDebugShapes (true); // Muestra formas debug
20 CreateInitialWorld(); // Inicializa el mundo
21 }

4 Foros OgreBullet: http://www.ogre3d.org/addonforums/viewforum.php?f=12


19.7. Hola Mundo en OgreBullet [489]

El FrameListener mantiene dos colas de doble n (deque) de punteros a los Rigid-


Body (_bodies) y a las CollisionShape (_shapes), que facilitan la insercin y borrado
de elementos de un modo ms rpido que los vectores. As, cuando se aadan objetos
de forma dinmica a la escena, ser necesario aadirlos a estas estructuras para su
posterior liberacin en el destructor de la clase. El siguiente listado muestra la imple-
mentacin del destructor del FrameListener.

De igual modo es necesario liberar los recursos asociados al mundo dinmico y al
debugDrawer creado en el constructor (ver lneas 16-17 ).

Listado 19.8: Destructor


1 MyFrameListener::~MyFrameListener() {
2 // Eliminar cuerpos rigidos ------------------------------------
3 std::deque <OgreBulletDynamics::RigidBody *>::iterator
4 itBody = _bodies.begin();
5 while (_bodies.end() != itBody) {
6 delete *itBody; ++itBody;
7 }
8 // Eliminar formas de colision ---------------------------------
9 std::deque<OgreBulletCollisions::CollisionShape *>::iterator
10 itShape = _shapes.begin();
11 while (_shapes.end() != itShape) {
12 delete *itShape; ++itShape;
13 }
14 _bodies.clear(); _shapes.clear();
15 // Eliminar mundo dinamico y debugDrawer -----------------------
16 delete _world->getDebugDrawer(); _world->setDebugDrawer(0);
17 delete _world;
18 }


Para aadir un objeto de simulacin de OgreBullet debemos crear dos elementos
bsicos; por un
lado la CollisionShape (lneas ), y por otro lado el RigidBody
(lneas 17-18 ).
14-16

La asociacin del nodo de dibujado con el cuerpo rgido se establece en la misma


llamada en la que se asocia la forma de colisin al RigidBody. En la clase Ogre-
BulletDynamics::RigidBody existen dos mtodos que permiten asociar una forma de

C19
colisin a un RigidBody; setShape y setStaticShape. La segunda cuenta con varias
versiones; una de ellas no requiere especicar el SceneNode, y se corresponde con la
utilizada en la lnea 21 para aadir la forma de colisin al plano.
Aadir a las colas
Una vez aadido el objeto, deben Listado 19.9: CreateInitialWorld
aadirse las referencias a la forma 1 void MyFrameListener::CreateInitialWorld() {
de colisin y al cuerpo rgido en las 2 // Creacion de la entidad y del SceneNode -----------------------
colas de la clase (lnea 24). 3 Plane plane1(Vector3::Vector3(0,1,0), 0);
4 MeshManager::getSingleton().createPlane("p1",
5 ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane1,
6 200, 200, 1, 1, true, 1, 20, 20, Vector3::UNIT_Z);
7 SceneNode* node= _sceneManager->createSceneNode("ground");
8 Entity* groundEnt= _sceneManager->createEntity("planeEnt", "p1");
9 groundEnt->setMaterialName("Ground");
10 node->attachObject(groundEnt);
11 _sceneManager->getRootSceneNode()->addChild(node);
12
13 // Creamos forma de colision para el plano ----------------------
14 OgreBulletCollisions::CollisionShape *Shape;
15 Shape = new OgreBulletCollisions::StaticPlaneCollisionShape
16 (Ogre::Vector3(0,1,0), 0); // Vector normal y distancia
17 OgreBulletDynamics::RigidBody *rigidBodyPlane = new
18 OgreBulletDynamics::RigidBody("rigidBodyPlane", _world);
19
20 // Creamos la forma estatica (forma, Restitucion, Friccion) ----
[490] CAPTULO 19. SIMULACIN FSICA

21 rigidBodyPlane->setStaticShape(Shape, 0.1, 0.8);


22
23 // Anadimos los objetos Shape y RigidBody ----------------------
24 _shapes.push_back(Shape); _bodies.push_back(rigidBodyPlane);
25 }

La actualizacin del mundo se realiza de forma similar a la estudiada anterior- stepSimulation


de ejemplo
mente. La aplicacin adems, permite aadir objetos dinmicos cuando se
pulse la tecla B (lnea 9 ). A continuacin veremos el cdigo para aadir cuerpos La llamada a stepSimulation en
OgreBullet acepta dos parmetros
dinmicos. opcionales, el nmero de subpasos
de simulacin (por defecto a 1), y el
xedTimeStep (por defecto 1/60).
Listado 19.10: FrameStarted
1 bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
2 Ogre::Real deltaT = evt.timeSinceLastFrame;
3 _world->stepSimulation(deltaT); // Actualizar simulacion Bullet
4 _timeLastObject -= deltaT;
5
6 _keyboard->capture();
7 if (_keyboard->isKeyDown(OIS::KC_ESCAPE)) return false;
8 if ((_keyboard->isKeyDown(OIS::KC_B)) && (_timeLastObject <= 0))
9 AddDynamicObject();
10 // Omitido el resto del cogido del metodo ...
11 return true;
12 }

El siguiente listado implementa la funcionalidad de aadir cajas dinmicas a la


escena. Los objetos se crearn teniendo en cuenta la posicin y rotacin de la cmara.
la posicin de la
Para ello, se toma como vector de posicin inicial el calculado como
cmara desplazada 10 unidades segn su vector direccin (lneas 5-6 ).

Listado 19.11: AddDynamicObject


1 void MyFrameListener::AddDynamicObject() {
2 _timeLastObject = 0.25; // Segundos para anadir uno nuevo...
3
4 Vector3 size = Vector3::ZERO; // Tamano y posicion inicial
5 Vector3 position = (_camera->getDerivedPosition()
6 + _camera->getDerivedDirection().normalisedCopy() * 10);
7
8 // Creamos la entidad y el nodo de la escena -------------------
9 Entity *entity = _sceneManager->createEntity("Box" +
10 StringConverter::toString(_numEntities), "cube.mesh");
11 entity->setMaterialName("cube");
12 SceneNode *node = _sceneManager->getRootSceneNode()->
13 createChildSceneNode();
14 node->attachObject(entity);
15
16 // Obtenemos la bounding box de la entidad creada --------------
17 AxisAlignedBox boundingB = entity->getBoundingBox();
18 size = boundingB.getSize();
19 size /= 2.0f; // Tamano en Bullet desde el centro (la mitad)
20 OgreBulletCollisions::BoxCollisionShape *boxShape = new
21 OgreBulletCollisions::BoxCollisionShape(size);
22 OgreBulletDynamics::RigidBody *rigidBox = new
23 OgreBulletDynamics::RigidBody("rigidBox" +
24 StringConverter::toString(_numEntities), _world);
25 rigidBox->setShape(node, boxShape,
26 /* Restitucion, Friccion, Masa */ 0.6, 0.6, 5.0,
27 /* Pos. y Orient. */ position , Quaternion::IDENTITY);
28 rigidBox->setLinearVelocity(
29 _camera->getDerivedDirection().normalisedCopy() * 7.0);
30 _numEntities++;
31 // Anadimos los objetos a las deques ---------------------------
32 _shapes.push_back(boxShape); _bodies.push_back(rigidBox);
19.8. RayQueries [491]

33 }


En el listado
anterior, el nodo asociado a cada caja se aade en la llamada a setSha-
pe (lneas 25-27 ). Pese a que Bullet soporta multitud de propiedades en la estructura
btRigidBodyConstructionInfo, el wrapper se centra exclusivamente en la denicin de
la masa, y los coecientes de friccin y restitucin. La posicin inicial y el cuaternio
se indican igualmente en la llamada al mtodo, que nos abstrae de la necesidad de
denir el MotionState.
a laescena con una velocidad lineal relativa a la rotacin de
Las cajas se aaden
la cmara (ver lneas 28-29 ).

19.8. RayQueries
Al inicio del captulo estudiamos algunos tipos de preguntas que podan realizarse
al motor de simulacin fsica. Uno de ellos eran los RayQueries que permitan obtener
las formas de colisin que intersecaban con un determinado rayo.
Ogre? Bullet? Utilizaremos esta funcionalidad del SDC para aplicar un determinado impulso
al primer objeto que sea tocado por el puntero del ratn. De igual forma, en este
Recordemos que los objetos de si- ejemplo se aadirn objetos deniendo una forma de colisin convexa. El resultado
mulacin fsica no son conocidos
por Ogre. Aunque en el mdulo 2 de la simulacin (activando la representacin de las formas de colisin) se muestra en
del curso estudiamos los RayQue- la Figura 19.27.
ries en Ogre, es necesario realizar la
pregunta en Bullet para obtener las La llamada al mtodo AddDynamicObject recibe como parmetro un tipo enume-
referencias a los RigidBody. rado, que indica si queremos aadir una oveja o una caja. La forma de colisin de
la cajase calcula automticamente empleando la clase StaticMeshToShapeConverter
(lnea 17 ).

Reutiliza las formas de colisin! Tanto en el ejemplo de la seccin anterior


como en este cdigo, no se reutilizan las formas de colisin. Queda como ejer-
cicio propuesto para el lector mantener referencias a las formas de colisin

C19
(para las ovejas y para las cajas), y comprobar la diferencia de rendimiento
en frames por segundo cuando el nmero de objetos de la escena crece.

Listado 19.12: AddDynamicObject


1 void MyFrameListener::AddDynamicObject(TEDynamicObject tObject) {
2 // Omitido codigo anterior del metodo ---------------------------
3 Entity *entity = NULL;
4 switch (tObject) {
5 case sheep:
6 entity = _sceneManager->createEntity("Sheep" +
7 StringConverter::toString(_numEntities), "sheep.mesh");
8 break;
9 case box: default:
10 // (Omitido) Analogamente se carga el modelo de la caja...
11 }
12
13 SceneNode *node = _sceneManager->getRootSceneNode()->
14 createChildSceneNode();
15 node->attachObject(entity);
16
17 OgreBulletCollisions::StaticMeshToShapeConverter *
trimeshConverter = NULL;
[492] CAPTULO 19. SIMULACIN FSICA

18 OgreBulletCollisions::CollisionShape *bodyShape = NULL;


19 OgreBulletDynamics::RigidBody *rigidBody = NULL;
20
21 switch (tObject) {
22 case sheep:
23 trimeshConverter = new
24 OgreBulletCollisions::StaticMeshToShapeConverter(entity);
25 bodyShape = trimeshConverter->createConvex();
26 delete trimeshConverter;
27 break;
28 case box: default:
29 // (Omitido) Crear bodyShape como en el ejemplo anterior...
30 }
31
32 rigidBody = new OgreBulletDynamics::RigidBody("rigidBody" +
33 StringConverter::toString(_numEntities), _world);
34 // Omitido resto de codigo del metodo ---------------------------
35 }

El objeto de la clase StaticMeshToShapeConverter recibe como parmetro una En-


tity de Ogre en el constructor. Esta entidad puede ser convertida a multitud de formas
de colisin. En el momento de la creacin, la clase reduce el nmero de vrtices de la
forma de colisin.
Cuando se pincha con el botn derecho o izquierdo del ratn sobre algn objeto
la
de simulacin, se aplicar un impulso con diferente fuerza (denida en F , ver lnea
18 del siguiente cdigo). El mtodo pickBody se encarga de obtener el primer cuerpo
que colisiona con el rayo denido por la posicin de la cmara y el puntero del ratn.
Este mtodo devuelve igualmente en los dos primeros parmetros el punto de colisin
en el objeto y el rayo utilizado para construir el RayQuery.
El mtodo pickBody primero obtiene el rayo utilizando la funcionalidad
de Ogre,
empleando las coordenadas de pantalla normalizadas (lneas 20-21 ). Hecho esto, se
crea una Query que requiere como tercer parmetro la distancia mxima a la que se Figura 19.27: Resultado de la si-

la colisin, en la direccin del rayo, teniendo en cuenta su posicin inicial


mulacin del ejemplo.
calcular
(lnea 4 ).

en algn cuerpo (lnea 6 ), se devuelve el cuerpo y el punto de
Conversor a Shape

Si el rayo colisiona
colisin (lneas 7-11 ). Adems de la llamada a createCon-
vex, el conversor estudiado en el c-
digo anterior puede generar otras
formas de colisin con createSphe-
Listado 19.13: RayQuery en Bullet re, createBox, createTrimesh, crea-
1 RigidBody* MyFrameListener::pickBody (Vector3 &p, Ray &r, float x, teCylinder y createConvexDecom-
float y) { position entre otras.
2 r = _camera->getCameraToViewportRay (x, y);
3 CollisionClosestRayResultCallback cQuery =
4 CollisionClosestRayResultCallback (r, _world, 10000);
5 _world->launchRay(cQuery);
6 if (cQuery.doesCollide()) {
7 RigidBody* body = static_cast <RigidBody *>
8 (cQuery.getCollidedObject());
9 p = cQuery.getCollisionPoint();
10 return body;
11 }
12 return NULL;
13 }
14
15 bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
16 // Omitido codigo anterior del metodo ---------------------------
17 if (mbleft || mbright) { // Con botones del raton, impulso ------
18 float F = 10; if (mbright) F = 100;
19 RigidBody* body; Vector3 p; Ray r;
20 float x = posx/float(_win->getWidth()); // Pos x normalizada
21 float y = posy/float(_win->getHeight()); // Pos y normalizada
22 body = pickBody (p, r, x, y);
19.9. TriangleMeshCollisionShape [493]

23
24 if (body) {
25 if (!body->isStaticObject()) {
26 body->enableActiveState ();
27 Vector3 relPos(p - body->getCenterOfMassPosition());
28 Vector3 impulse (r.getDirection ());
29 body->applyImpulse (impulse * F, relPos);
30 }
31 }
32 }
33 // Omitido resto de codigo del metodo ---------------------------
34 }


Para nalizar, si hubo colisin con algn cuerpo que no sea esttico (lneas 24-25 ),
se aplicar un impulso. La llamada a enableActiveState permite activar un cuerpo. Por
defecto, Bullet automticamente desactiva objetos dinmicos cuando la velocidad es
menor que un determinado umbral.
Los cuerpos desactivados en realidad estn se encuentran en un estado de dormi-
dos, y no consumen tiempo de ejecucin salvo por la etapa de deteccin de colisin
broadphase. Esta etapa automticamente despierta a los objetos que estuvieran dor-

midos si se encuentra colisin con otros elementos de la escena.
Impulso En las lneas 27-29 se aplica un impulso sobre el objeto en la direccin del rayo
que se calcul desde la cmara, con una fuerza proporcional a F . El impulso es una
El impulso puede denirse como

F dt = (dp/dt)dt, siendo p el fuerza que acta en un cuerpo en un determinado intervalo de tiempo. El impulso
momento. implica un cambio en el momento, siendo la Fuerza denida como el cambio en el
momento. As, el impulso aplicado sobre un objeto puede ser denido como la integral
de la fuerza con respecto del tiempo.
El wrapper de OgreBullet permite denir un pequeo subconjunto de propiedades
de los RigidBody de las soportadas en Bullet. Algunas de las principales propiedades
son la Velocidad Lineal, Impulsos y Fuerzas. Si se requieren otras propiedades, ser
necesario acceder al objeto de la clase btRigidBody (mediante la llamada a getBulle-
tRigidBody) y especicar manualmente las propiedades de simulacin.

C19
19.9. TriangleMeshCollisionShape
En este ejemplo se cargan dos objetos como mallas triangulares estticas. El re-
sultado de la ejecucin puede verse en la Figura 19.28. Al igual que en el ejemplo
anterior, se utiliza la funcionalidad proporcionada por el conversor de mallas, pero
generando una TriangleMeshCollisionShape (lnea 11-12 ).

Listado 19.14: Static Mesh


1 void MyFrameListener::CreateInitialWorld() {
2 // Creacion del track ------------------------------------------
3 Entity *entity = _sceneManager->createEntity("track.mesh");
4 SceneNode *node = _sceneManager->createSceneNode("track");
5 node->attachObject(entity);
6
7 _sceneManager->getRootSceneNode()->addChild(node);
Figura 19.28: Resultado de la eje- 8 OgreBulletCollisions::StaticMeshToShapeConverter *
cucin del ejemplo de carga de ma- trimeshConverter = new
9 OgreBulletCollisions::StaticMeshToShapeConverter(entity);
llas triangulares. 10
11 OgreBulletCollisions::TriangleMeshCollisionShape *trackTrimesh =
12 trimeshConverter->createTrimesh();
13
14 OgreBulletDynamics::RigidBody *rigidTrack = new
15 OgreBulletDynamics::RigidBody("track", _world);
[494] CAPTULO 19. SIMULACIN FSICA

Figura 19.29: Conguracin de la malla esttica utilizada en el ejemplo. Es importante aplicar la escala y
rotacin a los objetos antes de su exportacin, as como las dimensiones del objeto ball para aplicar los
mismos lmites a la collision shape.

16 rigidTrack->setShape(node, trackTrimesh, 0.8, 0.95, 0,


17 Vector3::ZERO, Quaternion::IDENTITY);
18
19 delete trimeshConverter;
20 // (Omitido) Creacion del sumidero de forma similar ------------
21 }

Es importante consultar en la posicin de los generadores de objetos en el espacio


3D (ver Figura 19.29), as como las dimensiones de los objetos que van a intervenir en
la simulacin. Por ejemplo, las esferas se creaban con una forma de colisin de tipo
SphereCollisionShape de 0.02 unidades de radio porque su dimensin en el espacio
3D es de 0.04 unidades (ver Figura 19.29). De igual modo, una de las posiciones de
generacin es Vector3(-0.14, 1.07, -0.07) situada en el interior de una de las cajas.

19.10. Deteccin de colisiones


Una de las formas ms sencillas de detectar colisiones entre objetos del mundo es
iterar sobre los colectores de contactos (contact manifold). Los contact manifold son
en realidad caches que contienen los puntos de contacto entre parejas de objetos de
colisin. El siguiente listado muestra una forma de iterar sobre los pares de objetos en
el mundo dinmico.

Listado 19.15: DetectCollisionDrain.


1 void MyFrameListener::DetectCollisionDrain() {
2 btCollisionWorld *bulletWorld=_world->getBulletCollisionWorld();
3 int numManifolds=bulletWorld->getDispatcher()->getNumManifolds();
4
5 for (int i=0;i<numManifolds;i++) {
6 btPersistentManifold* contactManifold =
7 bulletWorld->getDispatcher()->getManifoldByIndexInternal(i);
8 btCollisionObject* obA =
9 static_cast<btCollisionObject*>(contactManifold->getBody0());
10 btCollisionObject* obB =
11 static_cast<btCollisionObject*>(contactManifold->getBody1());
12
13 Ogre::SceneNode* drain = _sceneManager->getSceneNode("drain");
14
19.10. Deteccin de colisiones [495]

15 OgreBulletCollisions::Object *obDrain =
16 _world->findObject(drain);
17 OgreBulletCollisions::Object *obOB_A = _world->findObject(obA);
18 OgreBulletCollisions::Object *obOB_B = _world->findObject(obB);
19
20 if ((obOB_A == obDrain) || (obOB_B == obDrain)) {
21 Ogre::SceneNode* node = NULL;
22 if ((obOB_A != obDrain) && (obOB_A)) {
23 node = obOB_A->getRootNode(); delete obOB_A;
24 }
25 else if ((obOB_B != obDrain) && (obOB_B)) {
26 node = obOB_B->getRootNode(); delete obOB_B;
27 }
28 if (node) {
29 std::cout << node->getName() << std::endl;
30 _sceneManager->getRootSceneNode()->
31 removeAndDestroyChild (node->getName());
32 }
33 }
34 }
35 }


OgreBullet... En la lnea 2 se obtiene el puntero directamente a la clase btCollisionWorld, que
se encuentra oculta en la implementacin de OgreBullet. Con este puntero se acceder
El listado anterior muestra adems
cmo acceder al objeto del mundo
directamente a la funcionalidad de Bullet sin emplear la clase de recubrimieneto de
de bullet, que permite utilizar gran OgreBullet. La clase btCollisionWorld sirve a la vez como interfaz y como contenedor

cantidad de mtodos que no estn de las funcionalidades relativas a la deteccin de colisiones.
Mediante la llamada a getDispatcher (lnea 3 ) se obtiene un puntero a la clase
implementados en OgreBullet.

btDispather, que se utiliza en la fase de colisin broadphase para la gestin de pares



de colisin. Esta clase nos permite obtener el nmero de colectores que hay activos en

cada instante. El bucle de las lneas 5-34 se encarga de iterar sobre los colectores. En
la lnea 6-7 se obtiene un puntero a un objeto de la clase btPersistentManifold. Esta
clase es una implementacin de una cach persistente mientras los objetos colisionen
en la etapa de colisin broadphase.

C19
Los puntos de contacto se crean en la etapa de deteccin de colisiones na
(narrow phase). La cache de btPersistentManifold puede estar vaca o conte-
ner hasta un mximo de 4 puntos de colisin. Los algoritmos de deteccin de
la colisin aaden y eliminan puntos de esta cach empleando ciertas heurs-
ticas que limitan el mximo de puntos a 4. Es posible obtener el nmero de
puntos de contacto asociados a la cache en cada instante mediante el mtodo
getNumContacts().


La cache de colisin mantiene punteros a los dos objetos que estn colisionando.
Estos objetos pueden obtenerse mediante la llamada a mtodos get (lneas 8-11 ).
La clase CollisionsWorld de OgreBullet proporciona un mtodo ndObject que

permite obtener un puntero aobjeto genrico a partir de un SceneNode o un btColli-
sionObject (ver lneas 15-18 ).

La ltima parte del cdigo (lneas 20-32 ) comprueba si alguno de los dos objetos
de la colisin son el sumidero. En ese caso, se obtiene el puntero al otro objeto (que
se corresponder con un objeto de tipo esfera creado dinmicamente), y se elimina de
la escena. As, los objetos en esta segunda versin del ejemplo no llegan a aadirse en
la caja de la parte inferior del circuito.
[496] CAPTULO 19. SIMULACIN FSICA

Figura 19.30: Ejemplo de deteccin de colisiones empleando colectores de contactos.

Otros mecanismos de colisin. En la documentacin de Bullet se comentan


brevemente otros mecanismos que pueden utilizarse para la deteccin de co-
lisiones, como los objetos de la clase btGhostObject. Los objetos de esta cla-
se pueden tener asociadas llamadas de callback de modo que se invoquen
automticamente cuando los objetos se solapen en la etapa de deteccin de
colisiones mediante el test de cajas AABB.

19.11. Restriccin de Vehculo


En esta seccin estudiaremos cmo utilizar un tipo de restriccin especca para la
denicin de vehculos. OgreBullet cuenta con abstracciones de alto nivel que traba-
jan internamente con llamadas a las clases derivadas btRaycastVehicle, que permiten
convertir un cuerpo rgido en un vehculo.
19.11. Restriccin de Vehculo [497]

Figura 19.31: Ejemplo de denicin de un vehculo en OgreBullet.

A continuacin estudiaremos algunos fragmentos de cdigo empleados en el si-


guiente ejemplo para la construccin del vehculo. Queda como ejercicio propuesto
aadir obstculos y elementos de interaccin en la escena empleando mallas triangu-
lares estticas.

Listado 19.16: Fragmento de MyFrameListener.h

C19
1 OgreBulletDynamics::WheeledRigidBody *mCarChassis;
2 OgreBulletDynamics::VehicleTuning *mTuning;
3 OgreBulletDynamics::VehicleRayCaster *mVehicleRayCaster;
4 OgreBulletDynamics::RaycastVehicle *mVehicle;
5 Ogre::Entity *mChassis;
6 Ogre::Entity *mWheels[4];
7 Ogre::SceneNode *mWheelNodes[4];
8 float mSteering;

En el anterior archivo de cabecera se denen ciertas variables miembro de la cla-


se que se utilizarn en la denicin del vehculo. mCarChassis es un puntero a una
clase que ofrece OgreBullet para la construccin de vehculos con ruedas. La clase
VehicleTuning es una clase de cobertura sobre la clase btVehicleTuning de Bullet que
permite especicar ciertas propiedades del vehculo (como la compresin, suspensin,
deslizamiento, etc).
VehicleRayCaster es una clase que ofrece un interfaz entre la simulacin del vehcu-
lo y el RayCasting (usado para localizar el punto de contacto entre el vehculo y el
suelo). La clase RaycastVehicle es una clase de cobertura sobre la clase base de Bu-
llet btRaycastVehicle. Las lneas 5-7 denen los nodos y entidades necesarias para el
chasis y las ruedas del vehculo. Finalmente, la varible Steering de la lnea 8 dene
la direccin del vehculo.
[498] CAPTULO 19. SIMULACIN FSICA


A continuacin estudiaremos ladenicin del vehculo en el mtodo CreateInitial-
World del FrameListener. La lnea 1 del siguiente listado dene el vector de altura del
chasis (elevacin sobre el suelo), y la altura de conexin de las ruedas en l (lnea 2 )

que ser utilizado ms adelante. En la construccin inicial del vehculo se establece la
direccin del vehculo como 0.0 (lnea 3 ).

La lneas 5-9 crean la entidad del chasis y el nodo que la contendr. La lnea 10
utiliza el vector de altura del chasis para posicionar el nodo del chasis.

Listado 19.17: Fragmento de CreateInitialWorld (I).


1 const Ogre::Vector3 chassisShift(0, 1.0, 0);
2 float connectionHeight = 0.7f;
3 mSteering = 0.0;
4
5 mChassis = _sceneManager->createEntity("chassis", "chassis.mesh");
6 SceneNode *node = _sceneManager->getRootSceneNode()->
createChildSceneNode ();
7
8 SceneNode *chassisnode = node->createChildSceneNode();
9 chassisnode->attachObject (mChassis);
10 chassisnode->setPosition (chassisShift);


El chasis tendr asociada una forma de colisin de tipo caja (lnea 1 ). Esta
caja
formar parte de una forma de colisin compuesta, que se dene en la lnea 2 , y a la
que se aade la caja anterior desplazada segn el vector chassisShift (lnea 3 ).

En la lnea 4 se dene el cuerpo rgido
del vehculo,
al que se asocia la forma de
colisin creada anteriormente (lnea 6 ). En la lnea 9 se establecen los valores
de
suspensin del vehculo, y se evita que el vehculo pueda desactivarse (lnea 8 ), de
modo que el objeto no se dormir incluso si se detiene durante un tiempo continua-
do.

Listado 19.18: Fragmento de CreateInitialWorld (II).


1 BoxCollisionShape* chassisShape = new BoxCollisionShape(Ogre::
Vector3(1.f,0.75f,2.1f));
2 CompoundCollisionShape* compound = new CompoundCollisionShape();
3 compound->addChildShape(chassisShape, chassisShift);
4 mCarChassis = new WheeledRigidBody("carChassis", _world);
5 Vector3 CarPosition = Vector3(0, 0, -15);
6 mCarChassis->setShape (node, compound, 0.6, 0.6, 800, CarPosition,
Quaternion::IDENTITY);
7 mCarChassis->setDamping(0.2, 0.2);
8 mCarChassis->disableDeactivation();

En el siguiente fragmento
se comienza deniendo algunos parmetros de tuning
del vehculo (lnea 1 ). Estos parmetros son la rigidez, compresin
y amortiguacin
de la suspensin y la friccin de deslizamiento. La lnea 5 establece el sistema de

coordenadas local del vehculo mediante los ndices derecho, superior y adelante.
Las lneas 7 y 8 denen los ejes que se utilizarn como direccin del vehculo y

en la denicin de las ruedas.
El bucle de las lneas 10-16 construye los nodos de las ruedas, cargando 4 instan-
cias de la malla wheel.mesh.
19.11. Restriccin de Vehculo [499]

El ejemplo desarrollado en esta seccin trabaja nicamente con las dos ruedas
delanteras (ndices 0 y 1) como ruedas motrices. Adems, ambas ruedas giran
de forma simtrica segn la variable de direccin mSteering. Queda propuesto
como ejercicio modicar el cdigo de esta seccin para que la direccin se
pueda realizar igualmente con las ruedas traseras, as como incorporar otras
opciones de motricidad (ver Listado de FrameStarted).

Listado 19.19: Fragmento de CreateInitialWorld (III).


1 mTuning = new VehicleTuning(20.2, 4.4, 2.3, 500.0, 10.5);
2 mVehicleRayCaster = new VehicleRayCaster(_world);
3 mVehicle = new RaycastVehicle(mCarChassis, mTuning,
mVehicleRayCaster);
4
5 mVehicle->setCoordinateSystem(0, 1, 2);
6
7 Ogre::Vector3 wheelDirectionCS0(0,-1,0);
8 Ogre::Vector3 wheelAxleCS(-1,0,0);
9
10 for (size_t i = 0; i < 4; i++) {
11 mWheels[i] = _sceneManager->createEntity("wheel"+i,"wheel.mesh");
12 mWheels[i]->setCastShadows(true);
13
14 mWheelNodes[i] = _sceneManager->getRootSceneNode()->
createChildSceneNode();
15 mWheelNodes[i]->attachObject (mWheels[i]);
16 }

El siguiente fragmento de listado se repite para cada rueda, calculando el punto


de conexin en funcin del ancho de cada rueda y la altura de conexin. Este punto
es pasado al mtodo addWheel, junto con informacin relativa a ciertas propiedades
fsicas de cada rueda. La variable isFrontWheel (ver lnea 3 ) indica si la rueda aa-
dida forma parte del conjunto de ruedas delanteras (en este caso, nicamente las dos
primeras ruedas tendrn esta variable a true en el momento de creacin.

C19
Listado 19.20: Fragmento de CreateInitialWorld (IV).
1 Ogre::Vector3 connectionPointCS0 (1-(0.3*gWheelWidth),
connectionHeight, 2-gWheelRadius);
2
3 mVehicle->addWheel(mWheelNodes[0], connectionPointCS0,
wheelDirectionCS0, wheelAxleCS, gSuspensionRestLength,
gWheelRadius, isFrontWheel, gWheelFriction, gRollInfluence);

Finalmente el mtodo de callback del FrameStarted se encarga de modicar la


fuerza que se aplica sobre el motor del vehculo cuando se utilizan los cursores supe-

rior e inferior del teclado. De igual modo, empleando los cursores
izquierdo y derecho
del teclado se modica la direccin del vehculo (ver lneas 11-15 ).

Listado 19.21: Fragmento de FrameStarted.


1 bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
2 // Omitido el codigo anterior...
3 mVehicle->applyEngineForce (0,0);
4 mVehicle->applyEngineForce (0,1);
5
6 if (_keyboard->isKeyDown(OIS::KC_UP)) {
7 mVehicle->applyEngineForce (gEngineForce, 0);
[500] CAPTULO 19. SIMULACIN FSICA

Figura 19.32: La gestin del determinismo puede ser un aspecto crtico en muchos videojuegos. El error
de determinismo rpidamente se propaga haciendo que la simulacin obtenga diferentes resultados.

8 mVehicle->applyEngineForce (gEngineForce, 1);


9 }
10
11 if (_keyboard->isKeyDown(OIS::KC_LEFT)) {
12 if (mSteering < 0.8) mSteering+=0.01;
13 mVehicle->setSteeringValue (mSteering, 0);
14 mVehicle->setSteeringValue (mSteering, 1);
15 }
16
17 // Omitido el resto del codigo...
18 }

19.12. Determinismo
El determinismo en el mbito de la simulacin fsica puede denirse de forma
intuitiva como la posibilidad de repeticin de un mismo comportamiento. En el
caso de videojuegos esto puede ser interesante en la repeticin de una misma jugada
en un videojuego deportivo, o en la ejecucin de una misma simulacin fsica en los
diferentes ordenadores de un videojuego multijugador. Incluso aunque el videojuego
siga un enfoque con clculos de simulacin centrados en el servidor, habitualmente es
necesario realizar ciertas interpolaciones del lado del cliente para mitigar los efectos
de la latencia, por lo que resulta imprescindible tratar con enfoques deterministas.
Para lograr determinismo es necesario lograr que la simulacin se realice exacta-
mente con los mismos datos de entrada. Debido a la precisin en aritmtica en punto
otante, es posible que v 2 dt no de el mismo resultado que v dt + v dt. As,
es necesario emplear el mismo valor de dt en todas las simulaciones. Por otro lado,
utilizar un dt jo hace que no podamos representar la simulacin de forma indepen-
diente de las capacidades de la mquina o la carga de representacin grca concreta
en cada momento. As, nos interesa tener lo mejor de ambas aproximaciones; por un
lado un tiempo jo para conseguir el determinismo en la simulacin, y por otro lado
la gestin con diferentes tiempos asociados al framerate para lograr independencia de
la mquina.
19.12. Determinismo [501]

Una posible manera de realizar la simulacin sera la siguiente: el motor de simu-


lacin fsica se ejecuta por adelantado en intervalos de tiempo discretos dt, de modo
que se mantengan los incrementos del motor grco con un intervalo adecuado. Por
ejemplo, si queremos tener 50fps y la simulacin fsica se ejecuta a 100fps, entonces
tendramos que ejecutar dos veces la simulacin fsica por cada despliegue grco.
Esto es correcto con esos clculos sencillos, pero qu ocurre si queremos dibujar
a 200fps?. En ese caso tendramso que ejecutar la mitad de veces el simulador fsico,
pero no podemos calcular por adelantado un valos de dt. Adems, podra ocurrir que
no existiera un mltiplo cmodo para sincronizar el motor de simulacin fsica y el
motor de despliegue grco.
La forma de resolver el problema pasa por cambiar el modo de pensar en l. Po-
demos pensar que el motor de render produce tiempo, y el motor de simulacin fsica
tiene que consumirlo en bloques discretos de un tamao determinado.

Puede ayudar pensar que el motor de render produce chunks de tiempo dis-
creto, mientras que el motor de simulacin fsica los consume.

A continuacin se muestra un sencillo game loop que puede emplearse para con-
seguir determinismo de una forma sencilla.
Los tiempos mostrados en este pseudocdigo se especican en milisegundos, y se

obtienen a partir de una hipottica funcin getMillisecons().
La lnea 1 dene TickMs, una variable que nos dene la velocidad del reloj in-
terno de nuestro juego (por ejemplo, 32ms). Esta variable no tiene que ver con el reloj
el comporta-
de Bullet. Las variables relativas al reloj de simulacin fsica describen
miento independiente y asncrono del reloj interno de Bullet (lnea 2 ) y el reloj del
motor de juego (lnea 3 ).

C19
Listado 19.22: Pseudocdigo fsica determinista.
1 const unsigned int TickMs 32
2 unsigned long time_physics_prev, time_physics_curr;
3 unsigned long time_gameclock;
4
5 // Inicialmente reseteamos los temporizadores
6 time_physics_prev = time_physics_curr = getMilliseconds();
7 time_gameclock = getMilliseconds();
8
9 while (1) {
10 video->renderOneFrame();
11 time_physics_curr = getMilliseconds();
12 mWorld->stepSimulation(((float)(time_physics_curr -
13 time_physics_prev))/1000.0, 10);
14 time_physics_prev = time_physics_curr;
15 long long dt = getMilliseconds() - time_gameclock;
16
17 while(dt >= TickMs) {
18 dt -= TickMs;
19 time_gameclock += TickMs;
20 input->do_all_your_input_processing();
21 }
22 }
[502] CAPTULO 19. SIMULACIN FSICA


Como se indica en las lneas 6-7 , inicialmente se resetean los temporizadores.

El pseudocdigo del bucle principal del juego se resume en las lneas 9-21 . Tras
representar un
frame, se obtiene el tiempo transcurrido desde la ltima simulacin
fsica (lnea 11 ), y se avanza un paso de simulacin en segundos (como la llamada al
sistema lo obtiene en milisegundos y Bullet lo requiere en segundos, hay que dividir
por 1000).
Por ltimo, se actualiza la parte relativa al reloj de juego. Se calcula en dt la
diferencia entre los milisegundos que pasaron desde la ltima vezque se acualiz el
reloj de juego, y se dejan pasar (en el bucle denido en las lneas 17-21 ) empleando
ticks discretos. En cada tick consumido se procesan los eventos de entrada.

19.13. Escala de los Objetos


Como se ha comentado en secciones anteriores, Bullet asume que las unidades de
espacio se denen en metros y el tiempo en segundos. El movimieneto de los objetos
se dene entre 0.05 y 10 unidades. As, la escala habitual para denir los pasos de
simulacin suelen ser 1/60 segundos. Si los objetos son muy grandes, y se trabaja con
la gravedad por defecto (9,8m/s2 ), los objetos parecern que se mueven a cmara
lenta. Si esto ocurre, muy probablemente tengamos un problema relativo a la escala
de los mismos.
Una posible solucin puede pasar por aplicar una escala al mundo de la simula-
cin. Esto esquivale a utilizar un conjunto diferente de unidades, como centmetros en
lugar de metros. Si se seleccionan con cuidado, esto puede permitir realizar simulacio-
nes ms realistas. Por ejemplo, si queremos disear un videojuego de billar, escalamos
el mundo en un factor de 100, de modo que 4 unidades equivaldrn a 4cm (dimetro
de las bolas de billar).

19.14. Serializacin
La serializacin de objetos en Bullet es una caracterstica propia de la biblioteca
que no requiere de ningn plugin o soporte adicional. La serializacin de objetos pre-
senta grandes ventajas relativas al preclculo de formas de colisin complejas. Para
guardar un mundo dinmico en un archivo .bullet, puede utilizarse el siguiente frag-
mento de cdigo de ejemplo:

Listado 19.23: Ejemplo de Serializacin.


1 btDefaultSerializer* serializer = new btDefaultSerializer();
2 dynamicsWorld->serialize(serializer);
3
4 FILE* file = fopen("testFile.bullet","wb");
5 fwrite(serializer->getBufferPointer(),serializer->
getCurrentBufferSize(),1, file);
6 fclose(file);

Aunque lo ms sencillo es serializar un mundo completo, es igualmente posible


serializar nicamente algunas partes del mismo. El foramto de los archivos .bullet
soporta la serializacin parcial de elementos empleando chunks independientes.
19.14. Serializacin [503]

En la posterior carga de los archivos .bullet, se debe utilizar la cabecera de Bullet-


WorldImporter, creando un objeto de esa clase. El constructor de esa clase requiere
que se le especique el mundo dinmico sobre el que crear los objetos que fueron
serializados. El uso del importador puede resumirse en el siguiente fragmento de c-
digo:

Listado 19.24: Importacin de datos serializados.


1 #include "btBulletWorldImporter.h"
2 btBulletWorldImporter* f = new btBulletWorldImporter(_dynWorld);
3 f->loadFile("testFile.bullet");

C19
Tcnicas Avanzadas
El objetivo de este mdulo lado Tcnicas Avanzadas dentro
del Curso de Experto en Desarrollo de Videojuegos, es profundizar
es aspectos de desarrollo ms avanzados que complementen el resto
de contenidos de dicho curso y permitan explorar soluciones ms
eficientes en el contexto del desarrollo de videojuegos. En este
mdulo se introducen aspectos bsicos de jugabilidad y se describen
algunas metodologas de desarrollo de videojuegos. As mismo,
tambin se estudian los fundamentos bsicos de la validacin y
pruebas en este proceso de desarrollo. No obstante, uno de los
componentes ms importantes del presente mdulo est relacionado
con aspectos avanzados del lenguaje de programacin C++, como
por ejemplo el estudio en profundidad de la biblioteca STL, y las
optimizaciones. Finalmente, el presente mdulo se complementa con
aspectos de representacin avanzada, como los filtros de partculas o
la programacin de shaders, y con un estudio en detalle de tcnicas
de optimizacin para escenarios interiores y exteriores. Por otra
parte, se realiza un estudio de la plataforma de desarrollo de
videojuegos Unity, especialmente ideada para el desarrollo de juegos
en plataformas mviles.
Aspectos de Jugabilidad y
Captulo 20
Metodologas de Desarrollo
Miguel ngel Redondo Duque
Jos Luis Gonzlez

E
n este captulo se introducen aspectos bsicos relativos al concepto de jugabili-
dad, como por ejemplo aquellos vinculados a su caractarizacin en el mbito
del desarrollo de videojuegos o las facetas ms relevantes de los mismos, ha-
ciendo especial hincapi en la parte relativa a su calidad.
Por otra parte, en este captulo tambin se discuten los fundamentos de las me-
todologas de desarrollo para videojuegos, estableciendo las principales fases y las
actividades desarrolladas en ellas.

20.1. Jugabilidad y Experiencia del Jugador

20.1.1. Introduccin
En el desarrollo de sistemas interactivos es fundamental la participacin del usua-
rio. Por ello, se plantean los denominados mtodos de Diseo Centrado en el Usuario
que se aplican, al menos, al desarrollo software que soporta directamente la interac-
cin con el usuario. En otras palabras, es fundamental contar con su participacin para
que tengamos la garanta de que se le va a proporcionar buenas experiencias de uso.
El software para videojuegos se puede considerar como un caso particular de sistema
interactivo por lo que requiere de un planteamiento similar en este sentido, aunque
en este mbito, los trminos y conceptos que se emplean para este propsito, varan
ligeramente.

507
[508] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO

En el desarrollo de videojuegos es importante tener siempre presente que hay que


lograr que el jugador sienta las mejores experiencias (entretenimiento, diversin, et.)
posibles durante su utilizacin . El incremento de estas experiencias revierte directa-
mente en el xito del videojuego. As pues, es conveniente conocer las propiedades
que caracterizan dichas experiencias, poder medirlas durante el proceso de desarrollo
y as asegurar su xito y calidad. En adelante, nos referiremos a esto como Experiencia
del Jugador.
La Experiencia del Jugador suele medirse utilizndose el concepto de Jugabilidad
como propiedad caracterstica de un videojuego, aunque su caracterizacin y forma de
medirla no es algo plenamente formalizado e implantado en la industria del desarrollo
de videojuegos.
Como paso previo para entender los conceptos de Jugabilidad y, por extensin, de
Experiencia del Jugador, conviene repasar un concepto fundamental en el mbito de
los sistemas interactivos que es la Usabilidad. La Usabilidad se reere a la capaci-
dad de un software de ser comprendido, aprendido, usado y ser satisfactorio para el
usuario, en condiciones especicas de uso o la eciencia y satisfaccin con la que un
producto permite alcanzar objetivos especcos a usuarios especcos en un contexto
de uso especico. Para entender y medir la Usabildiad, se han identicado una serie
de propiedades como son: efectividad, eciencia, satisfaccin, aprendizaje y seguri-
dad [4] [85] [66] [28]. Estas son las propiedades que son objeto de medicin y, a partir
de ellas, se puede valorar el grado de Usabilidad de un sistema.
El desarrollo de software usable redunda directamente en reduccin de costes de
produccin, optimizacin del mantenimiento e incremento de la calidad nal del pro-
ducto. Adems, las propiedades que caracterizan la Usabilidad inuyen muy directa-
mente en el uso que los usuarios hacen, contribuyendo incrementar su satisfaccin, su
productividad en la realizacin de tareas y reduciendo su nivel de estrs. En denitiva,
la Usabilidad puede considerarse como un reejo de la Experiencia del Usuario en un
sistema interactivo que soporta la realizacin de una serie de tareas especcas para
lograr un objetivo bien denido.
Segn Nielsen Norman Group se dene la Experiencia del Usuario como la sensa-
cin, sentimiento, respuesta emocional, valoracin y satisfaccin del usuario respecto
a un producto, resultado del proceso de interaccin con el producto y de la interac-
cin con su proveedor [67]. En este sentido, cabe destacar la importancia que juegan
diversos conceptos como la utilidad, la usabilidad, la deseabilidad, la accesibilidad,
facilidad de uso, lo valioso del producto y lo creble que pueda ser para el usuario. La
Experiencia de Usuario est estrechamente relacionada con el contexto de uso del sis-
tema interactivo, el contenido manipulado y los usuarios que lo usan. Lo que signica
que variando alguno de estos elementos, el resultado puede ser totalmente diferente e
incluso opuesto.
La relacin que existe entre Experiencia de Usuario y Usabilidad puede conside-
rarse equivalente a la que existe entre Experiencia del Jugador y Jugabilidad, aunque
no se trata de una simple traslacin del dominio de aplicacin. As lo vamos a conside-
rar para explicar cmo se puede caracterizar la Jugabilidad y que en base a su medicin
se obtenga una valoracin de la Experiencia del Jugador. Adems, se apuntarn algu-
nas ideas metodolgicas orientadas a lograr mejores desarrollos de videojuegos, desde
el punto de vista de la Jugabilidad.

20.1.2. Caracterizacin de la Jugabilidad


La Jugabilidad extiende el concepto de Usabilidad, pero no se reduce nicamente
la idea de Usabilidad en el caso particular de los videojuegos. Tampoco sera correcto
reducirla nicamente al grado de diversin de un juego. Para diferenciar claramente
este concepto que es un tanto difuso, lo adecuado es representarlo por un conjunto de
20.1. Jugabilidad y Experiencia del Jugador [509]

atributos o propiedades que lo caracterizan. Estos atributos podrn ser medidos y va-
lorados, para as comparar y extraer conclusiones objetivas. Este trabajo fue realizado
por Jos Luis Gonzlez [40] que dene la Jugabilidad como el conjunto de propieda-
des que describen la experiencia del jugador ante un sistema de juego determinado,
cuyo principal objetivo es divertir y entretener de forma satisfactoria y creble, ya
sea solo o en compaa.
Es importante remarcar los conceptos de satisfaccin y credibilidad. El primero
es comn a cualquier sistema interactivo. Sin embargo, la credibilidad depender del
grado en el que se pueda lograr que los jugadores se impliquen en el juego.
Hay que signicar que los atributos y propiedades que se utilizan para caracterizar
la Jugabilidad y la Experiencia del Jugador, en muchos casos ya se han utilizado para
caracterizar la Usabilidad, pero en los videojuegos presentan matices distintos. Por
ejemplo, el Aprendizaje en un videojuego puede ser elevado, lo que puede provocar
que el jugador se vea satisfecho ante el reto que supone aprender a jugarlo y, pos-
teriormente, desarrollar lo aprendido dentro del juego. Un ejemplo lo tenemos en el
videojuego Prince of Persia, donde es difcil aprender a controlar nuestro personaje a
travs de un mundo virtual, lo que supone un reto en los primeros compases del juego.
Sin embargo, en cualquier otro sistema interactivo podra suponer motivo suciente
de rechazo. Por otro lado, la Efectividad en un juego no busca la rapidez por com-
pletar una tarea, pues entra dentro de la naturaleza del videojuego que el usuario est
jugando el mximo tiempo posible y son muchos los ejemplos que podramos citar.
Los atributos a los que hacemos referencia para caracterizar la Jugabilidad son los
siguientes:

Satisfaccin. Agrado o complacencia del jugador ante el videojuego y el pro-


ceso de jugarlo.
Aprendizaje. Facilidad para comprender y dominar el sistema y la mecnica
del videojuego. Ms adelante se indica cmo estos conceptos se denen en lo
que se denomina Gameplay y que se construye durante el proceso de desarrollo
del juego.
Efectividad. Tiempo y recursos necesarios para ofrecer diversin al jugador
mientras ste logra los objetivos propuestos en el videojuego y alcanza su meta
nal.

C20
Inmersin. Capacidad para creerse lo que se juega e integrarse en el mundo
virtual mostrado en el juego.
Motivacin. Caracterstica del videojuego que mueve a la persona a realizar
determinadas acciones y a persistir en ellas para su culminacin.
Emocin. Impulso involuntario originado como respuesta a los estmulos del
videojuego, que induce sentimientos y que desencadena conductas de reaccin
automtica.
Socializacin. Atributos que hacen apreciar el videojuego de distinta manera al
jugarlo en compaa (multijugador), ya sea de manera competitiva, colaborativa
o cooperativa.

La gura 20.1 muestra como estos atributos y algunos otros ms pueden estar rela-
cionados con el concepto de Usabilidad tal y como se recoge en las normas ISO/IEC-
9241. Hay algunos atributos que estn relacionados con el videojuego (producto) y
otros se vinculan al proceso de desarrollo del juego (desarrollo), algunos hacen refe-
rencia a su inuencia sobre el jugador/es (usuarios o grupos de usuarios).
[510] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO

Figura 20.1: Relacin entre atributos de Usabilidad y de Jugabilidad

20.1.3. Facetas de la Jugabilidad


Uno de los objetivos, una vez denida la Jugabilidad, es poder medirla o cuanti-
carla. Este proceso es costoso debido a la cantidad de objetivos no funcionales que
afectan a la Experiencia del Jugador. Como plantea [40], una buena estrategia es la
de considerar una representacin de la Jugabilidad basada en facetas de la misma. La
organizacin en facetas puede considerarse una subdivisin lgica de la Jugabilidad
global en jugabilidades un poco ms especcas. Cada una de estas facetas facilitar
la identicacin y medicin de las propiedades introducidas anteriormente. Adems,
as ser ms fcil relacionar la Jugabilidad con los elementos particulares de un video-
juego.
Como facetas particulares podran considerarse las siguientes, aunque no es algo
cerrado y en algn juego particular podra aparecer y proponerse alguna otra faceta
que fuese objeto de consideracin:
20.1. Jugabilidad y Experiencia del Jugador [511]

Jugabilidad Intrnseca. Se trata de la Jugabilidad medida en la propia naturale-


za del juego y cmo se proyecta al jugador. Est ligada al diseo del Gameplay
que se describe ms adelante. La forma de valorarla pasa por analizar cmo se
representan las reglas, los objetivos, el ritmo y las mecnicas del videojuego.
Jugabilidad Mecnica. Es la Jugabilidad asociada a la calidad del videojuego
como sistema software. Est ligada a lo que sera el motor del juego, haciendo
hincapi en caractersticas como la uidez de las escenas cinemticas, la correc-
ta iluminacin, el sonido, los movimientos grcos y el comportamiento de los
personajes del juego y del entorno, sin olvidar los sistemas de comunicacin en
videojuegos multijugador.
Jugabilidad Interactiva. Es la faceta asociada a todo lo relacionado con la
interaccin con el usuario, el diseo de la interfaz de usuario, los mecanismos
de dilogo y los sistemas de control.
Jugabilidad Artstica. Est asociada a la calidad y adecuacin artstica y es-
ttica de todos los elementos del videojuego y a la naturaleza de ste. Entre
ellos estarn la calidad grca y visual, los efectos sonoros, la banda sonora y
las melodas del juego, la historia y la forma de narracin de sta, as como la
ambientacin realizada de todos estos elementos dentro del videojuego.
Jugabilidad Intrapersonal (o Personal). Est relacionada con la percepcin
que tiene el propio usuario del videojuego y los sentimientos que a ste le pro-
duce. Como tal, tiene un alto valor subjetivo.
Jugabilidad Interpersonal (o de Grupo). Muestra las sensaciones o percepcio-
nes de los usuarios que aparecen cuando se juega en grupo, ya sea de forma
competitiva, cooperativa o colaborativa. En relacin a cualquier sistema inter-
activo con soporte para grupos, se relacionara con lo que tiene que ver con
percepcin del grupo (o awareness de grupo).

En [40] incluso se relacionan, a nivel interactivo, estas facetas para ilustrar cmo
pueden ser las implicaciones e inuencias que presentan. Esta relacin se resume en
la gura 20.2.

C20

Figura 20.2: Relaciones entre las Facetas de la Jugabilidad


[512] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO

Con todo lo anterior, se puede concluir que la Jugabilidad de un juego podra con-
siderarse como el anlisis del valor de cada una de las propiedades y de los atributos
en las facetas consideradas.

20.1.4. Calidad de un juego en base a la Jugabilidad


Como ha quedado patente, el anlisis de la calidad de un videojuego nicamente
a partir de la Usabilidad o de la calidad de uso es insuciente. Por esta razn, la
caracterizacin de la Experiencia del Jugador en base a la Jugabilidad mediante una
serie de propiedades, atributos y facetas proporciona un instrumento adicional. Con
esto se pueden obtener medidas de la calidad de las experiencias durante el juego
e incluso pueden utilizarse para extender el estndar de calidad ISO 25010:2011 al
contexto de los videojuegos.
Se puede destacar que hay una serie de propiedades de la Jugabilidad que inuyen
directamente en la Calidad del Producto y otras en la Calidad del Proceso de Uso y
que, fundamentalmente tienen que ver con la habilidad del jugador para utilizarlo.
La Jugabilidad puede entenderse como la calidad de uso de un videojuego, pe-
ro la denicin de ciertos atributos de la calidad en uso, segn ISO, debe reescribirse
adaptndose al contexto de ocio en el que estamos envueltos. Partiendo de estas consi-
deraciones y entrando en mayor detalle respecto de la denicin previa, la Jugabilidad
representa el grado por el que usuarios especcos (jugadores) alcanzan metas de un
juego con efectividad, eciencia, exibilidad, seguridad y, especialmente, satisfaccin
en un contexto jugable de uso.
Estas ideas sern las bases para la extensin del modelo de calidad 25010 ba-
sndose en el modelo de la Jugabilidad. Deniremos el modelo de calidad en base a
los pilares bsicos necesarios para ello: propiedades o factores de calidad, mtricas y
herramientas de evaluacin.
Como propiedades o factores de calidad son consideradas las siguientes, siempre
y en todos los casos ajustado al contexto de uso concreto que aporta el videojuego
objeto de estudio:

Efectividad. La denimos como el grado en el que usuarios especcos (juga-


dores) pueden lograr las metas propuestas con precisin y completitud en un
contexto de uso concreto.
Eciencia. Es el grado con el que usuarios especcos (jugadores) pueden lo-
grar las metas propuestas invirtiendo una cantidad apropiada de recursos en re-
lacin a la efectividad lograda en un contexto de uso concreto. Este factor est
determinado por la facilidad de aprendizaje y la inmersin.
Flexibilidad. Es el grado con el que el videojuego se puede usar en distintos
contextos posibles o por los distintos perles de jugadores y de juego existentes.
Seguridad/Prevencin. Nivel aceptable de riesgo para la salud del jugador, o
los datos de ste, en un contexto de uso concreto.
Satisfaccin. Grado con el que los usuarios (jugadores) estn satisfechos en un
contexto de uso concreto, el que le aporta un videojuego. En este factor con-
sideramos distintos atributos como: agrado, atraccin, placentero, confortable,
conable, motivador, emocionable y sociable.

El diagrama de la gura 20.4 muestra la relacin de estas propiedades con los


principales conceptos que las denen y caracterizan.
20.1. Jugabilidad y Experiencia del Jugador [513]

C20

Figura 20.3: Clasicacin de las propiedades de la calidad del producto y del proceso en un videojuego
[514] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO

Figura 20.4: Propiedades o factores de calidad y conceptos que las caracterizan

El modelo de Jugabilidad presentado se completa con la identicacin y asocia-


cin de mtricas para los los factores y atributos que hemos apuntado. Estas mtricas
(ver guras 20.5 a 20.9) son consecuencia de la adaptacin de mtricas propuestas
en otros estndares internacionales pero particularizadas para el caso particular de los
videojuegos.

Figura 20.5: Mtricas para atributos de Efectividad


20.1. Jugabilidad y Experiencia del Jugador [515]

Figura 20.6: Mtricas para atributos de Eciencia

Figura 20.7: Mtricas para atributos de Flexibilidad

C20
Figura 20.8: Mtricas para atributos de Seguridad

Las mtricas para la efectividad estn basadas en metas ya que en su objetivo


principal como mecanismo de entretenimiento, el usuario debe superar unos retos para
alcanzar una meta con libertad de acciones dentro de las mecnicas del juego. Por
tanto, podramos decir que lo importante es superar el reto, el cmo depender de las
[516] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO

habilidades y maneras de jugar del jugador. Por otro lado, en un videojuego hay metas
que debe realizar el jugador, pero sin embargo, la facilidad de consecucin de esas
metas no es el principal objetivo. De hecho, ms bien podra decirse que es justo lo
contrario, el uso del juego debe ser una motivacin y presentar cierta dicultad de
consecucin, de lo contrario el jugador perder motivacin por el uso del videojuego.
As mismo, la medida de la frecuencia de error en el software tradicional con
un valor cercano a 0 siempre es mejor, pero en videojuegos podemos encontrar tanto
valores cercanos a 0 como a 1. Si el valor es cercano a 0, nos encontramos ante un
jugador experto o que la dicultad del juego es muy baja. Cercano a 1, nos informa
que nos encontramos ante un jugador novato, o que se encuentra en los primeros com-
pases del juego, o que la dicultad es muy elevada. Es por ello que los videojuegos
ofrecen distintos niveles de dicultad para atraer a los nuevos jugadores, evitando, por
ejemplo, que una dicultad extremadamente fcil haga que el juego pierda inters y
se vuelva aburrido.
La ecacia en el caso de los videojuegos es relativa, es decir, el usuario querr ju-
gar de forma inmediata, sin perder tiempo en recibir excesiva informacin, pero, de la
misma manera que comentbamos con anterioridad, el juego debe aportar dicultad y
el usuario debera encontrar cierta resistencia y progresiva dicultad en la consecucin
de las metas que lleve asociado.
La personalizacin tambin es algo especialmente deseable en el mundo del vi-
deojuego, porque en l coexisten muchos elementos de diseo que tratan de distraer,
de acompaar y de establecer la forma de interaccin. sta ltima debera ser exible
en cuanto a poder dar soporte a diferentes formas de interactuar: teclas, mandos, soni-
dos, etc. El atributo de la accesibilidad, aunque deseable y exigible, tradicionalmente
no ha contado con mucha atencin en el desarrollo de videojuegos.
Este aspecto est cambiando y la presencia de este atributo contribuye al uso del
mismo ya sea en la interfaz de usuario o en las mecnicas del juego. En este modelo de
Jugabilidad este atributo se consider implcitamente dentro de otros. Los problemas
de la accesibilidad pueden considerarse problemas de Usabilidad/Jugabilidad para,
por ejemplo, jugadores con algn tipo de discapacidad.
Si un jugador no puede entender lo que se dice en determinadas escenas u or
si otro personaje camina detrs de l por problemas de sonido, es recomendable el
uso de subttulos. Si el jugador no puede manejar determinado control de juego, se
recomienda el uso de dispositivos alternativos para facilitar el control de juego.
La seguridad/prevencin es un factor que, en el caso de los videojuegos, toma
cada vez ms importancia. El juego, en la actualidad, no es slo esttico, mental y
de sobremesa, sino que supone, en algunos casos, exigencias fsicas, por ejemplo el
uso de un control de juego que demande un esfuerzo corporal o movimientos bruscos,
los cuales pueden ser potencialmente peligrosos o dainos para la salud si el jugador
desarrolla la actividad de juego con ellos durante un tiempo prolongado de ocio.
La satisfaccin es el atributo ms determinante al tratar con videojuegos. Muchos
aspectos: cognitivos, emocionales, fsicos, de conanza y sociales pueden considerar-
se bajo este factor de la Jugabilidad. La estimacin de la misma se realiza fundamen-
talmente con cuestionarios y observando al jugador mientras juega y viendo cules son
sus preferencias de un momento de ocio para el siguiente. Probablemente, este atribu-
to en videojuegos es el ms rico y subjetivo. Por lo tanto, es el que se ha enriquecido
ms con atributos y propiedades para mejorar su medida y estimacin.
Finalmente, en la ltima columna de la tabla se proponen diversos mtodos de
evaluacin para cada mtrica. Estos mtodos pueden enriquecerse y guiarse por las
facetas, las cuales nos pueden ayudar a identicar la calidad de elementos concretos
de un videojuego segn el uso y las acciones mostradas por el conjunto de jugadores.
20.1. Jugabilidad y Experiencia del Jugador [517]

Figura 20.9: Mtricas para atributos de Satisfaccin

Las principales formas de medicin son la observacin, donde podemos medir con
herramientas cmo y de qu manera acta el jugador con el videojuego, usando por
ejemplo las mtricas presentadas o usar cuestionarios o tests heursticos para preguntar
o interrogar por atributos de la Jugabilidad. Estos cuestionarios pueden ir guiados por
facetas para facilitar su anlisis.

C20
[518] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO

20.2. Metodologas de Produccin y Desarrollo


Como en el desarrollo de cualquier producto software, para el construccin de un
videojuego se requiere tener presente los principios fundamentales de la Ingeniera del
Software y, especialmente, la metodologa de desarrollo adecuada para el producto
que se pretende construir y el contexto en el que se llevar a cabo. Sin embargo, el
diseo y desarrollo de un videojuego no slo se reduce al desarrollo tcnico de un
producto software sino que supone una actividad multidisciplinar que abarca desde la
idea y concepcin inicial hasta su versin nal. Adems, hay que tener presente que
el desarrollo de un videojuego suele ser un proyecto de gran envergadura en tiempo
y en dinero. Por ejemplo, la produccin de Half-Life 2 supuso ms de cuatro aos
de trabajo y un presupuesto nal que se situ alrededor de los cincuenta millones
de dlares. En estas situaciones, hay aspectos clave que requieren de una minuciosa
planicacin y metodologa, ya que desde que se concibe un proyecto hasta que se
comercializa transcurren grandes periodos de tiempo lo que en el mbito tecnolgico
puede ser la causa de presentar importantes desfases y, por lo tanto, desembocar en un
estrepitoso fracaso.
As pues, se puede asegurar que la realizacin de un videojuego es una tarea de-
licada que requiere de una metodologa especca. Sin embargo, las metodologas
claramente establecidas para desarrollo de software no se adaptan a este proceso con
garantas de calidad sucientes y no existe en este mbito un claro planteamiento de
cmo afrontar el trabajo. No obstante, son muchos expertos los que coinciden en que
el ciclo de vida del desarrollo de videojuegos se debe aproximar al del desarrollo
de una pelcula de cine, estableciendo tres fases claramente diferencias que son Pre-
Produccin, Produccin y Post-Produccin. A su vez en cada una de estas fases se
identican diversas etapas signicativas y el equipo de produccin se distribuye para
colaborar en cada una de ellas.
El equipo de personas que suelen trabajan en un proyecto de desarrollo de un
videojuego comercial de tamao medio-alto oscina entre 40 y 140. Adems, el tiempo
que dura el proceso puede llegar a superar los tres aos. Teniendo presente esto y,
especialmente, su similitud con la produccin de una pelcula en [12] se propone una
organizacin de referencia para el equipo de produccin. Esta organizacin es la que
aparece en la gura 20.10 y que ha sido traducida en [40].
La organizacin de las etapas del proceso de produccin y la relacin entre las
mismas da lugar a un modelo de proceso que se asemeja al denominado Modelo en
Cascada de Royce [78] en el que se establece la realizacin secuencial de una serie de
etapas, impidiendo el comienzo de una nueva etapa sin la nalizacin de la anterior.
Esta caracterstica sacrica de forma importante la posibilidad de paralelismo en el
desarrollo de un videojuego y puede suponer una mala utilizacin de los recursos
disponibles.
La distribucin de las distintas etapas entre las tres fases mencionadas anterior-
mente tampoco est ampliamente consensuado. Predomina la idea de que la fase de
Produccin agrupa todo aquello que conlleva la obtencin de elementos tangibles y
elaborados para el juego mientras que la fase de Pre-Produccin se asocia con los
procesos de obtencin de elementos poco tangibles o preliminares, aunque con ms
propiedad y en el mundo del desarrollo de software, se puede denominar Diseo Con-
ceptual del Juego.
En cualquier caso, cabe destacar que la principal carga de trabajo se sita en lo que
puede denominarse Diseo General del Juego y en el Diseo Tcnico que es donde
se aborda fundamentalmente el desarrollo del software del videojuego. As pues, son
estas etapas las que requieren mayor nmero de recursos y una mayor coordinacin
entre ellos. La gura 20.11 ilustra un posible planteamiento de organizacin de fases
y etapas extrado de [12].
20.2. Metodologas de Produccin y Desarrollo [519]

Figura 20.10: Organizacin de referencia de un equipo de produccin de videojuegos

Describimos a continuacin cada una de sus etapas de forma ms detallada para


comprender su objetivo de una forma ms clara.

20.2.1. Pre-Produccin
En la fase de Pre-Produccin se lleva a cabo la concepcin de la idea del juego,

C20
identicando los elementos fundamentales que lo caracterizarn y nalizando, si es
posible, en un diseo conceptual del mismo. Esta informacin se organiza para dar
lugar a lo que puede considerarse una primera versin del documento de diseo del
juego o ms conocido como GDD (Game Design Document). En este GDD, que debe
ser elaborado por el equipo creativo del diseo de videojuegos, se debe identicar
y jar todo lo relacionado con el Diseo del Videojuego que ser necesario abordar
posteriormente (normalmente en la fase de Produccin).
Como patrn de referencia y de acuerdo a lo establecido en [12], el GDD debe
contener lo siguiente:

Genero. Clasicacin del juego segn su naturaleza. La identicacin del gne-


ro al que pertenece el juego servir para jar una serie de caractersticas bsicas
para su posterior diseo.
Jugadores. Modalidad de juego: individual o colectivo; multijugador o no; si
los jugadores son personas o son mquinas; etc.
[520] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO

Figura 20.11: Organizacin de fases y etapas en la produccin de un videojuego

Historia. Resumen de la historia del juego. Se realizar una primera aproxima-


cin de la trama o la historia a desarrollar durante el juego, destacando qu se
quiere contar y cmo se pretende hacerlo. Esto se denomina storyline y storyte-
lling respectivamente.
Bocetos. Los bocetos son diseos preliminares, fundamentalmente, de los per-
sonajes y de los escenarios por los que se desarrollar la accin del juego.
Look and Feel. A partir de los bocetos se dene el aspecto graco y artstico
del juego, colores, temas dominantes, musicalidad, tcnicas de diseo 3D 2D,
posiciones de cmaras, etc.
Interfaz de Usuario. Se apuntar la forma en la que el jugador interactuar
con el juego y con qu mecanismos contar para ello: estilos de interaccin,
metforas de interaccin, paradigma de interaccin, etc.
Objetivos: Se jan las metas del juego de acuerdo a la historia que se va a
desarrollar.
Reglas: Se establece qu acciones podr desarrollar el jugador y cmo podr
hacerlo.
20.2. Metodologas de Produccin y Desarrollo [521]

Caractersticas. Se recogen las caractersticas principales de cada personaje del


juego y de los elementos que intervienen durante su historia.
Gameplay. Este es un concepto poco preciso y de muy amplio alcance, siendo
ligeramente diferente su aplicacin a cada tipo de juego. En esencia se trata
de la naturaleza general del videojuego y de la interactividad que soportar. Es
decir, los aspectos fundamentales que caracterizan la forma en la que se va a
jugar, las cosas que el jugador va a poder hacer en el juego, la forma en la que
el entorno del juego reaccionar a las acciones del jugador, mediadas por los
correspondientes personajes, etc. Estos aspectos se describirn sin detallar en
exceso a nivel de grcos, sonido o de la propia historia.
Diseo de Niveles. Se describen los niveles de dicultad que presentar el juego
indicando cuntos ser y cmo sern, as como los retos a los que el jugador se
enfrentar en cada uno de ellos. En algunos casos, estos niveles tambin pueden
estar asociados a etapas o fases del juego.
Requerimientos tcnicos. Se denen los requerimientos tcnicos de mquina
y dispositivos que requerir el videojuego para su utilizacin.
Marketing. Esta es una parte esencial en cualquier producto, pero especialmen-
te en el caso de un videojuego todava ms. Muchos videojuegos con fuertes in-
versiones han sido prcticamente un fracaso por no abordar este aspecto desde
las primeras faces de desarrollo. Por lo tanto, es necesario plantear, desde esta
fase, la lneas maestras por las que se va a regir la generacin de marketing y
publicidad del producto.
Presupuesto. Se realizar una primera aproximacin al presupuesto que sopor-
tar el proyecto de desarrollo del videojuego.

Como se ha indicado anteriormente, esta primera versin del GDD ser el punto
de partida para iniciar la fase de Produccin, pero cabe insistir sobre la importancia de
uno de sus elementos: se trata del Gameplay. Dado el carcter un tanto difuso de este
concepto, consideremos como ejemplo el caso particular del conocido y clsico jue-
go Space Invaders. En este juego indicaramos que se debe poder mover una nave
alrededor del cuadrante inferior de la pantalla y disparar a una serie de enemigos que
aparecen por la parte superior de la pantalla y que desaparecen cuando son alcanzados

C20
por los disparos. Estos enemigos tratan de atacarnos con sus disparos y presionn-
donos mediante la reduccin de nuestro espacio de movimientos e intentando chocar
contra nuestra nave.
El Gameplay tiene una implicacin importantsima en la calidad nal del juego
y, por lo extensin, en la Jugabilidad del mismo. Luego los esfuerzos destinados a su
anlisis y planteamiento revertirn directamente en las propiedades que caracterizan la
Juabilidad. No obstante y para profundizar en ms detalle sobre este aspecto, se reco-
mienda consultar los siguientes libros: Rules of Play: Game Design Fundamentals
[80] y Game Design: Theory and Practice [77].

20.2.2. Produccin
La fase de Produccin es la fase donde se concentra el trabajo principal, en volu-
men y en nmero de participantes, del proceso de desarrollo del videojuego, especial-
mente en lo que se denomina Diseo del Juego y Diseo Tcnico. Hay que signicar
que este curso est orientado, fundamentalmente, a las tareas y tcnicas relacionadas
con el Diseo Tcnico, pero no queremos dejar de situarlo en el contexto del proceso
global que requiere llevarse a cabo para concebir y desarrollar un producto de estas
caractersticas.
[522] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO

Siguiendo lo presentado en la gura 20.11, las etapas principales que se identican


en esta fase son las siguientes:

Diseo de Juego. Esta es una etapa fundamental en la que se describen con


alto nivel de detalle todos los elementos que formarn parte del juego. Princi-
palmente, lo que se hace es renar lo contemplado en el GDD para obtener su
versin denitiva, diseando en profundidad todos sus aspectos anteriormente
especicados. As se obtiene lo que se denomina DTD (Documento Tcnico de
Diseo) junto con la Biblia de la Historia, la Biblia del Arte y la primera ver-
sin del Motor del Juego. Fundamentalmente, se debe trabajar en tres lneas de
trabajo que vienen a caracterizar lo que se denomina diseo del juego y son las
siguientes:

Diseo Artstico que incluye:


La Biblia de la Historia donde se recogen todas las historias de los
personajes y del mundo donde se desarrolla el juego as como el ar-
gumento completo del juego.
Biblia del Arte que incluye:
Elementos sonoros del juego, es decir, voces, efectos, msica,
ambiente, etc. Incluso se empieza a trabajar en lo que debe dar
lugar al Motor de Sonido.
Visualizacin grca de los elementos con los que interactuarn
los jugadores.
Elementos grcos como los modelos en 3D, las cmaras, las
luces, los sprites, los tiles, etc. De igual manera que en el caso del
sonido, esto sirve de punto de partida para comenzar a trabajar en
lo que se denomina Motor Grco.
Diseo de la Mecnica del Juego, en el que se trabaja en lo aspectos que
se enumeran a continuacin:
Cmo se va a interactuar en el juego, cules son las reglas que lo rigen
y cmo es la comunicacin que tendr lugar en caso de tratarse de un
juego on-line.
Se debe disear el comportamiento, habilidades y otros detalles sig-
nicativos de los personajes y del mundo que les rodea.
Se empieza a trabajar en el diseo del motor de IA que pueda requerir
y en todo lo asociado con esto.
Se disea lo que se denomina el Motor Fsico con el objetivo de ge-
nerar los aspectos fsicos del juego como explosiones, disparos, etc.
Motor del Juego que hace referencia a una serie de rutinas que permiten
la representacin de todos los elementos funcionales del juego. En snte-
sis puede decirse que agrupa todo lo relacionado con el Motor Grco,
el Motor de Sonido, el Gestor de IA, el Motor Fsico y todo el resto de
gestores que pueden ser necesario para manejar el universo completo del
videojuego.

Diseo Tcnico. sta se trata de la etapa que directamente est relacionada


el desarrollo del software del juego y con lo se aborda en profundidad como
contenido tcnico esencial de este curso. Es aqu donde de describe cmo ser
implementado el juego. Para ello se hace uso de notaciones como UML (Uni-
ed Modeling Language) y se plantea y decide la metodologa de desarrollo
software ms apropiada segn las caractersticas y, sobretodo, envergadura del
producto software que se pretende implementar. Es importante tener una des-
cripcin conceptual y precisa que permita ver el funcionamiento del software
20.2. Metodologas de Produccin y Desarrollo [523]

desde puntos de vistas estructurales, dinmicos, de interaccin y de despliegue.


En denitiva, se trata de un proyecto de desarrollo de software completo que
debe incluir tambin una planicacin de tareas a realizar, una asignacin a los
miembros del equipo de desarrolladores. Esto incluye la identicacin de hitos
importantes, las fechas de entrega y el anlisis de riesgos.
Implementacin. En esta etapa debe abordarse la implementacin de los ele-
mentos software del proyecto que se describieron en la etapa anterior, utilizando
para ello mtodos, tcnicas y herramientas como las que se trabajan a lo largo de
este curso. Es posible que se detecten algunos errores del diseo inicial y que se
requieran revisiones. En muchos casos, esta etapa y la anterior son repetidas de
forma iterativa o se someten a ciclos iterativos. Esto, en muchos casos viene de-
terminado por la metodologa de desarrollo software que se emplea y que, como
se ha apuntado anteriormente, depende de muchos factores como la envergadu-
ra del proyecto, los recursos disponibles, etc. Generalmente, en este momento
se suelen construir demos reducidas del juego que son objeto de publicacin,
contribuyendo as a materializar la campaa de marketing y publicidad que tan
esenciar es para lograr el xito comercial del producto.
Pruebas Alpha. Estas pruebas se abordan cuando tenemos ya partes del produc-
to software terminado. Tambin se suelen denominan pruebas Code Complete.
Mediante las mismas, el producto se somete a diversas pruebas que realizan pe-
queos equipos que han estado llevando a cabo el proceso de diseo y desarrollo
del juego. El objetivo de las mismas es buscar pequeos errores y renar algu-
nos aspectos. Uno de los aspectos ms importantes que se valoran en esta etapa
es la Jugabilidad del juego a travs de diversas propiedades y facetas como se
describi anteriormente.
Pruebas Beta. En las pruebas Beta o tambin denominadas Content Complete
se naliza todo lo relacionado con contenidos como el decorado de las misiones,
los grcos, los textos en diferentes idiomas, doblaje del sonido, etc. Adems,
se trabaja para asegurar que los contenidos incluidos en el juego se ajustan a
las leyes vigentes y a la tica establecida en aquellos pases donde se pretende
comercializar el juego. Estas pruebas son llevadas a cabo por personas ajenas al
equipo de desarrollo.
Gold Master. Esta etapa aborda una prueba denitiva con el producto nal que

C20
se publicar y que se producir. Obviamente, incluye todo el contenido artstico,
tcnico y documental (es decir, los manuales de usuario). En este momento, la
publicidad deber ser la mayor posible, incluyndose la realizacin de reportajes,
artculos, etc.

20.2.3. Post-Produccin
La fase de Post-Produccin, en la que no nos vamos a detener ya que se aleja
bastante del contenido tratado en el curso, aborda fundamentalmente la explotacin y
el mantenimiento del juego como si de cualquier otro producto software se tratase.
[524] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO

20.3. Metodologas Alternativas


El mtodo descrito anteriormente prcticamente es un caso particular de aplicacin
del Modelo de Proceso en Cascada, que conlleva la nalizacin de una etapa antes
de poder abordar la siguiente. En el caso del desarrollo de software, esto condiciona
bastante lo relacionado con las etapas de pruebas, cuya realizacin se retrasa en exceso
quedando situada casi al nal del desarrollo. En ese momento, depurar y solucionar
cualquier problema, si es que es posible, puede resultar excesivamente costoso en
tiempo y, en consecuencia, en dinero.
Precisamente, en el rea del desarrollo de sistemas interactivos, est claramente es-
tablecido que las pruebas, sobretodo de Usabilidad, deben hacerse desde las primeras
fases, incluso cuando los prototipos estn nicamente a nivel de bocetos y en papel.
As pues, eso entra rmemente en contradiccin con el hecho de que un videojuego se
considere como un caso particular de sistema interactivo.
Por otro lado, la necesidad de evaluar lo antes posible las propiedades relaciona-
das con la Jugabilidad y la Experiencia del Jugador requieren plantear variaciones a
la metodologa de produccin y desarrollo anteriormente presentada. Por esta razn,
se describen a continuacin algunos otros mtodos alternativos que se utilizan en la
industria del desarrollo de software de videojuegos.

20.3.1. Proceso Unicado del Juego


Tomando como punto de partida el PUD (Proceso Unicado de Desarrollo) de
IBM, en [35] se plantea la metodologa denominada Proceso Unicado del Juego (o
GUP (Game Unied Process)). Este mtodo se caracteriza por incentivar la comunica-
cin entre los equipos de trabajo que abordan cada etapa del desarrollo, la documenta-
cin estricta de cada paso y por abordar el proceso de desarrollo de una forma iterativa
y en ciclos muy cortos. Se puede considerar como una versin gil de la metodologa
PUD particularizada para el desarrollo de software de videojuegos.
Adems, este mtodo propone la utilizacin del paradigma de Programacin Ex-
trema [11] como instrumento para agilizar el desarrollo del software del videojuego.
Por lo tanto, esto es especialmente aplicable a lo que seran las etapas de Diseo del
Juego, Diseo Tcnico, Implementacin y Pruebas.

20.3.2. Desarrollo Incremental


Otro mtodo que puede ser adecuado, si se pretende potenciar la realizacin de
pruebas en las fases ms tempranas y obtener la correspondiente realimentacin, es el
Desarrollo Incremental de Sikora [88]. Bsicamente, se introduce la idea de disponer
de un equipo de jugadores dentro del equipo de desarrolladores encargados de las
pruebas. Estos jugadores siempre realizan una subetapa de pruebas en cada etapa
antes de validar los resultados y poder asumir las tareas de la siguiente etapa.

20.3.3. Desarrollo gil y Scrum


Una de las metodologas que mejores resultados est produciendo recientemente
en la industria del software de videojuegos es la propuesta por Clinton Keith dentro
de su estudio de desarrollo High Moon [54]. Como ejemplo de caso de xito en el que
se ha aplicado esta metodologa, cabe mencionar DarkWatch.
20.3. Metodologas Alternativas [525]

Esta metodologa plantea la utilizacin de procesos giles de desarrollo de soft-


ware, unido a los pilares bsico de la metodologa de desarrollo de productos Scrum
[95].
El objetivo principal del mtodo de Keith es hacer un diseo centrado en el jugador
y en los resultados del proceso de desarrollo en cada una de sus fases. As, se resalta
la importancia de obtener la opinin del usuario en cada momento, por lo que intenta
involucrar al equipo de pruebas lo antes posible. De esta forma, se facilitar la posi-
bilidad detectar y solucionar a tiempo todos los posibles errores y se podr analizar
la Juabilidad en cada momento para ir mejorndola continuamente, del mismo modo
que se hace para el caso particular de la Usabilidad en un sistema interactivo.
Esta metodologa requiere de la realizacin de importantes esfuerzos iniciales para
lograr obtener prototipos bsicos pero jugables y, por lo tanto, evaluables. Con estos
prototipos se inicia un proceso iterativo en el que el equipo de pruebas lo utiliza y
proporciona realimentacin orientada a la mejora, especialmente de la Jugabilidad
pero tambin de otros detalles que pueden caracterizar el producto nal.
Informacin mucho ms detallada de cmo aplicar esta metodologa puede encon-
trarse en el libro Agile Game Development with Scrum de [55].

20.3.4. Desarrollo Centrado en el Jugador


En esta subseccin se va a describir la propuesta de [40] que est inspirada direc-
tamente en los principios fundamentales del DCU (Diseo Centrado en el Usuario) y
de las metodologas de desarrollo software que se han derivado de los mismos.
La idea fundamental del DCU, como ya se ha apuntado anteriormente, es la in-
volucrar al usuario y hacerlo al principio de cualquier proceso de desarrollo, ya que
muchos de los problemas del software se deben a una carencia en las fases iniciales
del desarrollo, concretamente en las fases de elicitacin y de anlisis de requisitos.
Esto ya ha sido contemplado en diversos estndares que plantean ciclos de vida del
proceso que incluyen modelos de madurez para la Usabilidad como pilar fundamental
que garantizar el xito del producto en cuanto a la Experiencia del Usuario.
De la misma forma que el DCU es necesario para el desarrollo de aplicaciones que
cubran los requisitos del usuario de forma adecuada, el Diseo Centrado en el Juga-
dor es especialmente importante para considerar la diversidad y subjetividad de los

C20
perles de jugadores existentes. Adems, esto contribuye directamente a la reduccin
de la proliferacin de productos que requieren numerosos parches incluso desde los
primeros meses de vida en el mercado.
En este sentido [40] propone un mtodo inspirado directamente en la metodologa
PPIu+a propuesta en [41] para Ingeniera de la Usabilidad y que se resume en la
gura 20.12. Para facilitar su comprensin puede utilizarse la gura 20.13 en la que
se relaciona y compara esta metodologa MPIu+a.
En las fuentes citadas pueden encontrar muchos ms detalles sobre la fases ms
destacables que son las de anlisis, diseo, desarrollo y evaluacin de elementos juga-
bles. Especialmente, se plantea un patrn a seguir para la obtencin de requisitos de
Jugabilidad con ejemplos de aplicacin, se proponen una serie de guas de estilo para
llevar a cabo un diseo que fomente la Jugabilidad, se muestra cmo aplicar Scrum y
programacin extrema para la construccin de prototipos jugables y se describe cmo
evaluar la Jugabilidad de los prototipos para obtener conclusiones sobre la experiencia
del jugador.
[526] CAPTULO 20. ASPECTOS DE JUGABILIDAD Y METODOLOGAS DE DESARROLLO

Figura 20.12: Mtodo de Diseo Centrado en el Jugador de [40]

Figura 20.13: Comparacin entre el mtodo de Diseo Centrado en el Jugador y el de Diseo Centrado en
el Usuario de MPIu+a
Captulo

C++ Avanzado
21
Francisco Moya Fernndez
David Villa Alises
Sergio Prez Camacho
Cleto Martn Angelina

21.1. Estructuras de datos no lineales


Cualquier programa donde la eciencia sea importante, y es el caso de la mayora
de los videojuegos, necesitan estructuras de datos especcas. Hay varios motivos para
ello:

Hasta ahora hemos estudiado fundamentalmente la STL, que oculta la estruc-


tura real de los contenedores ofreciendo un aspecto de estructura lineal. As,
por ejemplo, los objetos de tipo map o set se representan realmente mediante
rboles, aunque el programador est completamente aislado de ese detalle de
implementacin. Solo podemos anticipar la estructura subyacente mediante in-
dicadores indirectos, como la complejidad de las operaciones o la estabilidad de
los iteradores.
Algunas estructuras, como es el caso de los grafos, no tienen una representacin
lineal evidente y se pueden recorrer de distintas formas. Por tanto debe existir
un nmero variable de iteradores.
Las estructuras de la STL estn diseadas para uso general. El diseador no
puede anticipar en qu condiciones se van a usar por lo que toma las decisio-
nes apropiadas para el mayor nmero de casos posible. Conociendo los deta-
lles (gestin de memoria, algoritmos que se van a aplicar) se pueden obtener
rendimientos muy superiores con mnimas modicaciones sobre la estructura
subyacente.

527
[528] CAPTULO 21. C++ AVANZADO

Como ya hemos puntualizado en captulos anteriores, es muy importante no op-


timizar de manera prematura. Para ilustrar este aspecto veamos el siguiente ejemplo
tomado de [17], captulo 11.
Anti-optimizaciones
Listado 21.1: Dos formas de sumar enteros
Con los compiladores actuales es
1 int myIntegerSum(int* a, int size) { muy difcil implementar cdigo
2 int sum=0; equivalente a la STL ms ecien-
3 int* begin = a; te. Algunos ejemplos de [17] hoy en
4 int* end = a + size; da son completamente diferentes.
5 for (int* p = begin; p != end; ++p)
6 sum += *p;
7 return sum;
8 }
9
10 int stlIntegerSum(int* a, int size) {
11 return accumulate(a, a+size, 0);
12 }

En dicho libro se argumentaba que la funcin myIntegerSum() es casi cuatro


veces ms rpida que stlIntegerSum(). Y probablemente era verdad en el ao
1999. Sin embargo hoy en da, empleando GNU g++ 4.6.2 o clang++ 3.0 el resultado
es prcticamente idntico, con una muy ligera ventaja hacia la versin basada en la
STL.

21.1.1. rboles binarios


Las estructuras arborescentes se encuentran entre las ms utilizadas en la progra-
macin de todo tipo de aplicaciones. Ya hemos visto en el mdulo 2 algunas de sus
aplicaciones para el mezclado de animaciones (Priority Blend Tree), o para indexar
el espacio (BSP Tree, quatree, octree, BBT). Estudiaremos su funcionamiento en este
captulo, pero el desarrollo de videojuegos no se limita a los grcos, por lo que otro
tipo de rboles ms generales pueden resultar tambin necesarios.
Los rboles se utilizan con frecuencia como mecanismo eciente de bsqueda.
Para este n implementan un rico conjunto de operaciones: bsqueda de un elemento,
mnimo o mximo, predecesor o sucesor de un elemento y las clsicas operaciones
de insercin y borrado. Se pueden emplear como un diccionario o como una cola con
prioridad.
Todas estas operaciones estn presentes en los contenedores ordenados de la STL,
singularmente set, multiset, map y multimap. No debe extraar por tanto que
en todos ellos se emplea una variante de rbol binario denominada red-black tree.
Un nodo de rbol contiene habitualmente un atributo key que se emplea para
compararlo con otros nodos y adems mantiene un conjunto de punteros a otros nodos
que mantienen su relacin con el resto de la estructura. As, por ejemplo, los nodos de
rboles binarios mantienen un atributo parent que apunta al nodo padre, y un par
de punteros left y right que apuntan al hijo por la izquierda y por la derecha respecti-
vamente. A su vez cada hijo puede tener otros nodos hijos, por lo que realmente cada
nodo cuenta con dos subrboles (izquierdo y derecho).

Las operaciones bsicas de los rboles se ejecutan en un tiempo proporcional


a la altura del rbol. Eso implica O(log n) en el caso peor si est correcta-
mente balanceado, pero O(n) si no lo est.
21.1. Estructuras de datos no lineales [529]

rboles de bsqueda binaria

10 1

4 17 4

1 5 16 21 10

5 17

16 21

Figura 21.1: Dos rboles de bsqueda binaria. Ambos contienen los mismos elementos pero el de la iz-
quierda es mucho ms eciente

Los rboles de bsqueda binaria se denen por la siguiente propiedad:

Todos los nodos del subrbol izquierdo de un nodo tienen una clave
menor o igual a la de dicho nodo. Anlogamente, la clave de un nodo es
siempre menor o igual que la de cualquier otro nodo del subrbol derecho.

Por tratarse del primer tipo de rboles expondremos con cierto detalle su imple-
mentacin. Como en cualquier rbol necesitamos modelar los nodos del rbol, que
corresponden a una simple estructura:

Listado 21.2: Estructura de un nodo de rbol de bsqueda binaria


1 template <typename KeyType>
2 struct Node {

C21
3 typedef Node<KeyType> NodeType;
4
5 KeyType key;
6 NodeType* parent;
7 NodeType* left;
8 NodeType* right;

Sobre esta misma estructura es posible denir la mayora de las operaciones de


un rbol. Por ejemplo, el elemento ms pequeo podra denirse como un mtodo
esttico de esta manera:

Listado 21.3: Bsqueda del elemento mnimo en un rbol de bsqueda binaria


1 static NodeType* minimum(NodeType* x) {
2 if (x == 0) return x;
3 if (x->left != 0) return minimum(x->left);
4 return x;
5 }
[530] CAPTULO 21. C++ AVANZADO

Para obtener el mnimo basta recorrer todos los subrboles de la izquierda y anlo-
gamente para encontrar el mximo hay que recorrer todos los subrboles de la derecha
hasta llegar a un nodo sin subrbol derecho.

Listado 21.4: Bsqueda del elemento mximo en un rbol de bsqueda binaria


1 static NodeType* maximum(NodeType* x) {
2 if (x == 0) return x;
3 if (x->right != 0) return maximum(x->right);
4 return x;
5 }

El motivo de utilizar mtodos estticos en lugar de mtodos normales es poder


invocarlos para el nodo nulo. Los mtodos de clase invocados sobre un objeto nulo
tienen un comportamiento indenido.

Nuestra implementacin del mtodo esttico minimum() es recursiva. Con


frecuencia se argumenta que una implementacin iterativa es ms eciente
porque no crea un nmero indenido de marcos de pila. Realmente eso de-
pende del tipo de recursin. Cuando el compilador puede detectar recursin
por la cola, es decir, cuando tras la llamada recursiva no quedan operaciones
pendientes de realizar, el compilador puede optimizar el cdigo y eliminar
completamente la llamada recursiva.

Las instancias de Node no tienen por qu ser visibles directamente al programa-


dor, al igual que los contenedores tipo set de la STL. Por ejemplo, esto puede lograrse
utilizando un namespace privado.
La bsqueda de un elemento tambin puede plantearse con un algoritmo recursivo
aprovechando la propiedad que dene a los rboles de bsqueda binaria:

Listado 21.5: Bsqueda de una clave en un rbol de bsqueda binaria


1 static NodeType* search(NodeType* x, KeyType k) {
2 if (x == 0 || x->key == k) return x;
3 else if (k < x->key) return search(x->left, k);
4 else return search(x->right, k);
5 }

Tambin pueden implementarse de manera directa los mtodos successor()


y predecesor() para encontrar el nodo siguiente o anterior a uno dado segn el
orden de las claves:

Listado 21.6: Bsqueda del sucesor de un nodo en un rbol de bsqueda binaria


1 static NodeType* successor(NodeType* x) {
2 if (x->right != 0) return minimum(x->right);
3 NodeType* parent = x->parent;
4 while (parent != 0 && x == parent->right) {
5 x = parent;
6 parent = x->parent;
7 }
8 }
21.1. Estructuras de datos no lineales [531]

Si hay un subrbol a la derecha del nodo entonces es el mnimo de ese subrbol


(en la gura 21.1 izquierda el sucesor de 10 es 16). Si no lo hay entonces tendremos
que subir hasta el primer padre que tiene al nodo como subrbol izquierdo (en la
gura 21.1 izquierda el sucesor de 5 es 10).
Se propone como ejercicio la implementacin de la bsqueda del predecesor de
un nodo determinado.
El resto de las operaciones bsicas sobre un rbol (insercin y borrado de elemen-
tos) requiere de una estructura que acta como fachada de los nodos del rbol.

Listado 21.7: Estructura de un rbol de bsqueda binaria


1 template <class KeyType>
2 struct Tree {
3 typedef Node<KeyType> NodeType;
4
5 NodeType* root;

El atributo root mantiene cul es el nodo raz del rbol. Los mtodos de insercin
y borrado deben actualizarlo adecuadamente.

Listado 21.8: Insercin en un rbol de bsqueda binaria


1 void insert(NodeType* z) {
2 NodeType* y = 0;
3 NodeType* x = root;
4 while (x != 0) {
5 y = x;
6 if (z->key < x->key)
7 x = x->left;
8 else
9 x = x->right;
10 }
11 z->parent = y;
12 if (y == 0) root = z;
13 else if (z->key < y->key)
14 y->left = z;
15 else
16 y->right = z;
17 }

Bsicamente replica el procedimiento de bsqueda para encontrar el hueco don-

C21
de debe insertar el elemento, manteniendo el padre del elemento actual para poder
recuperar el punto adecuado al llegar a un nodo nulo.
El procedimiento ms complejo es el de borrado de un nodo. De acuerdo a [20] se
identican los cuatro casos que muestra la gura 21.2. Un caso no representado es el
caso trivial en el que el nodo a borrar no tenga hijos. En ese caso basta con modicar
el nodo padre para que el hijo correspondiente sea el objeto nulo. Los dos primeros
casos de la gura corresponden al borrado de un nodo con un solo hijo, en cuyo caso el
hijo pasa a ocupar el lugar del nodo a borrar. El tercer caso corresponde al caso en que
el hijo derecho no tenga hijo izquierdo o el hijo izquierdo no tenga hijo derecho, en
cuyo caso se puede realizar la misma operacin que en los casos anteriores enlazando
adecuadamente las dos ramas. El cuarto caso corresponde al caso general, con dos
hijos no nulos. En ese caso buscamos un sucesor del subrbol izquierdo que no tenga
hijo izquierdo, que pasa a reemplazar al nodo, reajustando el resto para mantener la
condicin de rbol de bsqueda binaria.
Con el objetivo de facilitar el movimiento de subrboles denimos el mtodo
transplant(). El subrbol con raz u se reemplaza con el subrbol con raz v.
[532] CAPTULO 21. C++ AVANZADO

Listado 21.9: Transplantado de subrboles en un rbol de bsqueda binaria


1 void transplant(NodeType* u, NodeType* v) {
2 if (u->parent == 0)
3 root = v;
4 else if (u == u->parent->left)
5 u->parent->left = v;
6 else
7 u->parent->right = v;
8 if (v != 0)
9 v->parent = u->parent;
10 }

Ntese que no alteramos el nodo padre de v ni los hijos de v. La responsabilidad


de actualizarlos corresponde al que llama a transplant().
Empleando este procedimiento auxiliar es muy sencilla la implementacin de
remove().

Listado 21.10: Borrado en un rbol de bsqueda binaria


1 void remove(NodeType* z) {
2 if (z->left == 0)
3 transplant(z, z->right);
4 else if (z->right == 0)
5 transplant(z, z->left);
6 else {
7 NodeType* y = NodeType::minimum(z->right);
8 if (y->parent != z) {
9 transplant(y, y->right);
10 y->right = z->right;
11 y->right->parent = y;
12 }
13 transplant(z, y);
14 y->left = z->left;
15 y->left->parent = y;
16 }
17 }

Todos los procedimientos bsicos (minimum(), maximum(), search(), predecesor(),


successor(), insert() y remove()) se ejecutan en tiempo O(h) donde h es
la altura del rbol. Si el rbol est equilibrado esto implica O(log n).

Red-black trees

La eciencia de un rbol de bsqueda binaria depende enormemente del orden


en que se introduzcan los elementos. Pueden ser muy ecientes o en el caso peor
degenerar a una simple lista doblemente enlazada. Para resolver este problema se han
propuesto multitud de esquemas que garantizan que el rbol siempre est equilibrado
complicando ligeramente la insercin y borrado.
Los rboles rojo-negro son un caso de estos rboles de bsqueda binaria balancea-
dos. Cada nodo almacena un bit extra, el color, que puede ser rojo o negro. En cada
camino simple desde el nodo raz a una hoja se restringen los colores de manera que
nunca pueda ser un camino ms del doble de largo que otro cualquiera:

1. Cada nodo es rojo o negro.


2. El nodo raz es negro.
3. Las hojas del rbol (objetos nulos) son negras.
21.1. Estructuras de datos no lineales [533]

(a) q q

z r

NIL r

(b) q q

z l

l NIL

(c) q q

z y

l y l x

NIL x

C21
(d) q q q

z z y y

l r l NIL r l r

y x x

NIL x

Figura 21.2: Casos posibles segn [20] en el borrado de un nodo en un rbol de bsqueda binaria
[534] CAPTULO 21. C++ AVANZADO

10

4 17

1 5 16 21

NIL NIL NIL NIL NIL NIL NIL NIL

Figura 21.3: Ejemplo de rbol rojo-negro. Los nodos hoja no se representarn en el resto del texto.

4. Los hijos de un nodo rojo son negros.


5. Los caminos desde un nodo a todas sus hojas descendientes contienen el mismo
nmero de nodos negros.

Podemos simplicar los algoritmos eliminando la necesidad de comprobar si es


un nodo nulo antes de indexar un elemento sin ms que utilizar un nodo especial que
usamos como centinela. La estructura del nodo podra ser algo as:

Listado 21.11: Denicin de un nodo de un rbol rojo-negro.


1 template <typename KeyType>
2 struct Node {
3 typedef Node<KeyType> NodeType;
4 enum Color { Red = false, Black = true };
5
6 KeyType key;
7 NodeType* parent;
8 NodeType* left;
9 NodeType* right;
10 Color color;
11
12 Node() {
13 left = right = parent = nil();
14 }
15
16 static NodeType* nil() {
17 if (!_nil)
18 _nil = new Node(Black);
19 return &_nil;
20 }

Las operaciones maximum(), minimum(), search(), successor() y predecesor()


son completamente anlogas a las de los rboles de bsqueda binaria tradicionales,
salvo que ahora est garantizado que se ejecutan en tiempo O(log n). Por ejemplo, la
funcin maximum() sera:
21.1. Estructuras de datos no lineales [535]

Listado 21.12: Bsqueda del mayor elemento en un rbol rojo-negro.


1 static NodeType* maximum(NodeType* x) {
2 if (x->right != NodeType::nil()) return maximum(x->right);
3 return x;
4 }

Ntese que ya no es necesario comprobar si x es nulo antes de indexar su miembro


right, puesto que para representar al nodo nulo usamos un centinela perfectamente
vlido.
En cambio las operaciones de insercin y borrado deben ser modicadas para
garantizar que se mantienen las propiedades de rbol rojo-negro. Para ello nos apoya-
remos en dos funciones auxiliares: rotate_left() y rotate_right():

Listado 21.13: Rotacin a la izquierda en un rbol de bsqueda binaria


1 void rotate_left(NodeType* x) {
2 NodeType* y = x->right;
3 x->right = y->left;
4 if (y->left != NodeType::nil())
5 y->left->parent = x;
6 y->parent = x->parent;
7 if (x->parent == NodeType::nil())
8 root = y;
9 else if (x == x->parent->left)
10 x->parent->left = y;
11 else
12 x->parent->right = y;
13 y->left = x;
14 x->parent = y;
15 }

y rotate right
x

x c a y
rotate left
a b b c

C21
Figura 21.4: Operacin de rotacin a la derecha o a la izquierda en un rbol de bsqueda binaria

La operacin dual rotate_right() puede implementarse simplemente inter-


cambiando en el algoritmo anterior x por y, y left por right.
La insercin puede ahora realizarse de una forma muy parecida al caso general
asumiendo que el color del nodo a insertar es rojo y despus arreglando el rbol con
rotaciones y cambios de color.

Listado 21.14: Insercin en un rbol rojo-negro


1 void insert(NodeType* z) {
2 NodeType* y = NodeType::nil();
3 NodeType* x = root;
4 while (x != NodeType::nil()) {
5 y = x;
6 if (z->key < x->key)
7 x = x->left;
8 else
[536] CAPTULO 21. C++ AVANZADO

9 x = x->right;
10 }
11 z->parent = y;
12 if (y == NodeType::nil())
13 root = z;
14 else if (z->key < y->key)
15 y->left = z;
16 else
17 y->right = z;
18 z->left = NodeType::nil();
19 z->right = NodeType::nil();
20 z->color = Node::Color::Red;
21 insert_fixup(z);
22 }

Al asumir el color rojo podemos haber violado las reglas de los rboles rojo-negro.
Por esta razn llamamos a la funcin insert_fixup() que garantiza el cumpli-
miento de las reglas tras la insercin:

Listado 21.15: Reparacin tras la insercin en rbol rojo-negro


1 void insert_fixup(NodeType* z) {
2 while (z->parent->color == Node::Color::Red) {
3 if (z->parent == z->parent->parent->left) {
4 NodeType* y = z->parent->parent->right;
5 if (y->color == Node::Color::Red) {
6 z->parent->color = Node::Color::Black;
7 y->color = Node::Color::Black;
8 z->parent->parent->color = Node::Color::Red;
9 z = z->parent->parent;
10 }
11 else {
12 if (z == z->parent->right) {
13 z = z->parent;
14 rotate_left(z);
15 }
16 z->parent->color = Node::Color::Black;
17 z->parent->parent->color = Node::Color::Red;
18 rotate_right(z->parent->parent);
19 }
20 }
21 else {
22 NodeType* y = z->parent->parent->left;
23 if (y->color == Node::Color::Red) {
24 z->parent->color = Node::Color::Black;
25 y->color = Node::Color::Black;
26 z->parent->parent->color = Node::Color::Red;
27 z = z->parent->parent;
28 }
29 else {
30 if (z == z->parent->left) {
31 z = z->parent;
32 rotate_right(z);
33 }
34 z->parent->color = Node::Color::Black;
35 z->parent->parent->color = Node::Color::Red;
36 rotate_left(z->parent->parent);
37 }
38 }
39 }
40 root->color = Node::Color::Black;
41 }
21.1. Estructuras de datos no lineales [537]

La insercin de un nodo rojo puede violar la regla 2 (el nodo raz queda como
rojo en el caso de un rbol vaco) o la regla 4 (el nodo insertado pasa a ser hijo de
un nodo rojo). Este ltimo caso es el que se contempla en el bucle de la funcin
insert_fixup(). Cada una de las dos ramas del if sigue la estrategia dual, depen-
diendo de si el padre es un hijo derecho o izquierdo. Basta estudiar el funcionamiento
de una rama, dado que la otra es idntica pero intercambiando right y left. Bsi-
camente se identican tres casos.

El primero corresponde a las lneas 6 a 9 . Es el caso en que el nodo a insertar
pasa a ser hijo de un nodo rojo cuyo hermano tambin es rojo (e.g. gura 21.5.a).
En este caso el nodo padre y el nodo to se pasan a negro mientras que el abuelo
se pasa a rojo (para mantener el nmero de nodos negros en todos los caminos).
Al cambiar a rojo el nodo abuelo es posible que se haya vuelto a violar alguna
regla, y por eso se vuelven a comprobar los casos.
Otra posibilidad es que el nodo to sea negro y adems el nodo insertado sea hijo
derecho (e.g. gura 21.5.b). En ese caso se realiza una rotacin a la izquierda
para reducirlo al caso siguiente y se aplica lo correspondiente al ltimo caso.
El ltimo caso corresponde a que el nodo to sea negro y el nodo insertado sea
hijo izquierdo (e.g. gura 21.5.c). En ese caso se colorea el padre como negro, y
el abuelo como rojo, y se rota a la derecha el abuelo. Este mtodo deja un rbol
correcto.

z z z

C21
(a) (b) (c)
Figura 21.5: Casos contemplados en la funcin insert_fixup().

El borrado tambin se apoya en la funcin transplant() que es muy similar


al caso de los rboles de bsqueda binaria.

Listado 21.16: Transplantado de subrboles en rbol rojo-negro


1 void transplant(NodeType* u, NodeType* v) {
2 if (u->parent == NodeType::nil())
3 root = v;
4 else if (u == u->parent->left)
5 u->parent->left = v;
6 else
7 u->parent->right = v;
8 v->parent = u->parent;
9 }
[538] CAPTULO 21. C++ AVANZADO

Con este procedimiento auxiliar el borrado de un nodo queda relativamente similar


al caso de rboles de bsqueda binaria.

Listado 21.17: Borrado de un nodo en rboles rojo-negro


1 void remove(NodeType* z) {
2 NodeType* y = z;
3 NodeType* x;
4 NodeType::Color y_orig_color = y->color;
5 if (z->left == NodeType::nil()) {
6 x = z->right;
7 transplant(z, z->right);
8 }
9 else if (z->right == NodeType::nil()) {
10 x = z->left;
11 transplant(z, z->left);
12 }
13 else {
14 y = Node::minimum(z->right);
15 y_orig_color = y->color;
16 x = y->right;
17 if (y->parent == z) {
18 x->parent = y;
19 }
20 else {
21 transplant(y, y->right);
22 y->right = z->right;
23 y->right->parent = y;
24 }
25 transplant(z, y);
26 y->left = z->left;
27 y->left->parent = y;
28 y->color = z->color;
29 }
30 if (y_orig_color == Node::Color::Black)
31 rb_remove_fixup(x);
32 }

El nodo y corresponde al nodo que va a eliminarse o moverse dentro del rbol. Ser
el propio z si tiene menos de dos hijos o el nodo y de los casos c y d en la gura 21.2.
Mantenemos la variable y_orig_color con el color que tena ese nodo que se ha
eliminado o movido dentro del rbol. Solo si es negro puede plantear problemas de
violacin de reglas, porque el nmero de nodos negros por cada rama puede variar.
Para arreglar los problemas potenciales se utiliza una funcin anloga a la utilizada en
la insercin de nuevos nodos.

Listado 21.18: Reparacin tras borrar un nodo en rboles rojo-negro


1 void remove_fixup(NodeType* x) {
2 while (x != root && x->color == Node::Color::Black) {
3 if (x == x->parent->left) {
4 NodeType* w = x->parent->right;
5 if (w->color == Node::Color::Red) {
6 w->color = Node::Color::Black;
7 x->parent->color = Node::Color::Red;
8 rotate_left(x->parent);
9 w = x->parent->right;
10 }
11 if (w->left->color == Node::Color::Black
12 && w->right->color == Node::Color::Black) {
13 w->color = Node::Color::Red;
14 x = x->parent;
15 }
16 else {
17 if (w->right->color == Node::Color::Black) {
18 w->left->color = Node::Color::Black;
21.1. Estructuras de datos no lineales [539]

19 w->color = Node::Color::Red;
20 rotate_right(w);
21 w = x->parent->right;
22 }
23 w->color = x->parent->color;
24 x->parent->color = Node::Color::Black;
25 w->right->color = Node::Color::Black;
26 rotate_left(x->parent);
27 x = root;
28 }
29 }
30 else {
31 NodeType* w = x->parent->left;
32 if (w->color == Node::Color::Red) {
33 w->color = Node::Color::Black;
34 x->parent->color = Node::Color::Red;
35 rotate_right(x->parent);
36 w = x->parent->left;
37 }
38 if (w->right->color == Node::Color::Black
39 && w->left->color == Node::Color::Black) {
40 w->color = Node::Color::Red;
41 x = x->parent;
42 }
43 else {
44 if (w->left->color == Node::Color::Black) {
45 w->right->color = Node::Color::Black;
46 w->color = Node::Color::Red;
47 rotate_left(w);
48 w = x->parent->left;
49 }
50 w->color = x->parent->color;
51 x->parent->color = Node::Color::Black;
52 w->left->color = Node::Color::Black;
53 rotate_right(x->parent);
54 x = root;
55 }
56 }
57 }
58 x->color = Node::Color::Black;
59 }

Nuevamente se trata de un cdigo dual. En el if ms exterior se distinguen los


casos de borrar un hijo derecho o izquierdo. En ambas ramas se encuentra el mismo
cdigo intercambiando left por right. Por tanto basta analizar la primera de ellas.
Se distinguen cuatro casos:

C21
El hermano w es rojo. En ese caso forzosamente los hijos de w deben ser negros.
Por tanto se puede intercambiar los colores del hermano y del padre y hacer una
rotacin a la izquierda sin violar nuevas reglas. De esta forma el nuevo hermano
ser forzosamente negro, por lo que este caso se transforma en alguno de los
posteriores.
El hermano w es negro y los dos hijos de w son negros. En ese caso cambiamos
el color del hermano a rojo. De esta forma se equilibra el nmero de negros por
cada rama, pero puede generar una violacin de reglas en el nodo padre, que se
tratar en la siguiente iteracin del bucle.
El hermano w es negro y el hijo izquierdo de w es rojo. En ese caso intercambia-
mos los colores de w y su hijo izquierdo y hacemos una rotacin a la derecha.
De esta forma hemos reducido este caso al siguiente.
El hermano w es negro y el hijo derecho de w es rojo. En este caso cambiando
colores en los nodos que muestra la gura 21.6.d y rotando a la izquierda se
obtiene un rbol correcto que compensa el nmero de negros en cada rama.
[540] CAPTULO 21. C++ AVANZADO

(a)

B D
x A D w B E

C E x A w C

(b)

B x B
x A D w A D

C E C E

(c)

B B
x A D w x A C w

C E D

(d)

B D
x A D w B E

C E A C

Figura 21.6: Casos contemplados en la funcin remove_fixup() segn [20].

AVL trees

Los rboles AVL (Adelson-Velskii and Landis) son otra forma de rbol balanceado
en el que se utiliza la altura del rbol como criterio de balanceo. Solo puede haber una
diferencia de 1 entre la altura de dos ramas. Es por tanto un criterio ms estricto que
los red-black trees, lo que lo hace menos eciente en las inserciones y borrados pero
ms eciente en las lecturas.
rboles balanceados
Los red-black trees son ms ecien-
tes en insert() y remove(),
pero los AVL trees son ms ecien-
tes en search().
21.1. Estructuras de datos no lineales [541]

Cada nodo tiene informacin adicional con la altura del rbol en ese punto. En
realidad tan solo es necesario almacenar el factor de equilibrio que es simplemente
la diferencia entre las alturas del subrbol izquierdo y el derecho. La ventaja de esta
ltima alternativa es que es un nmero mucho ms reducido (siempre comprendido en
el rango -2 a +2) por lo que puede almacenarse en solo 3 bits.
Para insertar elementos en un rbol AVL se utiliza un procedimiento similar a
cualquier insercin en rboles de bsqueda binaria, con dos diferencias:

La insercin debe computar el factor de equilibrio en los nodos afectados.


Finalmente hay que equilibrar el rbol si es necesario.

El equilibrado se realiza con rotaciones siguiendo el procedimiento representado


en la gura 21.7. Es importante destacar que las propias funciones de rotacin alteran
los factores de equilibrio de los nodos involucrados (nodos x e y en la gura 21.4).

Las operaciones insert(), rotate_right(), rotate_left() y


remove() sobre rboles AVL deben recalcular el factor de equilibrio en
los nodos afectados. Adems, en caso de dejar un rbol desequilibrado, las
operaciones insert() y remove() deben equilibrar el rbol segn el pro-
cedimiento descrito en la gura 21.7.

Radix tree

An hay otro tipo de rboles binarios que vale la pena comentar por sus implicacio-
nes con los videojuegos. Se trata de los rboles de prejos, frecuentemente llamados
tries1 .
La gura 21.8 muestra un ejemplo de rbol de prejos con un conjunto de enteros
binarios. El rbol los representa en orden lexicogrco. Para cada secuencia binaria si
empieza por 0 est en el subrbol izquierdo y si empieza por 1 en el subrbol derecho.
Conforme se recorren las ramas del rbol se obtiene la secuencia de bits del nmero
a buscar. Es decir, el tramo entre el nodo raz y cualquier nodo intermedio dene el
prejo por el que empieza el nmero a buscar. Por eso a este rbol se le llama prex

C21
tree o radix tree.
Un rbol de prejos (pero no binario) se utiliza frecuentemente en los diccionarios
predictivos de los telfonos mviles. Cada subrbol corresponde a una nueva letra de
la palabra a buscar.
Tambin se puede utilizar un rbol de prejos para indexar puntos en un segmento
de longitud arbitraria. Todos los puntos en la mitad derecha del segmento estn en
el subrbol derecho, mientras que todos los puntos de la mitad izquierda estn en
el subrbol izquierdo. Cada subrbol tiene las mismas propiedades con respecto al
subsegmento que representa. Es decir, el subrbol derecho es un rbol de prejos que
representa a medio segmento derecho, y as sucesivamente. El nmero de niveles del
rbol es ajustable dependiendo de la precisin que requerimos en el posicionamiento
de los puntos.
1 El nombre en singular es trie, que deriva de retrieve. Por tanto la pronunciacin correcta se asemeja a

la de tree, aunque muchos autores la pronuncian como try.


[542] CAPTULO 21. C++ AVANZADO

(a) A A
h+2 h h h+2

B B

C C

rotate left(B) rotate right(B)

(b) A A
h+2 h h h+2

C C

B B

rotate right(A) rotate left(A)

(c) C C
h+1 h+1 h+1 h+1

B A B A

Figura 21.7: Casos contemplados en la funcin de equilibrado de rboles AVL.

En los rboles de prejos la posicin de los nodos est prejada a priori por el
valor de la clave. Estos rboles no realizan ninguna funcin de equilibrado por lo que
su implementacin es trivial. Sin embargo estarn razonablemente equilibrados si los
nodos presentes estn uniformemente repartidos por el espacio de claves.

21.1.2. Recorrido de rboles


En multitud de ocasiones es necesario recorrer los elementos de un rbol en un or-
den determinado. Son frecuentes los recorridos en orden, en preorden, y en postorden.
El recorrido en orden sigue el orden del campo clave. Es decir, para cualquier
nodo primero se visitan los nodos del subrbol izquierdo, luego el nodo y nalmente
los nodos del subrbol derecho.
21.1. Estructuras de datos no lineales [543]

0 1
0
1 0
10
1 0 1
011 100
1
1011
Figura 21.8: Un ejemplo de trie extraido de [20]. Contiene los elementos 1011, 10, 011, 100 y 0.

Listado 21.19: Recorrido en orden en un rbol de bsqueda binaria


1 template <typename Func>
2 void inorder_tree_walk(Func f) {
3 inorder_tree_walk(root, f);
4 }
5
6 template <typename Func>
7 void inorder_tree_walk(NodeType* x, Func f) {
8 if (x == 0) return;
9 inorder_tree_walk(x->left, f);
10 f(x);
11 inorder_tree_walk(x->right, f);
12 }

El recorrido en preorden visita el nodo antes de cualquiera de sus subrboles.

Listado 21.20: Recorrido en preorden en un rbol de bsqueda binaria

C21
1 template <typename Func>
2 void preorder_tree_walk(Func f) {
3 preorder_tree_walk(root, f);
4 }
5
6 template <typename Func>
7 void preorder_tree_walk(NodeType* x, Func f) {
8 if (x == 0) return;
9 f(x);
10 preorder_tree_walk(x->left, f);
11 preorder_tree_walk(x->right, f);
12 }

Finalmente el recorrido en postorden visita el nodo despus de visitar ambos


subrboles.

Listado 21.21: Recorrido en postorden en un rbol de bsqueda binaria


1 template <typename Func>
2 void postorder_tree_walk(Func f) {
[544] CAPTULO 21. C++ AVANZADO

3 postorder_tree_walk(root, f);
4 }
5
6 template <typename Func>
7 void postorder_tree_walk(NodeType* x, Func f) {
8 if (x == 0) return;
9 postorder_tree_walk(x->left, f);
10 postorder_tree_walk(x->right, f);
11 f(x);
12 }

Pero para el recorrido de estructuras de datos con frecuencia es mucho mejor em-
plear el patrn iterador. En ese caso puede reutilizarse cualquier algoritmo de la STL.
Incluir el orden de recorrido en el iterador implica almacenar el estado necesario.
Las funciones de recorrido anteriormente descritas son recursivas, por lo que el es-
tado se almacenaba en los sucesivos marcos de pila correspondientes a cada llamada
anidada. Por tanto necesitamos un contenedor con la ruta completa desde la raz has-
ta el nodo actual. Tambin tendremos que almacenar el estado de recorrido de dicho
nodo, puesto que el mismo nodo es visitado en tres ocasiones, una para el subrbol
izquierdo, otra para el propio nodo, y otra para el subrbol derecho.

Listado 21.22: Iterador en orden en un rbol de bsqueda binaria


1 class inorder_iterator : public std::iterator<std::
input_iterator_tag,
2 Node<KeyType>,
3 ptrdiff_t,
4 const Node<KeyType>*,
5 const Node<KeyType>&>
{
6 typedef Node<KeyType> NodeType;
7 enum IteratorState { VisitingLeft, VisitingNode, VisitingRight
};
8 std::vector<std::pair<NodeType*,IteratorState> > _current;
9
10 public:
11 inorder_iterator(NodeType* x) {
12 _current.push_back(std::make_pair(x,VisitingLeft));
13 goToNextNode();
14 }
15
16 const NodeType& operator*() const {
17 return *_current.back().first;
18 }
19
20 const NodeType* operator->() const {
21 return _current.back().first;
22 }
23
24 bool equal(inorder_iterator<KeyType> const& rhs) const {
25 return *this == rhs;
26 }
27
28 inorder_iterator<KeyType>& operator++() {
29 goToNextNode();
30 }
31
32 inorder_iterator<KeyType> operator++(int) {
33 inorder_iterator<KeyType> ret(*this);
34 goToNextNode();
35 return ret;
36 }
37
38 private:
39 void goToNextNode();
40 };
21.1. Estructuras de datos no lineales [545]

41
42 template<typename KeyType>
43 inline bool operator== (inorder_iterator<KeyType> const& lhs,
44 inorder_iterator<KeyType> const& rhs) {
45 return lhs.equal(rhs);
46 }

En el caso del iterador en orden la funcin de recorrido sera similar a la siguiente:

Listado 21.23: Funcin para obtener el siguiente nodo en un iterador en orden.


1 void
2 inorder_iterator<KeyType>::goToNextNode()
3 {
4 if (_current.empty()) return;
5
6 std::pair<NodeType*,IteratorState>& last = _current.back();
7
8 if (last.second == VisitingLeft) {
9 NodeType* l = last.first->left;
10 if (l == 0) last.second = VisitingNode;
11 else {
12 _current.push_back(std::make_pair(l,VisitingLeft));
13 goToNextNode();
14 }
15 }
16 else if (last.second == VisitingNode) {
17 NodeType* r = last.first->right;
18 if (r == 0) _current.pop_back();
19 else {
20 last.second = VisitingRight;
21 _current.push_back(std::make_pair(r,VisitingLeft));
22 }
23 goToNextNode();
24 }
25 else if (last.second == VisitingRight) {
26 _current.pop_back();
27 goToNextNode();
28 }
29 }

Se propone como ejercicio la denicin de iteradores para el recorrido en preorden


y postorden.

C21
21.1.3. Quadtree y octree
Los rboles binarios particionan de forma eciente un espacio de claves de una
sola dimensin. Pero con pequeas extensiones es posible particionar espacios de dos
y tres dimensiones. Es decir, pueden ser usados para indexar el espacio de forma
eciente.
Los quadtrees y los octrees son la extensin natural de los rboles binarios de
prejos (tries) para dos y tres dimensiones respectivamente. Un quadtree es un rbol
cuyos nodos tienen cuatro subrboles correspondientes a los cuatro cuadrantes de un
espacio bidimensional. Los nodos de los octrees tienen ocho subrboles correspon-
dientes a los ocho octantes de un espacio tridimensional.
La implementacin y el funcionamiento es anlogo al de un rbol prejo utilizado
para indexar los puntos de un segmento. Adicionalmente, tambin se emplean para
indexar segmentos y polgonos.
[546] CAPTULO 21. C++ AVANZADO

Figura 21.9: Ejemplo de quadtree de puntos. Fuente: Wikipedia.

Cuando se utilizan para indexar segmentos o polgonos puede ocurrir que un mis-
mo segmento cruce el lmite de un cuadrante o un octante. En ese caso existen dos
posibles soluciones:

Hacer un recortado (clipping) del polgono dentro de los lmites del cuadrante
u octante.
Poner el polgono en todos los cuadrantes u octantes con los que intersecta.

En este ltimo caso es preciso disponer de alguna bandera asociada a los polgonos
para no recorrerlos ms veces de las necesarias.
Simon Perreault distribuye una implementacin sencilla y eciente de octrees en
C++2 . Simplicando un poco esta implementacin los nodos son representados de esta
forma:

Listado 21.24: Representacin de nodos en un octree.


1 enum NodeType { BranchNode, LeafNode };
2
3 class Node {
4 public:
5 NodeType type() const;
6
7 private:
8 NodeType type_ : 2;
9 };
10
11 class Branch : public Node {
12 public:
13 Node*& child( int x, int y, int z );
14 Node*& child( int index );
15
16 private:
2 En el momento de editar estas notas se distribuye bajo la GPL en http://nomis80.org/code/

octree.html.
21.1. Estructuras de datos no lineales [547]

17 Node* children[2][2][2];
18 };
19
20 class Leaf : public Node {
21 public:
22 Leaf( const T& v );
23
24 const T& value() const;
25 T& value();
26 void setValue( const T& v );
27
28 private:
29 T value_;
30 };

Esta representacin de rboles diferencia entre nodos hoja y nodos de ramica-


cin. Los valores solo se almacenan en los nodos hoja y stos no tienen la sobrecarga
de los punteros a los ocho subrboles. Por contra, los nodos de ramicacin no tienen
sobrecarga de valores asociados, puesto que para la insercin de un elemento puede
ser necesario aadir un nmero de nodos de ramicacin sin valor alguno.
El uso bsico es muy sencillo. El contenido a incluir puede ser cualquier cosa,
desde simples valores (color de un punto), pasando por un voxel (pixel 3D) hasta
polgonos o poliedros. El tipo de contenido puede ser tambin una referencia a un
objeto grco. De esta forma se podra incluir el mismo elemento (e.g. un polgono) en
mltiples nodos hoja. En el captulo siguiente veremos cmo la tcnica de referencias
con contador puede ayudar en casos como ste.

Listado 21.25: Representacin de nodos en un octree.


Figura 21.10: Ejemplo del motor
de Sparse Voxel Octree de nVidia. 1 #include "octree.h"
2
3 int main()
4 {
5 Octree<double> o(4096);
6 o(1,2,3) = 3.1416;
7 o.erase(1,2,3);
8 }


La lnea 5 construye un octree de 4096 puntos de ancho en cada dimensin.
Esta implementacin requiere que sea una potencia de dos, siendo posible indexar
4096 4096 4096 nodos.

C21
La regularidad de los octree los hacen especialmente indicados para la parale-
lizacin con GPU y recientemente estn teniendo cierto resurgimiento con su
utilizacin en el renderizado de escenas con raycasting o incluso raytracing
en una tcnica denominada Sparse Voxel Octree.

La propia nVidia3 ha desarrollado una biblioteca que implementa esta tcnica de


sparse voxel octree sobre GPU con CUDA (Compute Unied Device Architecture), y
se distribuye bajo licencia Apache. Otra excelente fuente de informacin es la tesis de
Cyril Crassin de INRIA4 que explica los fundamentos tericos de GigaVoxels, tambin
basada en octrees.
3 Ver http://research.nvidia.com/publication/efficient-sparse-voxel-octrees
4 Disponible en lnea en http://maverick.inria.fr/Membres/Cyril.Crassin/

thesis/CCrassinThesis_EN_Web.pdf
[548] CAPTULO 21. C++ AVANZADO

21.2. Patrones de diseo avanzados


En el mdulo 1 ya se expusieron un buen nmero de patrones. En esta seccin
completaremos la coleccin con algunos patrones muy utilizados en todo tipo de apli-
caciones.

21.2.1. Smart pointers


Los punteros inteligentes (smart pointers) son tipos de datos que simplican de
alguna manera la gestin de la memoria dinmica. Facilitan la gestin del ciclo de
vida de las variables dinmicas para evitar los problemas frecuentemente asociados a
los punteros, especialmente la liberacin de la memoria.
La biblioteca estndar de C++ incorpora una plantilla denominada auto_ptr. Su
objetivo es envolver un puntero normal de tal forma que la destruccin del puntero lle-
ve consigo tambin la destruccin del objeto apuntado. Por lo dems, un auto_ptr
se comporta como si se tratara del propio puntero. Por ejemplo, es frecuente encontrar
cdigo como el que sigue:

Listado 21.26: Ejemplo de uso inseguro de punteros.


1 T* p = new T();
2
3 // cuerpo de la funcin
4
5 delete p;

Este fragmento tiene dos problemas:

Es relativamente fcil olvidar llamar a delete. Conforme evoluciona el cdigo


pueden aparecer puntos de retorno que no invocan al destructor.
En esta secuencia no es posible garantizar que el ujo del programa ser secuen-
cial. Es perfectamente posible que en medio del cdigo de la funcin se eleve
una excepcin. En ese caso no se ejecutar el delete. Por supuesto siempre
es posible utilizar construcciones try/catch pero el cdigo cada vez se hara
menos legible.

Bjarne Stroustrup invent una tcnica de aplicacin general para resolver este tipo
de problemas. Se llama RAII (Resource Acquisition Is Initialization) y bsicamente
consiste en encapsular las operaciones de adquisicin de recursos y liberacin de re-
cursos en el constructor y destructor de una clase normal. Esto es precisamente lo que
hace auto_ptr con respecto a la reserva de memoria dinmica. El mismo cdigo
del fragmento anterior puede reescribirse de forma segura as:

Listado 21.27: Ejemplo de uso seguro de punteros.


1 auto_ptr<T> p = new T();
2
3 // cuerpo de la funcin

No importa el camino que siga el programa, aunque se eleve una excepcin. En el


momento en que se abandone el bloque en el que se ha declarado el auto_ptr se
invocar a su destructor, que a su vez invocar delete.
21.2. Patrones de diseo avanzados [549]

Como puede verse hemos ligado el tiempo de vida del objeto construido en memo-
ria dinmica al tiempo de vida del auto_ptr, que suele ser una variable automtica o
un miembro de clase. Se dice que el auto_ptr posee al objeto dinmico. Pero puede
ceder su posesin simplemente con una asignacin o una copia a otro auto_ptr.

Listado 21.28: Cesin de la posesin del objeto dinmico.


1 auto_ptr<T> q(p);
2 auto_ptr<T> r;
3 p->f(); // error (NULL ref)
4 q->f(); // ok
5 r = q;
6 q->f(); // error (NULL ref)
7 r->f(); // ok

Es decir, auto_ptr garantiza que solo hay un objeto que posee el objeto di-
nmico. Tambin permite desligar el objeto dinmico del auto_ptr para volver a
gestionar la memoria de forma explcita.

Listado 21.29: Recuperacin de la propiedad del objeto dinmico.


1 T* s = r.release();
2 delete s;

Nunca se deben usar auto_ptr en contenedores estndar, porque los contene-


dores de la STL asumen una semntica de copia incompatible con la del auto_ptr.
La copia de un auto_ptr no genera dos objetos equivalentes.
Esta limitacin, que no es detectada en tiempo de compilacin, es una de las
motivaciones de un completo rediseo de esta funcionalidad para el estndar C++
de 2011. An sigue soportando auto_ptr pero se desaconseja su uso en favor de
unique_ptr. El nombre deriva de que, al igual que auto_ptr, garantiza que solo
un unique_ptr puede estar apuntando a un mismo recurso. Sin embargo, a dife-
rencia de auto_ptr no es posible copiarlos. Sin embargo existe la posibilidad de
transferencia de propiedad entre unique_ptr utilizando la nueva semntica de mo-
vimiento del estndar C++11.

Listado 21.30: Ejemplo de uso de unique_ptr.

C21
1 unique_ptr<T> p(new T());
2 unique_ptr<T> q;
3 q = p; // error (no copiable)
4 q = std:move(p);

La plantilla unique_ptr no tiene un constructor de copia, pero s cuenta con


un constructor de movimiento. Este nuevo constructor se aplica cuando el parmetro
es un rvalue, es decir, una expresin del tipo de las que aparecen en el lado derecho
de una asignacin (de ah el nombre, right value) o un valor de retorno de funcin,
o la copia temporal de un parmetro pasado por copia (ahora se puede pasar tambin
por movimiento). Este tipo de expresiones se caracterizan en C++ porque generan un
temporary, una variable temporal.
La semntica de movimiento resuelve el problema de la generacin inconscien-
te de multitud de variables temporales y la separacin entre constructor de copia y
constructor de movimiento permite detectar en tiempo de compilacin los problemas
semnticos. La copia siempre debera generar dos objetos equivalentes.
[550] CAPTULO 21. C++ AVANZADO

Tanto auto_ptr como unique_ptr proporcionan un mtodo sencillo para


gestionar variables en memoria dinmica casi como si se tratara de variables auto-
mticas. Por ejemplo:

Listado 21.31: Funcin que reserva memoria dinmica y traspasa la propiedad al llamador.
Tambin funcionara correctamente con auto_ptr.
1 unique_ptr<T> f() {
2 unique_ptr<T> p(new T());
3 // ...
4 return p;
5 }

La funcin f() devuelve memoria dinmica. Con simples punteros eso implicaba
que el llamante se haca cargo de su destruccin, de controlar su ciclo de vida. Con esta
construccin ya no es necesario. Si el llamante ignora el valor de retorno ste se libera
automticamente al destruir la variable temporal correspondiente al valor de retorno.
Si en cambio el llamante asigna el valor de retorno a otra variable unique_ptr
entonces est asumiendo la propiedad y se liberar automticamente cuando el nuevo
unique_ptr sea destruido.

Las nuevas caractersticas de la biblioteca estndar para la gestin del ci-


clo de vida de la memoria dinmica estn ya disponibles en los compilado-
res libres GCC y clang. Tan solo hay que utilizar la opcin de compilacin
-std c++0x.

Tanto con auto_ptr como con unique_ptr se persigue que la gestin de me-
moria dinmica sea anloga a la de las variables automticas con semntica de copia.
Sin embargo no aprovechan la posibilidad de que el mismo contenido de memoria sea
utilizado desde varias variables. Es decir, para que la semntica de copia sea la natural
en los punteros, que se generen dos objetos equivalentes, pero sin copiar la memoria
dinmica. Para ese caso el nico soporte que ofreca C++ hasta ahora eran los punteros
y las referencias. Y ya sabemos que ese es un terreno pantanoso.
La biblioteca estndar de C++11 incorpora dos nuevas plantillas para la gestin
del ciclo de vida de la memoria dinmica que ya existan en la biblioteca Boost:
shared_ptr y weak_ptr. Ambos cooperan para disponer de una gestin de me-
moria muy exible. La plantilla shared_ptr implementa una tcnica conocida co-
mo conteo de referencias.
Cuando se asigna un puntero por primera vez a un shared_ptr se inicializa
un contador interno a 1. Este contador se almacena en memoria dinmica y es com-
partido por todos los shared_ptr que apunten al mismo objeto. Cuando se asigna
este shared_ptr a otro shared_ptr o se utiliza el constructor de copia, se in-
crementa el contador interno. Cuando se destruye un shared_ptr se decrementa
el contador interno. Y nalmente cuando el contador interno llega a 0, se destruye
automticamente el objeto dinmico.

Listado 21.32: Ejemplos de uso de shared_ptr.


1 shared_ptr<T> p(new T());
2 shared_ptr<T> q;
3 {
4 q = p;
5 shared_ptr<T> r(p);
6 // ...
21.2. Patrones de diseo avanzados [551]

7 }
8 // ...


En la lnea 1 se construye un shared_ptr que apunta a un objeto dinmi-
co. Esto pone el contador interno de referencias a 1. En la lnea 4 se asigna este
shared_ptr a otro. No se copia el objeto dinmico, sino solo su direccin y la del
que adems es automticamente incrementado (pasa a va-
contador de referencias,
ler 2). En la lnea 5 se utiliza el constructor de copia de otro shared_ptr, que

nuevamente copia solo el puntero y el puntero al contador de referencias, adems de
incrementar su valor (pasa a valer 3). En la lnea 7 se destruye automticamente r,
con lo que se decrementa el contador de referencias (vuelve a valer 2). Cuando acabe
el bloque en el que se han declarado p y q se destruirn ambas variables, y con ello se
decrementar dos veces el contador de referencias. Al llegar a 0 automticamente se
invocar el operador delete sobre el objeto dinmico.
El conteo de referencias proporciona una poderosa herramienta para simplicar
la programacin de aplicaciones con objetos dinmicos. Los shared_ptr pueden
copiarse o asignarse con total libertad y con una semntica intuitiva. Pueden emplearse
en contenedores de la STL y pasarlos por valor libremente como parmetros a funcin
o como valor de retorno de una funcin. Sin embargo no estn totalmente exentos de
problemas. Considera el caso en el que main() tiene un shared_ptr apuntando
a una clase A y sta a su vez contiene directa o indirectamente un shared_ptr que
vuelve a apuntar a A. Tendramos un ciclo de referencias y el contador de referencias
con un balor de 2. En caso de que se destruyera el shared_ptr inicial seguiramos
teniendo una referencia a A, por lo que no se destruir.
Para romper los ciclos de referencias la biblioteca estndar incluye la plantilla
weak_ptr. Un weak_ptr es otro smart pointer a un objeto que se utiliza en estas
condiciones:

1. Solo se necesita acceso al objeto si existe.


2. Puede ser borrado por otros en cualquier momento.
3. Debe ser destruido tras su ltimo uso.
Figura 21.11: El propio creador de
C++ pone como ejemplo el video-
juego asteroids para explicar las ex- Bjarne Stroustrup 5 pone un ejemplo que tiene mucho que ver con la programacin
tensiones a la biblioteca estndar. de videojuegos. Consideremos el caso del juego de los asteroides. Todos los asteroides
son posedos por el juego pero cada asteroide tiene que seguir los movimientos de

C21
los asteroides vecinos para detectar colisiones. Una colisin lleva a la destruccin
de uno o ms asteroides. Cada asteroide debe almacenar una lista de los asteroides
vecinos. Pero el hecho de pertenecer a esa lista no mantiene al asteroide vivo. Por
tanto el uso de shared_ptr sera inapropiado. Por otro lado un asteroide no debe ser
destruido mientras otro asteroide lo examina (para calcular los efectos de una colisin,
por ejemplo), pero debe llamarse al destructor en algn momento para liberar los
recursos asociados. Necesitamos una lista de asteroides que podran estar vivos y una
forma de sujetarlos por un tiempo. Eso es justo lo que hace weak_ptr.

Listado 21.33: Esquema de funcionamiento del propietario de los asteroides. Usa shared_ptr
para representar propiedad.
1 vector<shared_ptr<Asteroid>> va(100);
2 for (int i=0; i<va.size(); ++i) {
3 // ... calculate neighbors for new asteroid ...
4 va[i].reset(new Asteroid(weak_ptr<Asteroid>(va[neighbor])))
;
5 launch(i);
5 http://www.research.att.com/~bs/C++0xFAQ.html#std-weak_ptr
[552] CAPTULO 21. C++ AVANZADO

6 }
7 // ...

El clculo de colisiones podra tener una pinta similar a esto:

Listado 21.34: Esquema de funcionamiento de la deteccin de colisiones. Usa weak_ptr para


representar la relacin con los vecinos.
1 if (shared_ptr<Asteroid> q = p.lock()) {
2 // ... Asteroid still alive: calculate ...
3 }
4 else {
5 // ... oops: Asteroid already destroyed
6 }

Aunque el propietario decidiera terminar el juego y destruir todos los asteroides


(destruyendo los correspondientes shared_ptr que representan la relacin de pro-
piedad) todo funcionara con normalidad. Cada asteroide que se encuentra en mitad del
clculo de colisin todava terminara correctamente puesto que el mtodo lock()
proporciona un shared_ptr que no puede quedar invalidado.
Por ltimo merece la pena comentar en esta seccin un conjunto de reglas para
escribir cdigo correcto con smart pointers:

Siempre que aparezca un operador new debe ser en un constructor de un smart


pointer.
Evitar el uso de smart pointers sin nombre (e.g. temporaries).

La primera regla impide tener punteros normales coexistiendo con los smart poin-
ters. Eso solo puede generar quebraderos de cabeza, puesto que el smart pointer no es
capaz de trazar los accesos al objeto desde los punteros normales.
La segunda regla garantiza la liberacin correcta de la memoria en presencia de
excepciones6 . Veamos un ejemplo extrado de la documentacin de Boost:

Listado 21.35: Uso de smart pointers en presencia de excepciones.


1 void f(shared_ptr<int>, int);
2 int g();
3
4 void ok() {
5 shared_ptr<int> p(new int(2));
6 f(p, g());
7 }
8
9 void bad() {
10 f(shared_ptr<int>(new int(2)), g());
11 }

Para entender por qu la linea 10 es peligrosa basta saber que el orden de eva-
luacin de los argumentos no est especicado. Podra evaluarse primero el operador
new, despus llamarse a la funcin g(), y nalmente no llamarse nunca al constructor
de shared_ptr porque g() eleva una excepcin.
6 Este caso ha sido descrito en detalle por Herb Sutter en http://www.gotw.ca/gotw/056.htm.
21.2. Patrones de diseo avanzados [553]

En la mayora de las bibliotecas de relativa complejidad encontramos al-


gn tipo de smart pointer. En Ogre ya hemos visto Ogre::SharedPtr,
en ZeroC Ice hemos visto IceUtil::Handle, en Boost hay una am-
plia coleccin de smart pointers que incluye boost::shared_ptr y
boost::unique_ptr. Ahora que el nuevo estndar C++ incluye conteo
de referencias veremos una progresiva evolucin de las bibliotecas para adop-
tar la versin estndar. Mientras tanto, es muy importante utilizar en cada
biblioteca los mecanismos que incluye y no mezclarlos con otras bibliotecas.

Un problema evidente al usar shared_ptr es la existencia de dos objetos en


el heap por cada objeto. Si construimos un objeto con new y entonces lo asignamos
a un shared_ptr est claro que el propio shared_ptr deber construir en el
heap un entero con el que lleva el conteo de referencias y que ser compartido por
todos los shared_ptr que apunten al mismo objeto. Esto es bastante grave en la
prctica porque cada objeto reservado en el heap lleva algo de informacin asociada
para gestionar las zonas libres y ocupadas. Si se reservan multitud de pequeos objetos
dinmicos estaramos dilapidando memoria.
En C++11 se evita el problema recomendando encarecidamente que no se utilice
new ni delete en absoluto. En su lugar se proporciona un objeto funcin denomi-
nado make_shared y se delega la llamada a delete a los smart pointers.

Pimpl con auto_ptr o unique_ptr

Ya hemos visto en el mdulo 1 el patrn idiomtico del handle-body o Pimpl. Sin


embargo con el uso de smart pointers puede conseguirse una implementacin ms
elegante del patrn.
Un buen compromiso entre automatizacin de la gestin de memoria y exibilidad
en la implementacin de este patrn es la plantilla auto_ptr (o unique_ptr para
C++11) de la biblioteca estndar de C++. La implementacin del patrn Pimpl puede
simplicarse an ms como recomienda Herb Sutter7 :

Listado 21.36: Ejemplo mejorado del patrn Pimpl (archivo de cabecera).


1 class C {

C21
2 public:
3 C();
4 /*...*/
5 private:
6 class CImpl;
7 auto_ptr<CImpl> pimpl_;
8 };

La diferencia clave es la declaracin del puntero a la implementacin como un


auto_ptr en la lnea 7 . La declaracin anticipada de la clase implementacin se
ha metido tambin en la parte privada del handle para mejorar la ocultacin.

Listado 21.37: Ejemplo mejorado del patrn Pimpl (archivo de implementacin).


1 class C::CImpl { /*...*/ };
2
3 C::C() : pimpl_( new CImpl ) { }

7 Por ejemplo, en

http://www.gotw.ca/publications/using_auto_ptr_effectively.htm.
[554] CAPTULO 21. C++ AVANZADO

Ahora no es necesario incluir un destructor explcitamente porque el destructor


por defecto llamar a los destructores de los miembros, en particular de pimpl_. Y
el destructor de un auto_ptr llama automticamente al operador delete con el
puntero interno.

21.2.2. Command
El patrn command (se traducira como orden en castellano) se utiliza frecuente-
mente en interfaces grcas para el manejo de las rdenes del usuario. Consiste en
encapsular las peticiones en objetos que permiten desacoplar la emisin de la orden
de la recepcin, tanto desde el punto de vista lgico como temporal.

Problema

Existe un gran nmero de situaciones en las que la sincrona inherente a la invo-


cacin directa a mtodos resulta poco conveniente:

La invocacin directa solamente involucra a emisor y receptor de la orden, por


lo que resulta complicado trazar la actividad del sistema en otros componen-
tes (barras de progreso, capacidad de deshacer las rdenes ejecutadas, ayuda
contextual, etc.).
En algunas ocasiones es necesario un modelo de ejecucin transaccional, o con
limitaciones de orden. As, por ejemplo si se ejecuta una accin tambin deben
ejecutarse todas las acciones relacionadas. Y si no se ejecuta una accin deben
deshacerse todas las relacionadas. Las acciones sobre un mundo virtual (e.g.
un MMORPG) deben garantizar la ejecucin en orden causal para todos los
jugadores (la causa precede al efecto).
En ocasiones conviene grabar y reproducir una secuencia de rdenes (e.g para
la implementacin de macros o simplemente para la prueba del juego).
Muchas acciones conllevan la interaccin con el usuario en forma de wizards o
cuadros de dilogo para congurar la accin. El patrn command permite que
el objeto orden sea creado en el momento de mostrar el wizard, que el usuario
congure el objeto mediante la interaccin con el wizard, y nalmente, al cerrar
el wizard se desencadena el proceso de emisin del mensaje. De esta forma la
orden no necesita nada de cdigo de interfaz de usuario.
La mayora de los juegos actuales son programas multi-hilo. Las rdenes pue-
den ser generadas desde multitud de hilos, y el procesamiento de stas puede
corresponder a otro conjunto de hilos diferente. El patrn command proporciona
un mtodo sencillo para desacoplar productores y consumidores de rdenes.
En los juegos en red necesitamos ejecutar rdenes en todos los ordenadores
participantes. El patrn command facilita la serializacin de las rdenes sin ms
que serializar los objetos que las representan.
Muchos juegos aaden algn tipo de consola para interactuar directamente con
el motor empleando un intrprete de rdenes. El patrn command permite sin-
tetizar rdenes en el juego como si se hubieran producido en el propio juego, lo
que facilita enormemente la prueba y depuracin.
21.2. Patrones de diseo avanzados [555]

Figura 21.12: Estructura del patrn command.

Solucin

La gura 21.12 muestra un diagrama de clases con las entidades involucradas. El


cliente es el que crea los objeto command concretos y los asocia con el receptor de la
accin. Posteriormente, y de forma totalmente desacoplada, un invocador llamar al
mtodo execute() de cada objeto orden creado. Los objetos command concretos
implementan el mtodo execute(), normalmente delegando total o parcialmente
sobre el receptor de la accin.
Figura 21.13: Las acciones de los Un ejemplo de aplicacin en un videojuego podra ser el que se muestra en la
personajes de un juego son perfec- gura 21.14.
tas para el patrn command.

C21
Figura 21.14: Ejemplo de aplicacin del patrn command.

El interfaz de usuario crea las rdenes a realizar por el personaje o los personajes
que estn siendo controlados, as como la asociacin con su personaje. Estas acciones
se van procesando por el motor del juego, posiblemente en paralelo.

Implementacin

En trminos generales el patrn command permite descargar ms o menos inteli-


gencia sobre el objeto ConcreteCommand. Se juega entre los dos posibles extre-
mos.
[556] CAPTULO 21. C++ AVANZADO

El objeto ConcreteCommand no realiza ninguna funcin por s mismo, sino


que delega todas las acciones en el objeto Receiver. A este tipo de rdenes
se les llama forwarding commands.
El objeto ConcreteCommand implementa absolutamente todo, sin delegar
nada en absoluto al objeto Receiver.

Entre estos dos extremos se encuentran las rdenes que realizan algunas funcio-
nes pero delegan otras en el receptor. En general a todo este tipo de rdenes se les
denomina active commands.
Desde el punto de vista de la implementacin hay poco que podamos aadir a una
orden activa. Tienen cdigo de aplicacin especco que hay que aadir en el mtodo
execute().
Sin embargo, los forwarding commands actan en cierta forma como si se tratara
de punteros a funcin. El Invoker invoca el mtodo execute() del objeto orden y
ste a su vez ejecuta un mtodo del objeto Receiver al que est asociado. En [9] se
describe una tcnica interesante para este n, los generalized functors o adaptadores
polimrcos para objetos funcin. Se trata de una plantilla que encapsula cualquier
objeto, cualquier mtodo de ese objeto, y cualquier conjunto de argumentos para dicho
mtodo. Su ejecucin se traduce en la invocacin del mtodo sobre el objeto con
los argumentos almacenados. Este tipo de functors permiten reducir sensiblemente
el trabajo que implicara una jerarqua de rdenes concretas. Boost implementa una
tcnica similar en la plantilla function, que ha sido incorporada al nuevo estndar
de C++ (en la cabecera functional). Por ejemplo:

Listado 21.38: Ejemplo de uso de generalized functors.


1 #include <functional>
2
3 using namespace std;
4
5 int f1(const char* s) { return 0; }
6
7 struct f2 {
8 int operator() (const char* s) { return 0; }
9 };
10
11 struct A {
12 int fa(const char* s) { return 0; }
13 };
14
15 int
16 main()
17 {
18 function<int (const char*)> f;
19
20 f = f1; f("test1");
21 f = f2(); f("test2");
22 A a;
23 auto f3 = bind1st(mem_fun(&A::fa), &a);
24 f = f3; f("test3");
25 }

La plantilla function se instancia simplemente indicando la signatura de las


llamadas que encapsula. A partir de ah se puede asignar cualquier tipo de objeto que
cumpla la signatura, incluyendo funciones normales, mtodos o functors de la STL,
functors implementados a mano, etc.
21.2. Patrones de diseo avanzados [557]

Consideraciones

El patrn command desacopla el objeto que invoca la operacin del objeto que
sabe cmo se realiza.
Al contrario que la invocacin directa, las rdenes son objetos normales. Pueden
ser manipulados y extendidos como cualquier otro objeto.
Las rdenes pueden ser agregadas utilizando el patrn composite.
Las rdenes pueden incluir transacciones para garantizar la consistencia sin nin-
gn tipo de precaucin adicional por parte del cliente. Es el objeto Invoker el
que debe reintentar la ejecucin de rdenes que han abortado por un interblo-
queo.
Si las rdenes a realizar consisten en invocar directamente un mtodo o una fun-
cin se puede utilizar la tcnica de generalized functors para reducir el cdigo
necesario sin necesidad de implementar una jerarqua de rdenes.

21.2.3. Curiously recurring template pattern


Este patrn fue inicialmente descrito y bautizado por James O. Coplien en [19].
Se trata de un patrn que ya se utilizaba aos antes, desde los primeros tiempos de las
plantillas de C++.

Problema

El patrn CRTP (Curiously Recurring Template Pattern) pretende extraer funcio-


nalidad comn a varias clases, pero que requieren especializacin parcial para cada
una de ellas.

Solucin

La solucin pasa por una interesante recurrencia.

Listado 21.39: Estructura bsica del patrn CRTP.

C21
1 template<typename T> class Base;
2
3 class Derived: public Base<Derived> {
4 // ...
5 };

La clase derivada hereda de una plantilla instanciada para ella misma. La clase
base cuenta en su implementacin con un tipo que deriva de ella misma. Por tanto la
propia clase base puede llamar a funciones especializadas en la clase derivada.

Implementacin

Se han propuesto multitud de casos donde puede aplicarse este patrn. Nosotros
destacaremos en primer lugar su uso para implementar visitantes alternativos a los ya
vistos en el mdulo 1.
Recordaremos brevemente la estructura del patrn visitante tal y como se cont en
el mdulo 1. Examinando la gura 21.15 podemos ver que:
[558] CAPTULO 21. C++ AVANZADO

Figura 21.15: Diagrama de clases del patrn Visitor

La clase base Visitor (y por tanto todas sus clases derivadas) es tremen-
damente dependiente de la jerarqua de objetos visitables de la izquierda. Si
se implementa un nuevo tipo de elemento ElementC (una nueva subclase de
Element) tendremos que aadir un nuevo mtodo visitElementB() en
la clase Visitor y con ello tendremos que reescribir todos y cada uno de
las subclases de Visitor. Cada clase visitable tiene un mtodo especco de
visita.
La jerarqua de elementos visitables no puede ser una estructura arbitraria, debe
estar compuesta por subclases de la clase Element e implementar el mtodo
accept().
Si se requiere cambiar la estrategia de visita. Por ejemplo, unicar el mtodo
de visita de dos tipos de elementos, es preciso cambiar la jerarqua de objetos
visitables.
El orden de visita de los elementos agregados est marcado por la implementa-
cin concreta de las funciones accept() o visitX(). O bien se introduce
el orden de recorrido en los mtodos accept() de forma que no es fcil cam-
biarlo, o bien se programa a medida en los mtodos visitX() concretos. No
es fcil denir un orden de recorrido de elementos (en orden, en preorden, en
postorden) comn para todos las subclases de Visitor.

En general, se considera que el patrn visitor introduce un excesivo acoplamiento


en el cdigo y resulta tremendamente invasivo. Sin embargo, el patrn CRTP permite
aliviar gran parte de los problemas.
La jerarqua de visitables implementa el mtodo accept() exclusivamente para
que puedan elegir el mtodo visit() correcto de la clase derivada de Visitor.
Por eso se le llama tambin despachado doble. El despachado de la funcin virtual
accept() selecciona la subclase de Element concreta y a su vez ese elemento
concreto desencadena el despachado de visitX() que selecciona la subclase de
Visitor concreta. El segundo despachado es esencial para cualquier recorrido. Sin
embargo el primer despachado no siempre es necesario si conocemos de alguna mane-
ra el tipo a visitar. Por ejemplo, en el ejemplo del patrn visitor mostrado en el mdulo
1 el tipo de objetos es completamente jo. Sabemos que hay un objeto Scene que
contiene un nmero variable de objetos ObjectScene. Otra forma de realizar este
primer despachado podra ser utilizando RTTI (Run Time Type Information) u otro
mecanismo de introspeccin.
21.2. Patrones de diseo avanzados [559]

En este caso en que no sea necesario el primer despachado virtual se puede lograr
de una manera mucho ms eciente sin ni siquiera usar funciones virtuales, gracias al
patrn CRTP. Por ejemplo, el mismo ejemplo del mdulo 1 quedara as:

Listado 21.40: Visitante genrico usando el patrn CRTP.


1 struct ObjectScene {
2 string name;
3 Point position;
4 int weight;
5 };
6
7 struct Scene {
8 template <typename Derived> friend class Visitor;
9 string name;
10 vector<ObjectScene> objects;
11 };
12
13 template <typename Derived>
14 class Visitor {
15 public:
16 void traverseObject(ObjectScene* o) {
17 getDerived().visitObject(o);
18 }
19 void traverseScene(Scene* s) {
20 getDerived().visitScene(s);
21 for (auto o : s->objects)
22 traverseObject(o);
23 }
24 void visitObject(ObjectScene* o) {}
25 void visitScene(Scene* s) {}
26 private:
27 Derived& getDerived() {
28 return *static_cast<Derived*>(this);
29 }
30 };
31
32 class NameVisitor : public Visitor<NameVisitor> {
33 vector<string> _names;
34 public:
35 void visitObject(ObjectScene* o) {
36 _names.push_back(o->name);
37 }
38 void visitScene(Scene* s) {
39 cout << "The scene " << s->name << " has the following
objects:"

C21
40 << endl;
41 for (auto n : _names) cout << n << endl;
42 }
43 };
44
45 class BombVisitor : public Visitor<BombVisitor> {
46 Bomb _bomb;
47 public:
48 BombVisitor(const Bomb& bomb) : _bomb(bomb) {}
49 void visitObject(ObjectScene* o) {
50 Point new_pos = calculateNewPosition(o->position,
51 o->weight,
52 _bomb.intensity);
53 o->position = new_pos;
54 }
55 };
[560] CAPTULO 21. C++ AVANZADO

Como puede observarse, ahora no tocamos en absoluto la jerarqua de visitables


(no se necesita mtodo accept) y no hay ninguna funcin virtual involucrada. En el
Visitor distinguimos entre las funciones de recorrido, que son comunes a cualquier
otro Visitor y las de visita, que se especican por cada visitante concreto. Su uso
es prcticamente igual de sencillo:

Listado 21.41: Utilizacin del visitante basado en CRTP.


1 Scene* scene = createScene();
2 NameVisitor nv;
3 nv.traverseScene(scene);
4 // ...
5 // bomb explosion occurs
6 BombVisitor bv(bomb);
7 bv.traverseScene(scene);

Pero la utilidad del patrn no se limita a implementar visitantes. Es un mecanismo


genrico para implementar mixins. En programacin orientada a objetos un mixin es
una clase que proporciona funcionalidad para ser reusada directamente por sus subcla-
ses. Es decir, las subclases no especializan al mixin sino que simplemente incorporan
funcionalidad derivando de l.
Un ejemplo clsico es la implementacin automtica de operadores a partir de
otros. Es muy utilizado en aritmtica, pero tambin utilizable en otros tipos, como el
siguiente ejemplo de Eli Bendersky8 :

Listado 21.42: Ejemplo de CRTP como mixin.


1 template <typename Derived>
2 struct Comparisons { };
3
4 template <typename Derived>
5 bool operator==(const Comparisons<Derived>& o1, const Comparisons<
Derived>& o2)
6 {
7 const Derived& d1 = static_cast<const Derived&>(o1);
8 const Derived& d2 = static_cast<const Derived&>(o2);
9
10 return !(d1 < d2) && !(d2 < d1);
11 }
12
13 template <typename Derived>
14 bool operator!=(const Comparisons<Derived>& o1, const Comparisons<
Derived>& o2)
15 {
16 return !(o1 == o2);
17 }

Y con ello podemos denir todos los operadores de golpe sin ms que denir
operator <.

Listado 21.43: Ejemplo de mixin con CRTP para implementacin automtica de operadores.
1 class Person : public Comparisons<Person> {
2 public:
3 Person(string name_, unsigned age_)
4 : name(name_), age(age_) {}
5
6 friend bool operator<(const Person& p1, const Person& p2);
7 private:
8 string name;
8 http://eli.thegreenplace.net/2011/05/17/the-curiously-recurring-template-pattern-in-c/
21.2. Patrones de diseo avanzados [561]

9 unsigned age;
10 };
11
12 bool operator<(const Person& p1, const Person& p2) {
13 return p1.age < p2.age;
14 }

Consideraciones

La tcnica que explota el patrn CRTP es denominada a veces como polimors-


mo esttico, por contraposicin al dinmico de las funciones virtuales. La clase base
utiliza la implementacin correcta de los mtodos redenidos en las clases derivadas
porque se le pasa como parmetro de plantilla. Esto es una ventaja y un inconveniente
a la vez.
Por un lado la utilizacin de funciones no virtuales elimina las indirecciones y
permite que sea lo ms eciente posible. Pero por otro lado no puede inferir el tipo de
un objeto a travs de un puntero a la clase base. Por ejemplo, si en el caso del visitante
hubiera varios tipos derivados de ObjectScene y la clase Scene almacenara pun-
teros a ObjectScene, el mtodo traverseObject() no podra determinar qu
funcin de visita debe invocar. La solucin estndar en este caso sera emplear RTTI
(run-time type information) para determinar el tipo de objeto en tiempo de ejecucin,
pero eso es mucho menos eciente que las funciones virtuales.

Listado 21.44: Uso de RTTI para especializar la visita de objetos.


1 void traverseObject(ObjectScene* o) {
2 Character* c = dynamic_cast<Character*>(o);
3 if (c) {
4 getDerived().visitCharacter(c);
5 return;
6 }
7 Weapon* w = dynamic_cast<Weapon*>(o);
8 if (w) {
9 getDerived().visitCharacter(w);
10 return;
11 }
12 }

C21
21.2.4. Reactor
El patrn Reactor es un patrn arquitectural para resolver el problema de cmo
atender peticiones concurrentes a travs de seales y manejadores de seales.

Problema

Existen aplicaciones, como los servidores web, cuyo comportamiento es reactivo,


es decir, a partir de la ocurrencia de un evento externo se realizan todas las operaciones
necesarias para atender a ese evento externo. En el caso del servidor web, una conexin
entrante (evento) disparara la ejecucin del cdigo pertinente que creara un hilo de
ejecucin para atender a dicha conexin. Pero tambin pueden tener comportamiento
proactivo. Por ejemplo, una seal interna puede indicar cundo destruir una conexin
con un cliente que lleva demasiado tiempo sin estar accesible.
[562] CAPTULO 21. C++ AVANZADO

En los videojuegos ocurre algo muy similar: diferentes entidades pueden lanzar
eventos que deben ser tratados en el momento en el que se producen. Por ejemplo, la
pulsacin de un botn en el joystick de un jugador es un evento que debe ejecutar el
cdigo pertinente para que la accin tenga efecto en el juego.

Solucin

En el patrn Reactor se denen una serie de actores con las siguientes responsabi-
lidades (vase gura 21.16):

Figura 21.16: Diagrama de clases del patrn Reactor

Eventos: los eventos externos que puedan ocurrir sobre los recursos (Handles).
Normalmente su ocurrencia es asncrona y siempre est relaciona a un recurso
determinado.
Recursos (Handles): se reere a los objetos sobre los que ocurren los eventos.
La pulsacin de una tecla, la expiracin de un temporizador o una conexin en-
trante en un socket son ejemplos de eventos que ocurren sobre ciertos recursos.
La representacin de los recursos en sistemas tipo GNU/Linux es el descriptor
de chero.
Manejadores de Eventos: Asociados a los recursos y a los eventos que se pro-
ducen en ellos, se encuentran los manejadores de eventos (EventHandler)
que reciben una invocacin a travs del mtodo handle() con la informacin
del evento que se ha producido.
Reactor: se trata de la clase que encapsula todo el comportamiento relativo a
la desmultiplexacin de los eventos en manejadores de eventos (dispatching).
Cuando ocurre un cierto evento, se busca los manejadores asociados y se les
invoca el mtodo handle().

En general, el comportamiento sera el siguiente:

1. Los manejadores se registran utilizando el mtodo regHandler() del Reac-


tor. De esta forma, el Reactor puede congurarse para esperar los eventos del
recurso que el manejador espera. El manejador puede dejar de recibir notica-
ciones con unregHandler().
2. A continuacin, el Reactor entra en el bucle innito (loop()), en el que se
espera la ocurrencia de eventos.
21.2. Patrones de diseo avanzados [563]

3. Utilizando alguna llamada al sistema, como puede ser select(), el Reactor


espera a que se produzca algn evento sobre los recursos monitorizados.
4. Cuando ocurre, busca los manejadores asociados a ese recurso y les invoca el
mtodo handle() con el evento que ha ocurrido como parmetro.
5. El manejador recibe la invocacin y ejecuta todo el cdigo asociado al evento.

Ntese que aunque los eventos ocurran concurrentemente el Reactor serializa las
llamadas a los manejadores. Por lo tanto, la ejecucin de los manejadores de eventos
ocurre de forma secuencial.

Implementacin

Desde el punto de vista de implementacin un Reactor se comporta como un en-


voltorio orientado a objetos de los servicios de demultiplexin de eventos del siste-
ma operativo. Ofrece una interfaz homognea para llamadas al sistema tales como
select(), poll(), o los IO Completion Ports de Windows.
Una implementacin exible de este patrn es la incluida en ACE (Adaptive Com-
munications Environment) (Adaptive Communications Environment9 ). ste sera un
ejemplo mnimo con un manejador de eventos de teclado:

Listado 21.45: Ejemplo de uso de patrn reactor.


1 #include <ace/Reactor.h>
2
3 class MyEvHandler : public ACE_Event_Handler {
4 virtual int handle_input (ACE_HANDLE h) {
5 char buf[256];
6 int n = ::read(h, buf, sizeof buf);
7 if (n <= 0) return -1;
8 // procesar buf ...
9 return 0;
10 }
11 };
12
13 int main (int argc, const char *argv[]) {
14 ACE_Reactor reactor;
15 MyEvHandler h;
16

C21
17 reactor.register_handler(ACE_STDIN,
18 &h,
19 ACE_Event_Handler::READ_MASK);
20 for(;;)
21 reactor.handle_events();
22
23 return 0;
24 }

Para mezclar el bucle de eventos de Ogre con cualquier im-


plementacin del patrn reactor basta eliminar la llamada a
Root::startRendering() e incluir dentro del bucle del Reactor
una llamada a Root::renderOneFrame().

9 http://www.cs.wustl.edu/~schmidt/ACE.html
[564] CAPTULO 21. C++ AVANZADO

Consideraciones

Al utilizar un Reactor, se deben tener las siguientes consideraciones:

1. Los manejadores de eventos no pueden consumir mucho tiempo. Si lo hacen,


pueden provocar un efecto convoy y, dependiendo de la frecuencia de los even-
tos, pueden hacer que el sistema sea inoperable. En general, cuanto mayor sea
la frecuencia en que ocurren los eventos, menos tiempo deben consumir los
manejadores.
2. Existen implementaciones de Reactors que permiten una desmultiplexacin con-
currente.
3. Desde un punto de vista general, el patrn Observer tiene un comportamiento
muy parecido. Sin embargo, el Reactor est pensado para las relaciones 1 a 1 y
no 1 a n como en el caso del Observer visto en el mdulo 1.

21.2.5. Acceptor/Connector
Acceptor-Connector es un patrn de diseo propuesto por Douglas C. Schmidt [82]
y utilizado extensivamente en ACE, su biblioteca de comunicaciones.
La mayora de los videojuegos actuales necesitan comunicar datos entre jugadores
de distintos lugares fsicos. En toda comunicacin en red intervienen dos ordenadores
con roles bien diferenciados. Uno de los ordenadores toma el rol activo en la comu-
nicacin y solicita una conexin con el otro. El otro asume un rol pasivo esperando
solicitudes de conexin. Una vez establecida la comunicacin cualquiera de los or-
denadores puede a su vez tomar el rol activo enviando datos o el pasivo, esperando
la llegada de datos. Es decir, en toda comunicacin aparece una fase de conexin e
inicializacin del servicio y un intercambio de datos segn un patrn de intercambio
de mensajes pre-establecido.
El patrn acceptor-connector se ocupa de la primera parte de la comunicacin.
Desacopla el establecimiento de conexin y la inicializacin del servicio del procesa-
miento que se realiza una vez que el servicio est inicializado. Para ello intervienen
tres componentes: acceptors, connectors y manejadores de servicio (service handlers.
Un connector representa el rol activo, y solicita una conexin a un acceptor, que re-
presenta el rol pasivo. Cuando la conexin se establece ambos crean un manejador de
servicio que procesa los datos intercambiados en la conexin.

Problema

El procesamiento de los datos que viajan por la red es en la mayora de los ca-
sos independiente de qu protocolos, interfaces de programacin de comunicaciones,
o tecnologas especcas se utilicen para transportarlos. El establecimiento de la co-
municacin es un proceso inherentemente asimtrico (uno inicia la conexin mientras
otro espera conexiones) pero una vez establecida la comunicacin el transporte de
datos es completamente ortogonal.
Desde el punto de vista prctico resuelve los siguientes problemas:

Facilita el cambio de los roles de conexin sin afectar a los roles en el intercam-
bio de datos.
Facilita la adicin de nuevos servicios y protocolos sin afectar al resto de la
arquitectura de comunicacin.
21.2. Patrones de diseo avanzados [565]

Figura 21.17: Estructura del patrn acceptor-connector.

En los juegos de red a gran escala (MMORPG (Massively Multiplayer Online


Role-Playing Game)) facilita la reduccin de la latencia en el establecimiento
de conexin usando mecanismos avanzados del sistema operativo, como cone-
xiones asncronas.

Solucin

El funcionamiento es como sigue:

Un Acceptor es una factora que implementa el rol pasivo para establecer co-
nexiones. Ante una conexin crea e inicializa un Transport Handle y un
Service Handler asociados. En su inicializacin, un Acceptor se asocia
a una direccin de transporte (e.g. direccin IP y puerto TCP), y se congura pa-
ra aceptar conexiones en modo pasivo. Cuando llega una solicitud de conexin
realiza tres pasos:

C21
1. Acepta la conexin creando un Transport Handle que encapsula un
extremo conectado.
2. Crea un Service Handler que se comunicar directamente con el del
otro extremo a travs del Transport Handle asociado.
3. Activa el Service Handler para terminar la inicializacin.
Un Connector es una factora que implementa el rol activo de la conexin. En
la inicializacin de la conexin connect()() crea un Transport Handle
que encapsula un extremo conectado con un Acceptor remoto, y lo asocia a
un Service Handler preexistente.

World of Warcraft Tanto Acceptor como Connector pueden tener separadas las funciones de
inicializacin de la conexin de la funcin de completado de la conexin (cuando ya
WoW es el mayor MMORPG de la se tiene garantas de que el otro extremo ha establecido la conexin). De esta forma es
actualidad con ms de 11.5 millo-
nes de suscriptores mensuales. fcil soportar conexiones asncronas y sncronas de forma completamente transparen-
te.
[566] CAPTULO 21. C++ AVANZADO

El Dispatcher es responsable de demultiplexar eventos del canal, tales como


peticiones de conexin o peticiones de datos. Para el Acceptor demultiplexa indi-
caciones de conexin a travs de los Transport Handles que encapsulan direc-
ciones de nivel de transporte. Para el Connector demultiplexa eventos de estableci-
miento de conexin que llegan cuando la solicitud de conexin es asncrona.
El patrn acceptor-connector coopera perfectamente con el patrn reactor. Tanto
el Transport Handle asociado al Acceptor, como el asociado al Connector,
e incluso los asociados a los manejadores de servicio pueden ser un manejadores de
eventos registrados en el reactor del sistema. De esta forma el Dispatcher pasa a
ser un reactor que demultiplexa no solo eventos de red, sino de interfaz de usuario, o
eventos del propio juego.

Implementacin

Desde el punto de vista de la implementacin, si nos restringimos a TCP y la


API sockets el Acceptor no es ms que una envoltura de la llamada al sistema
accept(), el Connector una envoltura de la llamada al sistema connect(), y
el Dispatcher o Reactor una envoltura de la llamada al sistema select() o
poll().
Una de las ms exibles implementaciones que existen de este patrn es la que
ofrece ACE (Adaptive Communications Environment), biblioteca creada por el inven-
tor del patrn y utilizada en multitud de sistemas de comunicaciones a escala global.
Otra implementacin muy escalable y extremadamente elegante del patrn acceptor-
connector es la incluida en la biblioteca ZeroC Ice, que ya conocemos. Sin embargo,
el objeto de Ice es implementar un middleware de comunicaciones basado en el mo-
delo de objetos distribuidos. Por tanto la implementacin del patrn es privada, y no
se expone a los usuarios. Ya examinaremos este modelo ms adelante.
En ACE un servidor TCP mnimo atendiendo conexiones en el puerto 9999 tendra
el siguiente aspecto:

Listado 21.46: Ejemplo de uso de patrn acceptor-connector (servidor).


1 #include <ace/SOCK_Acceptor.h>
2 #include <ace/Acceptor.h>
3 #include <ace/Svc_Handler.h>
4
5 class MySvcHandler : public ACE_Svc_Handler<ACE_SOCK_STREAM,
ACE_MT_SYNCH> {
6 virtual int handle_input (ACE_HANDLE) {
7 char buf[256];
8 int n = peer().recv(buf, sizeof buf);
9 if (n <= 0) return -1;
10 // procesar buf ...
11 return 0;
12 }
13 };
14
15 typedef ACE_Acceptor <MySvcHandler, ACE_SOCK_ACCEPTOR> MyAcceptor;
16
17 int main (int argc, const char *argv[]) {
18 ACE_Reactor reactor;
19 MyAcceptor acceptor;
20
21 acceptor.open(ACE_INET_Addr(9999), &reactor);
22 for(;;) reactor.handle_events();
23 return 0;
24 }
21.2. Patrones de diseo avanzados [567]

Especializamos la plantilla del Acceptor con un Svc_Handler que tiene la l-


gica de intercambio de mensajes. Al instanciar el Acceptor le pasamos un Reactor
para que automticamente registre los nuevos Svc_Handler que crea en las nuevas
conexiones.
El lado del cliente es muy similar, salvo que en este caso utilizamos un Connector.

Listado 21.47: Ejemplo de uso de patrn acceptor-connector (cliente).


1 #include <ace/SOCK_Connector.h>
2 #include <ace/Connector.h>
3 #include <ace/Svc_Handler.h>
4
5 class MySvcHandler : public ACE_Svc_Handler<ACE_SOCK_STREAM,
ACE_MT_SYNCH> {
6 virtual int handle_output (ACE_HANDLE) {
7 char buf[]="Hello, World!\n";
8 int n = peer().send(buf, sizeof buf);
9 if (n <= 0) return -1;
10 return 0;
11 }
12 };
13
14 typedef ACE_Connector <MySvcHandler, ACE_SOCK_CONNECTOR>
MyConnector;
15
16 int main (int argc, const char *argv[]) {
17 ACE_Reactor reactor;
18 MyConnector connector;
19 MySvcHandler* psvc = 0;
20
21 int n = connector.connect(psvc, ACE_INET_Addr(9999,"127.0.0.1")
);
22 if (n < 0) return 1;
23
24 reactor.register_handler(psvc, ACE_Event_Handler::WRITE_MASK);
25 for(;;)
26 reactor.handle_events();
27
28 return 0;
29 }

Como puede verse el Connector construye un Svc_Handler para procesar


eventos. Nosotros registramos ese manejador en el reactor para generar mensajes hacia
el servidor.

C21
Tngase en cuenta que estos ejemplos son simples en exceso, con el propsito de
ilustrar el uso del patrn. En un videojuego habra que tratar los errores adecuadamente
y ACE permite tambin congurar el esquema de concurrencia deseado.

Consideraciones

Este patrn permite manejar de forma uniforme las comunicaciones multi-protocolo


en juegos online. Adems, coopera con el reactor de manera que podemos tener una
nica fuente de eventos en el sistema. Esto es muy interesante desde todos los puntos
de vista, porque facilita enormemente la depuracin, la sntesis de eventos en el siste-
ma, la grabacin de secuencias completas de eventos para su posterior reproduccin,
etc.
[568] CAPTULO 21. C++ AVANZADO

21.3. Programacin genrica


La programacin genrica es un paradigma de programacin que trata de conse-
guir un mayor grado de reutilizacin tanto de las estructuras de datos como de los
algoritmos, evitando as la duplicidad de cdigo. Para lograrlo, los algoritmos deben
escribirse evitando asociar los detalles a tipos de datos concretos. Por ejemplo, en
un algoritmo de ordenacin, la operacin que compara dos elementos cualesquiera se
delega a una entidad ajena al algoritmo: un operador de comparacin.
Hoy en da, prcticamente todos los lenguajes de programacin importantes dispo-
nen o han adoptado caractersticas de programacin genrica (tal como los llamados
genricos en Java o C#).
El diseo de la librera STL pretende proporcionar herramientas bsicas de pro-
gramacin genrica. No es casualidad que la creacin de STL y las ideas tras el pa-
radigma de la programacin genrica fueran desarrolladas por los mismos autores,
especialmente Alexander Stepanov y David Musser [65]. Y de ah el inters por se-
parar las estructuras de datos (los contenedores) de los algoritmos. Como veremos,
los otros dos componentes de la STL (iteradores y functors) sirven tambin al mis-
mo propsito: posibilitan la interaccin entre contenedores y algoritmos, a la vez que
permiten un acoplamiento mnimo.
Es interesante indicar que la disociacin entre los datos y los algoritmos que los
manejan contradice en cierta medida los principios de la programacin orientada a ob-
jetos. En la POO las operaciones relativas a un tipo de dato concreto se ofrecen como
mtodos de dicha clase. El polimorsmo por herencia10 permite en la prctica utilizar
un algoritmo denido como un mtodo de la superclase con instancias de sus subcla-
ses. Sin embargo, esto no se considera programacin genrica pues la implementacin
del algoritmo normalmente depende al menos de la superclase de la jerarqua.
Figura 21.18: Alexander Stepanov,
En STL los algoritmos estn implementados normalmente como funciones (no
padre de la programacin genrica
mtodos) y por supuesto no tienen estado, algo que por denicin es ajeno a la POO. y la librera STL
A pesar de ello, en el diseo de la librera estn muy presentes los principios de orien-
tacin a objetos.

21.3.1. Algoritmos
Para conseguir estructuras de datos genricas, los contenedores se implementan
como plantillas como ya se discuti en captulos anteriores de modo que el tipo
de dato concreto que han de almacenar se especica en el momento de la creacin de
la instancia.
Aunque es posible implementar algoritmos sencillos del mismo modo parametrizando
el tipo de dato STL utiliza un mecanismo mucho ms potente: los iteradores. Los
iteradores permiten desacoplar tanto el tipo de dato como el modo en que se organizan
y almacenan los datos en el contenedor.
Lgicamente, para que un algoritmo pueda hacer su trabajo tiene que asumir que
tanto los elementos del contenedor como los iteradores tienen ciertas propiedades, o
siendo ms precisos, un conjunto de mtodos con un comportamiento predecible. Por
ejemplo, para poder comparar dos colecciones de elementos, deben ser comparables
dos a dos para determinar su igualdad sin entrar en qu signica eso realmente. As
10 tambin llamado polimorsmo de subclase o de inclusin, en contraposicin con el polimorsmo

paramtrico
21.3. Programacin genrica [569]

pues, el algoritmo equal() espera que los elementos soporten en modelo Equa-
lityComparable, que implica que tienen sobrecargado el mtodo operator==(),
adems de cumplir ste ciertas condiciones como reexibidad, simetra, transitividad,
etc.
algoritmos escalares
Aunque la mayora de los algorit- Escribiendo un algoritmo genrico
mos de la STL manejan secuencias
delimitadas por dos iteradores, tam- El mejor modo de comprender en qu consiste la genericidad de un algoritmo
bin hay algunos que utilizan da-
tos escalares, tales como min(), es crear uno desde cero. Escribamos nuestra propia versin del algoritmo genrico
max(), power() o swap() que count() (uno de los ms sencillos). Este algoritmo sirve para contar el nmero
pueden resultar tiles para compo- de ocurrencias de un elemento en una secuencia. Como una primera aproximacin
ner algoritmos ms complejos. veamos cmo hacerlo para un array de enteros. Podra ser algo como:

Listado 21.48: Escribiendo un algoritmo genrico: my_count() (1/4)


1 int my_count1(const int* sequence, int size, int value) {
2 int retval = 0;
3 for (int i=0; i < size; ++i)
4 if (sequence[i] == value)
5 retval++;
6
7 return retval;
8 }
9
10 void test_my_count1() {
11 const int size = 5;
12 const int value = 1;
13 int numbers[] = {1, 2, 3, 1, 2};
14
15 assert(my_count1(numbers, size, value) == 2);
16 }

Destacar el especicador const en el parmetro sequence (lnea 1). Le indica


al compilador que esta funcin no modicar el contenido del array. De ese modo es
ms general; se podr aplicar a cualquier array (sea constante o no).

Recuerda, en las funciones, aquellos parmetros que no impliquen copia (pun-


tero o referencia) deberan ser constantes si la funcin efectivamente no va a

C21
modicarlos.

En la siguiente versin vamos a cambiar la forma de iterar sobre el array. En lugar


de emplear un ndice vamos a utilizar un puntero que se desplaza a travs del array.
Esta versin mantiene el prototipo, es decir, se invoca de la misma forma.

Listado 21.49: Escribiendo un algoritmo genrico: my_count() (2/4)


1 int my_count2(const int* first, int size, int value) {
2 int retval = 0;
3 for (const int* it=first; it < first + size; ++it)
4 if (*it == value)
5 retval++;
6
7 return retval;
8 }
[570] CAPTULO 21. C++ AVANZADO

Dos cuestiones a destacar:

Utiliza aritmtica de punteros. Es decir, la direccin del puntero it (linea 3) no


se incrementa de uno en uno, sino que depende del tamao del tipo int.
El valor consultado en el array se obtiene de-referenciando el puntero (*it en
la lnea 4).

A continuacin la funcin cambia para imitar la signatura habitual de STL. En


lugar de pasar un puntero al comienzo y un tamao, se le pasan punteros al comienzo
y al nal-ms-uno.

Listado 21.50: Escribiendo un algoritmo genrico: my_count() (3/4)


1 int my_count3(const int* first, const int* last, int value) {
2 int retval = 0;
3 for (const int* it=first; it < last; ++it)
4 if (*it == value)
5 retval++;
6
7 return retval;
8 }
9
10 void test_my_count3() {
11 const int size = 5;
12 const int value = 1;
13 int numbers[] = {1, 2, 3, 1, 2};
14
15 assert(my_count3(numbers, numbers+size, value) == 2);
16 }

Se puede apreciar como el criterio del nal-mas-uno simplica la invocacin,


puesto que el valor correcto se consigue con numbers+size (lnea 15) y la con-
dicin de parada es tambin ms simple (it<last) en la lnea 3.
Por ltimo, veamos como queda la funcin cambiando los punteros por iteradores.
Es fcil comprobar como resultan funcionalmente equivalentes, hasta el punto de que
la funcin se puede utilizar tambin con un contenedor vector. Tambin se ha con-
vertido la funcin en una plantilla, de modo que se podr utilizar con cualquier tipo
de dato, a condicin de que sus elementos soporten la operacin de comprobacin de
igualdad:

Listado 21.51: Escribiendo un algoritmo genrico: my_count() (4/4)


1 template <typename Iter, typename T>
2 int my_count4(Iter first, Iter last, T value) {
3 int retval = 0;
4 for (Iter it=first; it < last; ++it)
5 if (*it == value)
6 retval++;
7
8 return retval;
9 }
10
11 void test_my_count4_numbers() {
12 const int size = 5;
13 const int value = 1;
14 int numbers[] = {1, 2, 3, 1, 2};
15 vector<int> numbers_vector(numbers, numbers + size);
16
17 assert(my_count4(numbers, numbers+size, value) == 2);
18 assert(my_count4(numbers_vector.begin(), numbers_vector.end(),
19 value) == 2);
21.3. Programacin genrica [571]

20 }
21
22 void test_my_count4_letters() {
23 const int size = 5;
24 const int value = a;
25 char letters[] = {a, b, c, a, b};
26 vector<char> letters_vector(letters, letters + size);
27
28 assert(my_count4(letters, letters+size, value) == 2);
29 assert(my_count4(letters_vector.begin(), letters_vector.end(),
30 value) == 2);
31 }

Esta ltima versin es bastante similar a la implementacin habitual del algoritmo


count() estndar con la salvedad de que ste ltimo realiza algunas comprobaciones
para asegurar que los iteradores son vlidos.
Comprobamos que nuestras funciones de prueba funcionan exactamente igual uti-
lizando el algoritmo count() estndar11 :

Listado 21.52: El algoritmo count() estndar se comporta igual


1 void test_count_numbers() {
2 const int size = 5;
3 const int value = 1;
4 int numbers[] = {1, 2, 3, 1, 2};
5 vector<int> numbers_vector(numbers, numbers + size);
6
7 assert(count(numbers, numbers+size, value) == 2);
8 assert(count(numbers_vector.begin(), numbers_vector.end(),
9 value) == 2);
10 }
11
12 void test_count_letters() {
13 const int size = 5;
14 const int value = a;
15 char letters[] = {a, b, c, a, b};
16 vector<char> letters_vector(letters, letters + size);
17
18 assert(count(letters, letters+size, value) == 2);
19 assert(count(letters_vector.begin(), letters_vector.end(),
20 value) == 2);
21 }

C21
21.3.2. Predicados
En el algoritmo count(), el criterio para contar es la igualdad con el elemento
proporcionado. Eso limita mucho sus posibilidades porque puede haber muchos otros
motivos por los que sea necesario contar elementos de una secuencia: esferas de color
rojo, enemigos con nivel mayor al del jugador, armas sin municin, etc.
Lgica de predicados Por ese motivo, muchos algoritmos de la STL tienen una versin alternativa que
permite especicar un parmetro adicional llamado predicado. El algoritmo invocar
En lgica, un predicado es una ex- el predicado para averiguar si se cumple la condicin indicada por el programador y
presin que se puede evaluar como
cierta o falsa en funcin del valor de as determinar cmo debe proceder con cada elemento de la secuencia.
sus variables de entrada. En progra-
macin, y en particular en la STL,
En C/C++, para que una funcin pueda invocar a otra (en este caso, el algoritmo
un predicado es una funcin (en el al predicado) se le debe pasar como parmetro un puntero a funcin.
sentido amplio) que acepta un va-
11 Para utilizar los algoritmos estndar se debe incluir el chero <algorithm>.
lor del mismo tipo que los elemen-
tos de la secuencia sobre la que se
usa y devuelve un valor booleano.
[572] CAPTULO 21. C++ AVANZADO

Veamos la denicin de un predicado (not_equal_2) que, como habr imagi-


nado, ser cierto para valores distintos a 2:

Listado 21.53: Predicado not_equal_2()


1 bool not_equal_2(int n) {
2 return n != 2;
3 }
4
5 void test_not_equal_2() {
6 const int size = 5;
7 int numbers[] = {1, 2, 3, 1, 2};
8
9 assert(count_if(numbers, numbers+size, not_equal_2) == 3);
10 }

Igual que con cualquier otro tipo de dato, cuando se pasa un puntero a funcin
como argumento, el parmetro de la funcin que lo acepta debe estar declarado con
ese mismo tipo. Concretamente el tipo del predicado not_equal_2 sera algo como:

Listado 21.54: Tipo para un predicado que acepta un argumento entero


1 bool (*)(int);

El algoritmo count_if() lo acepta sin problema. Eso se debe a que, como ya


hemos dicho, los algoritmos son funciones-plantilla y dado que la secuencia es un
array de enteros, asume que el valor que acepta el predicado debe ser tambin un
entero, es decir, el algoritmo determina automticamente la signatura del predicado.
Aunque funciona, resulta bastante limitado. No hay forma de modicar el com-
portamiento del predicado en tiempo de ejecucin. Es decir, si queremos contar los
elementos distintos de 3 en lugar de 2 habra que escribir otro predicado diferente.
Eso es porque el nico argumento que puede tener el predicado es el elemento de la
secuencia que el algoritmo le pasar cuando lo invoque12 , y no hay modo de darle
informacin adicional de forma limpia.

21.3.3. Functors
Existe sin embargo una solucin elegante para conseguir predicados congura-
bles. Consiste es declarar una clase que sobrecargue el operador de invocacin
mtodo operator()() que permite utilizar las instancias de esa clase como si
fueran funciones. Las clases que permiten este comportamiento se denominan fun-
ctors13 . Veamos como implementar un predicado not_equal() como un functor:

Listado 21.55: Predicado not_equal() para enteros (como functor)


1 class not_equal {
2 const int _ref;
3
4 public:
5 not_equal(int ref) : _ref(ref) {}
6
7 bool operator()(int value) {
8 return value != _ref;
9 }
10 };

12 En la terminologa de STL se denomina predicado unario


13 functor se traduce a veces como objeto-funcin.
21.3. Programacin genrica [573]

Y dos pruebas que demuestran su uso:

Listado 21.56: Ejemplos de uso de not_equal()


1 void test_not_equal_functor() {
2 not_equal not_equal_2(2);
3
4 assert(not_equal_2(0));
5 assert(not not_equal_2(2));
6 }
7
8 void test_not_equal_count_if() {
9 const int size = 5;
10 int numbers[] = {1, 2, 3, 1, 2};
11
12 assert(count_if(numbers, numbers+size, not_equal(2)) == 3);
13 }

Para disponer de un predicado lo ms exible posible deberamos implementarlo


como una clase plantilla de modo que sirva no solo para enteros:

Listado 21.57: Predicado not_equal() genrico como functor


1 template <typename _Arg>
2 class not_equal {
3 const _Arg _ref;
4
5 public:
6 not_equal(_Arg ref) : _ref(ref) {}
7
8 bool operator()(_Arg value) const {
9 return value != _ref;
10 }
11 };

Pero los predicados no son la nica utilidad interesante de los functors. Los pre-
dicados son una particularizacin de las funciones (u operadores). Los operadores
pueden devolver cualquier tipo. no slo booleanos.
La STL clasica los operadores en 3 categoras bsicas:

Generador Una funcin que no acepta argumentos.

C21
Unario Una funcin que acepta un argumento.
Binario Una funcin que acepta dos argumentos.

Aunque obviamente puede denirse un operador con 3 o ms argumentos, no hay


ningn algoritmo estndar que los utilice. Si el functor devuelve un booleano es cuan-
do se denomina predicado unario o binario respectivamente. Para ser un predica-
do debe tener al menos un argumento como hemos visto. Adems se habla tambin de
modalidades adaptables para las tres categoras, que se distinguen porque exponen
los tipos de sus argumentos y valor de retorno como atributos de la clase. Los veremos
ms adelante.
Los operadores (los functors que no son predicados) se utilizan normalmente en
algoritmos que realizan algn clculo con los elementos de una secuencia.
[574] CAPTULO 21. C++ AVANZADO

Como ejemplo, el siguiente listado multiplica los elementos del array numbers:

Listado 21.58: accumulate() multiplicando los elementos de una secuencia de enteros


1 void test_accumulate_multiplies() {
2 int numbers[] = {1, 2, 3, 4};
3 const int size = sizeof(numbers) / sizeof(int);
4
5 int result = accumulate(numbers, numbers+size,
6 1, multiplies<int>());
7 assert(result == 24);
8 }

El algoritmo accumulate() aplica el functor binario especicado como ltimo


parmetro (multiplies() en el ejemplo) empezando por el valor inicial indica-
do como tercer parmetro (1) y siguiendo con los elementos del rango especicado.
n
Corresponde con la operacin i=1 i.
Adems de multiplies(), la librera estndar incluye muchos otros functors
que se clasican en operaciones aritmticas (grupo al que corresponde multiplies()),
lgicas, de identidad y comparaciones. Los iremos viendo y utilizando a lo largo de
esta seccin.

21.3.4. Adaptadores
Es habitual que la operacin que nos gustara que ejecute el algoritmo sea un
mtodo (una funcin miembro) en lugar de una funcin estndar. Si tratamos de pasar
al algoritmo un puntero a mtodo no funcionar, porque el algoritmo no le pasar el
parmetro implcito this que todo mtodo necesita.
Una posible solucin sera escribir un functor que invoque el mtodo deseado,
como se muestra en el siguiente listado.

Listado 21.59: Adaptador manual para un mtodo


1 class Enemy {
2 public:
3 bool is_alive(void) const {
4 return true;
5 }
6 };
7
8 class enemy_alive {
9 public:
10 bool operator()(Enemy enemy) {
11 return enemy.is_alive();
12 }
13 };
14
15 void test_my_adapter() {
16 vector<Enemy> enemies(2);
17
18 assert(count_if(enemies.begin(), enemies.end(),
19 enemy_alive()) == 2);
20 }
mem_fun()
Existe un adaptador alternativo
Listado 21.60: Uso del adaptador mem_fun_ref() llamado mem_fun() que debe
utilizarse si los elementos del
1 void test_mem_fun_ref() { contenedor son punteros. Si son
2 vector<Enemy> enemies(2); objetos o referencias se utiliza
3 mem_fun_ref().
21.3. Programacin genrica [575]

4 assert(count_if(enemies.begin(), enemies.end(),
5 mem_fun_ref(&Enemy::is_alive)) == 2);
6 }

Veamos de nuevo el problema de tener una operacin o predicado que requiere un


argumento adicional aparte del elemento de la secuencia. En la seccin 21.3.3 resol-
vimos el problema creando un functor (not_equal) que sirviera como adaptador.
Bien, pues eso tambin lo prev la librera y nos proporciona dos adaptadores llama-
dos bind1st() y bind2st() para realizar justo esa tarea, y de manera genrica.
Veamos cmo gracias a bind2nd() es posible reescribir el listado 21.53 de
modo que se puede especicar el valor con el que comparar (parmetro ref) sin tener
que escribir un functor ad-hoc:

Listado 21.61: Uso del adaptador bind2nd()


1 bool not_equal(int n, int ref) {
2 return n != ref;
3 }
4
5 void test_not_equal_bind() {
6 const int size = 5;
7 int numbers[] = {1, 2, 3, 1, 2};
8
9 assert(count_if(numbers, numbers+size,
10 bind2nd(ptr_fun(not_equal), 2)) == 3);
11 }

Ntese que bind2nd() espera un functor como primer parmetro. Como lo que
tenemos es una funcin normal es necesario utilizar otro adaptador llamado ptr_fun(),
que como su nombre indica adapta un puntero a funcin a functor.
bind2nd() pasa su parmetro adicional (el 2 en este caso) como segundo par-
metro en la llamada a la funcin not_equal(), es decir, la primera llamada para
la secuencia del ejemplo ser not_equal(1, 2). El primer argumento (el 1) es
el primer elemento obtenido de la secuencia. El adaptador bind1st() los pasa en
orden inverso, es decir, pasa el valor extra como primer parmetro y el elemento de la
secuencia en segunda posicin.
Hay otros adaptadores de menos uso:

C21
not1() devuelve un predicado que es la negacin lgica del predicado unario al que
se aplique.
not2() devuelve un predicado que es la negacin lgica del predicado binario al
que se aplique.
Nomenclatura compose1() devuelve un operador resultado de componer las dos funciones una-
Los nombres de los algoritmos si- rias que se le pasan como parmetros. Es decir, dadas las funciones f (x) y g(x)
guen ciertos criterios. Como ya he- devuelve una funcin f (g(x)).
mos visto, aquellos que tienen una
versin acabada en el sujo _if
aceptan un predicado en lugar de compose2() devuelve un operador resultado de componer una funcin binaria y
utilizar un criterio implcito. Los dos funciones unarias que se le pasan como parmetro del siguiente modo. Da-
que tienen el sujo _copy gene- das las funciones f (x, y), g1 (x) y g2 (x) devuelve una funcin h(x) = f (g1 (x), g2 (x)).
ran su resultado en otra secuencia,
en lugar de modicar la secuencia
original. Y por ltimo, los que aca-
ban en _n aceptan un iterador y un
entero en lugar de dos iteradores; de
ese modo se pueden utilizar para in-
sertar en cosas distintas de conte-
nedores, por ejemplo ujos.
[576] CAPTULO 21. C++ AVANZADO

21.3.5. Algoritmos idempotentes


Los algoritmos idempotentes (non-mutating) son aquellos que no modican el
contenedor sobre el que se aplican. Podramos decir que son algoritmos funcionales
desde el punto de vista de ese paradigma de programacin. Ntese que aunque el al-
goritmo en s no afecte al contenedor, las operaciones que se realicen con l s pueden
modicar los objetos contenidos.

for_each()

El algoritmo for_each() es el ms simple y general de la STL. Es equivalente


a un bucle for convencional en el que se ejecutara un mtodo concreto (o una funcin
independiente) sobre cada elemento de un rango. Veamos un ejemplo sencillo en el
que se recargan todas las armas de un jugador:

Listado 21.62: Ejemplo de uso del algoritmo for_each()


1 class Weapon {
2 public:
3 void reload() { /* some code */ }
4 };
5
6 void test_for_each() {
7 vector<Weapon> weapons(5);
8 for_each(weapons.begin(), weapons.end(),
9 mem_fun_ref(&Weapon::reload));
10 }

find() / find_if()

Devuelve un iterador al primer elemento del rango que coincide con el indicado
(si se usa find()) o que cumple el predicado (si se usa find_if()). Devuelve el
iterador end si no encuentra ninguna coincidencia. Un ejemplo en el que se busca el
primer entero mayor que 6 que haya en el rango:

Listado 21.63: Ejemplo de find_if()


1 void test_find_if() {
2 const int size = 5;
3 const int value = 1;
4 int numbers[] = {2, 7, 12, 9, 4};
5
6 assert(find_if(numbers, numbers + size,
7 bind2nd(greater<int>(), 6)) == numbers+1);
8 }

Se utiliza greater, un predicado binario que se cumple cuando su primer par-


metro (el elemento del rango) es mayor que el segundo (el 6 que se pasa bind2nd()).
El resultado del algoritmo es un iterador al segundo elemento (el 7) que correspon-
de con numbers+1. Hay algunos otros functors predenidos para comparacin:
equal_to(), not_equal_to()14 , less(), less_equal() y greater_equal().
14 Equivalente al que implementamos en el listado 21.57.
21.3. Programacin genrica [577]

count() / count_if()

Como ya hemos visto en ejemplos anteriores count() devuelve la cantidad de


elementos del rango igual al dado, o que cumple el predicado, si se usa la modalidad
count_if().

mismatch()

Dados dos rangos, devuelve un par de iteradores a los elementos de cada rango en
el que las secuencias dieren. Veamos el siguiente ejemplo extrado de la documen-
tacin de SGI15 :

Listado 21.64: Ejemplo de uso de mismatch()


1 void test_mismatch() {
2 int A1[] = { 3, 1, 4, 1, 5, 9, 3};
3 int A2[] = { 3, 1, 4, 2, 8, 5, 7};
4 const int size = sizeof(A1) / sizeof(int);
5
6 pair<int*, int*> result = mismatch(A1, A1 + size, A2);
7 assert(result.first == A1 + 3);
8 assert((*result.first) == 1 and (*result.second) == 2);
9 }

Muchos algoritmos de transformacin que manejan dos secuencias requieren


solo tres iteradores. El tercer iterador indica el comienzo de la secuencia de
salida y se asume que ambas secuencias son del mismo tamao.

equal()

Indica si los rangos indicados son iguales. Por defecto utiliza el operator==(),
pero opcionalmente es posible indicar un predicado binario como cuarto parmetro
para determinar en qu consiste la igualdad. Veamos un ejemplo en el que se com-

C21
paran dos listas de guras que se considerarn iguales simplemente porque coincida
su color:

Listado 21.65: Ejemplo de uso de equal()


1 enum Color{BLACK, WHITE, RED, GREEN, BLUE};
2
3 class Shape {
4 public:
5 Color color;
6
7 Shape(void) : color(BLACK) {}
8 bool cmp(Shape other) {
9 return color == other.color;
10 }
11 };
12
13 void test_equal() {
14 const int size = 5;
15 http://www.sgi.com/tech/stl/mismatch.html
[578] CAPTULO 21. C++ AVANZADO

15 Shape shapes1[size], shapes2[size];


16 shapes2[3].color = RED;
17
18 assert(equal(shapes1, shapes1+size, shapes2,
19 mem_fun_ref(&Shape::cmp)) == false);
20 }

search()

Localiza la posicin del segundo rango en el primero. Devuelve un iterador al pri-


mer elemento. Opcionalmente acepta un predicado binario para especicar la igualdad
entre dos elementos. Veamos este ejemplo extrado de la documentacin de SGI.

Listado 21.66: Ejemplo de uso de search()


1 void test_search() {
2 const char s1[] = "Hello, world!";
3 const char s2[] = "world";
4 const int n1 = strlen(s1);
5 const int n2 = strlen(s2);
6
7 const char* p = search(s1, s1 + n1, s2, s2 + n2);
8 assert(p == s1 + 7);
9 }

El algoritmo find_end() (a pesar de su nombre) es similar a search() solo


que localiza la ltima aparicin en lugar de la primera.
El algoritmo search_n() tambin es similar. Busca una secuencia de n elemen-
tos iguales (no otro rango) que debera estar contenida en el rango indicado.

21.3.6. Algoritmos de transformacin


Normalmente, en los algoritmos de transformacin (mutating algorithms) se dis-
tingue entre el rango o secuencia de entrada y la de salida, ya que su operacin implica
algn tipo de modicacin (insercin, eliminacin, cambio, etc.) sobre los elementos
de la secuencia de salida.

Es importante recordar que las secuencias de salida que se utilizan en los


algoritmos de transformacin deben disponer de memoria suciente para los
datos que recibirn u obtendremos comportamientos errticos aleatorios y
errores de acceso a memoria en tiempo de ejecucin (SEGFAULT).

copy()

Copia los elementos de un rango en otro. No debera utilizarse para copiar una
secuencia completa en otra ya que el operador de asignacin que tienen todos los
contenedores resulta ms eciente. S resulta interesante para copiar fragmentos de
secuencias. Veamos un uso interesante de copy() para enviar a un ujo (en este caso
cout) el contenido de una secuencia.
21.3. Programacin genrica [579]

Listado 21.67: Ejemplo de uso de copy()


1 int main() {
2 int values[] = {1, 2, 3, 4, 5};
3 const int size = sizeof(values) / sizeof(int);
4
5 copy(values+2, values+size,
6 ostream_iterator<int>(cout, ", "));
7 cout << endl;
8 }

La plantilla ostream_iterator devuelve un iterador de insercin para un tipo


concreto (int en el ejemplo) que escribir en el ujo (cout) los elementos que se le
asignen, escribiendo adems una cadena opcional despus de cada uno (una coma).
Existe una variante llamada copy_backward() que copia desde el nal y en
la que se debe pasar un iterador de la secuencia de salida al que copiar el ltimo
elemento.

swap_ranges()

Intercambia el contenido de dos secuencias. Como es habitual, se pasan los ite-


radores a principio y n de la primera secuencia y al principio de la segunda, dado
que asume que los rangos deben ser del mismo tamao. Ntese que este algoritmo
modica ambos rangos.

transform()

El algoritmo transform() es uno de los ms verstiles de la librera. La versin


bsica (que opera sobre un nico rango de entrada) aplica un operador unario a cada
elemento del rango y escribe el resultado a un iterador de salida.
Existe una versin alternativa (sobrecargada) que acepta dos secuencias de entra-
da. En este caso, el algoritmo utiliza un operador binario al que pasa un elemento
que obtiene de cada una de las secuencias de entrada, el resultado se escribe sobre el
iterador de salida.
Es interesante destacar que en ambos casos, el iterador de salida puede referirse a
una de las secuencias de entrada.

C21
Veamos un ejemplo en el que se concatenan las cadenas de dos vectores y se
almacenan en un tercero haciendo uso del operador plus():

Listado 21.68: Ejemplo de uso de transform()


1 void test_transform() {
2 vector<string> v1, v2, result(2);
3 v1.push_back("hello ");
4 v1.push_back("bye ");
5 v2.push_back("world");
6 v2.push_back("hell");
7
8 transform(v1.begin(), v1.end(), v2.begin(),
9 result.begin(),
10 plus<string>());
11
12 assert(result[0] == "hello world");
13 assert(result[1] == "bye hell");
14 }
[580] CAPTULO 21. C++ AVANZADO

replace() / replace_if()

Dado un rango, un valor antiguo y un valor nuevo, substituye todas las ocurrencias
del valor antiguo por el nuevo en el rango. La versin replace_if() substituye
los valores que cumplan el predicado unario especicado por el valor nuevo. Ambos
utilizan un nica secuencia, es decir, hacen la substitucin in situ.
Existen variantes llamadas replace_copy() y replace_copy_if() res-
pectivamente en la que se copian los elementos del rango de entrada al de salida a la
vez que se hace la substitucin. En este caso la secuencia original no cambia.

fill()

Dado un rango y un valor, copia dicho valor en todo el rango:

Listado 21.69: Ejemplo de uso de fill()


1 void test_fill() {
2 vector<float> v(10);
3 assert(count(v.begin(), v.end(), 0));
4
5 fill(v.begin(), v.end(), 2);
6 assert(count(v.begin(), v.end(), 2) == 10);
7 }

La variante fill_n() utiliza un nico iterador de salida y copia sobre l n copias


del valor especicado. til con iteradores de insercin.

generate()

En realidad es una variante de fill() salvo que los valores los obtiene de un
operador que se le da como parmetro, en concreto un generador, es decir, una
funcin/functor sin parmetros que devuelve un valor:

Listado 21.70: Ejemplo de uso de generate()


1 class next {
2 int _last;
3 public:
4 next(int init) : _last(init) {}
5 int operator()() {
6 return _last++;
7 }
8 };
9
10 void test_generate() {
11 vector<int> v(10);
12 generate(v.begin(), v.end(), next(10));
13 assert(v[9] == 19);
14 }

Existe un algoritmo generate_n() al estilo de copy_n() o fill_n() que


en lugar de dos iteradores, espera un iterador y una cantidad de elementos a generar.
[582] CAPTULO 21. C++ AVANZADO

Listado 21.72: Ejemplo de uso de reverse()


1 void test_reverse() {
2 char word[] = "reversion";
3 const int size = strlen(word);
4
5 reverse(word + 5, word + size);
6
7 assert(strcmp(word, "revernois") == 0);
8 }

rotate()

Rota los elementos del rango especicado por 3 iteradores, que indican el inicio,
el punto medio y el nal del rango. Existe una modalidad rotate_copy(), que
como siempre aplica el resultado a un iterador en lugar de modicar el original.

Algoritmos aleatorios

Hay tres algoritmos que tienen que ver con operaciones aleatorias sobre una se-
cuencia:

random_shuffle() reordena de forma aleatoria los elementos del rango.


random_sample() elige aleatoriamente elementos de la secuencia de entrada y
los copia en la secuencia de salida. Es interesante destacar que este algoritmo
requiere 4 iteradores ya que se puede crear una secuencia de salida de tamao
arbitrario, siempre que sea menor o igual que la secuencia de entrada.
random_shuffle_n() realiza la misma operacin que random_sample() sal-
vo que la cantidad de elementos a generar se especica explcitamente en lugar
de usar un cuarto iterador. Eso permite utilizarlo con un iterador de insercin.

Los tres aceptan opcionalmente una funcin que genere nmeros aleatorios.

partition()

Dada una secuencia y un predicado, el algoritmo reordena los elementos de modo


que los que satisfacen el predicado aparecen primero y los que lo incumplen despus.
Devuelve un iterador al primer elemento que incumple el predicado.
La modalidad stable_partition() preserva el orden de los elementos en
cada parte respecto al orden que tenan en la secuencia original.

21.3.7. Algoritmos de ordenacin


Los algoritmos de ordenacin tambin son de transformacin, pero se clasican
como un grupo distinto dado que todos tiene que ver con la ordenacin de secuencias
u operaciones con secuencias ordenadas.

sort()

Ordena in situ el rango especicado por dos iteradores. La modalidad stable_sort()


preserva el orden relativo original a costa de algo menos de rendimiento. Veamos un
ejemplo sencillo tomado del manual de SGI para ordenar un array de caracteres:
21.3. Programacin genrica [583]

Listado 21.73: Ejemplo de uso de sort()


1 bool less_nocase(char c1, char c2) {
2 return tolower(c1) < tolower(c2);
3 }
4
5 void test_sort() {
6 char letters[] = "ZfdBeACFDbEacz";
7 const int size = strlen(letters);
8
9 sort(letters, letters+size, less_nocase);
10
11 char expected[] = "AaBbCcdDeEfFZz";
12 assert(equal(letters, letters+size, expected));
13 }

La mayora de los algoritmos de ordenacin aceptan un predicado especial para


comparacin de elementos dos a dos. Es muy habitual ordenar secuencias de elemen-
tos no numricos o por caractersticas que tienen poco que ver con la relacin mayor
o menor en el sentido tradicional del trmino.
El algoritmo partial_sort() ordena parcialmente una secuencia especicada
por tres iteradores de modo que solo el rango correspondiente a los dos primeros estar
ordenado en la secuencia resultante. Tiene una modalidad partial_sort_copy()
que no modica la secuencia original.

nth_element()

Dada una secuencia y tres iteradores, ordena la secuencia de modo que todos los
elementos en el subrango por debajo del segundo iterador (nth) son menores que
los elementos que quedan por encima. Adems, el elemento apuntado por el segundo
iterador es el mismo que si se hubiera realizado una ordenacin completa.

Operaciones de bsqueda

A continuacin se incluye una pequea descripcin de los algoritmos relacionados


con bsquedas binarias:

binary_search() determina si el valor indicado se encuentra en la secuencia.

C21
lower_bound() devuelve un iterador a la primera posicin en la que es posible
insertar el elemento indicado manteniendo el orden en la secuencia.
upper_bound() devuelve un iterador a la ltima posicin en la que es posible
insertar el elemento indicado manteniendo el orden en la secuencia.
equal_range() combina los dos algoritmos anteriores. Devuelve un par con los
iteradores a la primera y ltima posicin en la que es posible insertar el elemento
indicado manteniendo el orden de la secuencia.

Se muestra un ejemplo de los cuatro algoritmos:

Listado 21.74: Ejemplo de uso de los algoritmos de bsqueda


1 int numbers[] = {0, 3, 7, 7, 10, 11, 15};
2 int size = sizeof(numbers) / sizeof(int);
3
4 void test_binary_search() {
[584] CAPTULO 21. C++ AVANZADO

5 assert(binary_search(numbers, numbers+size, 6) == false);


6 assert(binary_search(numbers, numbers+size, 10));
7 }
8
9 void test_bounds() {
10 assert(lower_bound(numbers, numbers+size, 6) == numbers+2);
11 assert(upper_bound(numbers, numbers+size, 8) == numbers+4);
12 }
13
14 void test_equal_range() {
15 pair<int*, int*> bounds = equal_range(numbers, numbers+size, 7);
16 assert(bounds.first == numbers+2 and bounds.second == numbers+4);
17 }

merge() combina dos secuencias, dadas por dos pares de iteradores, y crea una
tercera secuencia que incluye los elementos de ambas, manteniendo el orden.

Mnimo y mximo

Los algoritmos min_element() y max_element() permiten obtener res-


pectivamente el elemento mnimo y mximo del rango especicado. Veamos un ejem-
plo:

Listado 21.75: Ejemplo de uso de max_element() y min_element()


1 char letters[] = "ZfdBeACFDbEacz";
2 const int size = strlen(letters);
3
4 void test_min() {
5 char* result = min_element(letters, letters+size);
6 assert(*result == A);
7 }
8
9 void test_max() {
10 char* result = max_element(letters, letters+size);
11 assert(*result == z);
12 }

21.3.8. Algoritmos numricos


accumulate()

Aplica un operador (la suma si no se especica otro) sobre el rango especicado


por dos iteradores. Debe indicarse tambin un valor inicial ya que el algoritmo opera
sobre un valor acumulado (de ah su nombre) y un elemento extrado de la secuencia.
El listado 21.58 muestra un ejemplo de uso.

partial_sum()

Calcula la suma parcial para cada elemento de una secuencia y lo almacena


sobre un iterador de salida:

Listado 21.76: Ejemplo de uso de partial_sum()


1 void test_partial_sum() {
2 const int size = 5;
21.3. Programacin genrica [585]

3 vector<int> values(size);
4 fill(values.begin(), values.end(), 1);
5
6 partial_sum(values.begin(), values.end(), values.begin());
7
8 int expected[size] = {1, 2, 3, 4, 5};
9 assert(equal(values.begin(), values.end(), expected));
10 }

adjacent_difference()

Calcula las diferencias entre elementos consecutivos de la secuencia de entrada y


los escribe sobre el iterador de salida:

Listado 21.77: Ejemplo de uso de adjacent_difference()


1 void test_adjacent_difference() {
2 int values[] = {1, 3, 0, 10, 15};
3 const int size = sizeof(values) / sizeof(int);
4 int result[size];
5
6 adjacent_difference(values, values+size, result);
7
8 int expected[size] = {1, 2, -3, 10, 5};
9 assert(equal(result, result+size, expected));
10 }

21.3.9. Ejemplo: inventario de armas


Veamos un programa concreto que ilustra como sacar partido de las algoritmos
genricos. Se trata del tpico inventario de armas habitual en cualquier videojuego
tipo shooter.
Lo primero es denir una clase para describir el comportamiento y atributos de
cada arma (clase Weapon*). El nico atributo es la municin disponible. Tiene otras
dos propiedades (accesibles a travs de mtodos virtuales) que indican la potencia del
disparo y la cantidad mxima de municin que permite:

C21
Listado 21.78: Inventario de armas: Clase Weapon
1 class Weapon {
2 int ammo;
3
4 protected:
5 virtual int power(void) const = 0;
6 virtual int max_ammo(void) const = 0;
7
8 public:
9 Weapon(int ammo=0) : ammo(ammo) { }
10
11 void shoot(void) {
12 if (ammo > 0) ammo--;
13 }
14
15 bool is_empty(void) {
16 return ammo == 0;
17 }
18
19 int get_ammo(void) {
20 return ammo;
[586] CAPTULO 21. C++ AVANZADO

21 }
22
23 void add_ammo(int amount) {
24 ammo = min(ammo + amount, max_ammo());
25 }
26
27 void add_ammo(Weapon* other) {
28 add_ammo(other->ammo);
29 }
30
31 int less_powerful_than(Weapon* other) const {
32 return power() < other->power();
33 }
34
35 bool same_weapon_as(Weapon* other) {
36 return typeid(*this) == typeid(*other);
37 }
38 };

Note cmo los mtodos shoot(), is_empty() y get_ammo() son auto-


explicativos. El mtodo add_ammo() est sobrecargado. La primera versin (lnea
23) aade al arma la cantidad especicada de balas respetando el lmite. Para esto se
utiliza el algoritmo min().
El mtodo less_powerful_than() compara esta instancia de arma con otra
para decidir cul es la ms potente, y por ltimo, el mtodo same_weapon_as()
indica si el arma es del mismo tipo utilizando RTTI.
El siguiente listado muestra tres especializaciones de la clase Weapon que nica-
mente especializan los mtodos privados power() y max_ammo() para cada uno
de los tipos Pistol, Shotgun y RPG.

Listado 21.79: Especializaciones de Weapon


1 class Pistol : public Weapon {
2 virtual int power(void) const { return 1; };
3 virtual int max_ammo(void) const { return 50; };
4
5 public:
6 Pistol(int ammo=0) : Weapon(ammo) {}
7 };
8
9 class Shotgun : public Weapon {
10 virtual int power(void) const { return 10; };
11 virtual int max_ammo(void) const { return 100; };
12
13 public:
14 Shotgun(int ammo=0) : Weapon(ammo) {}
15 };
16
17 class RPG : public Weapon {
18 virtual int power(void) const { return 100; };
19 virtual int max_ammo(void) const { return 5; };
20
21 public:
22 RPG(int ammo=0) : Weapon(ammo) {}
23 };

Veamos por ltimo la clase Inventory que representara la coleccin de armas


que lleva el jugador.

Listado 21.80: Inventario de armas: Clase Inventory


1 class Inventory : public vector<Weapon*> {
2 public:
21.3. Programacin genrica [587]

3 typedef typename Inventory::const_iterator WeaponIter;


4 typedef vector<Weapon*> WeaponVector;
5 class WeaponNotFound {};
6 ~Inventory();
7
8 void add(Weapon* weapon) {
9 WeaponIter it =
10 find_if(begin(), end(),
11 bind2nd(mem_fun(&Weapon::same_weapon_as), weapon));
12
13 if (it != end()) {
14 (*it)->add_ammo(weapon);
15 delete weapon;
16 return;
17 }
18
19 push_back(weapon);
20 }
21
22 WeaponVector weapons_with_ammo(void) {
23 WeaponVector retval;
24
25 remove_copy_if(begin(), end(), back_inserter(retval),
26 mem_fun(&Weapon::is_empty));
27
28 if (retval.begin() == retval.end())
29 throw Inventory::WeaponNotFound();
30
31 return retval;
32 }
33
34 Weapon* more_powerful_weapon(void) {
35 WeaponVector weapons = weapons_with_ammo();
36
37 sort(weapons.begin(), weapons.end(),
38 mem_fun(&Weapon::less_powerful_than));
39
40 return *(weapons.end()-1);
41 }

Algunos detalles interesantes de esta clase:

Inventory es-un contenedor de punteros a Weapon, concretamente un


vector (vector<Weapon*>) como se puede apreciar en la lnea 1.
La lnea 3 dene el tipo WeaponIter como alias del tipo del iterador para

C21
recorrer el contenedor.
En la lnea 4, la clase WeaponNotFound se utiliza como excepcin en las
bsquedas de armas, como veremos a continuacin.

El mtodo add() se utiliza para aadir un nuevo arma al inventario, pero con-
templa especcamente el caso habitual en los shotters en el que coger un arma
que ya tiene el jugador implica nicamente coger su municin y desechar el arma.
Para ello, utiliza el algoritmo find_if() para recorrer el propio contenedor espe-
cicando como predicado el mtodo Weapon::same_weapon_as(). Ntese el
uso de los adaptadores mem_fun() (por tratarse de un mtodo) y de bind2nd()
para pasar a dicho mtodo la instancia del arma a buscar. Si se encuentra un arma del
mismo tipo (lneas 1216) se aade su municin al arma existente usando el iterador
devuelto por find_if() y se elimina (lnea 14). En otro caso se aade la nueva arma
al inventario (lnea 18).
[588] CAPTULO 21. C++ AVANZADO

Por otra parte, el mtodo more_powerful_weapon() (lneas 3441) imple-


menta una funcionalidad tambin muy habitual en ese tipo de juegos: cambiar al arma
ms potente disponible. En este contexto, invoca weapons_with_ammo() (lneas
2232) para obtener las armas con municin. Utiliza el algoritmo remove_copy_if()
para crear un vector de punteros (mediante la funcin back_inserter()), evitan-
do copiar las vacas (lnea 26).
Ordena el vector resultante usando sort() y utilizando como predicado el m-
todo less_powerful_than() que vimos antes. Por ltimo, el mtodo retorna un
puntero al ltimo arma (lnea 40). Ntese que el * en esa lnea es la de-referencia
del iterador (que apunta a un puntero).
Para acabar, se muestra el destructor de la clase, que se encarga de liberar los
punteros que almacena:

Listado 21.81: Inventario de armas: Destructor


1 template<class T>
2 T* deleter(T* x) {
3 delete x;
4 return 0;
5 }
6 sort(weapons.begin(), weapons.end(),
7 mem_fun(&Weapon::less_powerful_than));
8
9 return *(weapons.end()-1);
10 }

Aqu se utiliza el functor (deleter) con el algoritmo transform() para li-


berar cada puntero. La razn de usar transform() en lugar de for_each() es
eliminar las direcciones de los punteros que dejan de ser vlidos en el contenedor.
Despus se borra todo el contenedor usando su mtodo clear().

21.4. Aspectos avanzados de la STL


En esta seccin veremos cmo explotar el potencial de la librera STL ms all del
mero uso de sus contenedores y algoritmos.

Principio de Pareto
21.4.1. Eciencia
El principio de Pareto tambin es
aplicable a la ejecucin de un pro-
La eciencia es sin duda alguna uno de los objetivos principales de la librera STL. grama. Estadsticamente el 80 %
Esto es as hasta el punto de que se obvian muchas comprobaciones que haran su uso del tiempo de ejecucin de un
ms seguro y productivo. El principio de diseo aplicado aqu es: programa es debido nicamente al
20 % de su cdigo. Eso signica
que mejorando ese 20 % se pue-
Es factible construir decoradores que aadan comprobaciones adiciona- den conseguir importantes mejoras.
Por ese motivo, la optimizacin del
les a la versin eciente. Sin embargo no es posible construir una versin programa (si se necesita) debera
eciente a partir de una segura que realiza dichas comprobaciones. ocurrir nicamente cuando se haya
identicado dicho cdigo por me-
dio de herramientas de perlado y
Algunas de estas comprobaciones incluyen la dereferencia de iteradores nulos, un adecuado anlisis de los ujos
invalidados o fuera de los lmites del contenedor, como se muestra en el siguiente de ejecucin. Preocuparse por opti-
listado. mizar una funcin lenta que solo se
invoca en el arranque de un servidor
Para subsanar esta situacin el programador puede optar entre utilizar una imple- que se ejecuta durante das es perju-
mentacin que incorpore medidas de seguridad con la consiguiente reduccin de dicial. Supone un gasto de recursos
y tiempo que probablemente produ-
eciencia o bien especializar los contenedores en clases propias y controlar espec- cir cdigo menos legible y mante-
camente las operaciones susceptibles de ocasionar problemas. nible.
21.4. Aspectos avanzados de la STL [589]

En cualquier caso el programador debera tener muy presente que este tipo de de-
cisiones ad hoc (eliminar comprobaciones) forman parte de la fase de optimizacin y
slo deberan considerarse cuando se detecten problemas de rendimiento. En general,
tal como dice Ken Beck, La optimizacin prematura es un lastre. Es costosa (en
tiempo y recursos) y produce normalmente cdigo ms sucio, difcil de leer y mante-
ner, y por tanto, de inferior calidad.

Listado 21.82: Situaciones no controladas en el uso de iteradores


1 void test_lost_iterator() {
2 vector<int>::iterator it;
3 int i = *it; // probably a SEGFAULT
4 }
5
6 void test_invalidated_iterator() {
7 vector<int> v1;
8 v1.push_back(1);
9 vector<int>::iterator it = v1.begin();
10 v1.clear();
11
12 int i = *it; // probably a SEGFAULT
13 }
14
15 void test_outbound_iterator() {
16 vector<int> v1;
17 vector<int>::iterator it = v1.end();
18
19 int i = *it; // probably a SEGFAULT
20 }

Sin embargo, existen otro tipo de decisiones que el programador puede tomar
cuando utiliza la STL, que tienen un gran impacto en la eciencia del resultado y que
no afectan en absoluto a la legibilidad y mantenimiento del cdigo. Estas decisiones
tienen que ver con la eleccin del contenedor o algoritmo adecuado para cada proble-
ma concreto. Esto requiere conocer con cierto detalle el funcionamiento y diseo de
los mismos.

Elegir el contenedor adecuado

A continuacin se listan los aspectos ms relevantes que se deberan tener en cuen-


ta al elegir un contenedor, considerando las operaciones que se realizarn sobre l:

C21
Tamao medio del contenedor.
En general, la eciencia en cuanto a tiempo de acceso solo es signicativa pa-
ra grandes cantidades de elementos. Para menos de 100 elementos (seguramente
muchos ms considerando las computadoras o consolas actuales) es muy pro-
bable que la diferencia entre un contenedor con tiempo de acceso lineal y uno
logartmico sea imperceptible. Si lo previsible es que el nmero de elementos
sea relativamente pequeo o no se conoce bien a priori, la opcin ms adecuada
es vector.
Insercin de elementos en los dos extremos de la secuencia.
Si necesita aadir al comienzo con cierta frecuencia (>10 %) debera elegir un
contenedor que implemente esta operacin de forma eciente como deque.
Insercin y borrado en posiciones intermedias.
El contenedor ms adecuado en este caso es list. Al estar implementado como
una lista doblemente enlazada, la operacin de insercin o borrado implica poco
ms que actualizar dos punteros.
[590] CAPTULO 21. C++ AVANZADO

Contenedores ordenados.
Algunos contenedores, como set y multiset, aceptan un operador de orde-
nacin en el momento de su instanciacin. Despus de cualquier operacin de
insercin o borrado el contenedor quedar ordenado. Esto es rdenes de mag-
nitud ms rpido que utilizar un algoritmo de ordenacin cuando se necesite
ordenarlo.

Otro aspecto a tener en cuenta es la distincin entre contenedores basados en blo-


ques (como vector, deque o string) y los basados en nodos (como list, set,
map, etc.). Los contenedores basados en nodos almacenan cada elemento como uni-
dades independientes y se relacionan con los dems a travs de punteros. Esto tiene
varias implicaciones interesantes:

Si se obtiene un iterador a un nodo, sigue siendo vlido durante toda la vida del
elemento. Sin embargo, en los basados en bloque los iteradores pueden quedar
invalidados si se realoja el contenedor.
Ocupan ms memoria por cada elemento almacenado, debido a que se requiere
informacin adicional para mantener la estructura: rbol o lista enlazada.

Elegir el algoritmo adecuado

Aunque los algoritmos de STL estn diseados para ser ecientes (el estndar in-
cluso determina la complejidad ciclomtica mxima permitida) ciertas operaciones
sobre grandes colecciones de elementos pueden implicar tiempos de cmputo muy
considerables. Para reducir el nmero de operaciones a ejecutar es importante consi-
derar los condicionantes especcos de cada problema.
Uno de los detalles ms simples a tener en cuenta es la forma en la que se espe-
cica la entrada al algoritmo. En la mayora de ellos la secuencia queda determinada
por el iterador de inicio y el de n. Lo interesante de esta interfaz es que darle al al-
goritmo parte del contenedor es tan sencillo como drselo completo. Se pueden dar
innumerables situaciones en las que es perfectamente vlido aplicar cualquiera de los
algoritmos genricos que hemos visto a una pequea parte del contenedor. Copiar,
buscar, reemplazar o borrar en los n primeros o ltimos elementos puede servir para
lograr el objetivo ahorrando muchas operaciones innecesarias.
Otra forma de ahorrar cmputo es utilizar algoritmos que hacen solo parte del
trabajo (pero suciente en muchos casos), en particular los de ordenacin y bsqueda
como partial_sort(), nth_element(), lower_bound(), etc.
Por ejemplo, una mejora bastante evidente que se puede hacer a nuestro inventario
de armas (ver listado 21.80) es cambiar el algoritmo sort() por max_element()
en el mtodo more_powerful_weapon().

Listado 21.83: Modicacin del inventario de armas


1 Weapon* more_powerful_weapon(void) {
2 WeaponVector weapons = weapons_with_ammo();
3
4 return *max_element(weapons.begin(), weapons.end(),
5 mem_fun(&Weapon::less_powerful_than));
6 }

Aunque no es previsible que sea un contenedor con muchos elementos, buscar el


mximo (que es la verdadera intencin del mtodo) es mucho ms rpido que ordenar
la coleccin y elegir el ltimo.
21.4. Aspectos avanzados de la STL [591]

Algoritmos versus mtodos del contenedor

Utilizar los algoritmos genricos de STL facilita obviamente escribir cdigo (o


nuevos algoritmos) que pueden operar sobre cualquier contenedor. Lamentablemen-
te, como no poda ser menos, la generalidad suele ir en detrimento de la eciencia.
El algoritmo genrico desconoce intencionadamente los detalles de implementacin
de cada contenedor, y eso implica que no puede (ni debe) aprovecharlos para traba-
jar del modo ms eciente posible. Resumiendo, para el algoritmo genrico es ms
importante ser genrico que eciente.
En aquellos casos en los que la eciencia sea ms importante que la generalidad (y
eso tambin hay que pensarlo con calma) puede ser ms adecuado utilizar los mtodos
del contenedor en lugar de sus algoritmos funcionalmente equivalentes. Veamos el
siguiente listado:

Listado 21.84: Algoritmo genrico vs. mtodo del contenedor


1 void test_algorithm_vs_method(void) {
2 int orig[] = {1, 2, 3, 4, 5};
3 const int SIZE = sizeof(orig) / sizeof(int);
4 vector <int> v1, v2;
5
6 copy(orig, orig + SIZE, back_inserter(v1));
7
8 v2.insert(v2.begin(), orig, orig + SIZE);
9
10 assert(equal(v1.begin(), v1.end(), v2.begin()));
11 }

Las lneas 6 y 8 realizan la misma operacin: aadir a un vector el contenido


del array orig, creando elementos nuevos (los vectores estn vacos). Sin embargo, la
versin con insert() (lnea 8) es ms eciente que copy(), ya que realiza menos
copias de los elementos.
Del mismo modo, aunque parece ms evidente, utilizar mtodos en los que se pue-
den especicar rangos es siempre ms eciente que usar sus equivalentes en los que
slo se proporciona un elemento (muchos mtodos estn sobrecargados para soportar
ambos casos).
El libro Effective STL [63] de Scott Meyers explica muchas otras reglas con-
cretas en las que el uso adecuado de STL puede aumentar notablemente la eciencia

C21
del programa.

21.4.2. Semntica de copia


Una cuestin que a menudo confunde a los programadores novatos es la semntica
de copia de STL. Signica que los contenedores almacenan copias de los elementos
aadidos, y del mismo modo, devuelven copias cuando se extraen. El siguiente listado
ilustra este hecho.

Listado 21.85: Semntica de copia de la STL


1 class Counter {
2 int value;
3 public:
4 Counter(void) : value(0) {}
5 void inc(void) { ++value; }
6 int get(void) { return value; }
7 };
8
[592] CAPTULO 21. C++ AVANZADO

9 void test_copy_semantics(void) {
10 vector<Counter> counters;
11 Counter c1;
12 counters.push_back(c1);
13 Counter c2 = counters[0];
14
15 counters[0].inc();
16
17 assert(c1.get() == 0);
18 assert(counters[0].get() == 1);
19 assert(c2.get() == 0);
20 }

Esto tiene graves implicaciones en la eciencia de las operaciones que se realizan


sobre el contenedor. Todos los algoritmos que impliquen aadir, mover y eliminar
elementos dentro de la secuencia (la prctica totalidad de ellos) realizan copias, al
menos cuando se trata de contenedores basados en bloque.
El siguiente listado es un decorador bastante rudimentario para string que
imprime informacin cada vez que una instancia es creada, copiada o destruida.

Listado 21.86: Semntica de copia de la STL


1 class String {
2 string value;
3 string desc;
4
5 public:
6 String(string init) : value(init), desc(init) {
7 cout << "created: " << desc << endl;
8 }
9 String(const String& other) {
10 value = other.value;
11 desc = "copy of " + other.desc;
12 cout << desc << endl;
13 }
14 ~String() {
15 cout << "destroyed: " << desc << endl;
16 }
17 bool operator<(const String& other) const {
18 return value < other.value;
19 }
20 friend ostream&
21 operator<<(ostream& out, const String& str) {
22 out << str.value;
23 return out;
24 }
25 };
26
27 void test_copy_semantics(void) {
28 vector<String> names;
29 names.push_back(String("foo"));
30 names.push_back(String("bar"));
31 names.push_back(String("buzz"));
32 cout << "-- init ready" << endl;
33
34 sort(names.begin(), names.end());
35 cout << "-- sort complete" << endl;
36 String i1 = names.front();
37 cout << "-- end" << endl;

El resultado al ejecutarlo puede resultar sorprendente:

created: foo
copy of foo
destroyed: foo
21.4. Aspectos avanzados de la STL [593]

created: bar
copy of bar
copy of copy of foo
destroyed: copy of foo
destroyed: bar
created: buzz
copy of buzz
copy of copy of copy of foo
copy of copy of bar
destroyed: copy of copy of foo
destroyed: copy of bar
destroyed: buzz
-- init ready
copy of copy of copy of bar
destroyed: copy of copy of copy of bar
copy of copy of buzz
destroyed: copy of copy of buzz
-- sort complete
copy of copy of copy of copy of bar
-- end
destroyed: copy of copy of copy of copy of bar
destroyed: copy of copy of copy of bar
destroyed: copy of copy of buzz
destroyed: copy of copy of copy of foo

Como se puede comprobar, las 6 primeras copias corresponden a las inserciones


(push_back()). El vector reubica todo el contenido cada vez que tiene que ampliar
la memoria necesaria, y eso le obliga a copiar en la nueva ubicacin los elementos que
ya tena. El algoritmo sort() reordena el vector usando solo 2 copias. La asigna-
cin implica una copia ms. Por ltimo se destruyen los tres objetos que almacena el
contenedor y la variable local.
Este ejemplo demuestra la importancia de que nuestras clases dispongan de un
constructor de copia correcto y eciente. Incluso as, muchos programadores pree-
ren utilizar los contenedores para almacenar punteros en lugar de copias de los objetos,
dado que los punteros son simples enteros, su copia es simple, directa y mucho ms
eciente. Sin embargo, almacenar punteros es siempre ms arriesgado y complica el
proceso de limpieza. Si no se tiene cuidado, puede quedar memoria sin liberar, algo
difcil de localizar y depurar. Los contenedores no liberan (delete()) los punteros
que contienen al destruirse. Debe hacerlo el programador explcitamente (ver lista-
do 21.81).
Un punto intermedio entre la eciencia de almacenar punteros y la seguridad de
almacenar copias es utilizar smart pointers (aunque nunca deben ser auto_ptr).

C21
Para profundizar en este asunto vea Implementing Reference Semantics [51].

21.4.3. Extendiendo la STL


La librera STL est especcamente diseada para que se pueda extender y adap-
tar de forma sencilla y eciente. En esta seccin veremos cmo crear o adaptar nues-
tros propios contenedores, functors y allocators. Ya vimos como crear un algoritmo en
la seccin 21.3.1.

Creando un contenedor

Dependiendo del modo en que se puede utilizar, los contenedores se clasican por
modelos. A menudo, soportar un modelo implica la existencia de mtodos concretos.
Los siguientes son los modelos ms importantes:
[594] CAPTULO 21. C++ AVANZADO

Forward container
Son aquellos que se organizan con un orden bien denido, que no puede cambiar
en usos sucesivos. La caracterstica ms interesante es que se puede crear ms
de un iterador vlido al mismo tiempo.
Reversible container
Puede ser iterado de principio a n y viceversa.
Random-access container
Es posible acceder a cualquier elemento del contenedor empleando el mismo
tiempo independientemente de su posicin.
Front insertion sequence
Permite aadir elementos al comienzo.
Back insertion sequence
Permite aadir elementos al nal.
Associative container
Aquellos que permiten acceder a los elementos en funcin de valores clave en
lugar de posiciones.

Cada tipo de contenedor determina qu tipo de iteradores pueden aplicarse para


recorrerlo y por tanto qu algoritmos pueden utilizarse con l.
Para ilustrar cules son las operaciones que debe soportar un contenedor se incluye
a continuacin la implementacin de carray. Se trata de una adaptacin (wrapper)
para utilizar un array C de tamao constante, ofreciendo la interfaz habitual de un con-
tenedor. En concreto se trata de una modicacin de la clase carray propuesta ini-
cialmente por Bjarne Stroustrup en su libro The C++ Programming Language [93]
y que aparece en [51].

Listado 21.87: carray: Wrapper STL para un array C


1 template<class T, size_t thesize>
2 class carray {
3
4 private:
5 T v[thesize];
6
7 public:
8 typedef T value_type;
9 typedef T* iterator;
10 typedef const T* const_iterator;
11 typedef T& reference;
12 typedef const T& const_reference;
13 typedef size_t size_type;
14 typedef ptrdiff_t difference_type;
15
16 // iteradores
17 iterator begin() { return v; }
18 const_iterator begin() const { return v; }
19 iterator end() { return v+thesize; }
20 const_iterator end() const { return v+thesize; }
21
22 // acceso directo
23 reference operator[](size_t i) { return v[i]; }
24 const_reference operator[](size_t i) const { return v[i]; }
25
26 // size
27 size_type size() const { return thesize; }
28 size_type max_size() const { return thesize; }
29
30 // conversin a array
21.4. Aspectos avanzados de la STL [595]

31 T* as_array() { return v; }
32 };

El siguiente listado muestra una prueba de su uso. Como los iteradores de carray
son realmente punteros ordinarios16 , este contenedor soporta los modelos forward y
reverse container adems de random access ya que tambin dispone del operador de
indexacin.

Listado 21.88: carray: Ejemplo de uso de carray


1 void test_carray() {
2 carray<int, 5> array;
3
4 for (unsigned i=0; i<array.size(); ++i)
5 array[i] = i+1;
6
7 reverse(array.begin(), array.end());
8
9 transform(array.begin(), array.end(),
10 array.begin(), negate<int>());
11
12 int expected[] = {-5, -4, -3, -2, -1};
13
14 assert(equal(array.begin(), array.end(), expected));
15 }

Functor adaptables

Los adaptadores que incorpora la librera (ptr_fun(), mem_fun(), etc.) ofre-


cen suciente exibilidad como para aprovechar los algoritmos genricos utilizando
predicados u operadores implementados como mtodos o funciones. An as, en mu-
chos casos puede ser conveniente escribir functors especcos (ver seccin 21.3.3).
Como vimos en la seccin 21.3.4 existen adaptadores ( bind1st(), not() o
compose(), etc.) que necesitan conocer el tipo de retorno o de los argumentos del
operador que se le pasa. Estos adaptadores requieren un tipo especial de functor, llama-
do functor adaptable, que contiene las deniciones de esos tipos (como typedefs).
Ese es el motivo por el que no se puede pasar una funcin convencional a estos adap-
tadores. Es necesario usar ptr_fun() para convertir la funcin convencional en
un functor adaptable.

C21
Del mismo modo que los predicados y operadores, STL considera los tipos de
functors adaptables correspondientes. As pues:

Los Generadores adaptables debern tener un campo con la denicin anidada


para su tipo de retorno llamada result_type.
Las Funciones unarias adaptables, adems del tipo de retorno, deben especicar
adems el tipo de su nico argumento, con el campo argument_type. En el
caso de los predicados adaptables se asume que el tipo de retorno es siempre
booleano.
Las Funciones binarias adaptables, adems del tipo de retorno, deben especi-
car el tipo de sus dos argumentos en los campos first_argument_type
y second_argument_type. Del mismo modo, los Predicados binarios no
necesitan especicar el tipo de retorno porque se asume que debe ser booleano.
16 No es extrao encontrar implementaciones de contenedores (como vector) perfectamente anes al

estndar que utilizan punteros convencionales como iteradores


[596] CAPTULO 21. C++ AVANZADO

Veamos la implementacin del ptr_fun() de g++-4.6 para funciones unarias,


que demuestra la utilidad de los functor adaptables:

Listado 21.89: Implementacin de ptr_fun()


1 template<typename _Arg, typename _Result>
2 inline pointer_to_unary_function<_Arg, _Result>
3 ptr_fun(_Result (*__x)(_Arg)) {
4 return pointer_to_unary_function<_Arg, _Result>(__x);
5 }

Vemos que ptr_fun() es una funcin-plantilla que se instancia (lnea 1) con


el tipo del argumento (_Arg) y el tipo de retorno (_Result). La funcin devuelve
una instancia de pointer_to_unary_function (lnea 2) instanciada con los
mismos tipos. Y el argumento de la funcin es un puntero a otra funcin (lnea 4) que
obviamente devuelve y acepta un parmetro de los tipos indicados en la plantilla. En
resumen, ptr_fun() es una factora que crea instancias del functor unario adaptable
pointer_to_unary_function.
Para facilitar la creacin de functor adaptables, STL ofrece plantillas17 que permi-
ten denir los tipos anidados anteriores para los tipos unary_function y binary-
_function. Veamos cmo convertir nuestro functor not_equal (ver listado 21.57)
en un predicado unario adaptable:

Listado 21.90: Predicado not_equal() adaptable


1 template <typename _Arg>
2 class not_equal : public unary_function<_Arg, bool> {
3 const _Arg _ref;
4
5 public:
6 not_equal(_Arg ref) : _ref(ref) {}
7
8 bool operator()(_Arg value) const {
9 return value != _ref;
10 }
11 };

21.4.4. Allocators
Los contenedores ocultan el manejo de la memoria requerida para almacenar los
elementos que contienen. Aunque en la gran mayora de las situaciones el comporta-
miento por defecto es el ms adecuado, pueden darse situaciones en las que el progra-
mador necesita ms control sobre el modo en que se pide y libera la memoria. Algunos
de esos motivos pueden ser:

Realizar una reserva contigua, reserva perezosa, cacheado, etc.


Registrar todas las operaciones de peticin y liberacin de memoria para deter-
minar cuando ocurren y qu parte del programa es la responsable.
Las caractersticas de la arquitectura concreta en la que se ejecuta el programa
permiten un manejo ms rpido o eciente de la memoria si se realiza de un
modo especco.
La aplicacin permite compartir memoria entre contenedores.
17 En el chero de cabecera <functional>
21.4. Aspectos avanzados de la STL [597]

Hacer una inicializacin especial de la memoria o alguna operacin de limpieza


adicional.

Para lograrlo la STL proporciona una nueva abstraccin: el allocator. Todos los
contenedores estndar utilizan por defecto un tipo de allocator concreto y permiten
especicar uno alternativo en el momento de su creacin, como un parmetro de la
plantilla.

Usar un allocator alternativo

Como sabemos, todos los contenedores de STL son plantillas que se instancian
con el tipo de dato que van a contener. Sin embargo, tienen un segundo parme-
tro: el allocator que debe aplicar. Veamos las primeras lneas de al denicin
de vector.

Listado 21.91: Denicin del contenedor vector


1 template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
2 class vector : protected _Vector_base<_Tp, _Alloc>
3 {
4 typedef typename _Alloc::value_type _Alloc_value_type;

Ese parmetro de la plantilla (_Alloc) es opcional porque la denicin propor-


ciona un valor por defecto (std::allocator). El allocator tambin es una
plantilla que se instancia con el tipo de elementos del contenedor.
Si se desea utilizar un allocator basta con indicarlo al instanciar el contenedor:

Listado 21.92: Especicando un allocator alternativo


1 vector<int, custom_alloc> v;

Creando un allocator

El allocator es una clase que encapsula las operaciones de peticin (a travs del
mtodo allocate()) y liberacin (a travs del mtodo deallocate()) de una

C21
cantidad de elementos de un tipo concreto. La signatura de estos mtodos es:

Listado 21.93: Mtodos bsicos del allocator


1 pointer allocate(size_type n, const void* hint=0);
2 void deallocate(pointer p, size_type n);

Crear un allocator no es una tarea sencilla. Lo aconsejable es buscar una librera


que proporcione allocators con la funcionalidad deseada, por ejemplo el pool_alloc
de Boost. Para entender cmo crear un allocator, sin tener que manejar la complejidad
que conlleva disear y manipular un modelo de memoria especial, se muestra a conti-
nuacin un wrapper rudimentario para los operadores new() y delete() estndar.
Es una modicacin del que propone [51] en la seccin 15.4.

Listado 21.94: Un allocator bsico con new y delete


1 template <class T>
2 class custom_alloc {
[598] CAPTULO 21. C++ AVANZADO

3 public:
4 typedef T value_type;
5 typedef T* pointer;
6 typedef const T* const_pointer;
7 typedef T& reference;
8 typedef const T& const_reference;
9 typedef size_t size_type;
10 typedef ptrdiff_t difference_type;
11
12
13 template <typename U>
14 struct rebind {
15 typedef custom_alloc<U> other;
16 };
17
18 custom_alloc() {}
19
20 custom_alloc(const custom_alloc&) {}
21
22 template <typename U>
23 custom_alloc(const custom_alloc<U>&) {}
24
25 pointer address(reference value) const {
26 return &value;
27 }
28
29 const_pointer address(const_reference value) const {
30 return &value;
31 }
32
33 size_type max_size() const {
34 return numeric_limits<size_t>::max() / sizeof(T);
35 }
36
37 pointer allocate(size_type n, const void* hint=0) {
38 return (pointer) (::operator new(n * sizeof(T)));
39 }
40
41 void deallocate(pointer p, size_type num) {
42 delete p;
43 }
44
45 void construct(pointer p, const T& value) {
46 new (p) T(value);
47 }
48
49 void destroy(pointer p) {
50 p->~T();
51 }

Las lneas 4 a 15 denen una serie de tipos anidados que todo allocator debe tener:

value_type
El tipo del dato del objeto que almacena.
reference y const_reference
El tipo de las referencia a los objetos.
pointer y const_pointer
El tipo de los punteros a los objetos.
size_type
El tipo que representa los tamaos (en bytes) de los objetos.
difference_type
El tipo que representa la diferencia entre dos objetos.
21.4. Aspectos avanzados de la STL [599]

Las lneas 18 a 23 contienen el constructor por defecto, el constructor de copia y


un constructor que acepta una instancia del mismo allocator para otro tipo. Todos ellos
estn vacos porque este allocator no tiene estado.
El mtodo polimrco address() (lneas 25 a 31) devuelve la direccin del
objeto. El mtodo max_size() devuelve el mayor valor que se puede almacenar
para el tipo concreto.
Por ltimo, los mtodos allocate() y deallocate() sirven para pedir y
liberar memoria para el objeto. Los mtodos construct() y desctroy() cons-
truyen y destruyen los objetos.

21.4.5. Novedades de la STL en C++11


El nuevo estndar C++11 viene con interesantes novedades a la librera STL. Es-
ta seccin aborda las ms importantes. Veremos cmo el nuevo estndar mejora la
eciencia y simplica algunos aspectos en el uso de la librera.

La funcin bind()

En realidad bind() es una plantilla. Se utiliza para crear un functor a partir de


una funcin, mtodo o functor y un especicacin de argumentos. bind() substituye
a los adaptadores bind1st() y bind2nd(). Permite especicar la posicin en la
que se desea pasar el argumento adicional indicado en el constructor y el de la invo-
cacin al functor resultante. Veamos un ejemplo de cmo bind() puede substituir a
bind2nd() en el listado 21.63.

Listado 21.95: Ejemplo de find_if() usando bind()


1 void test_find_if_cpp11() {
2 using namespace std::placeholders;
3
4 const int size = 5;
5 const int value = 1;
6 int numbers[] = {2, 7, 12, 9, 4};
7
8 assert(find_if(numbers, numbers + size,
9 bind(greater<int>(), _1, 6)) == numbers+1);

C21
El argumento especial _1 es un placeholder (ver lnea 18) que representa al primer
argumento posicional que recibir el functor generado. Es decir, si asumimos que la
invocacin a bind() crea un hipottico bool greater_than_6(int n), la
variable _1 representa al parmetro n.
Con esta nomenclatura tan simple es posible adaptar una llamada a funcin o m-
todo a otra aunque los parmetros estn dispuestos en un orden diferente o pudiendo
aadir u omitir algunos de ellos.
Tambin substituye a los adaptadores ptr_fun(), mem_fun() y, nalmente,
mem_fun_ref() dado que acepta cualquier entidad invocable y funciona tanto con
punteros como con referencias. Veamos la versin con bind() del listado 21.60:

Listado 21.96: Uso de bind() como reemplazo del adaptador mem_fun_ref()


1 void test_bind_replaces_mem_fun_ref() {
2 using namespace std::placeholders;
3
4 vector<Enemy> enemies(2);
[600] CAPTULO 21. C++ AVANZADO

5
6 assert(count_if(enemies.begin(), enemies.end(),
7 bind(&Enemy::is_alive, _1)) == 2);
8 }

Pero bind() hace mucho ms. Permite construir funciones parcialmente especi- bind()
cadas. Una funcin parcialmente especicada es una utilidad tpica de los lenguajes
Es el adaptador de funciones deni-
funcionales. Como su nombre indica, es una forma de jar el valor de uno o ms tivo. Substituye a todos los adapta-
parmetros de una funcin (o algo que se comporta como tal). El resultado es una dores que ofreca la STL antes del
nueva funcin (un functor realmente) con menos parmetros. Veamos un ejemplo (lis- estndar C++11.
tado 21.97). Tenemos una funcin que imprime un texto en un color determinado
indicado por parmetro. Utilizando bind() vamos a crear otra funcin que, a par-
tiendo de la primera, permite escribir texto en verde para imprimir un mensaje que
informa de un comportamiento correcto (como en un logger).

Listado 21.97: Una funcin parcialmente especicada


1 enum Color{black, red, green};
2
3 void print_color(string text, Color color) {
4 cout << text << " color:" << color << endl;
5 }
6
7 void test_bind() {
8 auto print_ok = bind(print_color, _1, green);
9 print_ok("Success");
10 }

Las funciones parcialmente especicadas resultan muy tiles cuando se requieren


manejadores de eventos. Normalmente stos deben cumplir un prototipo concreto y
ah es donde bind() simplica las cosas. En el siguiente ejemplo vemos un mapa de
manejadores para eventos de teclado en una aplicacin Ogre.

Listado 21.98: Creando un mapa de manejadores gracias a bind()


1 class WindowEventListener: public Ogre::WindowEventListener,
2 public OIS::KeyListener,
3 public OIS::MouseListener {
4 OIS::InputManager* inputManager;
5 OIS::Keyboard* keyboard;
6 OIS::Mouse* mouse;
7
8 typedef vector<OIS::KeyCode> KeyCodes;
9 map<KeyCodes, function<void()>> triggers_;
10
11 // ...
12
13 public:
14 void add_hook(KeyCodes keystroke, function<void()> callback) {
15 triggers_[keystroke] = callback;
16 }
17
18 // ...
19 };
20
21 int main() {
22 // ...
23
24 WindowEventListener listener(game.window);
25 Car car(chassis, wheels);
26
27 listener.add_hook({OIS::KC_W, OIS::KC_D},
28 bind(&Car::forward_right, &car));
29 listener.add_hook({OIS::KC_W, OIS::KC_A},
21.4. Aspectos avanzados de la STL [601]

30 bind(&Car::forward_left, &car));
31 listener.add_hook({OIS::KC_S, OIS::KC_D},
32 bind(&Car::backward_right, &car));
33 listener.add_hook({OIS::KC_S, OIS::KC_A},
34 bind(&Car::backward_left, &car));
35 listener.add_hook({OIS::KC_W},
36 bind(&Car::forward, &car));
37 listener.add_hook({OIS::KC_S},
38 bind(&Car::backward, &car));
39 listener.add_hook({OIS::KC_ESCAPE},
40 bind(&WindowEventListener::shutdown, &listener)
);
41
42 // ...

Fjese en el atributo triggers_ (lnea 9), el cual consiste en un mapa que rela-
ciona un vector de KeyCode (una combinacin de teclas) con un manejador (tipo
function<void()>). Esto permite cambiar las asignaciones de teclas segn las
preferencias del usuario, leyndolas por ejemplo de un chero de conguracin, y
sera muy sencillo tambin cambiarlas con la aplicacin en marcha si fuese necesario.

Contenedores

Con el nuevo estndar, todos los tipos se pueden inicializar de forma equivalente
a como se haca con los arrays, es decir, escribiendo los datos entre llaves:

Listado 21.99: Inicializacin uniforme: un array C y un vector


1 int numbers[] = {1, 2, 3, 4};
2 std::vector<int> numbers_vector = {1, 2, 3, 4};

Esto es gracias a un tipo llamado initializer_list que se puede usar para


crear un constructor alternativo. Pero tambin se puede utilizar en otros mtodos como
insert() o assign():

Listado 21.100: Usando initializer_list con insert()


1 numbers_vector.insert(numbers_vector.end(), {5, 6, 7});

C21
Puedes ver ms detalles y ejemplos sobre inicializacin uniforme en la seccin 21.5.2.
Se ha aadido el contenedor array. Es de tamao jo y tan eciente como un
array C nativo, y por supuesto, con una interfaz estilo STL similar a vector (ver
listado 21.101).

Listado 21.101: El nuevo contenedor array


1 std::array<int, 4> numbers = {10, 20, 30, 40};
2
3 std::cout << numbers.size() << std::endl;
4 for (auto n: numbers)
5 std::cout << n << " ";

Se ha aadido el contenedor forward_list. Se trata de una lista enlazada sen-


cilla, que nicamente soporta iteracin hacia adelante, no permite acceder al nal, ni
siquiera dispone del mtodo size().
[602] CAPTULO 21. C++ AVANZADO

Otra novedad interesante relacionada con la eciencia es la aparicin de los mto-


dos emplace() y emplace_back() en varios contenedores. emplace_back()
construye un objeto directamente en el contenedor, sin crear un objeto temporal, al-
go muy conveniente cuando nuestros objetos son complejos. Ejecuta el ejemplo del
listado 21.102 para comprobar el efecto.

Listado 21.102: Uso de emplace() para insercin eciente


1 struct A {
2 A(int) {
3 cout << "ctor" << endl;
4 }
5 A(const A& other) {
6 cout << "copy-ctor" << endl;
7 }
8 A(A&& other) {
9 cout << "move-ctor" << endl;
10 }
11 };
12
13 int main() {
14 std::vector<A> v;
15
16 v.emplace_back(1);
17 cout << "---" << endl;
18 v.push_back(A(1));
19 }

Iteradores

El nuevo estndar ha incorporado mtodos que retornan iteradores constantes en


todos los contenedores: cbegin(), cend() y tambin iteradores inversos contan-
tes: crbegin() y crend(). Adems estn disponibles versiones de begin() y
end() como funciones. De ese modo es posible escribir cdigo genrico que funcio-
na con una referencia a cualquier contenedor, incluyendo arrays C.

Algoritmos

Se han incorporado varios algoritmos nuevos. Aqu se incluye un resumen de al-


gunos de ellos:

all_of() Devuelve cierto slo si el predicado es cierto para todos los elementos
de la secuencia.
any_of() Devuelve cierto si el predicado es cierto para al menos uno de los ele-
mentos de la secuencia.
none_of() Devuelve cierto slo si el predicado es falso para todos los elementos
de la secuencia.
iota() Asigna valores crecientes a los elementos de una secuencia.
minmax() Devuelve los valores mnima y mximo de una secuencia.
is_sorted() Devuelve cierto si la secuencia est ordenada.
21.5. C++11: Novedades del nuevo estndar [603]

21.5. C++11: Novedades del nuevo estndar


El 12 de Agosto de 2011 la Organizacin Internacional de Estndares (ISO) aprob
el nuevo estndar de C++, anteriormente conocido como C++0x. Adems de aadir
funcionalidades nuevas al lenguaje, C++11 tambin ampla la STL, incluyendo en la
misma casi todas las plantillas y clases ya presentes en el TR1.
C++11 es compatible hacia atrs con C++98 (tambin con la correccin de 2003)
y con C. Aparte de esta, las cualidades que se han pretendido conseguir con el nuevo
estndar incluyen la mejora de rendimiento, una programacin ms evolucionada y su
accesibilidad para los programadores no-expertos sin privar al lenguaje de su potencia
habitual.
En los siguientes apartados se introducirn algunas de las nuevas caractersticas
que aade el estndar.

21.5.1. Compilando con g++ y clang


GCC y Clang son los dos compiladores que dan soporte a mayor nmero de
caractersticas del nuevo estndar. En el momento de escribir esta documentacin, la
ltima versin estable de GCC es la 4.8.2, con la que se puede disfrutar de todas las
novedades del ncleo del lenguaje y muchas de la biblioteca estndar.
Para compilar un programa de C++ usando el nuevo estndar hay que utilizar la
opcin -std=c++11 al compilar. Por ejemplo:

g++ -o main main.cc -std=c++11

Normalmente, si no se utiliza esta opcin, GCC compilar usando el estndar


C++03. Clang se usa exactamente de la misma forma.
Si se usa la librera estndar hay que linkarla (con g++ se puede omitir pues lo
hace de forma automtica):

clang -o main main.cc -std=c++11 -lstdc++

21.5.2. Cambios en el ncleo del lenguaje

C21
Expresiones constantes

Un compilador de C++ es capaz de optimizar ciertas expresiones que sern siem-


pre constantes, por ejemplo:

1 int a = 1 + 2;
2 cout << 3.2 - 4.5 << endl;
3
4 int miArray[4 * 2];

En este cdigo, el compilador sustituir las expresiones anteriores por su valor


en tiempo de compilacin. De este modo, en cualquier buen compilador, el cdigo
anterior no generar ninguna suma, resta o producto. Sin embargo, C++03 no permite
utilizar funciones que devuelvan constantes (por ejemplo return 5;).
[604] CAPTULO 21. C++ AVANZADO

C++11 introduce la palabra reservada constexpr para brindar la posibilidad de


utilizar funciones como expresiones constantes. Anteriormente no era posible puesto
que el compilador no tena ninguna forma de saber que poda aplicar esta optimiza-
cin. De este modo, es posible escribir algo como lo siguiente:

1 constexpr int siete(){ return 7; }


2
3 void miFunc(){
4 char cadena[siete() + 3];
5 cadena[0]=\0;
6 // ...
7 }

Un funcin se podr declarar como constexpr siempre que no devuelva void


y que termine del modo return <expresin>. Dicha expresin tendr que ser
constante una vez que se sustituyan todas las variables y si llama a otras funciones
tendrn que estar denidas como constexpr.
Es posible declarar variables utilizando constexpr que equivale al uso de const.

1 constexpr int saludJefeNivel = 1337;


2
3 const int saludJefeFinal = 31337;
4 const int saludJefeEspecial = 3110 + siete();

La introduccin de esta caracterstica en muy til con las plantillas. El siguiente


cdigo se evaluar en tiempo de compilacin y no en tiempo de ejecucin, sustituyen-
do la llamada por el valor devuelto.

1 template<typename T> constexpr T max(T a, T b)


2 {
3 return a < b ? b : a;
4 }

Inicializador de listas

Antes de la entrada del nuevo estndar, la inicializacin de los contenedores de la


STL era posible utilizando una zona de memoria con una secuencia de elementos del
tipo instanciado. Normalmente se utiliza un array para llevar esto a cabo.
Deniendo una estructura del siguiente modo

1 struct miStruct {
2 int a;
3 float b;
4 };

se podra inicializar un vector como sigue (tambin se incluyen ejemplos con en-
teros).

1 miStruct mS[] = { {0, 1.0}, {0, 0.0}, {0, 0.0} };


2 vector<miStruct> mVS(mS, mS + sizeof(mS)/sizeof(miStruct));
3
4 int mA[] = {1, 1, 1, 2, 3, 4, 1};
5 vector<int> mVI(mA, mA + sizeof(mA)/sizeof(int));
6
7 int mB[] = {1, 2, 3, 4};
8 set<int> mC(mB, mB + sizeof(mB)/sizeof(int));
21.5. C++11: Novedades del nuevo estndar [605]

A partir de ahora, es posible utilizar lo que se conoce como el inicializador de


listas, que permite realizar inicializaciones de manera mucho ms sencilla.

1 vector<miStruct> miVS {{0, 0.0}, {0, 0.0}, {0, 0.0}};


2 vector<int> miVI {1, 1, 1, 2, 3, 4, 1};
3 set<int> miC {0, 4, 5, 9};

Esto es posible gracias al uso de usa nueva sintaxis y del contenedor std::ini-
tializer_list.
Si se utiliza como parmetro en el constructor de una clase

Listado 21.103: Clase que utiliza un inicializador de listas


1 class LODDistancias {
2 public:
3 LODDistancias(std::initializer_list<int> entrada) :
4 distancias(entrada) {}
5 private:
6 vector<int> distancias;
7 };

es posible hacer uso de las llaves para inicializarla:

1 LODDistancias lodD {90, 21, 32, 32, 35, 45};

Hay que tener en cuenta que este tipo de contenedores se utilizan en tiempo de
compilacin, que slo pueden ser construidos estticamente por el compilador y que
no podrn ser modicados en tiempo de ejecucin. Aun as, como son un tipo, pueden
ser utilizados en cualquier tipo de funciones.
Tambin se pueden utilizar las llaves junto con el operador =, para inicializar o
para asignar nuevos valores.

1 vector<miStruct> miVS2 = {{0, 0.0}, {0, 0.0}, {0, 0.0}};


2 vector<int> miVI2 = {1, 1, 1, 2, 3, 4, 1};
3 set<int> miC2 = {0, 4, 5, 9};

C21
4
5 miVS2 = {{9, 1.2}};

Inicializacin uniforme

En C++03 no existe una forma uniforme de inicializar los objetos. En el aparatado


anterior, en la parte compatible con el antiguo estndar, se ha utilizado la inicializacin
de un array utilizando . Esto es posible ya que esa estructura es un agregado18 , ya que
slo este tipo de objetos y los arrays pueden ser inicializados de esta manera.
Con la aparicin de C++11, es posible utilizar las llaves para inicializar cualquier
clase o estructura. Por ejemplo, supongamos una clase para representar un vector de
tres dimensiones.
18 Un agregado (aggregate ) es una clase o estructura que no tiene destructor denido por el usuario ni

operador de asignacin. Tampoco tendrn miembros privados o protegidos que no sean estticos, ni una
clase base o funciones virtuales.
[606] CAPTULO 21. C++ AVANZADO

1 class Vector3D {
2 public:
3 Vector3D(float x, float y, float z):
4 _x(x), _y(y), _z(z) {}
5
6 private:
7 float _x;
8 float _y;
9 float _z;
10
11 friend Vector3D normalize(const Vector3D& v);
12 };

Es posible iniciar un objeto de tipo Vector3D de las dos formas siguientes.

1 Vector3D p{0.0, 1.1, -3.4};


2
3 Vector3D p1(1.8, 1.4, 2.3);

La primera utiliza la nueva inicializacin uniforme, la segunda la clsica, invocan-


do el constructor de forma explcita.
En C++11 tambin es posible utilizar esta inicializacin para construir de manera
implcita objetos que son devueltos por una funcin. El compilador utilizar el valor
de retorno del prototipo de la funcin y lo usar junto con los valores proporcionados
para construir y devolver un objeto de dicho tipo.

1 Vector3D normalize(const Vector3D& v){


2 float len = sqrt(v._x*v._x + v._y*v._y + v._z*v._z);
3
4 return {v._x/len, v._y/len, v._z/len};
5 }

Esta notacin no sustituye a la anterior. Cabe destacar que cuando se utiliza esta
sintaxis para inicializar un objeto, el constructor que acepta una lista de inicializacin
como las presentadas anteriormente tendr prioridad sobre otros. Debido a esto, algu-
nas veces ser necesario invocar directamente al constructor adecuado con la notacin
antigua.
Esta forma de devolver objetos es compatible con RVO (Return Value Optimi-
zation), que se ver en optimizaciones. Con lo cual una llamada como la siguiente
generar cdigo ptimo y seguramente sin ninguna copia.

1 Vector3D p2 = normalize(p);

Inferencia de tipos

Hasta ahora cada vez que se declaraba una variable en C++ haba que especicar
de qu tipo era de manera explcita. En C++11 existe la inferencia de tipos. Usando la
palabra reservada auto en una inicializacin en vez del tipo, el compilador deducir
el mismo de manera automtica.
En el ejemplo siguiente se ve cmo funciona esta caracterstica, tanto para tipos
bsicos como para la clase denida en el apartado anterior.

1 auto vidaJefe = 500;


2 auto precision = 1.00001;
3
21.5. C++11: Novedades del nuevo estndar [607]

4 Vector3D v(3.0, 2.1, 4.0);


5 auto v2 = normalize(v);

Esta nueva caracterstica es especialmente adecuada para simplicar algunas de-


claraciones complejas. A continuacin se muestra la diferencia en la declaracin del
iterador al recorrer un contenedor.

1 for (vector<double>::iterator it = dist.begin();


2 it != dist.end(); ++it)
3 cout << *it << endl ;
4
5 for (auto it = dist.begin(); it != dist.end(); ++it)
6 cout << *it << endl ;

Existe otra palabra reservada que se usa de forma similar a sizeof(), pero que
devuelve el tipo de una variable. Esta palabra es decltype y se puede usar para
extraer el tipo de una variable y usarlo para la declaracin de otra.

1 decltype(v2) otro_vector3d = {4.1, 3.0, 1.1};

Bucle for basado en rangos

En C++11 se introduce una caracterstica muy til para recorrer listas de ele-
mentos, ya sean arrays, lista de inicializacin o contenedores con las operaciones
begin() y end().

1 int records[4] = {900, 899, 39, 3};


2 for (int& i: records)
3 cout << i << endl;
4
5 list<float> punteria = {20.0, 10.9};
6 for (float& f: punteria)
7 cout << f << endl;

En el ejemplo anterior se utiliza una referencia para evitar la copia y la penaliza-


cin de rendimiento.

C21
Funciones Lambda

Las funciones lambda son simplemente funciones annimas. La sintaxis para de-
clarar este tipo de funciones es especial y es posible no declarar el tipo devuelto de
manera explcita sino que est denido de forma implcita mediante la construccin
decltype(<expresin_devuelta>). Las dos formas posible de declarar y
denir estas funciones son las siguientes.

[captura](parmetros)->tipo_de_retorno{cuerpo}

[captura](parmetros){cuerpo}

La primera hace explcito el tipo que se devuelve. De esto modo, las funciones que
se muestran a continuacin son equivalentes.

1 [](int p1, int p2)->int{ return p1+p2; };


2 [](int p1, int p2){ return p1+p2; };
[608] CAPTULO 21. C++ AVANZADO

Las variables que se utilizan dentro de estas funciones pueden ser capturadas para
utilizarse en el exterior. Se pueden capturar por valor o por referencia, dependiendo de
la sintaxis dentro de []. Si se utiliza por ejemplo [p1, &p2], p1 ser capturada por
valor y p2 por referencia. Si se usa [=,&p1], todas las variables sern capturadas por
valor (al usar =) excepto p1 que ser capturada por referencia. Si se utiliza [&,p2],
todas se capturarn por referencia (usando &), excepto p2.
En el siguiente ejemplo, se utiliza una funcin lambda para sumar la puntacio-
nes de todos los jugadores, que han sido previamente almacenadas en una lista. Se
muestran tres formas de hacerlo.

1 list<int> puntos = {330, 300, 200, 3892, 1222};


2 int suma = 0;
3
4 // 1)
5 auto f = [&suma](int& i){suma+=i;};
6 for (int& i: puntos)
7 f(i);
8 // 2)
9 for_each(puntos.begin(), puntos.end(),
10 [&suma](int& i){suma+=i;} );
11 // 3)
12 for_each(puntos.begin(), puntos.end(), f);

Declaracin alternativa de funciones

C++11 introduce una nueva forma de declarar funciones. Su utilidad es permi-


tir declarar los tipos de retorno de funciones templatizadas donde ste no se puede
averiguar a priori.
En el ejemplo siguiente se dene una clase y se declaran dos funciones templati-
zadas.

1 class K {
2 public:
3 int operator*(const K& k) const {return 2;}
4 };
5
6 template <typename T>
7 T pow2Bad(const T& t){return t*t;}
8
9 template <typename T>
10 auto pow2(const T& t)->decltype(t*t){return t*t;}

La primera funcin no compilar si el tipo que se devuelve al ejecutar la operacin


es diferente al tipo para el que se invoca. La segunda s lo har.

1 K kObj;
2 cout << pow2Bad(kObj) << endl; // <- no compila
3 cout << pow2(kObj) << endl;

Tambin se puede usar estar nueva sintaxis para funciones no templatizadas.

1 auto getHours()->int{ return _hours;}


21.5. C++11: Novedades del nuevo estndar [609]

Mejora en la construccin de objetos: delegacin

En C++03 es imposible invocar a un constructor desde otro constructor del mismo


objeto. En C++11 s es posible.

1 class playerInfo {
2 public:
3 playerInfo(const string& name) :
4 _name(name) {}
5
6 playerInfo() : playerInfo("default") {}
7
8 private:
9 string _name;
10 };

Sobrescritura explcita y declaracin nal

En C++11 es posible utilizar dos descriptores para aadir funcionalidad e infor-


macin para el compilador a la declaracin de los mtodos de una clase.
El descriptor override proporciona una forma de expresar que el mtodo que se
est declarando sobrescribe a otro de una clase base. Esto es til para expresar expl-
citamente las intenciones y facilitar la deteccin de fallos en tiempos de compilacin.
As, si se declara un mtodo como usando override y no existe uno con el mismo
prototipo que ste en una base clase, el compilador mostrar un error.

Listado 21.104: Uso de final y override


1 class Base {
2 public:
3 virtual int getX(){return _x;}
4 virtual bool isValid() { return true; }
5 private:
6 int _x;
7 };
8
9 class Derivada : public Base {
10 public:
11 //Ok, compila.
12 virtual int getX() override {

C21
13 return _anotherX;
14 }
15 //Fallo al compilar
16 virtual int getX(int a) override {
17 return _anotherX;
18 };
19 bool isValid() final { return false; }
20 private:
21 int _anotherX;
22 };
23
24 class MasDerivada : public Derivada {
25 public:


En el ejemplo anterior tambin se muestra (lneas 4-6 y 22 ) el uso de final.
Cuando se utiliza en la declaracin de un mtodo, indica que ninguna clase que herede
de sta podr sobrescribirlo.
[610] CAPTULO 21. C++ AVANZADO

Puntero null

Se introduce tambin un nuevo valor slo asignable a punteros: nullptr. Este


valor no se puede asignar a ningn otro tipo. En C++03, se usaba el valor 0 para los
punteros null, de este modo, se poda asignar el valor de estos punteros a un entero o
a un booleano. Con nullptr esto ya no es posible, ayudando a prevenir errores y a
sobrescribir funciones.

1 int* c = nullptr;

Cabe destacar que es un tipo compatible con los booleanos, pero que no es com-
patible con los enteros.

1 bool isNull = nullptr;


2 int zero = nullptr; // <- Error

Enumeraciones fuertemente tipadas

En las enumeraciones de C++03 no se poda distinguir el tipo de entero utilizado


para las mismas. En C++11 s, y adems se brinda la posibilidad de usar una visibilidad
ms restrictiva, para agrupar la enumeraciones sin tener que anidarlas dentro de clases.

1 enum TipoPortal :unsigned char {


2 NORMAL,
3 MIRROR
4 };
5
6 enum class TipoArma : unsigned short {
7 BLANCA,
8 EXPLOSIVA
9 };

Para utilizarlo, se har igual que en C++03, excepto en el segundo caso.

1 TipoPortal ptype = NORMAL;


2 TipoArma atype = TipoArma::BLANCA;

Adems de esto, ahora se permite la declaracin anticipada (forward declaration)


de enumeraciones.

Alias de plantillas

Ya que typedef no se puede utilizar con plantillas, C++11 incluye una forma de
crear alias para las mismas. Se basa en utilizar using.

1 template<typename T, typename M>


2 class miTipo;
3
4 template<typename N>
5 using miTipo2 = miTipo<N,N>;

Tambin se puede utilizar la nueva sintaxis para realizar las deniciones de tipo
que se hacan con typedef.
21.5. C++11: Novedades del nuevo estndar [611]

1 typedef unsigned int uint;


2 using uint = unsigned int;

Uniones sin restricciones

Ahora se permite la creacin de uniones con la participacin de objetos no triviales


en las mismas. El siguiente fragmento de cdigo slo compilar usando el estndar
C++11.

1 class Vector3D {
2 public:
3 Vector3D(float x, float y, float z) {}
4 };
5
6 union miUnion {
7 int a;
8 float b;
9 Vector3D v;
10 };

Nuevos literales de cadenas

C++03 no soportaba ningn tipo de codicacin Unicode. Slo se podan utilizar


dos tipos de literales: los que estaban entrecomillados (hola, que se convertan en
arrays de const char, y los entrecomillados con una L delante (Lhola), que
se transformarn en arrays de const wchar_t.
Se introduce tres nuevos tipos de literales, para UTF-8, UTF-16 y UTF-32, que
sern arrays de const char, const char16_t y const char32_t respec-
tivamente.

1 const char cad1[] = u8"Cadena UTF-8";


2 const char16_t cad2[] = u"Cadena UTF-16";
3 const char32_t cad3[] = U"Cadena UTF-32";

Tambin se permite la construccin de cadenas raw, que no interpretarn los ca-


racteres de escape (_), ni las propias comillas ("). Para denir este tipo de cadenas

C21
se usa R"(literal)". Tambin es posible usar cadenas raw con la modicacin
Unicode.

1 string raw(R"(Cadena "RAW" \n %d)");


2
3 const char16_t rcad2[] = uR"(Cadena UTF-16 RAW\n)";
4 const char32_t rcad3[] = UR"(Cadena UTF-32 RAW %d)";

Literales creados a medida

C++11 brinda al programador con la capacidad de crear nuevos tipos de literales.


Anteriormente los literales estaban preestablecidos, por ejemplo 9 es un literal entero,
9.0 uno double, y 9.0f uno de tipo oat.
[612] CAPTULO 21. C++ AVANZADO

A partir de ahora se pueden crear nuevos literales usando sujos. Los sujos po-
drn ir detrs de nmeros (los que puedan ser representados por unsigned long
long o long double) o detrs de literales de cadena. Estos sujos corresponden a
funciones con el prototipo retval operator _sufijo ( unsigned long
long ).

1 double operator"" _d (unsigned long long i) {


2 return (double) i;
3 }

La funcin anterior dene el sujo _d, que podr ser usado para crear un double
usando un nmero natural como literal.

1 auto d = 30_d;

Un ejemplo un poco ms complejo del uso de este operador se expone a continua-


cin. Sea la siguiente una clase que podra representar un vector de tres dimensiones,
incluyendo la operacin de suma.

1 class Vector3D {
2 public:
3 Vector3D() :
4 x_(0), y_(0), z_(0) {} ;
5
6 Vector3D(float x, float y, float z) :
7 x_(x), y_(y), z_(z) {} ;
8
9 Vector3D operator+(const Vector3D& v) {
10 return Vector3D(x_ + v.x_,
11 y_ + v.y_,
12 z_ + v.z_ );
13 }
14
15 private:
16 float x_;
17 float y_;
18 float z_;
19
20 friend Vector3D operator"" _vx(long double x);
21 friend Vector3D operator"" _vy(long double y);
22 friend Vector3D operator"" _vz(long double z);
23 };

Se podran denir los siguientes literales de usuario, por ejemplo para construir
vectores ortogonales.

1 Vector3D operator"" _vx(long double x) {


2 return Vector3D(x, 0, 0);
3 }
4
5 Vector3D operator"" _vy(long double y) {
6 return Vector3D(0, y, 0);
7 }
8
9 Vector3D operator"" _vz(long double z) {
10 return Vector3D(0, 0, z);
11 }

Como se deni la suma, se podra crear un vector con la misma.


21.5. C++11: Novedades del nuevo estndar [613]

1 auto v = 1.0_vx + 3.0_vy + 8.1_vz;

Para utilizar los sujos con los literales de cadenas, se muestra el siguiente ejem-
plo, que representa un jugador, con un nombre.

1 class Player {
2 public:
3 Player(string name):
4 name_(name) {}
5
6 private:
7 string name_;
8 };
9
10 Player operator"" _player(const char* name, size_t nChars) {
11 return Player(name);
12 };

Se podr entonces crear un jugador como sigue.

1 auto p = "bRue"_player;

Aserciones estticas

Algo muy til que ya inclua Boost es una asercin esttica. Este tipo de aserciones
se comprobarn en tiempo de compilacin, y ser el mismo compilador el que avise
de la situacin no deseada.
En C++11 se puede usar static_assert (cont-expr, error-message)
para utilizar estas aserciones en cualquier punto del cdigo.

1 template <typename T>


2 bool equal(T a, T b, T epsilon) {
3
4 static_assert( sizeof(T) >= 8, "4 bytes como poco" );
5
6 return (a > b - epsilon || a < b + epsilon);
7 }
8

C21
9 int main(int argc, char *argv[])
10 {
11 equal(8.0, 8.0000001, 0.00001); // OK (double 8 bytes)
12 equal(8.0f, 8.0000001f, 0.00001f); // Error!!
13
14 return 0;
15 }

La salida de la compilacin ser la siguiente:

$ g++ -o statica statica.cc -std=c++0x

statica.cc: In instantiation of bool equal(T, T, T)


[with T = float]:
statica.cc:17:35: required from here
statica.cc:9:3: error: static assertion failed:
4 bytes como poco
[614] CAPTULO 21. C++ AVANZADO

Eliminacin y seleccin por defecto explcita de funciones

En C++11 es posible prohibir el uso de las funciones de una clase, incluyendo


los constructores, haciendo uso de la palabra reservada delete. Esto es muy til
para evitar que alguien use un constructor no deseado (en C++03 se declaraba como
privado para obtener el mismo resultado).

1 class NoCopiable {
2 public:
3 NoCopiable(){}
4 NoCopiable(const NoCopiable&) = delete;
5 NoCopiable& operator=(const NoCopiable&) = delete;
6 };

Tambin es til para evitar llamadas implcitas a funciones. En C++03 esto es


posible para los constructores, utilizando la palabra reservada explicit. En C++11,
se pueden evitar la invocacin no deseada de funciones usando delete.
En el ejemplo siguiente, si no se declara la funcin que acepta un entero, si se
realizase una llamada de tipo setA(3), se realizara una conversin implcita desde
un entero a un double. Este tipo de comportamientos no siempre es deseable y puede
provocar sorpresas, sobre todo con tipos no-bsicos.

1 class Ex {
2 public:
3 explicit Ex(double a) :
4 a_(a) {}
5
6 Ex() = default;
7
8 void setA(double a) {
9 a_ = a;
10 }

En el mismo ejemplo se usa default con uno de los constructores, lo que pide
de forma explcita al compilador que l cree uno por defecto.

Constructores de movimiento

Se introduce el concepto de constructor de movimiento, en contraste con el aun


necesario constructor de copia. Mientras que es este ltimo se usa para determinar la
forma en la que se copian los objetos, el de movimiento determina qu signica mover
las propiedades de un objeto a otro (no son dos objetos independientes).
Aunque sea de forma transparente al programador, el compilador genera variables
temporales para realizar determinadas operaciones. El constructor de movimiento es
una forma de evitar este tipo de variables intermedias (de copia) y as poder optimizar
determinadas operaciones (asignaciones normalmente).
Se introduce tambin el concepto de referencias-rvalue (&&). Ya que en esta sec-
cin se introducen muchas caractersticas, esta en concreto slo se va a mencionar
por encima, puesto que profundizar en ella podra llevar tanto como para el resto jun-
tas. Como resumen, decir sobre ellas que son referencias especiales que apuntan a
variables sin una direccin de memoria (variables temporales).
El constructor de movimiento se declara como el de copia, pero el parmetro de
entrada usa &&. Lo mismo se aplica para la versin anloga del operador de asigna-
cin.
21.5. C++11: Novedades del nuevo estndar [615]

Es importante recalcar que, cuando se programa un constructor de movimiento,


hay que lidiar con el destructor del objeto temporal, puesto que se ejecutar cuando
este objeto salga de mbito. Normalmente esto implica tener la precaucin de evitar
llamar a un delete con un puntero no nulo que apunta a una direccin que ya ha
sido liberada. Para ello, al mover el objeto temporal, se tendr que evitar que se libere
la memoria del puntero que se ha movido, asignndole el valor nulo. De esto modo,
cuando el objeto temporal salga de mbito y se ejecute su destructor, delete no
actuar sobre su puntero.

Listado 21.105: Ejemplo de constructor de movimiento


1 #include <iostream>
2 #include <cstring>
3 #include <vector>
4
5 using namespace std;
6
7 class Movible {
8 public:
9 Movible(unsigned size) :
10 buffer_(new char[size]),
11 size_(size)
12 {
13 }
14
15 Movible(const Movible& m)
16 {
17 cout << "Constructor de copia" << endl;
18 if (this == &m)
19 return;
20 size_ = m.size_;
21 buffer_ = new char[size_];
22 memcpy(buffer_, m.buffer_, size_);
23 }
24
25 Movible(Movible&& m)
26 {
27 cout << "Constructor de movimiento" << endl;
28 size_ = m.size_;
29 buffer_ = m.buffer_;
30 m.buffer_ = nullptr;
31 }
32
33 Movible& operator=(Movible&& m)
34 {

C21
35 if (this == &m)
36 return *this;
37
38 cout << "Asignacion de movimiento" << endl;
39 size_ = m.size_;
40 buffer_ = m.buffer_;
41 m.buffer_ = nullptr;
42
43 return *this;
44 }
45
46
47 ~Movible()
48 {
49 cout << "Destructor" << endl;
50 if (buffer_ == nullptr)
51 cout << "--> con nullptr (moviendo)" << endl;
52
53 delete [] buffer_;
54 }
55
56
57
[616] CAPTULO 21. C++ AVANZADO

58
59 private:
60 char* buffer_;
61 unsigned size_;
62 };
63
64
65 Movible getM()
66 {
67 cout << "getM()" << endl;
68 Movible nuevo_objecto(20000);
69 return nuevo_objecto;
70 }
71
72 int main(int argc, char *argv[])
73 {
74 vector<Movible> v;
75
76 Movible k(234303);
77 k = getM();
78
79 v.push_back(Movible(4000));
80
81 return 0;
82 }

La salida del programa anterior es la siguiente:

$ ./move
getM()
Asignacion de movimiento
Destructor
--> con nullptr (moviendo)
Constructor de movimiento
Destructor
--> con nullptr (moviendo)
Destructor
Destructor

Cuando se usa la asignacin, el objeto temporal que se genera para devolver por
copia un objecto, es capturado por la asignacin con movimiento y no se realiza nin-
guna copia extra.
La biblioteca estndar est preparada para el uso de constructores de movimiento,
y como se ve en el ejemplo anterior, lo que en c++03 supondra una copia por cada
push_back() en c++11 supone una llamada transparente al constructor de movi-
miento, evitando as todas las copias que se realizaran de otra forma.
Ntese como en los movimientos se ejecuta el destructor del objeto,

21.5.3. Cambios en la biblioteca de C++


Una de las adiciones ms importantes a la STL es la inclusin de la mayora
del TR1. As, plantillas como auto_ptr (ahora unique_ptr), shared_ptr y
weak_ptr forman parte del estndar.

Generacin de nmero aleatorios

C++11 introduce una nueva forma de generar nmeros pseudo-aleatorios. La no-


vedad que se introduce es que el generador se divide en dos partes, el motor y la
distribucin que se usa.
21.5. C++11: Novedades del nuevo estndar [617]

Los posible motores a utilizar son: std::linear_congruential (genera-


dor linear congruencial), std::subtract_with_carry (resta con acarreo) y
std::mersenne_twister, que se representan con plantillas. Existen denicio-
nes de tipo, para poder usarlas sin congurar cada parmetro de las mismas: min-
std_rand0 y minstd_rand (lineales), mt19937 y mt19937_64 (mersenne
twister), y ranlux24_base, ranlux48_base y ranlux24 (resta con acarreo).
Las distribuciones: uniform_int_distribution,
bernoulli_distribution, geometric_distribution,
poisson_distribution, binomial_distribution,
uniform_real_distribution, exponential_distribution,
normal_distribution y gamma_distribution.
En el siguiente ejemplo se muestra un posible uso, sacando la semilla del reloj del
sistema en este caso.

1 #include <iostream>
2 #include <functional>
3 #include <random>
4 #include <sys/time.h>
5
6 using namespace std;
7
8 int main(int argc, char *argv[])
9 {
10 struct timeval now;
11 gettimeofday(&now, 0);
12
13 minstd_rand motor;
14 motor.seed(now.tv_usec);
15
16 uniform_int_distribution<int> dist(1,6);
17 uniform_int_distribution<int> dist_2(1,50);
18
19 int loto = dist(motor); // Uso directo
20
21 auto generador = bind(dist, motor); // Bind
22 int valor_dado = generador(); // Uso "bindeado"
23
24 cout << loto << " : " << valor_dado << endl;
25
26 return 0;
27 }

C21
Tablas Hash

Se introducen 4 tipos de tablas hash: std::unordered_set, std::unor-


dered_multiset, std::unordered_map y std::unordered_multimap.
La que se corresponde con el concepto tradicional en la que est representada por
std::unordered_map. Ya que su uso es similar a std::map y al resto de con-

tenedores asociativos antiguos, simplemente se muestra un ejemplo a continuacin.
Hay que incluir tambin la cabecera correspondiente (lnea 2 ).

1 #include <iostream>
2 #include <unordered_map>
3
4 int main(int argc, char *argv[])
5 {
6 std::unordered_map<int, float> miHash;
7 miHash[13] = 1.1;
8 std::cout << miHash[13] << std::endl;
9 return 0;
[618] CAPTULO 21. C++ AVANZADO

10 }

Expresiones regulares

Una de las caractersticas nuevas ms interesantes que se han aadido al estn-


dar son las expresiones regulares. Para ello se utiliza un objeto std::regex para
construir la expresin.

1
2 std::regex eRE1("\\b((c|C)ur)([^ ]*)");

Para almacenar las coincidencias ser necesario utilizar un objeto std::smatch


(si es una cadena de C, ser std::cmatch).

1 std::smatch match;

Es posible buscar todas las coincidencias dentro de una cadena como se muestra a
continuacin.

1 const std::string entrada ("Curso de experto en videojuegos.


Mucho curro.");
2
3 // Si hay coincidencias ...
4 auto aux = entrada;
5 while (std::regex_search( aux, match, eRE1)) {
6 cout << match.str() << endl;
7 aux = match.suffix().str();
8 }

Y saber si una cadena cumple la expresin regular as:

1 if (regex_match(entrada, eRE2))
2 cout << "[" << entrada << "] cumple la regex" << endl;

Las expresiones regulares, por defecto, se escribirn con la sintaxis ECMAScript.

Tuplas

C++11 da soporte a la creacin de tuplas que contengan diferentes tipos. Para ello
se utiliza la plantilla std::tuple. Como se ve en el ejemplo siguiente, para obtener
los valores se utiliza la funcin templatizada std::get().

1 #include <iostream>
2 #include <tuple>
3
4 using namespace std;
5
6 typedef tuple <string, int, float> tuplaPuntos;
7
8 int main(int argc, char *argv[])
9 {
10 tuplaPuntos p1("Bilbo", 20, 35.0);
11
12 cout << "El jugador " << get<0>(p1)
13 << " ha conseguido " << get<2>(p1)
21.6. Plugins [619]

14 << " puntos en " << get<1>(p1)


15 << " jugadas" << endl;
16
17 return 0;
18 }

Otras caractersticas

Aparte de las caractersticas mencionadas en las secciones anteriores, C++11 in-


cluye una biblioteca para el uso de traits para la metaprogramacin, tambin envolto-
rios para poder utilizar referencias en plantillas, mtodos uniformes para calcular el
tipo devuelto en objetos funciones y soporte multitarea.

21.6. Plugins
En trminos generales se denomina plug-in (o add-on) a cualquier componente
que aade (o modica) la funcionalidad de una aplicacin principal integrndose con
ella mediante un API proporcionando ex profeso. Los plugins son un mecanismo que
se emplea habitualmente cuando se desea que programadores ajenos al desarrollo del
proyecto matriz puedan integrarse con la aplicacin. Ofrece algunas ventajas intere-
santes respecto a una aplicacin monoltica:

Reduce la complejidad de la aplicacin principal.


Permite experimentar con nuevas caractersticas, que si resultan de inters, ms
tarde se pueden integrar en la lnea de desarrollo principal.
Ahorra mucho tiempo a los desarrolladores de las extensiones puesto que no
necesitan compilar el proyecto completo.
Permite a empresas o colectivos concretos implementar funcionalidades a la me-
dida de sus necesidades, que normalmente no seran admitidas en la aplicacin
principal.
En entornos de cdigo privativo, permite a los fabricantes distribuir parte del
programa en formato binario, ya sea la aplicacin central o alguno de los plu-

C21
gins. Tambin ocurre cuando partes distintas tienen licencias diferentes.

Asumiendo que la aplicacin principal est escrita en un lenguaje compilado (co-


mo C++) se pueden distinguir tres mecanismos bsicos que puede utilizar una aplica-
cin para ofrecer soporte de plugins:

Empotrar un interprete para un lenguaje dinmico, tal como Lua, Python o Sche-
me. Esta opcin se estudia ms adelante en el presente documento. Si la apli-
cacin matriz est escrita en un lenguaje dinmico no se requiere normalmente
ningn mecanismo especial ms all de localizar y cargar los plugins desde sus
cheros.
Proporcionar un protocolo basado en mensajes para que la aplicacin principal
se pueda comunicar con los plugins. Este es el caso de OSGi y queda fuera
el mbito de este curso. Este tipo de arquitectura es muy verstil (los plugins
pueden incluso estar escritos en distintos lenguajes) aunque resulta bastante in-
eciente.
[620] CAPTULO 21. C++ AVANZADO

Proporcionar un API binaria y cargar los plugins como bibliotecas dinmicas.


Es la opcin ms eciente ya que la nica penalizacin ocurre en el momento
de la carga. Esta seccin se ocupa de describir este mecanismo.

21.6.1. Entendiendo las bibliotecas dinmicas


En el mdulo 1 ya se mostr el proceso necesario para generar una biblioteca
dinmica. Hasta ahora hemos utilizado las bibliotecas como contenedores de funcio-
nalidad comn que puede ser utilizada por los ejecutables sin ms que indicarselo al
montador (linker). Sin embargo las bibliotecas dinmicas en los sistemas operativos
con formato de ejecutables ELF (Executable and Linkable Format) pueden servir para
mucho ms.
Una caracterstica interesante de los ejecutables y bibliotecas ELF es que pueden
tener smbolos no denidos, que son resueltos en tiempo de ejecucin. Con las biblio-
tecas esta caracterstica va ms all, hasta el punto de que no es necesario resolver
todos los smbolos en tiempo de compilacin. Veamos todo esto con ejemplos.
Hagamos un pequeo programa que utiliza una biblioteca.

Listado 21.106: El programa principal (main.c) simplemente usa una biblioteca


1 void mylib_func(const char* str, int val);
2
3 int main() {
4 mylib_func("test", 12345);
5 return 0;
6 }

E implementemos una biblioteca trivial.

Listado 21.107: La biblioteca (mylib.c) simplemente traza las llamadas


1 #include <stdio.h>
2
3 void mylib_func(const char* str, int val) {
4 printf("mylib_func %s %d\n", str, val);
5 }

Compilando el ejemplo como se indic en el mdulo 1 obtenemos el ejecutable


y la biblioteca. Recordemos que toda la biblioteca debe ser compilada con la opcin
-fPIC para generar cdigo independiente de posicin.

$ gcc -shared -fPIC -o libmylib.so mylib.c


$ gcc -o main main.c -L. -lmylib

Para ejecutarlo hay que indicarle al sistema operativo que tambin tiene que bus-
car bibliotecas en el directorio actual. Para eso basta denir la variable de entorno
LD_LIBRARY_PATH.

$ LD_LIBRARY_PATH=. ./main
mylib_func test 12345

Sin tocar para nada todo lo hecho hasta ahora, vamos a generar otra biblioteca
dinmica con la misma funcin denida de otra forma.
21.6. Plugins [621]

Listado 21.108: Otra implementacin de la biblioteca mnima (mylib2.c)


1 #include <stdio.h>
2
3 void mylib_func(const char* str, int val) {
4 printf("cambiada mylib_func %d %s\n", val, str);
5 }

Hemos cambiado ligeramente el mensaje, pero podramos haber implementado


algo completamente diferente. Ahora compilamos como una biblioteca dinmica, pero
ni siquiera tenemos que seguir el convenio de nombres tradicional.

$ gcc -shared -fPIC -o ml2.so mylib2.c

Y volvemos a ejecutar el programa de una forma muy peculiar:

$ LD_PRELOAD=ml2.so LD_LIBRARY_PATH=. ./main


cambiada mylib_func 12345 test

Sorprendido? No hemos recompilado el programa, no hemos cambiado la biblio-


teca original, pero hemos alterado el funcionamiento. Esta tcnica puede utilizarse
para multitud de nes, desde la depuracin (e.g. ElectricFence) hasta la alteracin de
los ejecutables para corregir errores cuando no se dispone del cdigo fuente.
Lo que pasa tras el teln podemos analizarlo con herramientas estndar:

$ ldd main
linux-vdso.so.1 => (0x00007fff701ff000)
libmylib.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f13043dd000)
/lib64/ld-linux-x86-64.so.2 (0x00007f130477c000)

Todos los ejecutables dinmicos estn montados con la biblioteca ld.so o ld--
linux.so. Se trata del montador dinmico. Obsrvese cmo se incluye en el eje-
cutable la ruta completa (ltima lnea). Esta biblioteca se encarga de precargar las
bibliotecas especicadas en LD_PRELOAD, buscar el resto en las rutas del sistema o
de LD_LIBRARY_PATH, y de cargarlas. El proceso de carga en el ejecutable incluye
resolver todos los smbolos que no estuvieran ya denidos.
Cuando desde la biblioteca dinmica es preciso invocar funciones (o simplemente
utilizar smbolos) denidas en el ejecutable, ste debe ser compilado con la opcin

C21
-rdynamic. Por ejemplo:

Listado 21.109: El programa principal dene smbolos pblicos


1 #include <stdio.h>
2
3 void mylib_func(const char* str, int val);
4 int main_i = 54321;
5
6 void main_func(int v) {
7 printf("main_func %d\n", v);
8 }
9
10 int main() {
11 mylib_func("test", 12345);
12 return 0;
13 }
[622] CAPTULO 21. C++ AVANZADO

Y la biblioteca llama a las funciones denidas en el ejecutable:

Listado 21.110: La biblioteca llama a una funcin denida en el programa


1 #include <stdio.h>
2
3 void main_func(int v);
4 extern int main_i;
5
6 void mylib_func(const char* str, int val) {
7 printf("mylib_func %d %s\n", val, str);
8 main_func(main_i);
9 }

Compilar este ejemplo solo cambia en la opcin -rdynamic.

$ gcc -shared -fPIC -o libmylib3.so mylib3.c


$ gcc -rdynamic -o main2 main2.c -L. -lmylib3

Y al ejecutarlo como antes:

$ LD_LIBRARY_PATH=. ./main2
mylib_func 12345 test
main_func 54321

Si todas estas actividades son realizadas por una biblioteca (ld.so) no debera
extraar que esta funcionalidad est tambin disponible mediante una API, para la
carga explcita de bibliotecas desde nuestro programa.

21.6.2. Plugins con libdl


El modo ms sencillo (aunque rudimentario) para implementar plugins es utilizar
la biblioteca libdl cuyo nombre signica exactamente eso: dynamic loading. El API
de esta biblioteca se encuentra en el chero de cabecera dlfcn.h es bastante simple:

1 void* dlopen(const char* filename, int flag);


2 void* dlsym(void* handle, const char* symbol);
3 int dlclose(void* handle);
4 char* dlerror(void);

La utilidad de las funciones es sencilla:

dlopen() abre una biblioteca dinmica (un chero .so) y devuelve un mane-
jador.
dlsym() carga y devuelve la direccin de smbolo cuyo nombre se especique
como symbol.
dlclose() le indica al sistema que ya no se va a utilizar la biblioteca y puede
ser descargada de memoria.
dlerror() devuelve una cadena de texto que describe el ltimo error produ-
cido por cualquiera de las otras funciones de la biblioteca.
21.6. Plugins [623]

Vamos a seguir un ejemplo muy sencillo en las prximas secciones. El ejemplo


est formado por un programa principal que tiene la lgica de registro de los plugins
(main.c), una biblioteca esttica (liba) y una (libb) que se cargar dinmica-
mente. Ambas bibliotecas tienen un chero de cabecera (a.h y b.h) y dos cheros
de implementacin cada una (a.c, a2.c, b.c y b2.c). La funcionalidad es abso-
lutamente trivial y sirve nicamente para ilustrar la ejecucin de las funciones corres-
pondientes.

Listado 21.111: Biblioteca esttica liba: a.h


1 #ifndef A_H
2 #define A_H
3
4 void a(int i);
5 int a2(int i);
6
7 #endif

Listado 21.112: Biblioteca esttica liba: a.c


1 #include <stdio.h>
2 #include "a.h"
3
4 void a(int i) {
5 printf("a( %d) returns %d\n", i, a2(i));
6 }

Listado 21.113: Biblioteca esttica liba: a2.c


1 #include "a.h"
2
3 int a2(int i) {
4 return i + 1;
5 }

Listado 21.114: Biblioteca dinmica libb: b.h


1 #ifndef B_H
#define B_H

C21
2
3
4 void b(int i);
5 int b2(int i);
6
7 #endif

Listado 21.115: Biblioteca dinmica libb: b.c


1 #include <stdio.h>
2 #include "b.h"
3
4 void b(int i) {
5 printf("b( %d) returns %d\n", i, b2(i));
6 }

Listado 21.116: Biblioteca dinmica libb: b2.c


1 #include "b.h"
[624] CAPTULO 21. C++ AVANZADO

2
3 int b2(int i) {
4 return i * i;
5 }

Estas bibliotecas se construyen exactamente del mismo modo que ya se explic en


el captulo Herramientas de Desarrollo. Veamos como ejemplo el Makefile para
libb:

Listado 21.117: Makele para la compilacin de libb


1 CC = gcc
2 CFLAGS = -Wall -ggdb -fPIC
3 LDFLAGS = -fPIC -shared
4
5 TARGET = libb.so.1.0.0
6
7 all: $(TARGET)
8
9 $(TARGET): b.o b2.o
10 $(CC) -Wl,-soname,libb.so.1.0.0 $(LDFLAGS) -o $@ $^
11
12 clean:
13 $(RM) *.o *~ *.a $(TARGET)

Carga explcita

En primer lugar veamos cmo cargar y ejecutar un smbolo (la funcin b()) de
forma explcita, es decir, el programador utiliza libdl para buscar la biblioteca y
cargar el smbolo concreto que desea:

Listado 21.118: Carga explcita de smbolos con libdl: main.c


1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <dlfcn.h>
4 #include "a.h"
5 #define LIBB_PATH "./dirb/libb.so.1.0.0"
6
7 void error() {
8 fprintf(stderr, dlerror());
9 exit(1);
10 }
11
12 int main() {
13 int i = 3;
14 void *plugin;
15 void (*function_b)(int);
16
17 if ((plugin = dlopen(LIBB_PATH, RTLD_LAZY)) == NULL)
18 error();
19
20 if ((function_b = dlsym(plugin, "b")) == NULL)
21 error();
22
23 printf("Results for %d:\n", i);
24 a(i);
25 function_b(i);
26
27 dlclose(plugin);
28 return 0;
29 }
21.6. Plugins [625]

La diferencia ms importante respecto al uso habitual de una biblioteca dinmica


es que no hay ninguna referencia a libb en la construccin del programa main.c
del listado anterior. Veamos el Makefile de la aplicacin:

Listado 21.119: Carga explcita de smbolos con libdl: Makefile


1 CC = gcc
2 CFLAGS = -Wall -ggdb -Idira
3 LDFLAGS = -Ldira
4 LDLIBS = -la -ldl
5
6 all: libs main
7
8 main: main.o
9
10 libs:
11 $(MAKE) -C dira
12 $(MAKE) -C dirb
13
14 clean:
15 $(RM) main *.o *~
16 $(MAKE) -C dira clean
17 $(MAKE) -C dirb clean

Carga implcita

Veamos ahora cmo construir un sencillo mecanismo que cargue automticamente


el smbolo en el momento de solicitar su uso.

Listado 21.120: Carga implcita de smbolos con libdl: plugin.c


1 typedef struct plugin {
2 char* key;
3 void (*function)(int);
4 struct plugin* next;
5 } plugin_t;
6
7 static plugin_t* plugins;
8
9 void
10 plugin_register(char* key, void (*function)(int)) {
11 plugin_t* p = (plugin_t*) malloc(sizeof(plugin_t));

C21
12 p->key = key;
13 p->function = function;
14 p->next = plugins;
15 plugins = p;
16 printf("** Plugin %s successfully registered.\n", key);
17 }
18
19 void
20 plugin_unregister(char* key) {
21 plugin_t *prev = NULL, *p = plugins;
22
23 while (p) {
24 if (0 == strcmp(p->key, key))
25 break;
26
27 prev = p;
28 p = p->next;
29 }
30
31 if (!p)
32 return;
33
34 if (prev)
[626] CAPTULO 21. C++ AVANZADO

35 prev->next = p->next;
36 else
37 plugins = p->next;
38
39 free(p);
40 }
41
42 static plugin_t*
43 plugin_find(char* key) {
44 plugin_t* p = plugins;
45 while (p) {
46 if (0 == strcmp(p->key, key))
47 break;
48
49 p = p->next;
50 }
51 return p;
52 }
53
54 void
55 call(char* key, int i) {
56 plugin_t* p;
57
58 p = plugin_find(key);
59 if (!p) {
60 char libname[PATH_MAX];
61 sprintf(libname, "./dir %s/lib %s.so", key, key);
62 printf("Trying load %s.\n", libname);
63 dlopen(libname, RTLD_LAZY);
64 p = plugin_find(key);
65 }
66
67 if (p)
68 p->function(i);
69 else
70 fprintf(stderr, "Error: Plugin %s not available.\n", key);
71 }

Los plugins (lneas 15) se almacenan en una lista enlazada (lnea 7). Las funcio-
nes plugin_register() y plugin_unregister() se utilizan para aadir y
eliminar plugins a la lista. La funcin call() (lneas 5471) ejecuta la funcin espe-
cica (contenida en el plugin) que se le pasa el parmetro, es decir, invoca una funcin
a partir de su nombre19 . Esa invocacin se puede ver en la lnea 10 del siguiente lista-
do:

Listado 21.121: Carga implcita de smbolos con libdl: main.c


1 #include <stdio.h>
2 #include "plugin.h"
3 #include "a.h"
4
5 int main() {
6 int i = 3;
7
8 printf("Results for %d:\n", i);
9 a(i);
10 call("b", i);
11 return 0;
12 }

Para que los plugins (en este caso libb) se registren automticamente al ser carga-
dos se requiere un pequeo truco: el atributo constructor (lnea 9) que provoca
que la funcin que lo tiene se ejecute en el momento de cargar el objeto:
19 Este proceso se denomina enlace tardo (late binding) o name binding.
21.6. Plugins [627]

Listado 21.122: Carga implcita de smbolos con libdl: b.c


1 #include <stdio.h>
2 #include "../plugin.h"
3 #include "b.h"
4
5 void b(int i) {
6 printf("b( %d) returns %d\n", i, b2(i));
7 }
8
9 static void init() __attribute__((constructor));
10
11 static void init() {
12 plugin_register("b", &b);
13 }

Aunque este sistema es muy simple (intencionadamente) ilustra el concepto de la


carga de smbolos bajo demanda desconocidos en tiempo de compilacin. A partir de
l es ms fcil entender mecanismos ms complejos puesto que se basan en la misma
idea bsica.
(constructor)
En C++ no es necesario indicar ma-
nualmente estos atributos, basta de- El atributo constructor indica que el smbolo al que va asociado debe almace-
nir un constructor para una varia- narse en una seccin de la biblioteca reservada para el cdigo de los construc-
ble esttica. tores de variables estticas. Estos constructores deben ejecutarse tan pronto
como la biblioteca se carga en memoria. Anlogamente, la seccin destructor
aglutina los destructores de las variables estticas, que se invocan tan pronto
como la biblioteca es cerrada.

21.6.3. Plugins con Glib gmodule


La biblioteca glib es un conjunto de utilidades, tipos abstractos de datos y otras
herramientas de uso general y absolutamente portables. Es una biblioteca muy utili-
zada en los desarrollos del proyecto GNU. Un buen ejemplo de su uso es la biblioteca
GTK y el entorno de escritorio GNOME (GNU Object Model Environment). Una de
esas utilidades es GModule, un sistema para realizar carga dinmica de smbolos com-
patible con mltiples sistemas operativos, incluyendo Sun, GNU/Linux, Windows, etc.

C21
GModule ofrece un API muy similar a libdl con funciones prcticamente equi-
valentes:

1 GModule* g_module_open(const gchar* file_name, GModuleFlags flags);


2 gboolean g_module_symbol(GModule* module, const gchar* symbol_name,
3 gpointer* symbol);
4 gboolean g_module_close(GModule* module);
5 const gchar * g_module_error(void);
[628] CAPTULO 21. C++ AVANZADO

Carga explcita

El siguiente listado muestra cmo hacer la carga y uso de la funcin b(), equiva-
lente al listado 21.118:

Listado 21.123: Carga explcita de smbolos con GModule: main.c


1 #include <stdio.h>
2 #include <glib.h>
3 #include <gmodule.h>
4 #include "a.h"
5
6 #define LIBB_PATH "./dirb/libb.so.1.0.0"
7
8 void error() {
9 g_error(g_module_error());
10 }
11
12 int main(){
13 int i = 3;
14 GModule* plugin;
15 void (*function_b)(int);
16
17 if ((plugin = g_module_open(LIBB_PATH, G_MODULE_BIND_LAZY)) ==
NULL)
18 error();
19
20 if (!g_module_symbol(plugin, "b", (gpointer*)&function_b))
21 error();
22
23 printf("Results for %d.\n",i);
24 a(i);
25 function_b(i);
26
27 g_module_close(plugin);
28
29 return 0;
30 }

Carga implcita

Por ltimo, este mdulo implementa el sistema de registro y carga automtica


usando una tabla hash de glib para almacenar los plugins:

Listado 21.124: Carga explcita de smbolos con GModule: plugin.c


1 #include <stdio.h>
2 #include <gmodule.h>
3 #include <glib/ghash.h>
4 #include "a.h"
5
6 #ifndef PATH_MAX
7 #define PATH_MAX 1024
8 #endif
9
10 static GHashTable* plugins = NULL;
11
12 void
13 plugin_register(char* key, void (*f)(int)) {
14 if (plugins == NULL)
15 plugins = g_hash_table_new_full(g_str_hash, g_str_equal, g_free
, g_free);
16 g_hash_table_insert(plugins, key, f);
17 g_message("Plugin %s succesfully registered.", key);
21.6. Plugins [629]

18 }
19
20 void
21 plugin_unregister(const char* key) {
22 if (plugins != NULL)
23 g_hash_table_remove(plugins, key);
24 }
25
26 void
27 call(const char* key, int i) {
28 void (*p)(int) = NULL;
29
30 if (plugins != NULL)
31 p = g_hash_table_lookup(plugins, key);
32
33 if (!p) {
34 char libname[PATH_MAX];
35
36 sprintf(libname, "./dir %s/lib %s.so", key, key);
37 g_message("Trying load %s.", libname);
38 if (g_module_open(libname, G_MODULE_BIND_LAZY) == NULL)
39 g_error("Plugin %s not available", libname);
40
41 if (plugins != NULL)
42 p = g_hash_table_lookup(plugins, key);
43 }
44
45 if (!p)
46 g_error("Plugin %s not available", key);
47
48 p(i);
49 }

21.6.4. Carga dinmica desde Python


El mdulo ctypes, de la librera estndar de Python, permite mapear los tipos
de datos de C a Python para conseguir una correspondencia binaria. Eso hace posi-
ble cargar funciones denidas en libreras dinmicas creadas con C/C++ y utilizarlas
directamente desde Python.
El siguiente listado muestra cmo cargar y utilizar la misma funcin b() de la
librera dinmica de las secciones anteriores:

C21
Listado 21.125: Carga de smbolos desde Python con ctypes
1 LIBB_PATH = "./dirb/libb.so.1.0.0"
2
3 import ctypes
4
5 plugin = ctypes.cdll.LoadLibrary(LIBB_PATH)
6 plugin.b(3)

21.6.5. Plugins como objetos mediante el patrn Factory Method


Los plugins implican la adicin y eliminacin de cdigo en tiempo de ejecucin.
Los problemas asociados tienen mucho que ver con los problemas que resuelven mu-
chos de los patrones que ya conocemos. En esta seccin veremos una pequea selec-
cin.
[630] CAPTULO 21. C++ AVANZADO

Recordemos el patrn factory method ya descrito en el mdulo 1. Se basa en la de-


nicin de una interfaz para crear instancias de objetos, permitiendo que las subclases
redenan este mtodo. Este patrn se utiliza frecuentemente acoplado con la propia
jerarqua de objetos, de forma parecida al patrn prototype, dando lugar a lo que se
conoce como constructor virtual. Veamos un ejemplo similar al que ponamos para
ilustrar el patrn prototipo, pero ahora empleando el patrn factory method.

Listado 21.126: Ejemplo de patrn factory method


1 class weapon {
2 public:
3 typedef shared_ptr<weapon> weapon_ptr;
4 virtual weapon_ptr make() = 0;
5 virtual void shoot() = 0;
6 virtual ~weapon() {}
7 };

Empleamos shared_ptr para simplicar la gestin de la memoria y denimos


un destructor virtual por si acaso alguna de las subclases necesitan liberar memoria
dinmica. Ahora cualquier instancia de rifle podra ser usada como factora, pero
para simplicar an ms su uso vamos a denir una factora que acte de fachada
frente a todos los factory method concretos. De esta forma disponemos de una factora
extensible.

Listado 21.127: Ejemplo de factora extensible de armamento


1 class weapon_factory {
2 public:
3 typedef shared_ptr<weapon> weapon_ptr;
4
5 weapon_ptr make(const string& key) {
6 weapon* aux = factories_[key];
7 if (aux)
8 return aux->make();
9
10 return 0;
11 }
12
13 void reg(const string& key, weapon* proto) {
14 factories_[key] = proto;
15 }
16
17 void unreg(const string& key) {
18 factories_.erase(key);
19 }
20
21 protected:
22 map<string,weapon*> factories_;
23 };

Para aadir o eliminar nuevas subclases de weapon tenemos que llamar a reg()
o unreg() respectivamente. Esto es adecuado para la tcnica RAII en la que la crea-
cin y destruccin de un objeto se utiliza para el uso y liberacin de un recurso:

Listado 21.128: Ejemplo de RAII para registro de nuevas armas


1 template <class weapon_type>
2 class weapon_reg {
3 weapon_factory& factory_;
4 const char* key_;
5 public:
6 weapon_reg(weapon_factory& factory, const char* key)
21.6. Plugins [631]

7 : factory_(factory), key_(key) {
8 factory_.reg(key_, new weapon_type());
9 }
10 ~weapon_reg() {
11 factory_.unreg(key_);
12 }
13 };

Tanto la factora como los objetos de registro podran ser tambin modelados con
el patrn singleton, pero para simplicar el ejemplo nos limitaremos a instanciarlos
sin ms:

Listado 21.129: Instancias de la factora extensible y una factora concreta


1 using namespace std::placeholders;

Veamos cmo ha quedado el ejemplo. Tenemos subclases derivadas de weapon


que saben cmo construir nuevos elementos. Tenemos una factora extensible que
se puede poblar con nuevas subclases de weapon. Y nalmente tenemos una clase
auxiliar para facilitar la extensin de la factora con cualquier subclase de weapon. Es
una estructura ideal para los plugins. Un plugin simplemente tiene que proporcionar
nuevas subclases de weapon e instanciar un weapon_reg por cada una de ellas.
Para ello tan solo habra que cambiar el mtodo make() de la factora:

Listado 21.130: Ejemplo de factora extensible con plugins


1 class dynamic_weapon_factory : public weapon_factory {
2 public:
3 weapon_ptr make(const string& key) {
4 weapon_ptr ret = weapon_factory::make(key);
5 if (ret)
6 return ret;
7 load_plugin(key);
8 return weapon_factory::make(key);
9 }
10
11 private:
12 void load_plugin(const string& key) {
13 string libname = "./fmethod-" + key + ".so";
14 dlopen(libname.c_str(), RTLD_LAZY);
15 }

C21
16 };

El cdigo de un plugin es completamente anlogo al de las otras factoras concre-


tas, como rifle.

Listado 21.131: Ejemplo de plugin para la factora extensible


1 #include "fmethod.hh"
2
3 class bow: public weapon {
4 public:
5 weapon_ptr make() { return weapon_ptr(new bow); }
6 void shoot() { cout << "shoot arrow." << endl; }
7 };
8
9 extern dynamic_weapon_factory dfactory;
10 weapon_reg<bow> bow_reg(dfactory, "bow");
[632] CAPTULO 21. C++ AVANZADO

La variable dfactory es la instancia de la factora dinmica extensible. Est de-


clarada en el programa principal, as que para poder ser utilizada desde una biblioteca
es preciso que el linker monte el programa principal con la opcin -rdynamic. Por
ltimo pondremos un ejemplo de uso de la factora:

Listado 21.132: Ejemplo de uso de la factora


1 void
2 load_and_shoot(const string name) {
3 shared_ptr<weapon> w = dfactory.make(name);
4 if (w)
5 w->shoot();
6 else
7 cout << "Missing weapon " << name << endl;
8 }

La descarga de la biblioteca dinmica (por ejemplo, utilizando la funcin dlclose)


provocara que se llamara al destructor de la clase bow_factory y con ello que se
des-registrara la factora concreta.
Ntese que en este ejemplo empleamos la infraestructura de plugins para man-
tener extensible nuestra aplicacin, pero no manejamos explcitamente los plugins.
As, por ejemplo, no hemos proporcionado ninguna funcin de descarga de plugins.
Incidiremos en este aspecto en el siguiente ejemplo.

21.6.6. Plugins multi-plataforma


La biblioteca GModule que hemos visto en la seccin es compatible con mltiples
sistemas operativos. Sin embargo, no est todo resuelto automticamente. Es preciso
conocer algunos detalles de las plataformas ms comunes para poder implantar con
xito una arquitectura de plugins. Para ello veremos una adaptacin del ejemplo ante-
rior para ejecutables PE (ReactOS, Microsoft Windows).
En el caso de los ejecutables PE no es posible compilar bibliotecas (DLL (Dynamic
Link Library)) sin determinar las referencias a todos los smbolos. Por tanto no es
posible referirnos a un smbolo denido en el programa principal (EXE). La solucin
ms sencilla es extraer la parte comn del ejecutable en una biblioteca dinmica que
se monta tanto con el ejecutable como con las otras bibliotecas. El programa principal
queda reducido a:

Listado 21.133: Programa principal para Windows


1 #include "fmethod-win.hh"
2
3 extern dynamic_weapon_factory dfactory;
4
5 int main(int argc, char* argv[]) {
6 while (argc > 1) {
7 shared_ptr<weapon> w = dfactory.make(argv[1]);
8 if (w)
9 w->shoot();
10 else
11 cout << "Missing weapon " << argv[1] << endl;
12
13 argc--; ++argv;
14 }
15 }

Y la parte comn se extraera en:


21.6. Plugins [633]

Listado 21.134: Biblioteca comn con la factora para Windows


1 #include "fmethod-win.hh"
2 #include <windows.h>
3
4 void
5 dynamic_weapon_factory::load_plugin(const string& key)
6 {
7 string libname = "./fmethod-" + key + "-win.dll";
8 LoadLibrary(libname.c_str());
9 }
10
11 dynamic_weapon_factory dfactory;
12 weapon_reg<rifle> rifle_reg(dfactory, "rifle");

Ntese cmo se cargan las bibliotecas con LoadLibrary(). El plugin es muy


similar a la versin ELF:

Listado 21.135: Plugin para Windows


1 #include "fmethod-win.hh"
2
3 class bow: public weapon {
4 public:
5 weapon_ptr make() { return weapon_ptr(new bow); }
6 void shoot() { cout << "shoot arrow." << endl; }
7 };
8
9 extern dynamic_weapon_factory dfactory;
10 weapon_reg<bow> bow_reg(dfactory, "bow");

Para compilar y probar todo no es necesario utilizar ReactOS o Microsoft Win-


dows. Podemos usar el compilador cruzado GCC para MINGW32 y el emulador
wine.

$ i586-mingw32msvc-g++ -std=c++0x -shared \


-Wl,--enable-runtime-pseudo-reloc \
-o fmethod-fac-win.dll fmethod-fac-win.cc
$ i586-mingw32msvc-g++ -std=c++0x \
-Wl,--enable-runtime-pseudo-reloc \
-Wl,--enable-auto-import -o fmethod-win.exe \
fmethod-win.cc fmethod-fac-win.dll
$ i586-mingw32msvc-g++ -std=c++0x -shared

C21
-Wl,--enable-runtime-pseudo-reloc \
-o fmethod-bow-win.dll fmethod-bow-win.cc \
fmethod-fac-win.dll
$ wine fmethod-win.exe rifle bow 2>/dev/null

La opcin del montador -enable-runtime-pseudo-reloc permite utili-


zar la semntica tradicional de visibilidad de smbolos de Unix. Todos los smbo-
los externos son automticamente exportados. La opcin -enable-auto-import
permite que todos los smbolos usados en el ejecutable que no estn denidos en el
propio ejecutable sean automticamente importados.

Se propone como ejercicio la generalizacin de este cdigo para que el mismo


programa compile correctamente con ejecutables ELF o con ejecutables PE.
Captulo

Tcnicas especcas
22
Francisco Moya Fernndez
Flix J. Villanueva Molina
Sergio Prez Camacho
David Villa Alises

22.1. Serializacin de objetos


La serializacin de objetos tiene que ver en parte con la persistencia del estado
de un videojuego. Serializar un objeto consiste en convertirlo en algo almacenable y
recuperable. De esto modo, el estado completo del objeto podr ser escrito en disco
o ser enviado a travs de la red, y su estado podr ser recuperado en otro instante de
tiempo o en otra mquina.
Puede parecer una operacin sencilla, despus de todo, bastara con almacenar el
pedazo de memoria que representa al objeto y volver a ponerlo en el mismo sitio des-
pus. Lamentablemente esto no es posible, puesto que la conguracin de la memoria
vara de ejecucin en ejecucin y de mquina en mquina. Adems, cada objeto tiene
sus particularidades. Por ejemplo, si lo que se desea serializar es una std::string
seguramente sea suciente con almacenar los caracteres que la componen.
Uno de los problemas a la hora de serializar objetos es que estos pueden contener
referencias o punteros a otros objetos, y este estado ha de conservarse de forma de-
digna. El problema de los punteros, es que la direcciones de memoria que almacenan
sern diferentes en cada ejecucin.
Antes de hablar de la serializacin propiamente dicha, se presentarn los streams
de C++.

635
[636] CAPTULO 22. TCNICAS ESPECFICAS

Figura 22.1: Jerarqua de streams

22.1.1. Streams
Un stream es como una tubera por donde uyen datos. Existen streams de en-
trada o de salida, y de ambas, de modo que un programa puede leer (entrada) de una
abstrayndose por completo de qu es lo que est llenando la misma. Esto hace que
los streams sean una forma de desacoplar las entradas de la forma de acceder a las
mismas, al igual que las salidas. No importa si quien rellena un stream es la entrada
del teclado o un archivo, la forma de utilizarla es la misma para ambos casos. De este
modo, controlar la entrada supondra conectar un stream a un chero (o al teclado)
y su salida al programa. Justo al revs (donde el teclado sera ahora la pantalla) se
controlara la salida.
Normalmente los streams tienen un buffer asociado puesto que escribir o leer en
bloques suele ser mucho ms eciente en los dispositivos de entrada y salida. El stream
se encargar (usando un streambuf1 ) de proporcionar o recoger el nmero de bytes
que se requiera leer o escribir en el mismo
En la gura 22.1 se muestra la jerarqua de streams en la biblioteca estndar de
C++. La clase ios_base representa la propiedades generales de un stream, como por
ejemplo si este es de entrada o de salida o si es de texto o binaria. La clase ios, que
hereda de la anterior, contiene un streambuf. Las clases ostream y istream,
derivan de ios y proporcionan mtodos de salida y de entrada respectivamente.

istream

La clase istream implementa mtodos que se utilizan para leer del buffer in-
terno de manera transparente. Existen dos formas de recoger la entrada: formateada
y sin formatear. La primera usa el operador >> y la segunda utiliza los siguientes
miembros de la clase:
1 streambuf es una clase que provee la memoria para dicho buffer incluyendo adems funciones para

el manejo del mismo (rellenado, ushing, etc. . . )


22.1. Serializacin de objetos [637]

gcount Devuelve el nmero de caracteres que retorn la


ltima lectura no formateada
get Obtiene datos sin formatear del stream
getline Obtiene una lnea completa del stream
ignore Saca caracteres del stream y los descarta
peek Lee el siguiente carcter sin extraerlo del stream
read Lee en bloque el nmero de caracteres que se le pidan
readsome Lee todo lo disponible en el buffer
putback Introduce de vuelta un carcter en el buffer
unget Decrementa el puntero get. Se leer de nuevo el mismo
carcter.
Utilizando tellg se obtiene la posicin (streampos) del puntero en el stream, y es
posible modicar la misma utilizando seekg con la posicin que de desee como en-
trada. La funcin seekg tambin se puede utilizar con un offset como primer parme-
tro y con una posicin base como segundo. As, ios_base::beg, ios_base::cur
y ios_base::end representan al principio del stream, a la posicin actual y al -
nal del mismo respectivamente. Es posible (y de hecho necesario con end) utilizar
nmeros negativos para posicionarse en un stream.

ostream

Un ostream representa una tubera en la que se puede escribir. Al igual que un


istream, se soportan los datos formateados, en este caso la insercin, usando el
operador <<.
Las operaciones para datos no formateados son las siguientes:
put Escribe un carcter en el stream
write Escribe un conjunto de caracteres desde un buffer

ifstream y ofstream

Estos streams que se utilizan para leer y escribir de archivos.


En el ejemplo siguiente se muestra cmo leer de un archivo utilizando los visto
sobre streams.

Listado 22.1: Ejemplo de lectura de un archivo

C22
1 #include <iostream>
2 #include <fstream>
3 #include <string>
4
5 using namespace std;
6
7 int main(int argc, char *argv[])
8 {
9 ifstream infile("prueba.txt", ios_base::binary);
10
11 if (!infile.is_open()) {
12 cout << "Error abriendo fichero" << endl;
13 return -1;
14 }
15
16 string linea;
17 getline(infile, linea);
18 cout << linea << endl;
19
20 char buffer[300];
[638] CAPTULO 22. TCNICAS ESPECFICAS

21 infile.getline(buffer, 300);
22 cout << buffer << endl;
23
24 infile.read(buffer,3);
25 buffer[3] = \0;
26 cout << "[" << buffer << "]" << endl;
27
28 streampos p = infile.tellg();
29 infile.seekg(2, ios_base::cur);
30 infile.seekg(-4, ios_base::end);
31 infile.seekg(p);
32
33 int i;
34 while ((i = infile.get()) != -1)
35 cout << "\" << (char) i << "\=int(" << i << ")" << endl;
36
37 return 0;
38 }


En la lnea 9 de crea
el stream del chero, y se intenta abrir para lectura como
un chero binario. En 11-14 secomprueba que el archivo se abri y se termina el
programa si no es as. En 16-18 se usa una funcin global de string para rellenar
una de estas con una lnea desde elchero. Se hace lo mismo con un buffer limitado
a 300 caracteres en la lneas 20-22
. Despus
se leen 3 caracteres sueltos (sin tener en
cuenta el nal de linea) (24-26 ). En 28-31 se juega con la posicin del puntero de
lectura, y en el resto, se lee carcter a carcter hasta el nal del archivo.
Los modos de apertura son los siguientes:
in Permitir sacar datos del stream
out Permitir introducir datos en el stream
ate Al abrir el stream, situar el puntero al nal del archivo.
app Poner el puntero al nal en cada operacin de salida
trunc Trunca el archivo al abrirlo
binary El stream ser binario y no de texto

En el listado 22.2 se muestra cmo copiar un archivo.

Operadores de insercin y extraccin

Es posible denir (sobrecargar) los operadores de insercin o de extraccin pa-


ra cualquier clase que nos interese, y as poder utilizarla para rellenar un stream o
para modicarla extrayendo datos de un stream. Estos operadores se usan para una

entrada/salida formateada. Vea el listado 22.3.
En la lneas 15-19 se dene el operador de insercin, y en las lneas 21-32 el
de
extraccin. Es necesario denir estos operadores como amigos de la clase (lneas

6-7 ) ya que necesitan acceso a los atributos privados.
La forma de utilizarlos es la siguiente:

Listado 22.4: Operadores de insercin y extraccin


1 int main(int argc, char *argv[])
2 {
3 Vector3D v(1.0, 2.3, 4.5);
4 cout << v << endl;
5 cin >> v ;
6 cout << v << endl;
7 return 0;
8 }
22.1. Serializacin de objetos [639]

Listado 22.2: Ejemplo de copia desde un archivo a otro


1 #include <fstream>
2 using namespace std;
3
4 int main()
5 {
6 fstream in ("prueba.txt", ios_base::in | ios_base::binary);
7 fstream out("copiaP.txt", ios_base::out | ios_base::binary
8 | ios_base::trunc );
9 if (!in.is_open() || !out.is_open())
10 return -1;
11
12 in.seekg(0, ios_base::end);
13 size_t size = in.tellg();
14 in.seekg(0, ios_base::beg);
15
16 char* buffer = new char[size];
17
18 in.read (buffer, size);
19 out.write(buffer, size);
20
21 delete [] buffer;
22 return 0;
23 }

del vector,
El programa anterior imprime el valor original y espera a la entrada
de un vector con el mismo formato. Al pulsar RETURN el vector original se relle-
nar con los nuevos datos tomados de la entrada estndar. De hecho, el programa
funciona tambin con una tubera del tipo echo "(1.0, 2.912, 3.123)"|
./ejecutable.

stringstream

La clase stringstream proporciona un interfaz para manipular cadenas como


si fueran streams de entrada/salida.
Su uso puede sustituir de algn modo al de sprintf, ya que es posible utilizar un
objeto de este tipo para transformar nmeros en cadenas y para realizar un formateo

bsico (ver listado 22.5).

C22
En las lneas 6-12 se dene una funcin templatizada que se usa en 33 para
transformar un nmero en una cadena, usando streamstream e invocando luego

su mtodo str(), que devuelve la cadena asociada.
En 14-23 se dene otra que se puede
utilizar para extraer un nmero de una cade-
na. Se ve un ejemplo de uso en la lnea 34 .

22.1.2. Serializacin y Dependencias entre objetos


A la hora de serializar un objeto, o un conjunto de objetos, se pueden dar diferentes
escenarios. No es lo mismo tener que escribir el contenido de un objeto que no tiene
ninguna dependencia con otros, que tener que escribir el contenido de un conjunto de
objetos que dependen unos de otros.
[640] CAPTULO 22. TCNICAS ESPECFICAS

Listado 22.3: Operadores de insercin y extraccin de Vector3D


1 #include <iostream>
2
3 using namespace std;
4
5 class Vector3D {
6 friend ostream& operator<<(ostream& o, const Vector3D& v);
7 friend istream& operator>>(istream& i,Vector3D& v);
8 public:
9 Vector3D(float x, float y, float z) :
10 x_(x), y_(y), z_(z) {}
11 private:
12 float x_, y_, z_;
13 };
14
15 ostream& operator<<(ostream& o, const Vector3D& v)
16 {
17 o << "(" << v.x_ << ", " << v.y_ << ", " << v.z_ << ")" ;
18 return o;
19 }
20
21 istream& operator>>(istream& i,Vector3D& v)
22 {
23 char par, coma;
24 // formato: (X, Y, Z)
25 i >> par;
26 i >> v.x_;
27 i >> coma;
28 i >> v.y_;
29 i >> coma;
30 i >> v.z_;
31 return i;
32 }

Sin dependencias

El escenario ms simple es la serializacin de un objeto sin dependencias con el


resto, es decir, un objeto que no apunta a ningn otro y que est autocontenido.
La serializacin ser entonces trivial, y bastar con escribir cada una de los valores
que contenga, y recuperarlo en el mismo orden.
Sea la siguiente una interfaz para objetos que puedan serializarse.

Listado 22.6: Interfaz simple para objetos serializables


1 class ISerializable {
2 public:
3 virtual void read (std::istream& in) = 0;
4 virtual void write(std::ostream& out) = 0;
5 };

De este modo, todos los objetos que deriven de esta clase tendrn que implementar
la forma de escribir y leer de un stream. Es til delegar los detalles de serializacin al
objeto.
Supngase ahora una clase muy sencilla y sin dependencias, con un double, un
int y un string para serializar.
22.1. Serializacin de objetos [641]

Listado 22.5: Usando un stringstream


1 #include <iostream>
2 #include <sstream>
3
4 using namespace std;
5
6 template<typename T>
7 string toString(T in)
8 {
9 stringstream ss;
10 ss << in;
11 return ss.str();
12 }
13
14 template<typename T>
15 T toNumber(const string& s)
16 {
17 stringstream ss(s);
18 T t;
19 ss << s;
20 if (!(ss >> t))
21 throw;
22 return t;
23 }
24
25
26 int main(int argc, char *argv[])
27 {
28 stringstream s;
29
30 s << 98 << endl << "texto" << endl;
31 cout << (s.str() += "op\n") ;
32
33 string str = toString(9.001);
34 long a = toNumber<long>("245345354525");
35 cout << a << endl;
36 return 0;
37 }

Listado 22.7: Objeto serializable sin dependencias

C22
1 class ObjetoSimple : public ISerializable
2 {
3 public:
4 ObjetoSimple(double a, int b, std::string cad);
5
6 ObjetoSimple();
7 virtual ~ObjetoSimple();
8
9 virtual void read (std::istream& in);
10 virtual void write(std::ostream& out);
11
12 private:
13
14 double a_;
15 int b_;
16 std::string cad_;
17 };

La implementacin de read() y write() sera como sigue:


[642] CAPTULO 22. TCNICAS ESPECFICAS

Listado 22.8: Detalle de implementacin de un serializable simple


1 void
2 ObjetoSimple::read(std::istream& in)
3 {
4 in.read((char*) &a_, sizeof(double));
5 in.read((char*) &b_, sizeof(int));
6
7 size_t len;
8 in.read((char*) &len, sizeof(size_t));
9 char* auxCad = new char[len+1];
10
11 in.read(auxCad, len);
12 auxCad[len] = \0;
13 cad_ = auxCad;
14
15 delete [] auxCad;
16
17 std::cout << "a_: " << a_ << std::endl;
18 std::cout << "b_: " << b_ << std::endl;
19 std::cout << "cad_: " << cad_ << std::endl;
20 }
21
22 void
23 ObjetoSimple::write(std::ostream& out)
24 {
25 out.write((char*) &a_, sizeof(double));
26 out.write((char*) &b_, sizeof(int));
27
28 size_t len = cad_.length();
29 out.write((char*) &len, sizeof(size_t));
30 out.write((char*) cad_.c_str(), len);
31 }

En la lectura y escritura se realiza un cast a char* puesto que as lo requieren las


funciones read y write de un stream. Lo que se est pidiendo a dichas funciones es:
desde/en esta posicin de memoria, tratada como un char*, lee/escribe el siguiente
nmero de caracteres.
El nmero de caracteres (bytes/octetos en x86+) viene determinado por el segundo
parmetro, y en este ejemplo se calcula con sizeof, esto es, con el tamao del tipo
que se est guardando o leyendo.
Un caso especial es la serializacin de un string, puesto que como se aprecia,
no se est guardando todo el objeto, sino los caracteres que contiene. Hay que tener
en cuenta que ser necesario guardar la longitud de la misma (lnea 30
) para poder
reservar la cantidad de memoria correcta al leerla de nuevo (8-9 ).
A continuacin se muestra un ejemplo de uso de dichos objetos utilizando archivos
para su serializacin y carga.

Listado 22.9: Uso de un objeto serializable simple


1 int main(int argc, char *argv[])
2 {
3 {
4 ofstream fout("data.bin", ios_base::binary | ios_base::trunc);
5 if (!fout.is_open())
6 return -1;
7
8 ObjetoSimple o(3.1371, 1337, "CEDV");
9 o.write(fout);
10 ObjetoSimple p(9.235, 31337, "UCLM");
11 p.write(fout);
12
13 }
14
22.1. Serializacin de objetos [643]

15 ifstream fin("data.bin", ios_base::binary);


16 ObjetoSimple q;
17 q.read(fin);
18 ObjetoSimple r;
19 r.read(fin);
20
21 return 0;
22 }

Se est utilizando un archivo para escribir el valor de un par de objetos, y tras


cerrarse, se vuelve a abrir para leer los datos almacenados y rellenar un par nuevo.

Con dependencias

Habr dependencia entre objetos, cuando la existencia de uno est ligada a la de


otro. Normalmente esto viene determinado porque uno de los miembros de una clase
es un puntero a la misma o a otra clase.
Cuando existen objetos con dependencias hay dos aproximaciones posibles para
su serializacin. La primera consiste en disear la arquitectura para que no se utilicen
punteros. En vez de esto se utilizarn UUID (Universally Unique Identier)s (IDs
nicas universales). Un objeto, en vez de almacenar un puntero al resto de objetos,
almacenar su UUID y har uso de factoras para recuperar el objeto en tiempo de
carga o de ejecucin. Las ventajas son claras, y las desventajas son el tiempo necesario
para mantener las referencias actualizadas, y que la arquitectura depender de esta
decisin de diseo completamente.
Otra forma de serializar clases con punteros es escribir sin preocupacin y reparar
el estado no-vlido de ese objeto teniendo en cuenta las propiedades de los mismos.
Un puntero referencia una direccin de memoria nica, es decir, dos objetos diferentes
no podrn compartir la misma direccin de memoria. Visto de otro modo, dos punteros
iguales apuntan al mismo objeto. Teniendo esto en cuenta, el propio puntero podra
valer como un UUID interno para la serializacin.
De este modo, la serializacin y deserializacin lectura de objetos con punteros
podra ser del siguiente modo:

Almacenar todos los objetos, teniendo en cuenta que lo primero que se almace-
nar ser la direccin de memoria que ocupa el objeto actual. Los punteros del
mismo se almacenarn como el resto de datos.
Al leer los objetos, poner en una tabla el puntero antiguo ledo, asociado a la

C22
nueva direccin de memoria.
Hacer una pasada corrigiendo el valor de los punteros, buscando la correspon-
dencia en la tabla.

Para ello necesitamos una interfaz nueva, que soporte la nueva funcin fixPtrs()
y otras dos para leer y recuperar la posicin de memoria del propio objeto.

Listado 22.10: Nueva interfaz de objeto serializable


1 class ISerializable {
2 public:
3 virtual void read (std::istream& in) = 0;
4 virtual void write(std::ostream& out) = 0;
5
6 virtual void fixPtrs () = 0;
7
[644] CAPTULO 22. TCNICAS ESPECFICAS

8 protected:
9 virtual void readMemDir (std::istream& in) = 0;
10 virtual void writeMemDir(std::ostream& out) = 0;
11 };

Esta vez se implementar dicha interfaz con la clase Serializable:

Listado 22.11: Implementacin de la interfaz ISerializable


1 class Serializable : public ISerializable {
2 public:
3 Serializable();
4 ~Serializable();
5
6 virtual void read (std::istream& in) = 0;
7 virtual void write(std::ostream& out) = 0;
8
9 virtual void fixPtrs () = 0;
10
11 protected:
12 virtual void readMemDir (std::istream& in);
13 virtual void writeMemDir(std::ostream& out);
14
15 Serializable* sPtr;
16 };


En la lnea 15 se aade un puntero que almacenar la direccin de memoria de la
propia clase.
La implementacin de las funciones de lectura y escritura se muestra a continua-
cin.

Listado 22.12: Implementacin de la interfaz ISerializable (II)


1 void Serializable::readMemDir(std::istream& in)
2 {
3 in.read((char*) &sPtr, sizeof(Serializable*) );
4 LookUpTable::getMap()[sPtr] = this;
5 }
6
7 void Serializable::writeMemDir (std::ostream& out)
8 {
9 sPtr = this;
10 out.write((char*) &sPtr, sizeof(Serializable*) );
11 }

Cuando se lee la antigua direccin de memoria readMemDir, esta se almacena


en
en una tabla junto con la nueva direccin (lnea 4 ). La implementacin de la tabla se
podra dar a travs de una especie de Singleton, que envolvera un map y lo mostrara
como una variable global.

Listado 22.13: Tabla de bsqueda de punteros


1 class Serializable; // Forward Dec.
2
3 class LookUpTable
4 {
5 friend class std::auto_ptr<LookUpTable*>;
6 public:
7 static std::map<Serializable*, Serializable*>& getMap();
8
9 typedef std::map<Serializable*, Serializable*>::iterator itMapS;
10
22.1. Serializacin de objetos [645]

11 private:
12 LookUpTable(){}
13
14 std::map<Serializable*, Serializable*> sMap_;
15
16 };

Listado 22.14: Tabla de bsqueda de punteros (II)


1 std::map<Serializable*, Serializable*>&
2 LookUpTable::getMap()
3 {
4 static std::auto_ptr<LookUpTable> instance_(new LookUpTable);
5 return instance_->sMap_;
6 }

El nuevo tipo de objeto compuesto tendr que derivar de la clase Serializable


y no de ISerializable como antes.

Listado 22.15: Declaracin de ObjetoCompuesto


1 class ObjetoCompuesto : public Serializable
2 {
3 public:
4 ObjetoCompuesto(double a, int b, std::string cad,
5 ObjetoCompuesto* other);
6
7 ObjetoCompuesto();
8 virtual ~ObjetoCompuesto();
9
10 virtual void read (std::istream& in);
11 virtual void write(std::ostream& out);
12
13 virtual void fixPtrs();
14
15 void printCad();
16 void printOther();
17
18 private:
19
20 double a_;
21 int b_;
22 std::string cad_;
23 ObjetoCompuesto* obj_;
24 };

C22
Uno de los constructores ahora acepta un puntero a un objeto del mismo tipo. En
23 se declara un puntero a un objeto del mismo tipo, y tendr que ser serializado,
recuperado y arreglado. Con motivo de probar si la lectura ha sido correcta, se han
aadido un par de funciones, printCad, que imprime la cadena serializada del pro-
pio objeto y printOther, que imprime la cadena del objeto apuntado a travs del
primer mtodo.
De esto modo, la implementacin de la clase anterior sera la siguiente. Primero
para las funciones de impresin, que son las ms sencillas:

Listado 22.16: Denicin de ObjetoCompuesto


1 void ObjetoCompuesto::printCad()
2 {
3 std::cout << cad_ << std::endl;
4 }
5
[646] CAPTULO 22. TCNICAS ESPECFICAS

6 void ObjetoCompuesto::printOther()
7 {
8 if (obj_) obj_->printCad();
9 }

Y a continuacin las de serializacin y deserializacin, con el aadido de que


de leer el resto del objeto, se lee la direccin de memoria que se almacen
justo antes
(lnea 4 ), que ser la encargada
de rellenar la tabla de punteros como se ha visto
anteriormente. En la lnea 19 se lee el puntero, como se hara de forma normal. En
este momento, el puntero contendra la direccin antigua fruto de la serializacin.
Para la escritura pasa exactamente lo mismo, simplemente se guardan los punteros
que corresponden a las direcciones de memoria en el momento de la escritura.

Listado 22.17: Denicin de ObjetoCompuesto (II)


1 void
2 ObjetoCompuesto::read(std::istream& in)
3 {
4 readMemDir(in);
5
6 in.read((char*) &a_, sizeof(double));
7 in.read((char*) &b_, sizeof(int));
8
9 size_t len;
10 in.read((char*) &len, sizeof(size_t));
11 char* auxCad = new char[len+1];
12
13 in.read(auxCad, len);
14 auxCad[len] = \0;
15 cad_ = auxCad;
16
17 delete [] auxCad;
18
19 in.read((char*) &obj_, sizeof(ObjetoCompuesto*) );
20
21 std::cout << "a_: " << a_ << std::endl;
22 std::cout << "b_: " << b_ << std::endl;
23 std::cout << "cad_: " << cad_ << std::endl;
24 std::cout << "obj_: " << obj_ << std::endl;
25 std::cout << "this: " << this << std::endl;
26 std::cout << "--------------" << std::endl;
27 }
28
29 void
30 ObjetoCompuesto::write(std::ostream& out)
31 {
32 writeMemDir(out);
33
34 out.write((char*) &a_, sizeof(double));
35 out.write((char*) &b_, sizeof(int));
36
37 size_t len = cad_.length();
38 out.write((char*) &len, sizeof(size_t));
39 out.write((char*) cad_.c_str(), len);
40
41 out.write((char*) &obj_, sizeof(ObjetoCompuesto*) );
42 std::cout << "* obj_: " << obj_ << std::endl;
43 }

La funcin que se encarga de arreglar los punteros es la siguiente:

Listado 22.18: Denicin de ObjetoCompuesto (III)


1 void ObjetoCompuesto::fixPtrs() {
2 if (obj_ == NULL)
22.1. Serializacin de objetos [647]

3 return;
4
5 LookUpTable::itMapS it;
6 it = LookUpTable::getMap().find(obj_);
7 if (it == LookUpTable::getMap().end()) {
8 std::cout << "Puntero no encontrado" << std::endl;
9 throw;
10 }
11 obj_ = (ObjetoCompuesto*) it->second;
12 std::cout << "obj_ FIXED: " << obj_ << std::endl;
13 }

Si el puntero almacenado es nulo, no cambiar nada. Si el puntero no es nulo,


se sustituir por el que est almacenado en la tabla, que ser precisamente la nueva
posicin del objeto apuntado en memoria. Hay que tener en cuenta que para que esta
funcin no falle, primero tendr que estar cargado en memoria en objeto al que se
debera estar apuntando.
As, una forma de utilizar todas estas clases sera esta:

Listado 22.19: Serializacin con dependencias


1 int main() {
2 cout << "Serializando" << endl; cout << "------------" << endl;
3 {
4 ofstream fout("data.bin", ios_base::binary | ios_base::trunc);
5 if (!fout.is_open())
6 return -1;
7
8 ObjetoCompuesto o(3.1371, 1337, "CEDV", NULL);
9 o.write(fout);
10 ObjetoCompuesto p(9.235, 31337, "UCLM", &o);
11 p.write(fout);
12 ObjetoCompuesto q(9.235, 6233, "ESI", &p);
13 q.write(fout);
14
15 ObjetoCompuesto* k = new ObjetoCompuesto(300.2, 1000, "BRUE",
&p);
16 k->write(fout);
17 delete k;
18
19 ObjetoCompuesto r(10.2, 3243, "2012", k);
20 r.write(fout);
21 }
22 cout << "\nRecuperando" << endl;
23 cout << "-----------" << endl;
24
25 ifstream fin("data.bin", ios_base::binary);

C22
26
27 std::vector<Serializable*> objetosLeidos;
28
29 for (int i = 0; i < 5; ++i) {
30 ObjetoCompuesto* o = new ObjetoCompuesto();
31 o->read(fin);
32 objetosLeidos.push_back(o);
33 }
34
35 cout << "\nFix punteros" << endl;
36 cout << "------------" << endl;
37
38 for_each(objetosLeidos.begin(), objetosLeidos.end(),
39 mem_fun(&Serializable::fixPtrs));
40
41 cout << "\nProbando" << endl;
42 cout << "--------" << endl;
43
44 std::vector<Serializable*>::iterator it;
45 for (it = objetosLeidos.begin();
[648] CAPTULO 22. TCNICAS ESPECFICAS

46 it != objetosLeidos.end();
47 ++it)
48 static_cast<ObjetoCompuesto*>((*it))->printOther();
49
50 return 0;
51 }


En las lneas 5-23 se crea el archivo que se usar como un stream y algunos
objetos que se van serializando. Algunos de ellos se crean en el stack y otro en el
heap. El archivo se cerrar puesto que la variable fout sale de contexto al terminar

el bloque.
En la lnea 27 se abre el mismo archivo para proceder a su lectura.
En 29-35 se
leen los datos del archivo y se van metiendo en un vector. En 40-41 se procede a
ejecutar la funcin fixPtrs de cada uno de los objetos almacenados dentro del vec-
tor. Justo despus se ejecutan las funciones que imprimen las cadenas de los objetos
apuntados, para comprobar que se han restaurado correctamente las dependencias.
La salida al ejecutar el programa anterior se muestra a continuacin:

Serializando
------------
* obj_: 0
* obj_: 0x7fff3f6dad80
* obj_: 0x7fff3f6dadb0
* obj_: 0x7fff3f6dadb0
* obj_: 0x11b3320
Recuperando
-----------
a_: 3.1371
b_: 1337
cad_: CEDV
obj_: 0
this: 0x11b3260
--------------
a_: 9.235
b_: 31337
cad_: UCLM
obj_: 0x7fff3f6dad80
this: 0x11b3370
--------------
a_: 9.235
b_: 6233
cad_: ESI
obj_: 0x7fff3f6dadb0
this: 0x11b3440
--------------
a_: 300.2
b_: 1000
cad_: BRUE
obj_: 0x7fff3f6dadb0
this: 0x11b3520
--------------
a_: 10.2
b_: 3243
cad_: 2012
obj_: 0x11b3320
this: 0x11b35d0
--------------
22.1. Serializacin de objetos [649]

Fix punteros
------------
obj_ FIXED: 0x11b3260
obj_ FIXED: 0x11b3370
obj_ FIXED: 0x11b3370
obj_ FIXED: 0x11b3520

Probando
--------
CEDV
UCLM
UCLM
BRUE

Cabe destacar que la direccin de memoria obtenida de los objetos en el stack


se diferencia notablemente de la obtenida del heap. Como se puede ver, la serializa-
cin y la posterior lectura es correcta cuando se arreglan los punteros con la tcnica
presentada.

22.1.3. Serializacin con Boost


Boost provee al programador de C++ con muchas utilidades, entre ellas la capaci-
dad para serializar objetos de forma muy sencilla y metdica, convirtiendo una tarea
tediosa en un mero trmite.

Objetos sin dependencias

Para serializar la clase simple expuesta en la seccin anterior, primero habra del
siguiente modo:

Listado 22.20: Serializando un objeto simple con Boost


1 #include <fstream>
2 #include <boost/archive/text_oarchive.hpp>
3 #include <boost/archive/text_iarchive.hpp>
4
5 class ObjetoSimple {
6 friend class boost::serialization::access;
7 public:
8 ObjetoSimple(double a, int b, std::string cad);
9 ObjetoSimple();

C22
10 virtual ~ObjetoSimple();
11
12 void print();
13
14 template<class Archive>
15 void serialize(Archive & ar, const unsigned int version) {
16 ar & a_;
17 ar & b_;
18 ar & cad_;
19 }
20
21 private:
22 double a_;
23 int b_;
24 std::string cad_;
25 };
[650] CAPTULO 22. TCNICAS ESPECFICAS


En la lnea 8 se permite el acceso a esta clase desde la funcin access de Boost,
que se usar para la invocacin de serialize 18-22 . El smbolo & utilizado dentro
de dicha funcin templatizada representa a << o >> segn sea el tipo de Archive,
que ser el envoltorio de fstreams de Boost usado para la serializacin. Es preci-
samente en esa funcin donde se lleva a cabo la serializacin, puesto que para cada
variable de la clase, se procede a su lectura o escritura.
A continuacin se muestra cmo utilizar esta clase en un programa:

Listado 22.21: Uso de un objeto simple serializable con Boost


1 {
2 ofstream fout ("dataSimple", ios_base::trunc);
3 ObjetoSimple oSimple(1.0, 2, "BOOST");
4 boost::archive::text_oarchive outA(fout);
5 outA << oSimple;
6 }
7
8 {
9 ObjetoSimple otherSimple;
10 ifstream fin("dataSimple", ios_base::binary );
11 boost::archive::text_iarchive inA(fin);
12 inA >> otherSimple;
13 otherSimple.print();
14 }

dosobjetos.
En el primer bloque se crea un archivo de salida, y se crean y escriben
En el segundo se leen y se imprimen. Como se muestra en la lneas 5 y 12 , se usan
los operadores de insercin y extraccin de las clases de Boost utilizadas.

Objetos con dependencias

Sea la siguiente clase una similar a la compuesta que se plante en la seccin


anterior, aadiendo adems un objeto de tipo ObjetoSimple como miembro.

Listado 22.22: Declarando un objeto compuesto serializable con Boost


1 class ObjetoCompuesto
2 {
3 friend class boost::serialization::access;
4 public:
5 ObjetoCompuesto(double a, int b, std::string cad,
6 ObjetoCompuesto* other);
7
8 ObjetoCompuesto();
9 virtual ~ObjetoCompuesto();
10
11 void print();
12 void printOther();
13
14 template<class Archive>
15 void serialize(Archive & ar, const unsigned int version) {
16 ar & a_;
17 ar & b_;
18 ar & cad_;
19 ar & simple_;
20 ar & obj_;
21 }
22
23 private:
24 double a_;
25 int b_;
26 std::string cad_;
27
22.1. Serializacin de objetos [651]

28 ObjetoSimple simple_;
29 ObjetoCompuesto* obj_;
30 };

Como se puede apreciar, la serializacin se lleva a cabo de la misma manera si se


utiliza Boost. De hecho la forma de utilizarlos es similar, excepto a la hora de crear
los objetos:

Listado 22.23: Uso de un objeto compuesto serializable


1 {
2 ofstream fout ("dataCompuesto", ios_base::trunc );
3 ObjetoCompuesto oComp (4.534, 90, "BOOST COMPO", NULL);
4 ObjetoCompuesto oComp2(43.234, 280, "OTRO BOOST COMPO", &oComp)
;
5 boost::archive::text_oarchive outA(fout);
6 outA << oComp;
7 outA << oComp2;
8 }
9
10 {
11 ObjetoCompuesto otherComp;
12 ObjetoCompuesto otherComp2;
13
14 ifstream fin("dataCompuesto", ios_base::binary );
15 boost::archive::text_iarchive inA(fin);
16
17 inA >> otherComp;
18 inA >> otherComp2;
19
20 otherComp.print();
21 cout << "\n\n\n";
22 otherComp2.print();
23 }

De hecho, dos de los pocos casos donde esta forma diere se muestran en el si-
guiente apartado.

Objetos derivados y con contenedores

En el cdigo siguiente se muestra una clase Base y una clase ObjetoDerivadoCont


que hereda de ella. Adems, incluye un contenedor vector que se serializar con la
misma.

C22
Listado 22.24: Declarando un objeto base serializable con Boost
1 class Base {
2 friend class boost::serialization::access;
3 public:
4 Base(const std::string& bName) :
5 baseName_(bName) {}
6
7 virtual void print() {
8 std::cout << "Base::print(): " << baseName_;
9 };
10
11 virtual ~Base() {}
12
13 template<class Archive>
14 void serialize(Archive & ar, const unsigned int version) {
15 ar & baseName_;
16 }
17
[652] CAPTULO 22. TCNICAS ESPECFICAS

18 protected:
19 std::string baseName_;
20 };

Listado 22.25: Declarando un objeto derivado y con contenedores serializable con Boost
1 class ObjetoDerivadoCont : public Base
2 {
3 friend class boost::serialization::access;
4 public:
5 ObjetoDerivadoCont(std::string s) :
6 Base(s) { }
7
8 ObjetoDerivadoCont() : Base("default") {}
9
10 virtual ~ObjetoDerivadoCont(){}
11
12 virtual void print();
13
14 void push_int(int i) {
15 v_.push_back(i);
16 };
17
18 template<class Archive>
19 void serialize(Archive & ar, const unsigned int version) {
20 ar & boost::serialization::base_object<Base>(*this);
21 ar & v_;
22 }
23
24 private:
25 std::vector<int> v_;
26 };

La nica cosa que hay que tener en cuenta a la hora de serializar este tipo de clases
la parte relativa a la clase base. Esto
es que hay que ser explcito a la hora de serializar
se lleva a cabo como se muestra en la lnea 20 del cdigo anterior.
Para que se puedan serializar contenedores, simplemente habr que incluir la ca-
becera de Boost correspondiente:

Listado 22.26: Cabecera de Boost para serializar vector


1 #include <boost/serialization/vector.hpp>

Si se quisiera serializar una list, se usara list.hpp.


A continuacin, se muestra un ejemplo de uso donde se ve cmo se rellenan los
vectors, para luego serilizar dos los objeto y proceder a recuperarlos en el segundo
bloque.

Listado 22.27: Uso de un objeto derivado y con contenedores


1 {
2 ofstream fout ("dataDerivadoCont", ios_base::trunc);
3 boost::archive::text_oarchive outA(fout);
4
5 ObjetoDerivadoCont oDeriv ("DERIVADO1");
6 oDeriv.push_int(38); oDeriv.push_int(485);
7 oDeriv.push_int(973); oDeriv.push_int(545);
8
9 ObjetoDerivadoCont oDeriv2("DERIVADO2");
10 oDeriv2.push_int(41356); oDeriv2.push_int(765);
11
22.2. C++ y scripting [653]

12 outA << oDeriv;


13 outA << oDeriv2;
14 }
15
16 {
17 ifstream fin("dataDerivadoCont", ios_base::binary );
18 boost::archive::text_iarchive inA(fin);
19
20 ObjetoDerivadoCont oD;
21 ObjetoDerivadoCont oD2;
22
23 inA >> oD;
24 inA >> oD2;
25
26 oD.print();
27 cout << "\n\n\n";
28 oD2.print();
29 cout << "\n\n\n";
30 }

Con todos los ejemplos anteriores se puede afrontar casi cualquier tipo de seriali-
zacin. Queda claro que el uso de Boost acelera el proceso, pero aun existen platafor-
mas donde Boost no est portada (aquellas con compiladores que no soportan todas
las caractersticas de C++, por ejemplo) y donde la STL aun lucha por parecerse al
estndar. Es en stas donde habr que realizar una serializacin ms artesana y usar
algn tipo de tcnica parecida a la vista en las primeras secciones.

22.2. C++ y scripting


A pesar de que el uso de un lenguaje de propsito general como C++ nos permite
abordar cualquier tipo de problema, existen lenguajes mas o menos apropiados para
tareas especcas. En el diseo de un lenguaje se tiene en mente aspectos como la
eciencia, portabilidad, simpleza, etc. y difcilmente se pueden alcanzar la excelencia
en todas las facetas.
No obstante, sera deseable que pudiramos realizar cada tarea en aquel lenguaje
mas apropiado para la tarea a realizar. Por ejemplo, mientras que C/C++ se caracteri-
zan, entre otras cosas, por su eciencia, lenguajes como Python nos proporcionan un
entorno de programacin simple y muy productivo de cara a prototipado rpido as
como una gran portabilidad.
Existen muchos proyectos que utilizan varios lenguajes de programacin, utili-
zando el mas apropiado para cada tarea. En esta seccin vamos a ver un ejemplo de

C22
esta interaccin entre diversos lenguajes de programacin. En concreto vamos a coger
C++, como ya hemos comentado, un lenguaje orientado a objetos muy eciente en
su ejecucin y Python, un lenguaje interpretado (como java, php, Lua etc.) muy apro-
piado por su simpleza y portabilidad que nos permite desarrollar prototipos de forma
rpida y sencilla.

22.2.1. Consideraciones de diseo


En el caso de juegos, el planteamiento inicial es qu partes implementar en C++ y
qu partes dejar al lenguaje de scripting.
[654] CAPTULO 22. TCNICAS ESPECFICAS

En el caso del desarrollo de juegos cuyo lenguaje principal sea de scripting (por
ejemplo, Python), una aproximacin genrica sera, desarrollar el juego por completo,
y despus, mediante tcnicas de proling se identican aquellas partes crticas pa-
ra mejorar las prestaciones, que son las que se implementan en C/C++. Obviamente
aquellas partes que, a priori, ya sabemos que sus prestaciones son crticas podemos
anticiparnos y escribirlas directamente en C/C++.
En el caso de que la aplicacin se implemente en C/C++, utilizamos un lenguaje Lua vs Python
de scripting para el uso de alguna librera concreta o para poder modicar/adapta-
r/extender/corregir el comportamiento sin tener que recompilar. En general, cuando Mientras que Lua est pensado para
extender aplicaciones y como len-
hablamos de C++ y scripting hablamos de utilizar las caractersticas de un lenguaje guaje de conguracin, Python es
de prototipado rpido desde C++, lo cual incluye, a grandes rasgos: mas completo y puede ser utilizado
para funciones mas complejas.
Crear y borrar objetos en el lenguaje de scripting e interaccionar con ellos invo-
cando mtodos .
pasar datos y obtener resultados en invocaciones a funciones
Gestionar posibles errores que pudieran suceder en el proceso de interaccin,
incluyendo excepciones.

Otro ejemplo de las posibilidades de los lenguajes de scripting son utilizar lengua-
jes especcos ampliamente usados en otros entornos como la inteligencia articial,
para implementar las partes relacionadas del juego. Ejemplos de este tipo de lenguajes
seran LISP y Prolog ampliamente usados en inteligencia articial, y por lo tanto, muy
apropiados para modelar este tipo de problemas.
En la actualidad, las decisiones de diseo en cuanto a qu lenguaje de scripting
usar viene determinado por las caractersticas de dichos lenguajes. Sin tener en cuen-
ta lenguajes muy orientados a problemas concretos como los mencionados LISP y
Prolog, y considerando slo aquellos lenguajes de scripting de propsito general, las
opciones actuales pasan por Lua y Python principalmente.
Atendiendo a sus caractersticas, Python:

Tiene una gran librera y, generalmente, bien documentada.


Facilita la gestin de cadenas y tiene operadores binarios.
A partir de la versin 2.4, Python tiene los denominados ctypes que permiten
acceder a tipos de libreras compartidas sin tener que hacer un wrapper C.
Tiene buenas prestaciones en computacin numrica (lo cual es muy deseable
en simuladores de eventos fsicos)

En contraste Lua es un lenguaje mas simple, originalmente pensado para labores


de conguracin y que ha sido orientado especcamente a la extensin de aplicacio-
nes, algunas de sus caractersticas son:

En general, usa menos memoria y el intrprete es mas rpido que el de Python.


Tiene una sintaxis simple y fcil de aprender si bien es cierto que no tiene la
documentacin, ejemplos y tutoriales que Python.

Es cierto que tanto Lua como Python pueden ser utilizados para extender apli-
caciones desarrolladas en C/C++, la decisin de qu lenguaje usar depende de qu
caractersticas queremos implementar en el lenguaje de scripting. Al ser Python un
lenguaje mas genrico, y por tanto mas verstil, que Lua ser el que estudiaremos mas
en profundidad.
22.2. C++ y scripting [655]

22.2.2. Invocando Python desde C++ de forma nativa

Uso de lenguajes compilados En nomenclatura Python, hablamos de extender Python cuando usamos funciones
y objetos escritos en un lenguaje (por ejemplo C++) desde programas en Python. Por
el contrario, se habla de Python embebido cuando es Python el que se invoca desde
Aquellas partes de clculo intensivo
deben ir implementadas en los len- una aplicacin desarrollada en otro lenguaje. Desde la nomenclatura C/C++ se habla
guajes ecientes (compilados) de scripting cuando accedemos a un lenguaje de script desde C++.
El interprete Python ya incluye extensiones para empotrar Python en C/C++. Es
requisito imprescindible tener instalado en la mquina a ejecutar los ejemplos de esta
seccin, el intrprete de Python (usaremos la versin 2.7) aunque dichas extensiones
estn desde la versin 2.2.
En el primer ejemplo, vamos a ver la versin Python del intrprete y que nos sirve
para ver cmo ejecutar una cadena en dicho intrprete desde un programa en C++.

Listado 22.28: Imprimiendo la versin del intrprete Python desde C++


1 #include <python2.7/Python.h>
2
3 int main(int argc, char *argv[])
4 {
5 Py_Initialize();
6 PyRun_SimpleString("import sys; print %d. %d % sys.version_info
[:2]\n");
7 Py_Finalize();
8 return 0;
9 }

La funcin Py_Initialize() inicializa el intrprete creando la lista de mdu-


los cargados (sys.modules), crea los mdulos bsicos (__main__, __builtin__ y sys) y
crea la lista para la bsqueda de mdulos sys.path. En denitiva lo prepara para recibir
rdenes.PyRun_SimpleString() ejecuta un comando en el intrprete, podemos
ver que en este caso, importamos el mdulo sys y a continuacin imprimimos la
versin del intrprete que estamos ejecutando. Por ltimo, nalizamos la instancia
del intrprete liberando la memoria utilizada y destruyendo los objetos creados en la
sesin.
Todas estas funciones se denen en el archivo Python.h que proporciona la
instalacin de Python y que proporciona un API para acceder al entorno de ejecucin
de este lenguaje. El propio intrprete de Python utiliza esta librera.
Estas extensiones permiten invocar todo tipo de sentencias e interaccionar con
el intrprete de Python, eso si, de forma no muy orientada a objetos. Utilizando el

C22
tipo PyObject (concretamente punteros a este tipo) podemos obtener referencias a
cualquier mdulo e invocar funciones en ellas. En la tabla 22.1 podemos ver, de forma
muy resumida, algunas funciones que nos pueden ser muy tiles. Por supuesto no estn
todas pero nos pueden dar una referencia para los pasos principales que necesitaramos
de cara a la interaccin C++ y Python.
La gestin de errores (del mdulo sys) en la actualidad est delegada en la funcin
exc_info()() que devuelve una terna que representan el tipo de excepcin que se
ha producido, su valor y la traza (lo que hasta la versin 1.5 representaban las variables
sys.exc_type, sys.exc_value y sys.exc_traceback).
Con el ejemplo visto en esta subseccin no existe un intercambio entre nuestro
programa C++ y el entorno Python. Por supuesto, el soporte nativo de Python nos
permite realizar cualquier forma de interaccin que necesitemos. No obstante, pode-
mos beneciarnos de libreras que nos hacen esta interaccin mas natural y orientada
a objetos. Vamos a estudiar la interaccin entre ambos entornos mediante la librera
boost.
[656] CAPTULO 22. TCNICAS ESPECFICAS

Funcin Cometido
Py_Initialize() Inicializa el intrprete
PyString_FromString(cadena) Retorna un puntero a PyObject con una cadena
(E.j. nombre del mdulo a cargar).
PyImport_Import(PyObject* name) Carga un mdulo, retorna un puntero a PyOb-
ject.
PyModule_GetDict(PyObject* modu- Obtiene el diccionario con atributos y mtodos
lo) del mdulo. Retorna un puntero a PyObject.
PyDict_GetItemString(PyObject *Dic- Obtiene una referencia a una funcin. Retorna
cionario, "funcin") un puntero a PyObject
PyObject_CallObject(PyObject *fun- Llama a la funcin con los argumentos propor-
cin, argumentos) cionados.
PyCallable_Check(PyObject *funcion) Comprueba que es un objeto invocable.
PyRun_File Interpreta un archivo
PyTuple_New(items) Crea una tupla
PyTuple_SetItem(tupla, posicin, item) Almacena un Item en una tupla
PyErr_Print() Imprime error.
PyList_Check(PyObject*) Comprueba si PyObject es una lista

Cuadro 22.1: Funciones tiles de invocacin de Python desde C++

22.2.3. Librera boost


La librera boost [1] nos ayuda en la interaccin de C++ y Python. Es necesario
resaltar que est mas evolucionada en el uso de C++ desde Python que al revs. Esto
es debido a que generalmente, es un caso de uso mas frecuente el usar C++ desde
Python por dos motivos principalmente:

Aumentar la eciencia del programa implementando partes crticas en C++.


Usar alguna librera C++ para la cual no existen bindings en Python.

No obstante, como ya hemos indicado anteriormente, el uso de Python desde C++


tambin cuenta con ventajas y para introducir la librera boost, vamos a continuar con
nuestro ejemplo de obtener la versin del interprete desde nuestro programa en C++.

Usando Python desde nuestro programa en C++

Nuestra primera modicacin va a ser imprimir la versin del intrprete desde


C++, por lo que debemos realizar un intercambio de datos desde el intrprete de Pyt-
hon al cdigo en C++.

Listado 22.29: Obteniendo informacin del intrprete Python desde C++


1 #include <boost/python.hpp>
2 #include <boost/python/import.hpp>
3 #include <iostream>
4
5 using namespace boost::python;
6 using namespace std;
7
8 int main(int argc, char *argv[])
9 {
22.2. C++ y scripting [657]

10
11 Py_Initialize();
12 PyRun_SimpleString("import sys; major, minor = sys.version_info
[:2]");
13 object mainobj = import("__main__");
14 object dictionary = mainobj.attr("__dict__");
15 object major = dictionary["major"];
16 int major_version = extract<int>(major);
17 object minor = dictionary["minor"];
18 int minor_version = extract<int>(minor);
19 cout<<major_version<<"."<<minor_version<<endl;
20 Py_Finalize();
21 return 0;
22 }

Debemos observar varios puntos en este nuevo listado:

Seguimos usando Py_Initialize y Py_Finalize. Estas funciones se utilizan siem-


pre y son obligatorias, en principio, no tienen equivalente en boost.
Se usa Run_SimpleString para seguir con el ejemplo anterior, luego veremos
como substituir esta sentencia por usos de la librera boost.
Para acceder al interprete de Python, necesitamos acceder al mdulo principal
y a su diccionario (donde se denen todos los atributos y funciones de dicho
mdulo). Este paso se realiza en las lineas 13 y 14.
Una vez obtenemos el diccionario, podemos acceder a sus variables obtenin-
dolas como referencias a object(), linea 15.

La plantilla extract() nos permite extraer de una instancia de object, en prin-


cipio, cualquier tipo de C++. En nuestro ejemplo extraemos un entero correspondiente
a las versiones del intrprete de Python (versin mayor y menor). De forma genrica,
si no existe una conversin disponible para el tipo que le pasamos a extract(), una
excepcin Python (TypeError) es lanzada.
Como vemos en este ejemplo, la exibilidad de Python puede simplicarnos la
interaccin con la parte de C++. La sentencia (linea 12) sys.version_info nos
devuelve un tupla en Python, no obstante, hemos guardado esa tupla como dos ente-
ros (major y minor) al cual accedemos de forma individual (lneas 16 y 19 mediante
extract). Como ya hemos comentado, esta plantilla es clave de cara a obtener referen-
cias a los tipos bsicos desde C++ y puede ser empleado para aquellos tipos bsicos
denidos como pueden ser std::string, double, oat, int, etc. Para estructuras mas com-

C22
plejas (por ejemplo, tuplas), esta extraccin de elementos se puede realizar mediante
el anidamiento de llamadas a la plantilla extract.
Modicando brevemente el ejemplo anterior podemos mostrar el caso ms bsico
de una tupla. tal y como podemos ver en este listado:

Listado 22.30: Extraccin de tipos compleja


1 PyRun_SimpleString("import sys; result = sys.version_info[:2]");
2 object mainobj = import("__main__");
3 object dictionary = mainobj.attr("__dict__");
4 object result = dictionary["result"];
5 tuple tup = extract<tuple>(result);
6 if (!extract<int>(tup[0]).check() || !extract<int>(tup[1]).check
())
7 return 0;
8 int major =extract<int>(tup[0]);
9 int minor =extract<int>(tup[1]);
10 cout<<major<<"."<<minor<<endl;
[658] CAPTULO 22. TCNICAS ESPECFICAS

11 Py_Finalize();
12 return 0;
13 }

ahora vemos como guardamos en result la tupla que, posteriormente, es guardada


en la variable tup mediante el extract() correspondiente (lnea 5).
A partir de este punto podemos obtener los elementos de la tupla (obviamente co-
nociendo de antemano los campos de dicha tupla y su disposicin en la misma) como
podemos ver en las lneas 8 y 9. Obviamente, es recomendable realizar la comproba-
cin de que la conversin de un entorno a otro se ha realizado correctamente mediante
el uso de la funcin check() (linea 6).
Para el siguiente ejemplo vamos a dar un paso mas all en nuestra forma de pasar
datos de un entorno a otro. Particularizando en la programacin de videojuegos, vamos
a suponer que tenemos una clase hero la cual, va a representar un hroe. Cada instancia
coge su nombre del hroe que representa y a continuacin se le asigna un arma.

Listado 22.31: Clase hero


1 class hero{
2 string _name;
3 string _weapon;
4 int amunnition;
5 public:
6 hero(){}
7 hero(string name){
8 _name=name;
9 }
10
11 void configure()
12 {
13 cout<<"Getting configuration:"<<_name<<": "<<_weapon<<endl;
14 }
15 void weapon(string weapon){
16 _weapon=weapon;
17 }
18 };

Adems se tiene un mtodo configure(), que nos permite obtener la congu-


racin del hroe en concreto, en este caso, simplemente la imprime. Bien asumimos
como decisin de diseo, que, salvo el nombre, el arma asignada a cada hroe ser
variable y podremos ir obteniendo diversas armas conforme avancemos en el juego.
Esta ltima parte la decidimos implementar en Python. Por lo tanto, habr un mtodo
en Python, al cual le pasaremos un objeto de la clase hero y ese mtodo lo congurar
de forma apropiada (en nuestro caso slo con el tipo de arma). En el siguiente listado
podemos ver esta funcin. En este ejemplo simplemente le pasa el arma (Kalasnikov)
invocando el mtodo correspondiente.

Listado 22.32: Congurar una instancia de la clase hero desde Python


1 def ConfHero(hero):
2 hero.weapon("Kalasnikov")
3 hero.configure()

Para conseguir este ejemplo, necesitamos exponer la clase hero al intrprete de


Python.
22.2. C++ y scripting [659]

En boost, se usa la macro BOOST_PYTHON_MODULE que bsicamente crea un


mdulo (ConfActors), que podremos usar en Python, deniendo las clases y mtodos
que le proporcionemos (en nuestro caso el constructor que acepta una cadena y los
mtodos configure() y weapon())

Listado 22.33: Exponer clases C++ a entornos Python


1 // Exposing class heroe to python
2 BOOST_PYTHON_MODULE( ConfActors )
3 {
4 class_<hero>("hero")
5 .def(init<std::string>() )
6 .def("configure", &hero::configure)
7 .def("weapon", &hero::weapon)
8 ;
9 }

Con esta infraestructura vamos a invocar la funcin en Python ConfHero() para


que le asigne el arma y, a continuacin vamos a comprobar que esa asignacin se
realiza de forma satisfactoria.

Listado 22.34: Pasando objetos C++ como argumentos de funciones en Python


1 int main(int argc, char *argv[])
2 {
3 Py_Initialize();
4 initConfActors(); //initialize the module
5 object mainobj = import("__main__");
6 object dictionary(mainobj.attr("__dict__"));
7 object result = exec_file("configureActors.py", dictionary,
dictionary);
8 object ConfHero_function = dictionary["ConfHero"];
9 if(!ConfHero_function.is_none())
10 {
11 boost::shared_ptr<hero> Carpanta(new hero("Carpanta"));
12 ConfHero_function(ptr(Carpanta.get()));
13 hero *obj = ptr(Carpanta.get());
14 obj->configure();
15 }
16 Py_Finalize();
17 return 0;
18 }

En el listado anterior, en la lnea 7 cargamos el contenido del archivo Python en


el diccionario, con esta sentencia ponemos en el diccionario toda la informacin rela-

C22
tiva a atributos y a funciones denidas en dicho archivo. A continuacin ya podemos
obtener un objeto que representa a la funcin Python que vamos a invocar (lnea 8).
Si este objeto es vlido (lnea 9), obtenemos un puntero compartido al objeto que
vamos a compartir entre el intrprete Python y el espacio C++. En este caso, creamos
un objeto de la clase hero (lnea 11).
Ya estamos listo para invocar la funcin proporcionndole la instancia que acaba-
mos de crear. Para ello, utilizamos la instancia del puntero compartido y obtenemos
con get() la instancia en C++, con el cual podemos llamar a la funcin (lnea 13) y
por supuesto comprobar que, efectivamente, nuestro hroe se ha congurado correc-
tamente (lnea 14).
[660] CAPTULO 22. TCNICAS ESPECFICAS

Invocando C++ desde el intrprete Python

Veamos ahora el caso contrario, es decir, vamos a tener una clase en C++ y vamos
a acceder a ella como si de un mdulo en Python se tratara. De hecho el trabajo duro
ya lo hemos realizado, en el ejemplo anterior, ya usbamos un objeto denido en C++
desde el interprete en Python.
Aprovechemos ese trabajo, si tenemos en un archivo el cdigo relativo a la clase
hero (listado 22.31) y la exposicin realizada de la misma (listado 22.33) lo que nos
falta es construir un mdulo dinmico que el intrprete Python pueda cargar. En este
punto nos puede ayudar el sistema de construccin del propio interprete. Efectivamen-
te podemos realizar un archivo setup.py tal y como aparece en el listado 22.35

Listado 22.35: Conguracin para generar el paquete Python a partir de los fuentes C++
1
2 from distutils.core import setup, Extension
3
4 module1 = Extension(ConfActors, sources = [hero.cc] , libraries
= [boost_python-py27])
5
6 setup (name = PackageName,
7 version = 1.0,
8 description = A C++ Package for python,
9 ext_modules = [module1])

De esta forma, podemos decirle a las herramientas de construccin y distribucin


de paquetes Python toda la informacin necesaria para que haga nuestro nuevo paquete
a partir de nuestros fuentes en C++. En l, se le indica los fuentes.
Para compilar y generar la librera que, con posterioridad, nos permitir importarla
desde el intrprete de comandos, debemos invocar el archivo setup.py con el intrprete
indicndole que construya el paquete:

python setup.py build

Esto nos generar la librera especca para la mquina donde estamos y lo alo-
jar en el directorio build que crear en el mismo directorio donde est el setup.py
(build/lib.linux-i686-2.7/ en nuestro caso) y con el nombre del mdulo (ConfAc-
tors.so) que le hemos indicado. A partir de este punto, previa importacin del m-
dulo ConfActors, podemos acceder a todas sus clases y mtodos directamente desde
el interprete de python como si fuera un mdulo mas escrito de forma nativa en este
lenguaje.

22.2.4. Herramienta SWIG


No se puede terminar esta seccin sin una mencin explcita a la herramienta
SWIG [3], una herramienta de desarrollo que permite conectar programas escritos en
C/C++ con una amplia variedad de lenguajes de programacin de scripting incluidos
Python, PHP, Lua, C#, Java, R, etc.
Para C++ nos automatiza la construccin de wrappers para nuestro cdigo me-
diante una denicin de las partes a utilizar en el lenguaje destino.
A modo de ejemplo bsico, vamos a usar una nueva clase en C++ desde el inter-
prete Python, en este caso una clase player al cual le vamos a proporcionar parmetros
de conguracin.
22.2. C++ y scripting [661]

Listado 22.36: Denicin de la clase Player


1
2 #include <string>
3 #include <iostream>
4
5 class Player
6 {
7 std::string _name;
8 std::string _position;
9 public:
10 Player(std::string name);
11 void position(std::string pos);
12 void printConf();
13 };

Y su implementacin:

Listado 22.37: Implementacin de la clase en C++


1
2 using namespace std;
3 Player::Player(string name){
4 _name=name;
5 }
6
7 void Player::position(string pos){
8 _position=pos;
9 }
10
11 void Player::printConf(){
12 cout<<_name<<" "<<_position<<endl;
13 }

Sin modicacin de estos archivos construimos un archivo de conguracin para


swig:

Listado 22.38: Archivo de conguracin de SWIG


1 #define SWIG_FILE_WITH_INIT
2 #include "player.h"
3 %}
4
5 %include "std_string.i"
6 %include "player.h"

C22
Con este archivo de conguracin generamos player_wrap.cc y player.py:

swig -shadow -c++ -python player.i

El wrapper se debe compilar y enlazar con la implementacin de la clase en una


librera dinmica que se puede importar directamente desde el intrprete.

Listado 22.39: Testeando nuestro nuevo mdulo Python


1 import player
2 p = player.Player(Carpanta)
3 dir(player)
4 p.printConf()
5 p.position("Goalkeeper")
6 p.printConf()
[662] CAPTULO 22. TCNICAS ESPECFICAS

22.2.5. Conclusiones
Realizar un tutorial completo y guiado de la interaccin entre C++ y los lenguajes
de scripting queda fuera del mbito de este libro. Hemos proporcionado, no obstante,
algunos ejemplos sencillos que permiten al lector hacerse una idea de los pasos bsicos
para una interaccin bsica entre C++ y un lenguaje de scripting de propsito general
como es Python.
Se inici esta seccin proporcionando los motivos por los que la integracin de
varios lenguajes de programacin en una misma aplicacin es una tcnica muy til
y ampliamente utilizada en el mundo de los videojuegos. El objetivo nal es utilizar
el lenguaje mas apropiado para la tarea que estamos desarrollando, lo cual da como
resultado una mayor productividad y juegos mas exibles y extensibles.
A continuacin hemos proporcionado algunas directivas bsicas de cmo deci-
dir entre lenguajes de scripting y compilados y qu partes son apropiadas para unos
lenguajes u otros.
La mayor parte de esta seccin se ha dedicado a mostrar cmo podemos integrar
C++ y Python de tres maneras posibles:

El soporte nativo del intrprete de Python es lo mas bsico y de ms bajo ni-


vel que hay para integrar Python en C++ o viceversa. La documentacin del
intrprete puede ayudar al lector a profundizar en este API.
La librera boost nos aporta una visin orientada a objetos y de mas alto nivel
para la interaccin entre estos lenguajes. Esta librera, o mejor dicho conjunto
de libreras, de propsito general nos ayuda en este aspecto particular y nos pro-
porciona otras potentes herramientas de programacin en otros mbitos como
hemos visto a lo largo de este curso.
Por ltimo, hemos introducido la herramienta SWIG que nos puede simplicar
de manera extraordinaria la generacin de wrappers para nuestro cdigo C++
de una forma automtica y sin tener que introducir cdigo adicional en nuestro
cdigo para interaccionar con Python.

Herramientas y libreras similares a estas estn disponibles para otros lenguajes de


programacin como Lua, prolog, etc.
Captulo

Optimizacin
23
Francisco Moya Fernndez

A
ntes de entrar en materia vamos a matizar algunos conceptos. Optimizar ha-
ce referencia a obtener el mejor resultado posible. Pero la bondad o maldad
del resultado depende fuertemente de los criterios que se pretenden evaluar.
Por ejemplo, si queremos hacer un programa lo ms pequeo posible el resultado se-
r bastante diferente a si lo que queremos es el programa ms rpido posible. Por
tanto cuando hablamos de optimizacin debemos acompaar la frase con el objetivo,
con la magnitud que se pretende mejorar hasta el limite de lo posible. As se habla
frecuentemente de optimizar en velocidad u optimizar en tamao.
La optimizacin normalmente es un proceso iterativo e incremental. Cada etapa
produce un resultado mejor (o por lo menos ms fcil de mejorar). A cada una de
estas etapas del proceso se les suele denominar tambin optimizaciones, aunque sera
ms correcto hablar de etapas del proceso de optimizacin. Pero adems el objetivo
de optimizacin se enmarca en un contexto:

Las mismas optimizaciones que en una arquitectura concreta generan mejores


resultados pueden afectar negativamente al resultado en otras arquitecturas. Por
ejemplo, la asignacin de variables (o parmetros) a registros en un PowerPC
aprovecha el hecho de disponer de un buen nmero de registros de propsito
general. Si se usara el mismo algoritmo para asignar registros en un x86, en el
que la mayora de los registros son de propsito especco, obligara a introducir
multitud de instrucciones adicionales para almacenar temporalmente en la pila.

663
[664] CAPTULO 23. OPTIMIZACIN

Las mismas optimizaciones que permiten mejorar el rendimiento en un procesa-


dor pueden perjudicar al rendimiento cuando usamos multiprocesadores o pro-
cesadores multi-core. Por ejemplo, el paso por referencia, que permite ahorrar
copias innecesarias, tambin exige utilizar primitivas de sincronizacin cuando
los datos se acceden desde diferentes procesos. Estas primitivas afectan al para-
lelismo global y los bloqueos pueden superar con mucho el tiempo de copia del
objeto.
Incluso dentro de una misma arquitectura hay optimizaciones que penalizan a
determinados procesadores de la misma familia. Por ejemplo en la familia Intel
Pentium la forma ms eciente para transferir bloques de memoria era mediante
el uso de instrucciones del coprocesador matemtico debido al mayor tamao
de dichos registros frente a los de propsito general [71]. Eso ya no aplica para
ninguna de las variantes modernas de la familia x86.

En cualquier caso es muy importante tener presente el objetivo global desde el


principio, porque las oportunidades de mejora ms destacables no estn en mano del
compilador, sino del programador. Los algoritmos y las estructuras de datos emplea-
dos son los que verdaderamente marcan la diferencia, varios rdenes de magnitud
mejor que otras alternativas.
El programador de videojuegos siempre tiene que mantener un equilibrio entre dos
frases clebres de Donald Knuth1 :

1. In established engineering disciplines a 12 % improvement, easily obtained, is


never considered marginal and I believe the same viewpoint should prevail in
software engineering. En las disciplinas de ingeniera tradicionales una mejora
de un 12 %, fcil de obtener, nunca se considera marginal y pienso que el mismo
punto de vista debe prevalecer en la ingeniera de software.
2. Premature optimization is the root of all evil. La optimizacin prematura es la
raz de toda maldad.

Es decir, cuando se est desarrollando un videojuego la optimizacin no es una


prioridad. No debemos ocuparnos de mejorar cuando todava no sabemos qu debe-
mos mejorar. Est ampliamente documentado que el ser humano es extremadamente
malo prediciendo cuellos de botella.
Pero eso no puede justicar la programacin descuidada. No es justicable incluir
fragmentos de cdigo o algoritmos claramente inecientes cuando se puede hacer bien
desde el principio a un mnimo coste, o incluso a un coste menor.

23.1. Perlado de programas


Una vez que se dispone de un prototipo o un fragmento funcional del programa
podemos determinar los cuellos de botella del programa para intentar mejorarlo. Pa-
ra ello se suele emplear una tcnica conocida como perlado de software (software
proling). El perlado permite contestar preguntas como:

Dnde se gasta la mayor parte del tiempo de ejecucin? De cara a concentrar


los esfuerzos de optimizacin donde ms se notar.
Cul es el camino crtico? Para incrementar las prestaciones globales. Por
ejemplo, el nmero de frames por segundo.
1 Ambas frases aparecen prcticamente juntas en la pgina 268 de [57].
23.1. Perlado de programas [665]

Cul es la tasa de fallos de la memoria cach? Con el objetivo de mejorar la


localidad de la memoria.

Normalmente recabar este tipo de informacin implica instrumentar el cdigo aa-


diendo algunas instrucciones que permiten acumularla en un archivo (o varios) para
cada ejecucin del programa. La informacin de perlado es posteriormente analizada
con un programa, el perlador o proler.
Cada proler implementa el registro de la informacin de forma diferente. B-
sicamente se utilizan cuatro tcnicas: trazas, muestreo estadstico, puntos de ruptura
hardware y contadores hardware. Veamos cada una de ellas en ms detalle:

Cuando el evento de inters corresponde a una operacin que requiere un tiempo


considerable es posible trazar cada ejecucin de la operacin sin un impacto
signicativo en las prestaciones del programa. sta es la tcnica empleada por
el perlador de Linux perf (descrito ms adelante) para trazar las operaciones
sobre el sistema de archivos, las operaciones de writeback, las operaciones de
gestin de energa, la recepcin y el manejo de interrupciones, las operaciones
de planicacin de procesos, etc. Tambin es la tcnica empleada por utilidades
como strace, que traza las llamadas al sistema de un proceso.
Sin embargo, en un programa de tamao considerable no es posible ejecutar
cdigo adicional en todos los eventos de inters (por ejemplo, en todas las lla-
madas a funcin). En ese caso se realiza un anlisis estadstico. Peridicamente
se realiza un muestreo del contador de programa y se analiza en qu funcin se
encuentra. Es ms, en lugar de solo observar el valor del contador de programa
puede analizar el contenido de la pila para determinar todos marcos de pila ac-
tivos, es decir, la call trace. Con esto es posible determinar el grafo de llamadas
y el tiempo estimado destinado a cada funcin.
En lugar de instrumentar el cdigo o muestrear de forma estadstica, es posi-
ble utilizar los mecanismos previstos en los procesadores actuales para facilitar
el perlado. Por ejemplo, una posibilidad es el empleo de puntos de ruptura
hardware para detectar cundo se escribe una posicin de memoria, cundo se
escribe, o cundo se ejecuta la instruccin que contiene. Esta tcnica se pue-
de emplear para trazar solo un conjunto limitado de funciones, o para estudiar
el patrn de accesos a un objeto. Tambin se emplea en la utilidad ltrace, que
traza las llamadas a procedimientos de bibliotecas dinmicas desde un proceso
determinado.
Por ltimo los procesadores modernos proporcionan otra funcionalidad espe-
cialmente interesante para el perlado. Disponen de una Performance Monito-
ring Unit que controla un conjunto de registros especiales denominados perfor-

C23
mance counters. Estos registros son capaces de contar determinados eventos,
tales como ciclos de la CPU, ciclos de bus, instrucciones, referencias a la cache,
fallos de la memoria cach, saltos o fallos en la prediccin de saltos. Estos regis-
tros pueden ser utilizados en prolers tales como perf para realizar mediciones
muy precisas.

Es importante conocer cundo se emplea cada una de estas tcnicas para poder
interpretar con precisin los datos del perlado. As, por ejemplo, las tcnicas basadas
en muestreo de la traza de llamadas debe entenderse en un contexto estadstico. Valo-
res bajos en los contadores de llamadas no tienen signicado absoluto, sino en relacin
a otros contadores. Es muy posible que tengamos que ejecutar el mismo fragmento de
cdigo mltiples veces para eliminar cualquier sesgo estadstico.
[666] CAPTULO 23. OPTIMIZACIN

Para cualquier anlisis que requiera examinar la pila (perlado de la traza de


llamadas, o del grafo de llamadas, o simplemente la depuracin interactiva),
se asume el convenio de que un registro contiene la direccin del marco de
pila actual (frame pointer) y al principio del marco de pila actual se almacena
una copia del frame pointer anterior a la llamada actual.
Sin embargo los compiladores actuales pueden generar cdigo perfectamente
funcional sin necesidad de frame pointer. Es importante compilar los progra-
mas evitando la opcin -fomit-frame-pointer o incluso explcitamen-
te indicando -fno-omit-frame-pointer durante el desarrollo para que
estos anlisis funcionen correctamente.

23.1.1. El perlador de Linux perf


El subsistema Linux Performance Counters proporciona una abstraccin de los
performance counters disponibles en los procesadores modernos. Independientemente
del hardware subyacente Linux ofrece una serie de contadores de 64 bits virtualizados
por CPU o por tarea y combinado con un sistema de traza de eventos de otro tipo
(eventos software, trazas). Es ms sencillo de lo que parece, veamos algn ejemplo.
En las distribuciones ms actuales, la herramienta perf est incluida en el paque-
te linux-base. Pero se trata de un simple envoltorio para ejecutar la correspon-
diente al kernel que se est ejecutando. El ejecutable real se encuentra en el paquete
linux-tools-X.Y donde X.Y hace referencia a la versin del kernel empleada.
Por ejemplo, linux-tools-3.2 o linux-tools-3.8.
Por tanto para instalar la herramienta deberemos ejecutar:

$ sudo apt-get install linux-base linux-tools-3.2

A continuacin conviene congurar el kernel para que permita a los usuarios nor-
males recabar estadsticas de todo tipo. Esto no debe hacerse con carcter general,
sino solo en las computadoras empleadas en el desarrollo, puesto que tambin facilita
la obtencin de informacin para realizar un ataque.

$ sudo sh -c "echo -1 > /proc/sys/kernel/perf_event_paranoid"

Ahora ya como usuarios normales podemos perlar cualquier ejecutable, e incluso


procesos en ejecucin. Tal vez la primera tarea que se debe realizar para perlar con
perf es obtener la lista de eventos que puede contabilizar. Esta lista es dependiente
de la arquitectura del procesador y de las opciones de compilacin del kernel.

$ perf list

List of pre-defined events (to be used in -e):


cpu-cycles OR cycles [Hardware event]
stalled-cycles-frontend OR idle-cycles-frontend [Hardware event]
stalled-cycles-backend OR idle-cycles-backend [Hardware event]
instructions [Hardware event]
cache-references [Hardware event]
cache-misses [Hardware event]
branch-instructions OR branches [Hardware event]
branch-misses [Hardware event]
bus-cycles [Hardware event]

cpu-clock [Software event]


task-clock [Software event]
23.1. Perlado de programas [667]

page-faults OR faults [Software event]


minor-faults [Software event]
major-faults [Software event]
context-switches OR cs [Software event]
cpu-migrations OR migrations [Software event]
alignment-faults [Software event]
emulation-faults [Software event]

L1-dcache-loads [Hardware cache event]


L1-dcache-load-misses [Hardware cache event]
L1-dcache-stores [Hardware cache event]
L1-dcache-store-misses [Hardware cache event]
L1-dcache-prefetches [Hardware cache event]
L1-dcache-prefetch-misses [Hardware cache event]
L1-icache-loads [Hardware cache event]
L1-icache-load-misses [Hardware cache event]
L1-icache-prefetches [Hardware cache event]
L1-icache-prefetch-misses [Hardware cache event]
LLC-loads [Hardware cache event]
LLC-load-misses [Hardware cache event]
LLC-stores [Hardware cache event]
LLC-store-misses [Hardware cache event]
LLC-prefetches [Hardware cache event]
LLC-prefetch-misses [Hardware cache event]
dTLB-loads [Hardware cache event]
dTLB-load-misses [Hardware cache event]
dTLB-stores [Hardware cache event]
dTLB-store-misses [Hardware cache event]
dTLB-prefetches [Hardware cache event]
dTLB-prefetch-misses [Hardware cache event]
iTLB-loads [Hardware cache event]
iTLB-load-misses [Hardware cache event]
branch-loads [Hardware cache event]
branch-load-misses [Hardware cache event]
node-loads [Hardware cache event]
node-load-misses [Hardware cache event]
node-stores [Hardware cache event]
node-store-misses [Hardware cache event]
node-prefetches [Hardware cache event]
node-prefetch-misses [Hardware cache event]

rNNN (...) [Raw hardware event descriptor]

mem:<addr>[:access] [Hardware breakpoint]

i915:i915_gem_object_create [Tracepoint event]


i915:i915_gem_object_bind [Tracepoint event]
i915:i915_gem_object_unbind [Tracepoint event]
...
sched:sched_wakeup [Tracepoint event]
sched:sched_wakeup_new [Tracepoint event]
sched:sched_switch [Tracepoint event]
...

C23
En la lista de eventos podemos apreciar seis tipos diferentes.

Software event. Son simples contadores del kernel. Entre otros permite contar
cambios de contexto o fallos de pgina.
Hardware event. Este evento se reere a los contadores incluidos en las PMU (Per-
formance Monitoring Units) de los procesadores modernos. Permite contar ci-
clos, intrucciones ejecutadas, fallos de cach. Algunos de estos contadores se
ofrecen de forma unicada como contadores de 64 bits, de tal forma que oculta
los detalles de la PMU subyacente. Pero en general su nmero y tipo depender
del modelo de rocesador donde se ejecuta.
[668] CAPTULO 23. OPTIMIZACIN

Hardware cache event. Dentro del subsistema de memoria las PMU modernas2
permiten extraer estadsticas detalladas de las memorias cach de primer nivel
de ltimo nivel o del TLB (Translation Lookaside Buffer). Nuevamente se trata
de contadores que dependen fuertemente del modelo de procesador sobre el que
se ejecuta.
Hardware breakpoint. Los puntos de ruptura hardware permiten detener la eje-
cucin del programa cuando el procesador intenta leer, escribir o ejecutar el
contenido de una determinada posicin de memoria. Esto nos permite monito-
rizar detalladamente objetos de inters, o trazar la ejecucin de instrucciones
concretas.
Tracepoint event. En este caso se trata de trazas registradas con la infraestructura
ftrace de Linux. Se trata de una infraestructura extremadamente exible para
trazar todo tipo de eventos en el kernel o en cualquier mdulo del kernel. Esto
incluye eventos de la GPU, de los sistemas de archivos o del propio scheduler.
Raw hardware event. En el caso de que perf no incluya todava un nombre
simblico para un contador concreto de una PMU actual se puede emplear el
cdigo hexadecimal correspondiente, de acuerdo al manual del fabricante.

23.1.2. Obteniendo ayuda


La primera suborden de perf que debe dominarse es help, que se emplea para
obtener ayuda. La ejecucin de perf help sin ms nos muestra todas las rdenes
disponibles. Las ms utilizadas son perf stat, perf record, perf report
y perf annotate.
Cada una de estas rdenes tienen ayuda especca que puede obtenerse con perf
help suborden.

23.1.3. Estadsticas y registro de eventos


La operacin ms sencilla que se puede hacer con perf es contar eventos. Eso
puede realizarse con la suborden perf stat:

$ perf stat glxgears

Performance counter stats for glxgears:

80,416861 task-clock # 0,069 CPUs utilized


171 context-switches # 0,002 M/sec
71 CPU-migrations # 0,001 M/sec
10732 page-faults # 0,133 M/sec
109061681 cycles # 1,356 GHz [86,41 %]
75057377 stalled-cycles-frontend # 68,82 % frontend cycles idle [85,21 %]
58498153 stalled-cycles-backend # 53,64 % backend cycles idle [62,34 %]
68356682 instructions # 0,63 insns per cycle
# 1,10 stalled cycles per insn [80,66 %]
14463080 branches # 179,851 M/sec [86,78 %]
391522 branch-misses # 2,71 % of all branches [80,19 %]

1,158777481 seconds time elapsed

2 Los eventos de las PMU se documentan en los manuales de los fabricantes. Por ejemplo, los conta-

dores de la arquitectura Intel 64 e IA32 se documentan en el apndice A de [48] disponible en http:


//www.intel.com/Assets/PDF/manual/253669.pdf y los de los procesadores AMD64 en [5]
disponible en http://support.amd.com/us/Processor_TechDocs/31116.pdf
23.1. Perlado de programas [669]

Basta indicar el ejecutable a continuacin de perf stat. Por defecto muestra


un conjunto de mtricas comunes, que incluye eventos hardware (como los ciclos o las
instrucciones), eventos software (como los cambios de contexto), y mtricas derivadas
a la derecha (como el nmero de instrucciones por ciclo).
Puede utilizarse perf para medir un tipo de eventos concreto empleando la opcin
-e:

$ perf stat -e cycles,instructions precompute_landscape

Performance counter stats for precompute_landscape:

4473759 cycles # 0,000 GHz


3847463 instructions # 0,86 insns per cycle

0,004595748 seconds time elapsed

Y podemos dividir entre los eventos que ocurren en espacio de usuario y los que
ocurren en espacio del kernel.

$ perf stat -e cycles:u,cycles:k precompute_landscape

Performance counter stats for precompute_landscape:

1827737 cycles:u # 0,000 GHz


2612202 cycles:k # 0,000 GHz

0,005022949 seconds time elapsed

Todos los eventos hardware aceptan los modicadores u para ltrar solo los que
ocurren en espacio de usuario, k para ltrar los que ocurren en espacio del kernel y
uk para contabilizar ambos de forma explcita. Hay otros modicadores disponibles,
incluso alguno dependiente del procesador en el que se ejecuta.

23.1.4. Multiplexacin y escalado


Las PMU tienen dos tipos de contadores: los contadores jos, que cuentan un nico
tipo de evento, y los contadores genricos, que pueden congurarse para contar cual-
quier evento hardware. Cuando el usuario solicita ms eventos de los que fsicamente
se pueden contar con los contadores implementados el sistema de perlado multiple-
xa los contadores disponibles. Esto hace que parte del tiempo se estn contando unos
eventos y parte del tiempo se estn contando otros eventos distintos.
Posteriormente el propio sistema escala los valores calculados en proporcin al
tiempo que se ha contado el evento respecto al tiempo total. Es muy fcil de ver el

C23
efecto con un ejemplo. El computador sobre el que se escriben estas lneas dispone de
un procesador Intel Core i5. Estos procesadores tienen 4 contadores genricos3 .
Vamos a ver qu pasa cuando se piden 4 eventos idnticos:

$ perf stat -e cycles,cycles,cycles,cycles render_frame

Performance counter stats for render_frame:

803261796 cycles # 0,000 GHz


803261796 cycles # 0,000 GHz
803261796 cycles # 0,000 GHz
803261799 cycles # 0,000 GHz
3 Lo ms normal es disponer de dos o cuatro contadores genricos y otros tantos especcos. Realiza la

misma prueba en tu ordenador para comprobar cuntos contadores genricos tiene.


[670] CAPTULO 23. OPTIMIZACIN

0,306640126 seconds time elapsed

Puede verse que la precisin es absoluta, los cuatro contadores han contado prcti-
camente la misma cantidad de ciclos. En cambio, veamos qu pasa cuando se solicitan
5 eventos idnticos:

$ perf stat -e cycles,cycles,cycles,cycles,cycles render_frame

Performance counter stats for render_frame:

801863997 cycles # 0,000 GHz [79,06 %]


801685466 cycles # 0,000 GHz [80,14 %]
792515645 cycles # 0,000 GHz [80,37 %]
792876560 cycles # 0,000 GHz [80,37 %]
793921257 cycles # 0,000 GHz [80,08 %]

0,306024538 seconds time elapsed

Los valores son signicativamente diferentes, pero los porcentajes entre corchetes
nos previenen de que se ha realizado un escalado. Por ejemplo, el primer contador ha
estado contabilizando ciclos durante el 79,06 % del tiempo. El valor obtenido en el
contador se ha escalado dividiendo por 0,7906 para obtener el valor mostrado.
En este caso los contadores nos dan una aproximacin, no un valor completamente
able. Nos vale para evaluar mejoras en porcentajes signicativos, pero no mejoras
de un 1 %, porque como vemos el escalado ya introduce un error de esa magnitud.
Adems en algunas mediciones el resultado depender del momento concreto en que
se evalen o de la carga del sistema en el momento de la medida. Para suavizar todos
estos efectos estadsticos se puede ejecutar varias veces empleando la opcin -r.

$ perf stat -r 10 -e cycles,cycles,cycles,cycles,cycles render_frame

Performance counter stats for render_frame (10 runs):

803926738 cycles # 0,000 GHz ( +- 0,15 % ) [79,42 %]


804290331 cycles # 0,000 GHz ( +- 0,14 % ) [79,66 %]
802303057 cycles # 0,000 GHz ( +- 0,17 % ) [80,21 %]
797518018 cycles # 0,000 GHz ( +- 0,11 % ) [80,59 %]
799832288 cycles # 0,000 GHz ( +- 0,19 % ) [80,15 %]

0,310143008 seconds time elapsed ( +- 0,39 % )

Entre parntesis se muestra la variacin entre ejecuciones.

23.1.5. Mtricas por hilo, por proceso o por CPU


Es posible contabilizar los eventos solo en un hilo, o en todos los hilos de un pro-
ceso, o en todos los procesos de una CPU, o de un conjunto de ellas. Por defecto perf
contabiliza eventos del hilo principal incluyendo todos los subprocesos, creados con
fork(), o hilos, creados con pthread_create(), lanzados durante la ejecucin.
Este comportamiento se implementa con un mecanismo de herencia de contadores que
puede desactivarse con la opcin -i de perf stat.
Alternativamente se puede recolectar datos de un conjunto de procesadores en
lugar de un proceso concreto. Este modo se activa con la opcin -a y opcionalmente
complementado con la opcin -C. Al utilizar la opcin -a se activa la recoleccin
de datos por CPU, pero por defecto se agregan todos los contadores de todas las CPU
23.1. Perlado de programas [671]

(recoleccin de datos a nivel de sistema). Con la opcin -C podemos seleccionar


la CPU o conjunto de CPUs de los que se recaban estadsticas. Por ejemplo, para
recolectar el nmero de fallos de pgina en espacio de usuario de las CPUs 0 y 2
durante 5 segundos:

$ perf stat -a -e faults -C 0,2 sleep 5

Performance counter stats for sleep 5:

233 faults

5,001227158 seconds time elapsed

Ntese que utilizamos la orden sleep para no consumir ciclos y de esta forma
no inuir en la medida.

23.1.6. Muestreo de eventos


Adems de contar eventos, perf puede realizar un muestreo similar a otros pro-
lers. En este caso perf record recolecta datos en un archivo llamado perf.data
que posteriormente puede analizarse con perf report o perf annotate.
El periodo de muestreo se especica en nmero de eventos. Si el evento que se
utiliza para el muestreo es cycles (es as por defecto) entonces el periodo tiene
relacin directa con el tiempo, pero en el caso general no tiene por qu. Incluso en el
caso por defecto la relacin con el tiempo no es lineal, en caso de que el procesador
tenga activos modos de escalado de frecuencia.
Por defecto perf record registra 1000 muestras por segundo y ajusta din-
micamente el periodo para que mantenga esta tasa. El usuario puede establecer una
frecuencia de muestreo utilizando la opcin -F o puede establecer un periodo jo con
la opcin -c.
A diferencia de otros prolers, perf record puede recoger estadsticas a nivel
del sistema completo o de un conjunto de CPUs concreto empleando las opciones -a
y -C que ya hemos visto al explicar perf stat.
Es especialmente interesante el muestreo de la traza de llamada empleando la
opcin -g, aunque para que esta caracterstica muestre resultados ables es nece-
sario mantener el convenio de marcos de llamada y no compilar mediante la opcin
-fomit-frame-pointer.
Para mostrar los resultados almacenados en perf.data puede emplearse perf
report. Por ejemplo, a continuacin recolectaremos datos de la actividad del sistema
durante 5 segundos y mostraremos el resultado del perlado.

C23
$ perf record -a sleep 10
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.426 MB perf.data (~18612 samples)
]
$ perf report

El muestreo permite analizar qu funciones se llevan la mayor parte del tiempo


de ejecucin y, en caso de muestrear tambin la traza de llamada permite identicar
de forma rpida los cuellos de botella. No se trata de encontrar la funcin que ms
tiempo se lleva, sino de identicar funciones que merezca la pena optimizar. No tiene
sentido optimizar una funcin que se lleva el 0,01 % del tiempo de ejecucin, porque
simplemente no se notara.
[672] CAPTULO 23. OPTIMIZACIN

Figura 23.1: Interfaz textual de perf report.

Una vez identicada la funcin o las funciones susceptibles de mejora podemos


analizarlas en mayor detalle, incluso a nivel del cdigo ensamblador empleando perf
annotate smbolo. Tambin desde la interfaz de texto es
posible examinar el cdigo
anotado seleccionando el smbolo y pulsando la tecla a .

Para poder utilizar las caractersticas de anotacin del cdigo de los perla-
dores es necesario compilar el programa con informacin de depuracin.

Para ilustrar la mecnica veremos un caso real. Ingo Molnar, uno de los principales
desarrolladores de la infraestructura de perlado de Linux, tiene multitud de mensajes
en diversos foros sobre optimizaciones concretas que fueron primero identicadas
mediante el uso de perf. Uno de ellos4 describe una optimizacin signicativa de
git, el sistema de control de versiones.
En primer lugar realiza una fase de anlisis de una operacin concreta que revela
un dato intranquilizador. Al utilizar la operacin de compactacin git gc descubre
un nmero elevado de ciclos de estancamiento (stalled cycles5 ):

$ perf record -e stalled-cycles -F 10000 ./git gc


$ perf report --stdio

# Events: 26K stalled-cycles


#
# Overhead Command Shared Object Symbol
# ........ .......... ..................... .......................
#
26.07 % git git [.] lookup_object
4 http://thread.gmane.org/gmane.comp.version-control.git/172286
5 En las versiones actuales de perf habra que usar stalled-cycles-frontend en lugar de

stalled-cycles pero mantenemos el texto del caso de uso original para no confundir al lector.
23.1. Perlado de programas [673]

10.22 % git libz.so.1.2.5 [.] 0xc43a


7.08 % git libz.so.1.2.5 [.] inflate
6.63 % git git [.] find_pack_entry_one
5.37 % git [kernel.kallsyms] [k] do_raw_spin_lock
4.03 % git git [.] lookup_blob
3.09 % git libc-2.13.90.so [.] __strlen_sse42
2.81 % git libc-2.13.90.so [.] __memcpy_ssse3_back

Ingo descubre que la funcin find_pack_entry_one() se lleva un porcenta-


je signicativo de los ciclos de estancamiento. Por tanto examina el contenido de esa
funcin con perf annotate. Para poder extraer todo el benecio de esta orden es
interesante compilar el programa con informacin de depuracin.

$ perf annotate find_pack_entry_one

Percent | Source code & Disassembly of git


---------------------------------------------
:
...
: int cmp = hashcmp(index + mi * stride, sha1);
0.90 : 4b9264: 89 ee mov %ebp, %esi
0.45 : 4b9266: 41 0f af f2 imul %r10d, %esi
2.86 : 4b926a: 4c 01 de add %r11, %rsi
53.34 : 4b926d: f3 a6 repz cmpsb %es:( %rdi), %ds:( %rsi)
14.37 : 4b926f: 0f 92 c0 setb %al
5.78 : 4b9272: 41 0f 97 c4 seta %r12b
1.52 : 4b9276: 41 28 c4 sub %al, %r12b

La mayora de la sobrecarga est en la funcin hashcmp() que usa memcmp(),


pero esta ltima se expande como instrucciones ensamblador por el propio compila-
dor.
Ingo Molnar estudia el caso concreto. La funcin hashcmp() compara hashes, y
por eso se utiliza memcmp(), pero si no coinciden el primer byte diferir en el 99 % de
los casos. Por tanto modica el programa para escribir la comparacin manualmente,
evitando entrar en la comparacin para la mayor parte de los casos.
El resultado es realmente sorprendente. Antes de la optimizacin obtuvo estos
nmeros:

$ perf stat --sync --repeat 10 ./git gc

Performance counter stats for ./git gc (10 runs):

2771.119892 task-clock # 0.863 CPUs utilized ( +- 0.16 % )


1,813 context-switches # 0.001 M/sec ( +- 3.06 % )
167 CPU-migrations # 0.000 M/sec ( +- 2.92 % )
39,210 page-faults # 0.014 M/sec ( +- 0.26 % )
8,828,405,654 cycles # 3.186 GHz ( +- 0.13 % )
2,102,083,909 stalled-cycles # 23.81 % of all cycles are idle ( +- 0.52 % )

C23
8,821,931,740 instructions # 1.00 insns per cycle
# 0.24 stalled cycles per insn ( +- 0.04 % )
1,750,408,175 branches # 631.661 M/sec ( +- 0.04 % )
74,612,120 branch-misses # 4.26 % of all branches ( +- 0.07 % )

3.211098537 seconds time elapsed ( +- 1.52 % )

La opcin -sync hace que se ejecute una llamada sync() (vuelca los buffers
pendientes de escritura de los sistemas de archivos) antes de cada ejecucin para re-
ducir el ruido en el tiempo transcurrido.
Despus de la optimizacin el resultado es:

$ perf stat --sync --repeat 10 ./git gc

Performance counter stats for ./git gc (10 runs):

2349.498022 task-clock # 0.807 CPUs utilized ( +- 0.15 % )


[674] CAPTULO 23. OPTIMIZACIN

1,842 context-switches # 0.001 M/sec ( +- 2.50 % )


164 CPU-migrations # 0.000 M/sec ( +- 3.67 % )
39,350 page-faults # 0.017 M/sec ( +- 0.06 % )
7,484,317,230 cycles # 3.185 GHz ( +- 0.15 % )
1,577,673,341 stalled-cycles # 21.08 % of all cycles are idle ( +- 0.67 % )
11,067,826,786 instructions # 1.48 insns per cycle
# 0.14 stalled cycles per insn ( +- 0.02 % )
2,489,157,909 branches # 1059.442 M/sec ( +- 0.02 % )
59,384,019 branch-misses # 2.39 % of all branches ( +- 0.22 % )

2.910829134 seconds time elapsed ( +- 1.39 % )

La misma operacin se aceler en un 18 %. Se han eliminado el 33 % de los ciclos


de estancamiento y la mayora de ellos se han traducido en ahorro efectivo de ciclos
totales y con ello en mejoras de velocidad.
Este ejemplo deja claro que las instrucciones ensamblador que emite el compi-
lador para optimizar memcmp() no son ptimas para comparaciones pequeas. La
instruccin repz cmpsb requiere un tiempo de setup considerable durante el cual
la CPU no hace nada ms.
Otro efecto interesante que observa Ingo Molnar sobre esta optimizacin es que
tambin mejora la prediccin de saltos. Midiendo el evento branch-misses obtie-
ne los siguientes resultados:

branch-misses % del total


Antes 74,612,120 4.26 % ( 0.07 % )
Despus 59,384,019 2.39 % ( 0.22 % )

Cuadro 23.1: Mejora en prediccin de saltos

Por alguna razn el bucle abierto es ms sencillo de predecir por parte de la CPU
por lo que produce menos errores de prediccin.
No obstante es importante entender que estas optimizaciones corresponden a pro-
blemas en otros puntos (compilador que genera cdigo subptimo, y arquitectura que
privilegia un estilo frente a otro). Por tanto se trata de optimizaciones con fecha de
caducidad. Cuando se utilice una versin ms reciente de GCC u otro compilador ms
agresivo en las optimizaciones esta optimizacin no tendr sentido.
Un caso clebre similar fue la optimizacin del recorrido de listas en el kernel
Linux6 . Las listas son estructuras muy poco adecuadas para la memoria cach. Al
no tener los elementos contiguos generan innumerables fallos de cach. Mientras se
produce un fallo de cach el procesador est parcialmente parado puesto que necesita
el dato de la memoria para operar. Por esta razn en Linux se emple una optimizacin
denominada prefetching. Antes de operar con un elemento se accede al siguiente. De
esta forma mientras est operando con el elemento es posible ir transriendo los datos
de la memoria a la cach.
Desgraciadamente los procesadores modernos incorporan sus propias unidades de
prefetch que realizan un trabajo mucho mejor que el manual, puesto que no interere
con el TLB. El propio Ingo Molnar reporta que esta optimizacin estaba realmente
causando un impacto de 0,5 %.
La leccin que debemos aprender es que nunca se debe optimizar sin medir, que
las optimizaciones dependen del entorno de ejecucin, y que si el entorno de ejecucin
vara las optimizaciones deben re-evaluarse.
6 https://lwn.net/Articles/444336/
23.1. Perlado de programas [675]

Paquete Herramienta Descripcin


Valgrind Callgrind7 kCacheGrind Excelentes capacidades de representacin grca.
Google Performance Tools8 google-pprof Permite perlado de CPU y de memoria dinmica. Per-
mite salida en formato callgrind para poder analizar con
kCacheGrind.
GNU Proler9 gprof Es una herramienta estndar pero ha ido perdiendo su uti-
lidad conforme fueron surgiendo los perladores basados
en PMU.
nVidia Visual Proler nvvp Es especco para GPUs nVidia.
AMD APP Proler sprofile Es especco para GPUs AMD/ATI Radeon.

Cuadro 23.2: Herramientas de perlado en GNU/Linux.

23.1.7. Otras opciones de perf


Puede resultar til tambin la posibilidad de contabilizar procesos o hilos que ya
estn en ejecucin (opciones -p y -t respectivamente). A pesar de usar cualquiera de
estas opciones se puede especicar una orden para limitar el tiempo de medicin. En
caso contrario medira el proceso o hilo hasta su terminacin.
Tambin es posible generar grcos de lneas temporales. Para ello es necesa-
rio utilizar la suborden perf timechart record para registrar los eventos de
forma similar a como se haca con perf record y posteriormente emplear perf
timechart para generar el archivo output.svg. Este archivo puede editarse o
convertirse a PDF (Portable Document Format) con inkscape. El problema es que el
tiempo de captura debe ser reducido o de lo contrario el archivo SVG se volver in-
manejable. No obstante es muy til para detectar problemas de bloqueo excesivo. Por
ejemplo, los datos de la gura 23.2 se grabaron con perf timechart record
-a sleep 1.
Por ltimo conviene citar la suborden perf top que permite monitorizar en
tiempo real el sistema para analizar quin est generando ms eventos.

23.1.8. Otros perladores


La tabla 23.2 muestra una coleccin de herramientas de perlado disponibles en
entornos GNU y GNU/Linux.
La mayora de los perladores requieren compilar el programa de una manera
especial. El ms extendido y portable es GNU Proler, incluido dentro de binutils,
que es directamente soportado por el compilador de GNU. Si se compilan y se montan

C23
los programas con la opcin -pg el programa quedar instrumentado para perlado.
Todas las ejecuciones del programa generan un archivo gmon.out con la infor-
macin recolectada, que puede examinarse con gprof. GNU Proler utiliza muestreo
estadstico sin ayuda de PMU. Esto lo hace muy portable pero notablemente impreci-
so.
Google Performance Tools aporta un conjunto de bibliotecas para perlado de
memoria dinmica o del procesador con apoyo de PMU. Por ejemplo, el perlado
de programas puede realizarse con la biblioteca libprofiler.so. Esta biblioteca
puede ser cargada utilizando la variable de entorno LD_PRELOAD y activada mediante
la denicin de la variable de entorno CPUPROFILE. Por ejemplo:

$ LD_PRELOAD=/usr/lib/libprofiler.so.0 CPUPROFILE=prof.data \
./light-model-test
[676] CAPTULO 23. OPTIMIZACIN

Figura 23.2: Ejemplo de perf timechart.

Esto genera el archivo prof.data con los datos de perlado, que luego pueden GPU Prolers
examinarse con google-pprof. Entre otras capacidades permite representacin
grca del grafo de llamadas o compatibilidad con el formato de kcachegrind. De momento solo nVidia pro-
porciona un proler con capaci-
Una caracterstica interesante de Google Performance Tools es la capacidad de dades grcas sobre GNU/Linux.
realizar el perlado solo para una seccin concreta del cdigo. Para ello, en lugar de AMD APP Proler funciona en
GNU/Linux pero no con interfaz
denir la variable CPUPROFILE basta incluir en el cdigo llamadas a las funciones grca.
ProfilerStart() y ProfilerStop().
Para un desarrollador de videojuegos es destacable la aparicin de perladores
especcos para GPUs. Las propias GPUs tienen una PMU (Performance Monitoring
Unit) que permite recabar informacin de contadores especcos. De momento en el
mundo del software libre han emergido nVidia Visual Proler, AMD APP Proler
y extensiones de Intel a perf para utilizar los contadores de la GPU (perf gpu).
Probablemente en un futuro cercano veremos estas extensiones incorporadas en la
distribucin ocial de linux-tools.
23.2. Optimizaciones del compilador [677]

23.2. Optimizaciones del compilador


Los compiladores modernos implementan un enorme abanico de optimizaciones.
Con frecuencia son tan ecientes como el cdigo ensamblador manualmente progra-
mado. Por esta razn es cada vez ms raro encontrar fragmentos de cdigo ensambla-
dor en programas bien optimizados.
El lenguaje C++, y su ancestro C son considerados como lenguajes de programa-
cin de sistemas. Esto se debe a que permiten acceso a caractersticas de muy bajo
nivel, hasta el punto de que algunos autores lo consideran un ensamblador portable.
Los punteros no dejan de ser una forma de expresar direccionamiento indirecto, as
como el operador de indexacin no deja de ser una expresin de los modos de direc-
cionamiento relativo.

Figura 23.3: Aspecto de la interfaz de nVidia Visual Proler.

C fue diseado con el objetivo inicial de programar un sistema operativo. Por este
motivo, desde las primeras versiones incorpora caractersticas de muy bajo nivel que
permite dirigir al compilador para generar cdigo ms eciente. Variables registro,
funciones en lnea, paso por referencia, o plantillas son algunas de las caractersticas
que nos permiten indicar al compilador cundo debe esforzarse en buscar la opcin
ms rpida. Sin embargo, la mayora de las construcciones son simplemente indica-

C23
ciones o sugerencias, que el compilador puede ignorar libremente si encuentra una
solucin mejor. En la actualidad tenemos compiladores libres maduros con capacida-
des comparables a los mejores compiladores comerciales, por lo que frecuentemente
las indicaciones del programador son ignoradas.

23.2.1. Variables registro


Los ms viejos del lugar recordarn un modicador opcional para las variables
denominado register. Este modicador indica al compilador que se trata de una
variable especialmente crtica, por lo que sugiere almacenarla en un registro del pro-
cesador. Era frecuente ver cdigo como ste:
[678] CAPTULO 23. OPTIMIZACIN

Listado 23.1: Utilizacin arcaica de register para sumar los 1000 primeros nmeros natu-
rales.
1 register unsigned i, sum = 0;
2 for (i=1; i<1000; ++i)
3 sum += i;

Esta palabra clave est en desuso porque los algoritmos de asignacin de registros
actuales son mucho mejores que la intuicin humana. Pero adems, aunque se utiliza-
ra, sera totalmente ignorada por el compilador. La mera aparicin de register en
un programa debe ser considerada como un bug, porque engaa al lector del programa
hacindole creer que dicha variable ser asignada a un registro, cuando ese aspecto
est fuera del control del programador.

23.2.2. Cdigo esttico y funciones inline


Ya se ha comentado el uso del modicador inline en el mdulo 1. Sirve para
indicar al compilador que debe replicar el cdigo de dicha funcin cada vez que apa-
rezca una llamada. Si no se hiciera generara cdigo independiente para la funcin, al
que salta mediante una instruccin de llamada a subrutina. Sin embargo no siempre
es posible la sustitucin en lnea del cdigo y adems el compilador es libre de hacer
sustitucin en lnea de funciones aunque no estn marcadas como inline. Veamos
un ejemplo:

Listado 23.2: Ejemplo sencillo de funcin apropiada para la expansin en lnea.


1 int sum(int* a, unsigned size)
2 {
3 int ret = 0;
4 for (int i=0; i<size; ++i) ret += a[i];
5 return ret;
6 }
7
8 int main() {
9 int a[] = { 1, 2, 3, 4, 5};
10 return sum(a, sizeof(a)/sizeof(a[0]));
11 }

Compilemos este ejemplo con mximo nivel de optimizacin. No es necesario


dominar el ensamblador de la arquitectura x86_64 para entender la estructura.

$ gcc -S -O3 -c inl.cc

El resultado es el siguiente:

Listado 23.3: Resultado de la compilacin del ejemplo anterior.


1 .file "inl.cc"
2 .text
3 .p2align 4,,15
4 .globl _Z3sumPij
5 .type _Z3sumPij, @function
6 _Z3sumPij:
7 .LFB0:
8 .cfi_startproc
9 xorl %eax, %eax
10 testl %esi, %esi
11 pushq %rbx
12 .cfi_def_cfa_offset 16
13 .cfi_offset 3, -16
23.2. Optimizaciones del compilador [679]

14 je .L2
15 movq %rdi, %r8
16 movq %rdi, %rcx
17 andl $15, %r8d
18 shrq $2, %r8
19 negq %r8
20 andl $3, %r8d
21 cmpl %esi, %r8d
22 cmova %esi, %r8d
23 xorl %edx, %edx
24 testl %r8d, %r8d
25 movl %r8d, %ebx
26 je .L11
27 .p2align 4,,10
28 .p2align 3
29 .L4:
30 addl $1, %edx
31 addl ( %rcx), %eax
32 addq $4, %rcx
33 cmpl %r8d, %edx
34 jb .L4
35 cmpl %r8d, %esi
36 je .L2
37 .L3:
38 movl %esi, %r11d
39 subl %r8d, %r11d
40 movl %r11d, %r9d
41 shrl $2, %r9d
42 leal 0(, %r9,4), %r10d
43 testl %r10d, %r10d
44 je .L6
45 pxor %xmm0, %xmm0
46 leaq ( %rdi, %rbx,4), %r8
47 xorl %ecx, %ecx
48 .p2align 4,,10
49 .p2align 3
50 .L7:
51 addl $1, %ecx
52 paddd ( %r8), %xmm0
53 addq $16, %r8
54 cmpl %r9d, %ecx
55 jb .L7
56 movdqa %xmm0, %xmm1
57 addl %r10d, %edx
58 psrldq $8, %xmm1
59 paddd %xmm1, %xmm0
60 movdqa %xmm0, %xmm1
61 psrldq $4, %xmm1
62 paddd %xmm1, %xmm0
63 movd %xmm0, -4( %rsp)
64 addl -4( %rsp), %eax
65 cmpl %r10d, %r11d
66 je .L2

C23
67 .L6:
68 movslq %edx, %rcx
69 leaq ( %rdi, %rcx,4), %rcx
70 .p2align 4,,10
71 .p2align 3
72 .L9:
73 addl $1, %edx
74 addl ( %rcx), %eax
75 addq $4, %rcx
76 cmpl %edx, %esi
77 ja .L9
78 .L2:
79 popq %rbx
80 .cfi_remember_state
81 .cfi_def_cfa_offset 8
82 ret
83 .L11:
84 .cfi_restore_state
[680] CAPTULO 23. OPTIMIZACIN

85 movl %r8d, %eax


86 jmp .L3
87 .cfi_endproc
88 .LFE0:
89 .size _Z3sumPij, .-_Z3sumPij
90 .section .text.startup,"ax",@progbits
91 .p2align 4,,15
92 .globl main
93 .type main, @function
94 main:
95 .LFB1:
96 .cfi_startproc
97 movl $15, %eax
98 ret
99 .cfi_endproc
100 .LFE1:
101 .size main, .-main
102 .ident "GCC: (Debian 4.6.3-1) 4.6.3"
103 .section .note.GNU-stack,"",@progbits

El smbolo _Z3sumPij corresponde a la funcin sum() aplicando las reglas de


mangling. Podemos decodicarlo usando c++filt.

$ echo _Z3sumPij | c++filt


sum(int*, unsigned int)

El smbolo codica la signatura entera de la funcin. Sin embargo no se utiliza en


ninguna parte. Observemos en detalle las instrucciones de la funcin main() elimi-
nando las directivas no necesarias.

Listado 23.4: Cdigo de la funcin main() del ejemplo anterior.


1 main:
2 movl $15, %eax
3 ret

El cdigo se limita a retornar el resultado nal, un 15. El compilador ha realizado


la expansin en lnea y sucesivamente ha aplicado propagacin de constantes y eva-
luacin de expresiones constantes para simplicarlo a lo mnimo. Y entonces por qu
aparece el cdigo de la funcin sum()?
El motivo es simple, la funcin puede ser necesaria desde otra unidad de compila-
cin. Por ejemplo, supngase que en otra unidad de compilacin aparece el siguiente
cdigo.

Listado 23.5: Otra unidad de compilacin puede requerir la funcin sum().


1 #include <iostream>
2
3 using namespace std;
4
5 int sum(int* a, unsigned sz);
6
7 struct A {
8 A() {
9 int a[] = { 1, 1, 1, 1 };
10 cout << sum(a, 4) << endl;
11 }
12 };
13
14 A a;
23.2. Optimizaciones del compilador [681]

Signica eso que el cdigo no utilizado ocupa espacio en el ejecutable? Podemos


responder a esa pregunta compilando el ejemplo inicial y examinando los smbolos
con nm:

$ g++ -O3 -o inl inl.cc


$ nm --dynamic inl
w _Jv_RegisterClasses
w __gmon_start__
U __libc_start_main

No ha quedado ningn smbolo reconocible. El montador ha optimizado el ejecuta-


ble para que solo contenga los smbolos utilizados. Y si un plugin necesita la funcin
sum()? La respuesta la conocemos, aunque no conocamos los detalles, basta montar
con la opcin -rdynamic:

$ g++ -O3 -rdynamic -o inl inl.cc


$ nm --dynamic inl
00000000004008d8 R _IO_stdin_used
w _Jv_RegisterClasses
0000000000400720 T _Z3sumPij
0000000000600c00 A __bss_start
0000000000600bf0 D __data_start
w __gmon_start__
00000000004007f0 T __libc_csu_fini
0000000000400800 T __libc_csu_init
U __libc_start_main
0000000000600c00 A _edata
0000000000600c10 A _end
00000000004008c8 T _fini
00000000004005f8 T _init
0000000000400638 T _start
0000000000600bf0 W data_start
0000000000400630 T main

Si el cdigo est en una biblioteca dinmica el montador no eliminar los smbolos


porque no puede determinar si se usarn en el futuro. Sin embargo algunas funciones
solo sern necesarias en un archivo concreto. En ese caso pueden declararse como
static, lo que evita que se exporte el smbolo.

La palabra static es seguramente la palabra clave ms sobrecargada de


C++. Aplicado a las funciones o las variables globales quiere decir que el
smbolo no se exporta. Aplicado a un mtodo quiere decir que se trata de
un mtodo de clase, no aplicado a una instancia concreta. Aplicado a una
variable local quiere decir que se almacena en la zona de datos estticos.

C23
Listado 23.6: Esta biblioteca solo exporta la funcin sum10().
1 static int sum(int* a, unsigned size)
2 {
3 int ret = 0;
4 for (int i=0; i<size; ++i) ret += a[i];
5 return ret;
6 }
7
8 int sum10(int* a)
9 {
10 return sum(a,10);
11 }
[682] CAPTULO 23. OPTIMIZACIN

La expansin en lnea de las funciones no siempre produce un cdigo ptimo.


Para ilustrar este punto vamos a utilizar un ejemplo ya conocido de la seccin ante-
rior. En dicha seccin describamos un caso de optimizacin de git de Ingo Molnar.
Simplicando al mximo el caso se trataba del siguiente fragmento de cdigo:

Listado 23.7: Funciones crticas en la ejecucin de git gc.


1 #include <string.h>
2
3 static inline int hashcmp(const char *sha1, const char *sha2)
4 {
5 return memcmp(sha1, sha2, 20);
6 }
7
8 extern const char null_sha1[20] __attribute__((aligned(8)));
9 static inline int is_null_sha1(const char *sha1)
10 {
11 return !hashcmp(sha1, null_sha1);
12 }
13
14
15 int ejemplo(char* sha1, char* index, unsigned mi)
16 {
17 int cmp, i;
18 for (i=0; i<mi; ++i) {
19 cmp = hashcmp(index + i * 1024, sha1);
20 if (cmp == 0) return 0;
21 }
22 return cmp;
23 }

Estas funciones, que eran expandidas en lnea por el compilador, exhiban un com-
portamiento anmalo con respecto a los ciclos de estancamiento y a la prediccin de
saltos. Por lo que Ingo propone la siguiente optimizacin:

Listado 23.8: Optimizacin de funciones crticas en la ejecucin de git gc.


1 static inline int hashcmp(const char *sha1, const char *sha2)
2 {
3 int i;
4
5 for (i = 0; i < 20; i++, sha1++, sha2++) {
6 if (*sha1 != *sha2)
7 return *sha1 - *sha2;
8 }
9
10 return 0;
11 }
12
13 extern const char null_sha1[20];
14 static inline int is_null_sha1(const char *sha1)
15 {
16 return !hashcmp(sha1, null_sha1);
17 }
18
19
20 int ejemplo(char* sha1, char* index, unsigned mi)
21 {
22 int cmp, i;
23 for (i=0; i<mi; ++i) {
24 cmp = hashcmp(index + i * 1024, sha1);
25 if (cmp == 0) return 0;
26 }
27 return cmp;
28 }
23.2. Optimizaciones del compilador [683]

Lo interesante de este caso de estudio es que parti de un anlisis con el perlador


que determinaba que la funcin memcmp() era subptima para comparaciones cortas.
La funcin memcmp() se expanda automticamente en lnea en forma de un pua-
do de instrucciones ensamblador. Una de ellas, repz cmpsb, era identicada como
la culpable del problema. Actualmente ni gcc-4.6 ni clang expanden automti-
camente la funcin memcmp(). Por tanto el resultado es bien distinto. Empleando
perf stat -r 100 -e cycles:u se obtienen los resultados que muestra la
tabla 23.3.

Compilador Ciclos Ciclos Opt. Mejora


gcc-4.6 192458 190022 1,3 %
clang-3.0 197163 198232 -0,5 %
llvm-gcc-4.6 189164 191826 -1,4 %

Cuadro 23.3: Resultados de la optimizacin de Ingo Molnar con compiladores actuales (100 repeticiones).

El mejor resultado lo obtiene llvm-gcc con el caso sin optimizar. El caso de


clang genera resultados absolutamente comparables, dentro de los mrgenes de error
de perf. En cualquiera de los casos el resultado es mucho menos signicativo que
los resultados que obtuvo Ingo Molnar. Una optimizacin muy efectiva en un contexto
puede no ser tan efectiva en otro, y el contexto es siempre cambiante (nuevas versiones
de los compiladores, nuevas arquitecturas, etc.).

23.2.3. Eliminacin de copias


En la mayor parte del estndar de C++ se suele indicar que el compilador tiene
libertad para optimizar siempre que el resultado se comporte como si esas optimiza-
ciones no hubieran tenido lugar. Sin embargo el estndar permite adems un rango
de optimizaciones muy concreto pero con gran impacto en prestaciones, que pueden
cambiar el comportamiento de un programa. En [49], seccin 12.8, 32 introduce la
nocin de copy elision. Lo que sigue es una traduccin literal del estndar.

Cuando se cumplen determinados criterios una implementacin pue-


de omitir la llamada al constructor de copia o movimiento de un objeto, in-
cluso cuando el constructor y/o destructor de dicho objeto tienen efectos
de lado. En estos casos la implementacin simplemente trata la fuente y el
destino de la operacin de copia o movimiento omitida como dos formas
diferentes de referirse al mismo objeto, y la destruccin de dicho objeto
ocurre cuando ambos objetos hubieran sido destruidos sin la optimiza-
cin. Esta elisin de las operaciones de copia o movimiento, denominada

C23
elisin de copia, se permite en las siguientes circunstancias (que pueden
ser combinadas para eliminar copias mltiples):
En una sentencia return de una funcin cuyo tipo de retorno sea
una clase, cuando la expresin es el nombre de un objeto automti-
co no voltil (que no sea un parmetro de funcin o un parmetro de
una clusula catch) con el mismo tipo de retorno de la funcin (que
no puede ser const ni volatile), la operacin de copia o movimiento
puede ser omitida mediante la construccin directa del objeto auto-
mtico en el propio valor de retorno de la funcin.
En una expresin throw, cuando el operando es el nombre de un ob-
jeto automtico no voltil (que no sea un parmetro de funcin o un
parmetro de una clusula catch) cuyo mbito de declaracin no se
extienda ms all del nal del bloque try ms interior que contenga a
[684] CAPTULO 23. OPTIMIZACIN

dicha expresin (si es que existe), la operacin de copia o movimien-


to desde el operando hasta el objeto excepcin puede ser omitida
mediante la construccin del objeto automtico directamente sobre
el objeto excepcin.
Cuando un objeto temporal de clase que no ha sido ligado a una
referencia sera copiado o movido a un objeto con la misma cali-
cacin de const/ volatile, la operacin de copia o movimiento puede
ser omitida construyendo el temporal directamente sobre el destino
de la copia o movimiento.
Cuando la declaracin de excepcin en una clausula catch declara
un objeto del mismo tipo (salvo por modicadores const o volatile)
como el objeto excepcin, la operacin de copia o movimiento puede
ser omitida tratando la declaracin de excepcin como un alias del
objeto excepcin siempre que el signicado del programa no sea
cambiado salvo por la ejecucin de constructores y destructores del
objeto de la declaracin de excepcin.

1 class Thing {
2 public:
3 Thing();
4 ~Thing();
5 Thing(const Thing&);
6 };
7
8 Thing f() {
9 Thing t;
10 return t;
11 }
12
13 Thing t2 = f();

Aqu los criterios de elisin pueden combinarse para eliminar dos lla-
madas al constructor de copia de Thing: la copia del objeto automtico
local t en el objeto temporal para el valor de retorno de la funcin f() y
la copia de ese objeto temporal al objeto t2. Por tanto la construccin del
objeto local t puede verse como la inicializacin directa del objeto t2, y
la destruccin de dicho objeto tendr lugar al terminar el programa. Aa-
dir un constructor de movimiento a Thing tiene el mismo efecto, en cuyo
caso es el constructor de movimiento del objeto temporal a t2 el que se
elide.

Copy elision es un concepto que incluye dos optimizaciones frecuentes en compi-


ladores de C++: RVO (tercera circunstancia contemplada en el estndar) y NRVO (Na-
med Return Value Optimization) (primera circunstancia contemplada en el estndar).

23.2.4. Volatile
Las optimizaciones del compilador pueden interferir con el funcionamiento del
programa, especialmente cuando necesitamos comunicarnos con perifricos. As por
ejemplo, el compilador es libre de reordenar y optimizar las operaciones mientras
mantenga una equivalencia funcional. As, por ejemplo este caso se encuentra no po-
cas veces en cdigo de videojuegos caseros para consolas.

1 void reset(unsigned& reg)


2 {
3 reg = 1;
4 for(int i=0; i<1000000; ++i);
5 reg = 0;
6 }
23.3. Conclusiones [685]

El programador piensa que el bucle implementa un retardo y por tanto la funcin


permite generar un pulso en el bit menos signicativo. Compilando el ejemplo con
mximo nivel de optimizacin obtenemos lo siguiente:

1 _Z5resetRj:
2 movl $0, ( %rdi)
3 ret

El compilador ha eliminado todo hasta el punto de que ni siquiera escribe el pul-


so. Una forma sencilla de corregir este comportamiento es declarar el contador i y
el registro reg como volatile. Esto indica al compilador que no debe hacer opti-
mizaciones con respecto a esas variables. Otra forma sera sustituir el bucle de espera
por una llamada a funcin (por ejemplo usleep(10)).

23.3. Conclusiones
La optimizacin de programas es una tarea sistemtica, pero a la vez creativa.
Toda optimizacin parte de un anlisis cuantitativo previo, normalmente mediante
el uso de perladores. Existe un buen repertorio de herramientas que nos permite
caracterizar las mejores oportunidades, pero no todo lo que consume tiempo est en
nuestra mano cambiarlo. Las mejores oportunidades provienen de la re-estructuracin
de los algoritmos o de las estructuras de datos.
Por otro lado el programador de videojuegos deber optimizar para una platafor-
ma o conjunto de plataformas que se identican como objetivo. Algunas de las opti-
mizaciones sern especcas para estas plataformas y debern re-evaluarse cuando el
entorno cambie.

C23
Captulo

Validacin y Pruebas
24
David Villa Alises

L
a programacin, igual que cualquier otra disciplina tcnica, debera ofrecer ga-
rantas sobre los resultados. La formalizacin de algoritmos ofrece garantas
indiscutibles y se utiliza con xito en el mbito de los algoritmos numricos y
lgicos. Sin embargo, el desarrollo de software en muchos mbitos est fuertemente
ligado a requisitos que provienen directamente de necesidades de un cliente. En la
mayora de los casos, esas necesidades no se pueden formalizar dado que el cliente
expresa habitualmente requisitos ambiguos o incluso contradictorios. El desarrollo de
software no puede ser ajeno a esa realidad y debe integrar al cliente de forma que
pueda ayudar a validar, renar o recticar la funcionalidad del sistema durante todo el
proceso.
Un programador responsable comprueba que su software satisface los requisitos
del cliente, comprueba los casos tpicos y se asegura que los errores detectados (ya
sea durante el desarrollo o en produccin) se resuelven y no vuelven a aparecer. Es
imposible escribir software perfecto (a da de hoy) pero un programador realmente
profesional escribe cdigo limpio, legible, fcil de modicar y adaptar a nuevas ne-
cesidades. En este captulo veremos algunas tcnicas que pueden ayudar a escribir
cdigo ms limpio y robusto.

24.1. Programacin defensiva


La expresin programacin defensiva se reere a las tcnicas que ayudan al pro-
gramador a evitar, localizar y depurar fallos, especialmente aquellos que se producen
en tiempo de ejecucin. En muchas situaciones, especialmente con lenguajes como
C y C++, el programa puede realizar una operacin ilegal que puede terminar con la
ejecucin del proceso por parte del sistema operativo. El caso ms conocido en este

687
[688] CAPTULO 24. VALIDACIN Y PRUEBAS

sentido se produce cuando se dereferencia un puntero que apunta a memoria fuera de


los lmites reservados para ese proceso: el resultado es el fatdico mensaje segmen-
tation fault (abreviado como SEGFAULT). Cuando esa situacin no ocurre en todos
los casos sino que aparece espordicamente, encontrar la causa del problema puede
ser realmente complicado y puede llevar mucho tiempo. Para este tipo de problemas
la depuracin postmortem es una gran ayuda, pero antes de llegar a la autopsia, hay
algunas medidas preventivas que podemos tomar: el control de invariantes.
En programacin, una invariante es un predicado que asumimos como cierto antes,
durante y despus de la ejecucin de un bloque de cdigo (tpicamente una funcin
o mtodo). Denir invariantes en nuestras funciones puede ahorrar mucho tiempo de
depuracin porque tenemos garantas de que el problema est limitado al uso correcto
de la funcin que corresponda.
Muy ligado al concepto de invariante existe una metodologa denominada diseo
por contrato. Se trata de un mtodo para denir la lgica de una funcin, objeto u otro
componente de modo que su interfaz no depende solo de los tipos de sus parmetros y
valor de retorno. Se aaden adems predicados que se evalan antes (pre-condiciones)
y despus (post-condiciones) de la ejecucin del bloque de cdigo. As, la interfaz de
la funcin es mucho ms rica, el valor del parmetro adems de ser del tipo especi-
cado debe tener un valor que cumpla con restricciones inherentes al problema.

Listado 24.1: Una funcin que dene una invariante sobre su parmetro
1 double sqrt(double x) {
2 assert(x >= 0);
3 [...]
4 }

Normalmente el programador aade comprobaciones que validan los datos de en-


trada procedentes de la interfaz con el usuario. Se trata principalmente de convertir y
vericar que los valores obtenidos se encuentran dentro de los rangos o tengan valores
segn lo esperado. Si no es as, se informa mediante la interfaz de usuario que corres-
ponda. Sin embargo, cuando se escribe una funcin que va a ser invocada desde otra
parte, no se realiza una validacin previa de los datos de entrada ni tampoco de los
producidos por la funcin. En condiciones normales podemos asumir que la funcin
va a ser invocada con los valores correctos, pero ocurre que un error en la lgica del
programa o un simple error-por-uno puede implicar que utilicemos incorrectamente
nuestras propias funciones, provocando errores difciles de localizar.
La herramienta ms simple, a la vez que potente, para denir invariantes, pre-
condiciones o post-condiciones es la funcin assert()1 , que forma parte de la li-
brera estndar de prcticamente todos los lenguajes modernos. assert() sirve, tal
como indica su nombre, para denir aserciones, que en el caso de C++ ser toda expre-
sin que pueda ser evaluada como cierta. El siguiente listado es un ejemplo mnimo
de usa asercin. Se muestra tambin el resultado de ejecutar el programa cuando la
asercin falla:
Error por uno
Listado 24.2: assert-argc.cc: Un ejemplo sencillo de assert() Se denomina as a los errores (bugs)
1 #include <cassert> debidos a comprobaciones inco-
2 rrectas (> por >=, < por <= o
3 int main(int argc, char *argv[]) { viceversa), en la indexacin de vec-
4 assert(argc == 2); tores en torno a su tamao, iteracio-
5 return 0; nes de bucles, etc. Estos casos de-
6 } ben ser objeto de testing concienzu-
do.

1 En C++. la funcin assert() se encuentra en el chero de cabecera <cassert>.


24.1. Programacin defensiva [689]

$ ./assert-argc hello
$ ./assert-argc
assert-argc: assert-argc.cc:4: int main(int, char**): Assertion argc
== 2 failed.
Abortado

Veamos algunos usos habituales de assert()

Validar los parmetros de una funcin (pre-condiciones). Por ejemplo, compro-


bar que una funcin recibe un puntero no nulo:

1 void Inventory::add(Weapon* weapon) {


2 assert(weapon);
3 [...]
4 }

Comprobar que el estado de un objeto es consistente con la operacin que se


est ejecutando, ya sea como pre-condicin o como post-condicin.
Comprobar que un algoritmo produce resultados consistentes. Este tipo de post-
condiciones se llaman a menudo sanity checks.
Detectar condiciones de error irrecuperables.

1 void Server::bind(int port) {


2 assert(port > 1024);
3 assert(not port_in_use(port));
4 [...]
5 }

24.1.1. Sobrecarga
Las aserciones facilitan la depuracin del programa porque ayudan a localizar el
punto exacto donde se desencadena la inconsistencia. Por eso deberan incluirse desde
el comienzo de la implementacin. Sin embargo, cuando el programa es razonable-
mente estable, las aserciones siempre se cumplen (o as debera ser). En una versin
de produccin las aserciones ya no son tiles2 y suponen una sobrecarga que puede
afectar a la eciencia del programa.
Obviamente, eliminar a mano todas las aserciones no parece muy cmodo. La
mayora de los lenguajes incorporan algn mecanismo para desactivarlas durante la
compilacin. En C/C++ se utiliza el preprocesador. Si la constante simblica NDEBUG
est denida la implementacin de assert() (que en realidad es una macro de
preprocesador) se substituye por una sentencia vaca de modo que el programa que se
compila realmente no tiene absolutamente nada referente a las aserciones.

C24
En los casos en los que necesitamos hacer aserciones ms complejas, que requieran
variables auxiliares, podemos aprovechar la constante NDEBUG para eliminar tambin
ese cdigo adicional cuando no se necesite:

1 [...]
2 #ifndef NDEBUG
3 vector<int> values = get_values();
4 assert(values.size());
5 #endif

2 Por contra, algunos autores como Tony Hoare, deenden que en la versin de produccin es dnde ms

necesarias son las aserciones.


[690] CAPTULO 24. VALIDACIN Y PRUEBAS

Aunque esta contante se puede denir simplemente con #define NDEBUG, lo


ms cmodo y aconsejable es utilizar el soporte que los compiladores suelen ofrecer
para denir contantes en lnea de comandos. En el caso de g++ se hace as:

$ g++ -DNDEBUG main.cc

Denir la constante en el cdigo, aparte de ser incmodo cuando se necesita acti-


var/desactivar con frecuencia, puede ser confuso porque podra haber cheros que se
preprocesan antes de que la constante sea denida.

24.2. Desarrollo gil


El desarrollo gil de software trata de reducir al mnimo la burocracia tpica de las
metodologas de desarrollo tradicionales. Se basa en la idea de que el software que
funciona es la principal medida de progreso. El desarrollo gil recoge la herencia de
varas corrientes de nales de los aos 90 como Scrum o la programacin extrema y
todas esas ideas se plasmaron en el llamado maniesto gil:

Estamos descubriendo formas mejores de desarrollar software tanto por


nuestra propia experiencia como ayudando a terceros. A travs de este
trabajo hemos aprendido a valorar:

Individuos e interacciones sobre procesos y herramientas.


Software funcionando sobre documentacin extensiva.
Colaboracin con el cliente sobre negociacin contractual.
Respuesta ante el cambio sobre seguir un plan.

Esto es, aunque valoramos los elementos de la derecha, valoramos ms Figura 24.1: Kent Beck, uno de
los de la izquierda. los principales creadores de eXtre-
me programing, TDD y los mtodos
giles.
Las tcnicas de desarrollo gil pretenden entregar valor al cliente pronto y a me-
nudo, es decir, priorizar e implementar las necesidades expresadas por el cliente para
ofrecerle un producto que le pueda resultar til desde el comienzo. Tambin favorecen
la adopcin de cambios importantes en los requisitos, incluso en las ltimas fases del
desarrollo.

24.3. TDD

Una de las tcnicas de desarrollo gil ms efectiva es el Desarrollo Dirigido por


Pruebas o TDD (Test Driven Development). La idea bsica consiste en empezar el pro-
ceso escribiendo pruebas que representen directamente requisitos del cliente. Algunos
autores creen que el trmino ejemplo describe mejor el concepto que prueba. Una
prueba es un pequeo bloque de cdigo que se ejecuta sin ningn tipo de interaccin
con el usuario (ni entrada ni salida) y que determina de forma inequvoca (la prueba
pasa o falla) si el requisito correspondiente se est cumpliendo.
En el desarrollo de software tradicional las pruebas se realizan una vez terminado
el desarrollo asumiendo que desarrollo y pruebas son fases estancas. Incluso en otros
modelos como el iterativo, en espiral o el prototipado evolutivo las pruebas se realizan
despus de la etapa de diseo y desarrollo, y en muchas ocasiones por un equipo de
programadores distinto al que ha escrito el cdigo.
24.3. TDD [691]

24.3.1. Las pruebas primero


Con TDD la prueba es el primer paso que desencadena todo el proceso de desarro-
llo. En este sentido, las pruebas no son una mera herramienta de testing. Las pruebas
se utilizan como un medio para capturar y denir con detalle los requisitos del usuario,
pero tambin como ayuda para obtener un diseo consistente evitando aadir comple-
jidad innecesaria. Hacer un desarrollo dirigido por pruebas acota el trabajo a realizar:
si todas las pruebas pasan, el programa est terminado, algo que puede no resultar
trivial con otros modelos de desarrollo.
Este proceso resulta muy til para evitar malgastar tiempo y esfuerzo aadien-
do funcionalidad que en realidad no se ha solicitado. Este concepto se conoce como
YAGNI (You Aint Gonna Need It) y aunque a primera vista pueda parecer una cues-
tin trivial, si se analiza detenidamente, puede suponer un gran impacto en cualquier
proyecto. Es frecuente que los programadores entusiastas y motivados por la tarea aca-
ben generando un diseo complejo plasmado en una gran cantidad de cdigo difcil
de mantener, mejorar y reparar.

24.3.2. rojo, verde, refactorizar


Cada uno de los requisitos identicados debe ser analizado hasta obtener una serie
de escenarios que puedan ser probados de forma independiente. Cada uno de esos
escenarios se convertir en una prueba. Para cada uno de ellos:

Escribe la prueba haciendo uso de las interfaces del sistema (es probable que
an no existan!) y ejectala. La prueba debera fallar y debes comprobar que es
as (rojo).
A continuacin escribe el cdigo de produccin mnimo necesario para que la
prueba pase (verde). Ese cdigo mnimo debe ser solo el imprescindible, lo
ms simple posible, hasta el extremo de escribir mtodos que simplemente re-
tornan el valor que la prueba espera3 . Eso ayuda a validar la interfaz y conrma
que la prueba est bien especicada. Pruebas posteriores probablemente obli-
garn a modicar el cdigo de produccin para que pueda considerar todas las
posibles situaciones. A esto se le llama triangulacin y es la base de TDD:
Las pruebas dirigen el diseo.
Por ltimo refactoriza si es necesario. Es decir, revisa el cdigo de produccin
y elimina cualquier duplicidad. Tambin es el momento adecuado para renom-
brar tipos, mtodos o variables si ahora se tiene ms claro cul es su objetivo
real. Por encima de cualquier otra consideracin el cdigo debe expresar clara-
mente la intencin del programador. Es importante refactorizar tanto el cdigo
de produccin como las propias pruebas.

C24
Este sencillo mtodo de trabajo (el algoritmo TDD) favorece que los programa-
dores se concentren en lo que realmente importa: satisfacer los requisitos del usuario.
Tambin ayuda al personal con poca experiencia en el proyecto a decidir cul es el
prximo paso en lugar de divagar o tratando de mejorar el programa aadiendo
funcionalidades no solicitadas.

3 Kent Beck se reere a esto con la expresin Fake until make it.
[692] CAPTULO 24. VALIDACIN Y PRUEBAS

Figura 24.2: Algoritmo TDD

24.4. Tipos de pruebas


Hay muchas formas de clasicar las pruebas, y todo lo referente al testing tradi-
cional es aplicable aqu, aunque quiz de un modo diferente: pruebas de caja negra y
blanca, pruebas de aceptacin, integracin, sistema, unitarias y largo etctera. En el
contexto de las metodologas giles podemos concretar los siguientes tipos de prue-
bas [13]:

De aceptacin Idealmente debera estar especicado por el cliente o al menos por


un analista con la ayuda del cliente. Se expresa en trminos del dominio de la
aplicacin, sin detalles de implementacin. Esto incluye los test no funcionales,
es decir, aquellos que expresan requisitos no relacionados con los resultados
obtenidos sino sobre cuestiones como tiempo de ejecucin, consumo de energa,
etc.
De sistema Es un test que utiliza el sistema completo, desde el interfaz de usuario
hasta la base de datos, y lo hace del mismo modo que lo haran los usuarios
reales. Son pruebas muy frgiles, es decir, pequeos cambios sin relacin apa-
rente pueden hacer fallar la prueba aunque funcionalmente el sistema sea co-
rrecto.
Unitarios Se utilizan para probar un nico componente del sistema: un mtodo o fun-
cin, y para unas condiciones concretas (un escenario). La validacin se puede
hacer bien comprobando el estado nal conocido el estado inicial o por la inter-
accin entre el componente que se est probando y sus colaboradores.

Desde el punto de vista gil hay una pauta clara: las pruebas se escriben para eje-
cutarse, y debera ocurrir tan a menudo como sea posible. Lo ideal sera ejecutar todas
las pruebas despus de cada cambio en cualquier parte de la aplicacin. Obviamente
eso resulta prohibitivo incluso para aplicaciones pequeas. Hay que llegar a una solu-
cin de compromiso. Por este motivo, las pruebas unitarias son las ms importantes.
Si estn bien escritas, las pruebas unitarias se deberan poder ejecutar en muy pocos
segundos. Eso permite que, con los frameworks y herramientas adecuadas se pueda
lanzar la batera de pruebas unitarias completa mientras se est editando el cdigo
(sea la prueba o el cdigo de produccin).
Para que una prueba se considere unitaria no basta con que est escrita en un
framework xUnit, debe cumplir los principios FIRST [60], que es un acrnimo para:
24.5. Pruebas unitarias con google-tests [693]

Fast Las pruebas unitarias deberan ser muy rpidas. Como se ha dicho, todas las
pruebas unitarias de la aplicacin (o al menos del mdulo) deberan ejecutarse
en menos de 23 segundos.
Independent Cada prueba debe poder ejecutarse por separado o en conjunto, y en
cualquier orden, sin que eso afecte al resultado.
Repeatable La prueba debera poder ejecutarse mltiples veces dando siempre el
mismo resultado. Por este motivo no es buena idea incorporar aleatoriedad a
los tests. Tambin implica que la prueba debe poder funcionar del mismo modo
en entornos distintos.
Self-validating La prueba debe ofrecer un resultado concreto: pasa o falla, sin que el
programador tenga que leer o interpretar un valor en pantalla o en un chero.
Timely El test unitario debera escribirse justo cuando se necesite, es decir, justo antes
de escribir el cdigo de produccin relacionado, ni antes ni despus.

En cuanto al resto de las pruebas: sistema, integracin y aceptacin; deberan eje-


cutarse al menos una vez al da. Existe toda una disciplina, llamada integracin conti-
nua que trata sobre la compilacin, integracin y prueba de todo el sistema de forma
totalmente automtica, incluyendo la instalacin de dependencias e incluso el empa-
quetado y despliegue. Esta operacin puede hacerse cada vez que un programador
aade nuevo cdigo al repositorio o bien una vez al da si la aplicacin es muy gran-
de. El objetivo es disponer de informacin precisa y actualizada sobre el estado de la
aplicacin en su conjunto y sobre los requisitos que est cumpliendo.

24.5. Pruebas unitarias con google-tests


En esta seccin veremos un ejemplo de TDD intencionadamente simple para crear
la funcin factorial()4 . Para ello vamos a utilizar el framework de pruebas google-
tests (gtest). El primer test prueba que el resultado de factorial(0) es 1:

Listado 24.3: factorial-test.cc: Pruebas para factorial()


1 #include "gtest/gtest.h"
2 #include "factorial.h"
3
4 TEST(FactorialTest, Zero) {
5 EXPECT_EQ(1, factorial(0));
6 }

Se incluye el archivo de cabecera de gtest (lnea 1) donde estn denidas las ma-
cros que vamos a utilizar para denir las pruebas. La lnea 4 dene una prueba llamada

C24
Zero mediante la macro TEST para el casa de prueba (TestCase) FactorialTest.
La lnea 5 especica una expectativa: el resultado de invocar factorial(0) debe
ser igual a 1.
Adems del chero con la prueba debemos escribir el cdigo de produccin, su
chero de cabecera y un Makefile. Al escribir la expectativa ya hemos decidido el
nombre de la funcin y la cantidad de parmetros (aunque no es tipo). Veamos estos
cheros:
4 Inspirado en el primer ejemplo del tutorial de GTests.
[694] CAPTULO 24. VALIDACIN Y PRUEBAS

Listado 24.4: Escribiendo factorial() con TDD: Makefile


1 CC=$(CXX)
2 CXXFLAGS=-Igtest/include -Igtest -pthread
3 LDFLAGS=-pthread -lpthread
4
5 GTEST_TARBALL=gtest-1.7.0.zip
6
7 all: factorial-test
8
9 factorial-test: factorial-test.o factorial1.o gtest_main.a
10
11 gtest_main.a: gtest-all.o gtest_main.o
12 ar rv $@ $^
13
14 gtest %.o: gtest/src/gtest %.cc
15 g++ $(CXXFLAGS) -c $<
16
17 gtest: $(GTEST_TARBALL)
18 unp $<
19 ln -s $(GTEST_TARBALL:.zip=) gtest
20
21 clean:
22 $(RM) factorial-test *.o *~
23 $(RM) *.a

Listado 24.5: Escribiendo factorial() con TDD: factorial.h


1 int factorial(int n);

Y el cdigo de produccin mnimo para pasar la prueba:

Listado 24.6: Escribiendo factorial() con TDD: factorial.cc (1)


1 #include "factorial.h"
2
3 int factorial(int n) {
4 return 1;
5 }

Compilamos y ejecutamos el binario obtenido:

$ make
g++ -c -o factorial.o factorial.cc
g++ factorial-test.o factorial.o -lpthread -lgtest -lgtest_main -o
factorial-test
$ ./factorial-test
Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from FactorialTest
[ RUN ] FactorialTest.Zero
[ OK ] FactorialTest.Zero (0 ms)
[----------] 1 test from FactorialTest (0 ms total)

[----------] Global test environment tear-down


[==========] 1 test from 1 test case ran. (0 ms total)
[ PASSED ] 1 test.

Esta primera prueba no la hemos visto fallar porque sin el chero de produccin
ni siquiera podramos haberla compilado.
Aadamos ahora un segundo caso de prueba al chero:
24.6. Dobles de prueba [695]

Listado 24.7: factorial-test.cc: Pruebas para factorial()


1 TEST(FactorialTest, Positive) {
2 EXPECT_EQ(1, factorial(1));
3 EXPECT_EQ(2, factorial(2));
4 }

Como es lgico, la expectativa de la lnea 2 tambin pasa ya que el resultado es el


mismo que para la entrada 0. Veamos el resultado:

1 $ ./factorial-test
2 Running main() from gtest_main.cc
3 [==========] Running 2 tests from 1 test case.
4 [----------] Global test environment set-up.
5 [----------] 2 tests from FactorialTest
6 [ RUN ] FactorialTest.Zero
7 [ OK ] FactorialTest.Zero (0 ms)
8 [ RUN ] FactorialTest.Positive
9 factorial-test.cc:10: Failure
10 Value of: factorial(2)
11 Actual: 1
12 Expected: 2
13 [ FAILED ] FactorialTest.Positive (0 ms)
14 [----------] 2 tests from FactorialTest (0 ms total)
15
16 [----------] Global test environment tear-down
17 [==========] 2 tests from 1 test case ran. (0 ms total)
18 [ PASSED ] 1 test.
19 [ FAILED ] 1 test, listed below:
20 [ FAILED ] FactorialTest.Positive
21
22 1 FAILED TEST

El resultado nos indica la prueba que ha fallado (lnea 8), el valor obtenido por
la llamada (lnea 11) y el esperado (lnea 12).
A continuacin se debe modicar la funcin factorial() para que cumpla la
nueva expectativa. Despus escribir nuevas pruebas que nos permitan comprobar que
la funcin cumple con su cometido para unos cuantos casos representativos.

24.6. Dobles de prueba


TDD, y el agilismo en general, est muy relacionada con la orientacin a objetos,
y en muchos sentidos se asume que estamos haciendo un diseo orientado a objetos
prcticamente en todos los casos.
La mayora de los lenguajes de programacin que soportan la orientacin a obje-
tos tienen herramientas para encapsulacin y ocultacin. La ocultacin (el hecho de
no exponer los detalles de implementacin de la clase) resulta crucial en un diseo

C24
orientado a objetos porque proporciona sustituibilidad (LSP (Liskov Substitution
Principle)). Pero la ocultacin diculta la denicin de pruebas en base a aserciones
sobre de estado porque el estado del objeto est denido por el valor de sus atributos.
Si tuviramos acceso a todos los atributos del objeto (sea con getters o no) sera una
pista de un mal diseo.
[696] CAPTULO 24. VALIDACIN Y PRUEBAS

Debido a ello, suele ser ms factible denir la prueba haciendo aserciones sobre la
interaccin que el objeto que se est probando (el SUT (Subject Under Test)5 ) realiza
con sus colaboradores. El problema de usar un colaborador real es que ste tendr a
su vez otros colaboradores de modo que es probable que para probar un nico mtodo
necesitemos montar gran parte de la aplicacin. Como lo que queremos es instanciar
lo mnimo posible del sistema real podemos recurrir a los dobles6 de prueba.
Un doble de prueba es un objeto capaz de simular la interfaz que un determinado SOLID
colaborador ofrece al SUT, pero que realmente no implementa nada de lgica. El doble
SOLID (SRP, OCP, LSP, ISP, DIP)7
(dependiendo de su tipo) tiene utilidades para comprobar qu mtodos y parmetros es una serie de 5 principios esencia-
us el SUT cuando invoc al doble. les para conseguir diseos orienta-
dos a objetos de calidad. Estos son:
SRP Single Responsibility
OCP Open Closed
Una regla bsica: Nunca se deben crear dobles para clases implementadas por LSP Liskov Substitution
DIP Dependency Inversion
terceros, slo para clases de la aplicacin.
ISP Interface Segregation

Un requisito importante para poder realizar pruebas con dobles es que las clases
de nuestra aplicacin permitan inyeccin de dependencias. Consiste en pasar (in-
yectar) las instancias de los colaboradores (dependencias) que el objeto necesitar en
el momento de su creacin. Pero no estamos hablando de un requisito impuesto por
las pruebas, se trata de otro de los principios SOLID, en concreto DIP (Dependency
Inversion Principle).
Aunque hay cierta confusin con la terminologa, hay bastante consenso en distin-
guir al menos entre los siguientes tipos de dobles:

Fake Es una versin rudimentaria del objeto de produccin. Funcionalmente equi-


valente, pero tomando atajos que no seran admisibles en el cdigo nal. Por
ejemplo, una base de datos cuya persistencia es un diccionario en memoria.
Stub Devuelve valores predenidos para los mtodos que el SUT va a invocar. Se
trata de un colaborador que le dice al SUT lo que necesita oir pero nada ms.
Mock El mock se programa con una serie de expectativas (invocaciones a sus mto-
dos) que debera cumplirse durante la ejecucin de la prueba. Si alguna de esas
llamadas no se produce, u ocurre en una forma diferente a lo esperado, la prueba
fallar.
Spy El spy es un objeto que registra todas las invocaciones que se hacen sobre l.
Despus de utilizado, se pueden hacer aserciones para comprobar que ciertas
llamadas a sus mtodos ocurrieron. A diferencia del mock, puede haber recibido
otras invocaciones adems de las que se compruebas y el comportamiento sigue
siendo vlido.

24.7. Dobles de prueba con google-mock


En esta seccin veremos un ejemplo muy simple de uso con google-mock, el fra-
mework de dobles C++ que complementa a google-test. Vamos a implementar el m-
todo notify() de la clase Observable (tambin llamada Subject) del patrn
observador.
5 SUT: Subject Under Test
6 Son dobles en el mismo sentido que los actores que ruedan las escenas arriesgadas en el cine.
24.7. Dobles de prueba con google-mock [697]

Los primeros cheros que se muestran son el chero de cabecera observable.h:

Listado 24.8: Patrn observador con TDD: observable.h


1 #ifndef _OBSERVABLE_H_
2 #define _OBSERVABLE_H_
3
4 #include <vector>
5 #include "observer.h"
6
7 class Observable {
8 std::vector<Observer*> observers;
9 public:
10 void attach(Observer* observer);
11 void detach(Observer* observer);
12 void notify(void);
13 };
14
15 #endif

Y el chero de implementacin observable.cc:

Listado 24.9: Patrn observador con TDD: observable.cc


1 #include <algorithm>
2 #include <functional>
3
4 #include "observable.h"
5 #include "observer.h"
6
7 void
8 Observable::attach(Observer* observer) {
9 observers.push_back(observer);
10 }
11
12 void
13 Observable::detach(Observer* observer) {
14 observers.erase(find(observers.begin(), observers.end(),
15 observer));
16 }
17
18 void
19 Observable::notify(void) {
20 observers[0]->update();
21 }

Para escribir un test que pruebe el mtodo notify() necesitamos un mock para
su colaborador (el observador). El siguiente listado muestra la interfaz que deben
implementar los observadores:

C24
Listado 24.10: Patrn observador con TDD: observer.h
1 #ifndef _OBSERVER_H_
2 #define _OBSERVER_H_
3
4 class Observer {
5 public:
6 virtual void update(void) = 0;
7 virtual ~Observer() {}
8 };
9
10 #endif

Con ayuda de google-mock escribimos el mock para este colaborador:


[698] CAPTULO 24. VALIDACIN Y PRUEBAS

Listado 24.11: Patrn observador con TDD: mock-observer.h


1 #ifndef MOCK_OBSERVER_H
2 #define MOCK_OBSERVER_H
3
4 #include <gmock/gmock.h>
5 #include "observer.h"
6
7 class MockObserver : public Observer {
8 public:
9 MOCK_METHOD0(update, void());
10 };
11
12 #endif

Lo interesante aqu es la denicin del mtodo mockeado en la lnea 9. La macro


MOCK_METHOD0 indica que es para un mtodo sin argumentos llamado update()
que devuelve void. Aunque podemos escribir este chero a mano sin demasiados
problemas, existe una herramienta llamada gmock_gen que los genera automtica-
mente a partir de los cheros de declaracin de las clases.
Es hora de escribir la prueba. Vamos a comprobar que si tenemos observable con
un observador registrado e invocamos su mtodo notify() el mtodo update()
del observador se ejecuta una vez (y solo una).

Listado 24.12: Patrn observador con TDD: observable-tests.cc


1 #include <gmock/gmock.h>
2 #include <gtest/gtest.h>
3
4 #include "observable.h"
5 #include "mock-observer.h"
6
7 TEST(ObserverTest, UpdateObserver) {
8 MockObserver observer;
9 EXPECT_CALL(observer, update()).Times(1);
10
11 Observable observable;
12 observable.attach(&observer);
13
14 observable.notify();
15 }

En la prueba creamos el doble para el observer (lnea 8) y creamos la expectativa


(lnea 9). Despus creamos el observable (lnea 11) y registramos el observador (lnea
12). Por ltimo invocamos el mtodo notify() (lnea 14).
Tambin necesitamos un Makefile para compilar y ejecutar la prueba:

Listado 24.13: Patrn observador con TDD: Makefile


1 GMOCK_DIR = gmock
2 GTEST_DIR = gmock/gtest
3
4 CC = g++
5 CXXFLAGS = -I $(GTEST_DIR)/include -I $(GTEST_DIR) -I $(GMOCK_DIR)
/include -I $(GMOCK_DIR) -pthread
6 LDLIBS = -pthread -lpthread
7
8 TARGET = observable-tests
9
10 vpath %.cc $(GMOCK_SRC)/src
11
12 $(TARGET): observable-tests.o observable.o libgmock.a
13
24.7. Dobles de prueba con google-mock [699]

14 gmock: gmock-1.7.0.zip
15 unp $<
16 ln -s gmock-1.7.0 gmock
17
18 gmock-1.7.0.zip:
19 wget https://googlemock.googlecode.com/files/gmock-1.7.0.zip
20
21 libgmock.a: gtest-all.o gmock-all.o gmock_main.o
22 ar rv $@ $^
23
24 gtest-all.o: $(GTEST_DIR)/src/gtest-all.cc
25 g++ $(CXXFLAGS) -c $<
26
27 gmock %.o: $(GMOCK_DIR)/src/gmock %.cc
28 g++ $(CXXFLAGS) -c $<
29
30 test: $(TARGET)
31 ./$<
32
33 clean:
34 $(RM) $(TARGET) *.o *~ *.a

Ejecutemos el Makefile:

1 $ make test
2 g++ -I /usr/src/gmock -c -o observable-tests.o observable-tests.cc
3 g++ -I /usr/src/gmock -c -o observable.o observable.cc
4 g++ -I /usr/src/gmock -c -o gmock_main.o /usr/src/gmock/src/
gmock_main.cc
5 g++ -I /usr/src/gmock -c -o gmock-all.o /usr/src/gmock/src/gmock-all
.cc
6 g++ observable-tests.o observable.o gmock_main.o gmock-all.o -
lpthread -lgtest -o observable-tests
7 $ ./observable-tests
8 Running main() from gmock_main.cc
9 [==========] Running 1 test from 1 test case.
10 [----------] Global test environment set-up.
11 [----------] 1 test from ObserverTest
12 [ RUN ] ObserverTest.UpdateObserver
13 observable-tests.cc:11: Failure
14 Actual function call count doesnt match EXPECT_CALL(observer, update
())...
15 Expected: to be called once
16 Actual: never called - unsatisfied and active
17 [ FAILED ] ObserverTest.UpdateObserver (1 ms)
18 [----------] 1 test from ObserverTest (1 ms total)
19
20 [----------] Global test environment tear-down
21 [==========] 1 test from 1 test case ran. (1 ms total)
22 [ PASSED ] 0 tests.
23 [ FAILED ] 1 test, listed below:
24 [ FAILED ] ObserverTest.UpdateObserver
25
26 1 FAILED TEST

C24
Despus de la compilacin (lneas 2-6) se ejecuta el binario correspondiente al
test (lnea 7). El test falla porque se esperaba una llamada a update() (lnea 15)
y no se produjo ninguna (lnea 16) de modo que la expectativa no se ha cumplido.
Es lgico porque el cuerpo del mtodo notify() est vaco. Siguiendo la losofa
TDD escribir el cdigo mnimo para que la prueba pase:

Listado 24.14: Cdigo mnimo para satisfacer la expectativa


1 void
2 Observable::notify(void) {
3 observers[0]->update();
4 }
[700] CAPTULO 24. VALIDACIN Y PRUEBAS

Volvemos a ejecutar la prueba:

1 $ make test
2 g++ -I /usr/src/gmock -c -o observable.o observable.cc
3 g++ observable-tests.o observable.o gmock_main.o gmock-all.o -
lpthread -lgtest -o observable-tests
4 $ ./observable-tests
5 Running main() from gmock_main.cc
6 [==========] Running 1 test from 1 test case.
7 [----------] Global test environment set-up.
8 [----------] 1 test from ObserverTest
9 [ RUN ] ObserverTest.UpdateObserver
10 [ OK ] ObserverTest.UpdateObserver (0 ms)
11 [----------] 1 test from ObserverTest (0 ms total)
12
13 [----------] Global test environment tear-down
14 [==========] 1 test from 1 test case ran. (0 ms total)
15 [ PASSED ] 1 test.

La prueba pasa. Hora de escribir otra prueba. Comprobemos que update() no


se invoca si nadie invoca notify():

Listado 24.15: Prueba negativa para Observer::update()


1 TEST(ObserverTest, NeverUpdateObserver) {
2 MockObserver observer;
3 EXPECT_CALL(observer, update()).Times(0);
4
5 Observable observable;
6 observable.attach(&observer);
7 }

La prueba pasa. Ahora comprobemos que funciona tambin para dos observado-
res:

Listado 24.16: Prueba para noticacin de dos observadores


1 TEST(ObserverTest, TwoObserver) {
2 MockObserver observer1, observer2;
3 EXPECT_CALL(observer1, update());
4 EXPECT_CALL(observer2, update());
5
6 Observable observable;
7 observable.attach(&observer1);
8 observable.attach(&observer2);
9
10 observable.notify();
11 }

Y ejecutamos la prueba:

1 $ make test
2 Running main() from gmock_main.cc
3 [==========] Running 3 tests from 1 test case.
4 [----------] Global test environment set-up.
5 [----------] 3 tests from ObserverTest
6 [ RUN ] ObserverTest.UpdateObserver
7 [ OK ] ObserverTest.UpdateObserver (0 ms)
8 [ RUN ] ObserverTest.NeverUpdateObserver
9 [ OK ] ObserverTest.NeverUpdateObserver (0 ms)
10 [ RUN ] ObserverTest.TwoObserver
11 observable-tests.cc:30: Failure
24.8. Limitaciones [701]

12 Actual function call count doesnt match EXPECT_CALL(observer2, update


())...
13 Expected: to be called once
14 Actual: never called - unsatisfied and active
15 [ FAILED ] ObserverTest.TwoObserver (0 ms)
16 [----------] 3 tests from ObserverTest (1 ms total)
17
18 [----------] Global test environment tear-down
19 [==========] 3 tests from 1 test case ran. (1 ms total)
20 [ PASSED ] 2 tests.
21 [ FAILED ] 1 test, listed below:
22 [ FAILED ] ObserverTest.TwoObserver
23
24 1 FAILED TEST

Y la segunda expectativa falla (lnea ) y nos la muestra en consola:

Actual function call count doesn t match EXPECT_CALL(observer2, update


())...

Implementemos notify() para recorrer todos los observadores:

Listado 24.17: Patrn observador con TDD: observable.cc


1 void
2 Observable::notify(void) {
3 std::for_each(observers.begin(), observers.end(),
4 std::mem_fun(&Observer::update));
5 }

Ejecutamos de nuevo la prueba:

1 $ make test
2 Running main() from gmock_main.cc
3 [==========] Running 3 tests from 1 test case.
4 [----------] Global test environment set-up.
5 [----------] 3 tests from ObserverTest
6 [ RUN ] ObserverTest.UpdateObserver
7 [ OK ] ObserverTest.UpdateObserver (0 ms)
8 [ RUN ] ObserverTest.NeverUpdateObserver
9 [ OK ] ObserverTest.NeverUpdateObserver (0 ms)
10 [ RUN ] ObserverTest.TwoObserver
11 [ OK ] ObserverTest.TwoObserver (0 ms)
12 [----------] 3 tests from ObserverTest (0 ms total)
13
14 [----------] Global test environment tear-down
15 [==========] 3 tests from 1 test case ran. (0 ms total)
16 [ PASSED ] 3 tests.

Todo correcto, aunque sera conveniente una prueba adicional para un mayor n-
mero de observadores registrados. Tambin podramos comprobar que los observado-

C24
res des-registrados (detached) efectivamente no son invocados, etc.
Aunque los ejemplos son sencillos, es fcil ver la dinmica de TDD.

24.8. Limitaciones
Hay ciertos aspectos importantes para la aplicacin en los que TDD, y el testing
en general, tienen una utilidad limitada (al menos hoy en da). Las pruebas permiten
comprobar fcilmente aspectos funcionales, pero es complejo comprobar requisitos
no funcionales.
[702] CAPTULO 24. VALIDACIN Y PRUEBAS

Los tests no pueden probar la ausencia de fallos,


slo su existencia
Kent Beck

Puede ser complicado probar rendimiento, abilidad, tiempo de respuesta y otros


aspectos importantes, al menos escribiendo las pruebas primero. TDD tampoco ayuda
a disear cuestiones de carcter general como la arquitectura de la aplicacin, la se-
guridad, la accesibilidad, el modelo de persistencia, etc. Se dice que estos detalles de
diseo no emergen de las pruebas.
Respecto al desarrollo de videojuegos, TDD no se adapta bien al diseo y prueba
de la concurrencia, y en particular es complejo probar todo lo relacionado con la re-
presentacin grca e interaccin con el usuario. A pesar de ello, los mtodos giles
tambin estn causando un importante impacto en el desarrollo de videojuegos y la
informtica grca en general. Cada da aparecen nuevos frameworks y herramientas
que hacen posible probar de forma sencilla cosas que antes se consideraban inviables.
Empaquetado y distribucin
Captulo 25
Francisco Moya Fernndez
David Villa Alises

C
ada desarrollador de videojuegos tiene sus propios motivos para invertir su
tiempo y esfuerzo en la elaboracin de un producto tan complejo. Algunos
programan juegos de forma altruista como carta de presentacin o como
contribucin a la sociedad. Pero la mayora de los desarrolladores de videojuegos
trabajan para ver recompensado su esfuerzo de alguna forma, ya sea con donaciones,
publicidad empotrada en el juego, o directamente cobrando por su distribucin, o por
las actualizaciones, o por nuevos niveles. En todos estos casos hay un factor comn, el
desarrollador quiere que el juego sea usado por la mayor cantidad posible de usuarios.
En captulos anteriores hemos visto diferencias entre MS Windows y GNU/Linux
desde el punto de vista de programacin (plugins). En general son diferencias
relativamente pequeas, es perfectamente posible programar el videojuego para que
se compile sin problemas en cualquiera de estas plataformas.
Pero el videojuego no solo tiene que compilarse, tiene que llegar al usuario
de alguna forma que le permita instalarlo sin dicultad. En este captulo veremos
precisamente eso, la construccin de paquetes instalables para algunas plataformas
(MS Windows y GNU/Linux).
A diferencia del resto de actividades de desarrollo los sistemas de empaquetado
de software son completamente diferentes de unas plataformas a otras, e incluso hay
alternativas muy diferentes en una misma plataforma. Nos centraremos en las que
consideramos con mayor proyeccin.

703
[704] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN

25.1. Empaquetado y distribucin en Windows


Existe una amplsima variedad de herramientas para el empaquetado en Microsoft
Windows, y ninguna aglutina todas las ventajas1 . En Microsoft Visual Studio anterio-
res a la versin 2012 (opcin no disponible en la versin Express) era posible crear
instaladores utilizando un tipo de proyecto especial (Visual Studio Setup Project, vd-
proj). Sin embargo esta opcin, que ya haba sido marcada como obsoleta en VS2010
termin de eliminarse en la versin 2012. Para reemplazar esta funcionalidad Micro-
soft propone dos alternativas:

InstaShield 2010 Limited Edition (ISLE). Consiste en una versin restringida


de la popular herramienta de generacin de instaladores, actualmente propiedad
de Flexera Software LLC. Esta edicin limitada se puede descargar de forma
gratuita previo registro, y se instala como un plugin de Visual Studio, pero limita
las caractersticas del producto2 .
Windows Installer XML Toolset (WIX). Se trata de un conjunto de herramien-
tas desarrolladas como un proyecto de software libre bajo la Microsoft Recipro-
cal License (Ms-RL. Fue inicialmente desarrollado por Microsoft y actualmente
mantenido por un grupo de programadores que incluye a algunos de los desarro-
lladores de Visual Studio (por ejemplo, el lder del proyecto WIX, Rob Mens-
ching). Tambin existe un plugin para Visual Studio (Votive) para simplicar su
uso.

En esta seccin describiremos la segunda de las opciones. Microsoft utiliza WIX


para desarrollar sus propios instaladores de Visual Studio, Ofce, o SQL Server. A
diferencia de ISLE, WIX tiene un conjunto de caractersticas muy rico que va en
progresivo aumento.

25.1.1. Creacin de un paquete bsico


Para ilustrar el funcionamiento de WIX utilizaremos un ejemplo sencillo de OGRE
que simplemente muestra al personaje Sinbad de forma esttica y permite el movi-
miento con cursores y ratn. Sin embargo empaquetaremos todos los componentes de
OGRE para que la estructura del paquete pueda ser reutilizada en desarrollos ms com-
plejos. El cdigo fuente del ejemplo puede obtenerse de https://bitbucket.
org/arco_group/ogre-hello/downloads/ogre-hello-0.1.tar.gz.

Pre-requisitos

Antes de empezar el empaquetado tenemos que tener disponible una versin del
proyecto correctamente compilada en MS Windows y probada. Puede utilizarse el
conjunto de compiladores de GNU para Windows (MinGW) o Visual Studio. No es
importante para esta seccin cmo se generan los ejecutables.
En la documentacin en lnea de OGRE 1.8.1 se explica cmo construir proyec-
tos con el SDK como proyecto de Visual Studio 2010 Express (Ver http://www.
ogre3d.org/tikiwiki/tiki-index.php, apartado Installing the Ogre SDK).
Es preciso puntualizar que no es necesario copiar el ejecutable al directorio de binarios
de OGRE. Tan solo hay que garantizar que:
1 Una comparativa simple con un subconjunto de las alternativas disponibles puede verse en http:

//en.wikipedia.org/wiki/List_of_installation_software.
2 Ver http://msdn.microsoft.com/en-us/library/ee721500(v=vs.100).aspx
25.1. Empaquetado y distribucin en Windows [705]

Las bibliotecas dinmicas de OGRE (*.DLL) estn en la ruta de bsqueda


(tpicamente en el mismo directorio de la aplicacin).
El directorio donde se meten los plugins de OGRE (tpicamente el mismo de
la aplicacin) est correctamente consignado en el archivo de conguracin
correspondiente (plugins.cfg).
El archivo de conguracin de los recursos (resources.cfg) incluye
referencias relativas al directorio de instalacin de la aplicacin.

En el mundo de las aplicaciones para Microsoft Windows es frecuente distribuir el


software con la mayora de las dependencias. As, por ejemplo, cada juego realizado
con OGRE tendra su propia versin de las DLL de OGRE. Esto tiene puntos positivos
y negativos:

El software ha sido realizado con una versin particular de OGRE. Si se utilizara


cualquier otra es posible que se expusieran errores que no han sido detectados en
el control de calidad. Distribuir OGRE con el propio juego permite que convivan
mltiples versiones sin afectarse.
Incluir en cada juego un conjunto de bibliotecas incrementa innecesariamente
las necesidades de disco. Si 50 juegos tuvieran su propia copia particular de
OGRE estaramos ocupando casi 1GB ms de lo necesario. En cualquier caso,
al precio actual del almacenamiento esto tiene poca importancia. En la prctica
real tiene an menos importancia porque los fabricantes de videojuegos suelen
desarrollar plataformas ms o menos genricas con los ejecutables, que se
personalizan con scripts, plugins y archivos de medios. Solo el paquete de la
plataforma tendr una copia de las bibliotecas de OGRE.
Los problemas arreglados en OGRE no se ven automticamente arreglados en
las aplicaciones que usan OGRE. No es posible actualizar OGRE en todos los
paquetes que lo usan a la vez, es preciso actualizar cada uno de los juegos de
forma independiente.
Por tanto cada distribuidor de videojuegos se convierte en el responsable de
actualizar todos los componentes de sus aplicaciones de la forma ms diligente
posible. Esto es tambin una oportunidad para crear ventajas competitivas
ofreciendo un mejor servicio.

MS Windows Installer

Desde la aparicin de Ofce 2000 Microsoft intenta estandarizar la instalacin


de software en los sistemas Windows alrededor de un software genrico de gestin de
paquetes de software denominado Windows Installer. Adems de distribuirlo dentro de
Ofce 2000, Microsoft prepar un componente redistribuible que contena Windows
Installer y lo incluy en todas las versiones posteriores de sus sistemas operativos. Se
trata de un software de relativo bajo nivel que gestiona la base de datos de paquetes, y
dene el formato y estructura de los paquetes y de las actualizaciones. Incluye todo el
software necesario para instalar, congurar, desinstalar, actualizar, reparar y analizar C25
el software de un equipo.
Hasta entonces la nica alternativa disponible era la distribucin de un programa
independiente en cada distribucin de software (tpicamente setup.exe) que deba
ser ejecutado con privilegios de administrador. Este programa era el responsable
de las tareas de instalacin de los archivos (archivos cab), la conguracin del
registro o de los usuarios, etc. Al instalar el producto se sola instalar otro programa
complementario (tpicamente uninstall.exe) que permita deshacer el proceso
de instalacin el producto.
[706] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN

Con Windows Installer toda la informacin pasa a estar contenida en un nico


archivo, el paquete de instalacin, con extensin msi. Tambin pueden instalarse
paquetes de parches (con extensin msp).

Instalacin mnima con WIX

Windows Installation XML Toolset es el primer proyecto liberado con una licencia
libre por Microsoft en 2004. Inicialmente fue programado por Rob Mensching con el
objetivo de facilitar la creacin de archivos msi y msp sin necesidad de utilizar una
interfaz grca.
WIX utiliza una aproximacin puramente declarativa. En un archivo XML (con
extensin wxs) se describe el producto y dnde se pueden encontrar cada uno de
sus elementos. Este archivo es posteriormente compilado mediante la herramienta
candle.exe para analizar su consistencia interna y generar un archivo intermedio,
con extensin wixobj. Estos archivos intermedios siguen siendo XML aunque su
contenido no est pensado para ser editado o ledo por seres humanos.
Uno o varios archivos wixobj pueden ser procesados por otra herramienta,
denominada light.exe para generar el archivo msi denitivo.
Este ujo de trabajo, muy similar al de la compilacin de programas, es el ms
simple de cuantos permite WIX, pero tambin va a ser el ms frecuente. Adems
de esto, WIX incluye herramientas para multitud de operaciones de gran utilidad en
proyectos grandes.

Preparacin del archivo XML

WIX sigue la losofa de que el paquete de distribucin es parte del desarrollo del
programa. Por tanto el archivo wxs que describe el paquete debe escribirse de forma
incremental junto al resto del programa.
Desgraciadamente en muchas ocasiones nos encontramos con que la tarea de
empaquetado no se ha considerado durante el desarrollo del proyecto. Ya sea porque se
ha utilizado otra plataforma para el desarrollo del videojuego, o por simple desidia, lo
cierto es que frecuentemente llegamos al nal del proceso de desarrollo sin ni siquiera
habernos planteado la construccin del paquete. Tambin en esos casos WIX aporta
una solucin, mediante la herramienta heat.exe. Esta herramienta puede construir
fragmentos del archivo wxs mediante el anlisis de directorios o archivos que van a
incluirse en el paquete.
Una de las formas ms sencillas de generar un paquete instalable es instalar los
binarios y los archivos de medios necesarios durante la ejecucin en un directorio
independiente. El archivo generado no especialmente legible ni siquiera est completo,
pero puede usarse como punto de partida.
Por ejemplo, si un programa instala todos los ejecutables y archivos auxiliares en
el subdirectorio bin podemos generar el archivo wxs inicial con:

> heat.exe dir bin -out nombre-del-juego.wxs

Sin embargo, para mayor legibilidad en este primer ejemplo vamos a construir
el archivo desde cero. Volvamos al ejemplo ogre-hello. Una vez generados los
ejecutables en modo Release tenemos un subdirectorio Release que contiene todos
los ejecutables, en este caso OgreHello.exe. Por s solo no puede ejecutarse,
25.1. Empaquetado y distribucin en Windows [707]

necesita las bibliotecas de OGRE, los archivos del subdirectorio media y los archivos
de conguracin resources.cfg y plugins.cfg que se incluyen con el cdigo
fuente. Es bastante habitual copiar las DLL al subdirectorio Release para poder
probar el ejemplo.
La estructura del archivo wxs es la siguiente:

1 <?xml version="1.0" encoding="utf-8"?>


2 <Wix xmlns=http://schemas.microsoft.com/wix/2006/wi>
3 <Product Name=OgreHello 1.0
4 Id=06d99eb2-5f61-4753-a6fb-ba90166119cf
5 UpgradeCode=36a85162-a254-44b7-af91-44416d1c1935
6 Language=3082 Codepage=1252
7 Version=1.0.0 Manufacturer=UCLM>
8
9 <!-- aqui va la descripcion de los componentes del producto -->
10
11 </Product>
12 </Wix>

La etiqueta Wix se usa para envolver toda la descripcin del instalador. Dentro
de ella debe haber un Product que describe el contenido del archivo msi. Todos los
productos tienen los atributos Id y UpgradeCode que contienen cada uno un GUID
(Globally Unique Identier). Un GUID (o UUID) es un nmero largo de 128 bits, que
puede generarse de manera que sea muy poco probable que haya otro igual en ningn
otro sitio. Se utilizan por tanto para identicar de manera unvoca. En este caso se
identica el producto y el cdigo de actualizacin.
Todas las versiones del producto tienen el mismo Id, mientras que para cambio
de major version se genera otro nuevo GUID para UpgradeCode (al pasar de
1.x a 2.x, de 2.x a 3.x, etc.). El UpgradeCode es utilizado por Windows Installer
para detectar cuando debe eliminar la versin vieja para reemplazarla por la nueva o
simplemente reemplazar determinados archivos. Para generar un GUID nuevo puede
utilizarse, por ejemplo, un generador de GUIDs en lnea, como http://www.
guidgenerator.com/.
Los cdigos numricos de Language y Codepage se corresponden a los valores in-
dicados por microsoft en http://msdn.microsoft.com/en-us/library/
Aa369771.aspx. En nuestro caso el idioma elegido es el espaol de Espaa y la
pgina de cdigos es la correspondiente al alfabeto latino.
Dentro de un producto pueden declararse multitud de aspectos sobre el instalador.
Lo primero es denir el paquete MSI que se va a generar:

1 <Package Id=*
2 Description=Instalador de OgreHello 0.1
3 Manufacturer=UCLM
4 InstallerVersion=100
5 Languages=3082
6 Compressed=yes
7 SummaryCodepage=1252 />

C25
Cada nuevo paquete generado tendr su propio GUID. Por este motivo WIX
permite simplicar la construccin con el cdigo especial * que indica que debe
ser auto-generado por el compilador. El atributo InstallerVersion indica la
versin mnima de Windows Installer requerida para poder instalar el paquete. Si no
se implementan aspectos avanzados, siempre ser 100, que corresponde a la versin
1.0.
[708] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN

A continuacin podemos declarar los archivos comprimidos que contiene el


instalador. Se trata de archivos cab que son incluidos dentro del archivo msi.
Habitualmente, dada la capacidad de los medios actuales solo es necesario incluir
un archivo cab.

1 <Media Id=1 Cabinet=OgreHello.cab EmbedCab=yes/>

Ya solo queda describir la estructura de directorios que debe tener el producto.


Se trata de una estructura jerrquica construida desde el directorio raz, que siempre
debe tener un atributo Id con valor TARGETDIR y un atributo Name con el valor
SourceDir.

1 <Directory Id=TARGETDIR Name=SourceDir>


2 <Directory Id=ProgramFilesFolder Name=PFiles>
3 <Directory Id=INSTALLDIR Name=OgreHello 1.0>
4 <Directory Id=MediaDir Name=media/>
5 <Directory Id="ProgramMenuFolder" Name="Programs">
6 <Directory Id="ProgramMenuDir" Name="OgreHello 1.0"/>
7 </Directory>
8 </Directory>
9 </Directory>
10 </Directory>

En este caso hemos creado un directorio OgreHello 1.0 dentro del directorio
estndar para los archivos de programa (normalmente C:\Program Files).
Dentro de este directorio hemos hecho un subdirectorio media que contendr los
archivos de medios (recursos).
Ahora podemos aadir componentes dentro de estos directorios. Cada componente
es un conjunto de archivos muy fuertemente relacionado, hasta el punto de que no
tiene sentido actualizar uno sin actualizar los dems. En general se tiende hacia
componentes lo ms pequeos posibles (un solo archivo), con objeto de que se puedan
hacer parches ms pequeos. Por ejemplo, el ejecutable principal es un componente,
pero por simplicidad aadiremos tambin los archivos de conguracin.

1 <DirectoryRef Id=INSTALLDIR FileSource=.>


2 <Component Id=MainExecutable Guid=1e71f142-c7cd-4525-980b-78
ebcafedeb1>
3 <File Name=OgreHello.exe KeyPath=yes>
4 <Shortcut Id=startmenuOgreHello Directory="ProgramMenuDir"
Name="OgreHello 1.0"
5 WorkingDirectory=INSTALLDIR Icon=OgreHello.exe
IconIndex=0 Advertise=yes />
6 </File>
7 <File Name=plugins.cfg/>
8 <File Name=resources.cfg/>
9 </Component>
10 </DirectoryRef>

Cada componente tiene tambin un GUID que lo identica. En este caso contiene
tres archivos, el ejecutable y los dos archivos de conguracin. Adems, para el
ejecutable creamos tambin un atajo en el men de inicio de Windows.
El atributo KeyPath de los archivos se pone a yes solamente para un archivo
dentro del componente. Este archivo ser utilizado por Windows Installer para
identicar si el componente est previamente instalado.
25.1. Empaquetado y distribucin en Windows [709]

Para simplicar el resto del paquete vamos a meter todas las bibliotecas de OGRE
en un nico componente. En un caso real probablemente convendra dividirlo para
permitir parches ms pequeos en caso de que no afecten a todas las bibliotecas de
OGRE.

1 <DirectoryRef Id=INSTALLDIR FileSource=$(env.OGRE_HOME)\bin\


release>
2 <Component Id=OgreDLLs Guid=373f56f3-82c8-4c94-a0f6-
d9be98d8d4af>
3 <File Name=OgreMain.dll KeyPath=yes/>
4 <File Name=OgrePaging.dll/>
5 <File Name=OgreProperty.dll/>
6 <File Name=OgreRTShaderSystem.dll/>
7 <File Name=OgreTerrain.dll/>
8 <File Name=cg.dll/>
9 <File Name=OIS.dll/>
10 <File Name=Plugin_BSPSceneManager.dll/>
11 <File Name=Plugin_CgProgramManager.dll/>
12 <File Name=Plugin_OctreeSceneManager.dll/>
13 <File Name=Plugin_OctreeZone.dll/>
14 <File Name=Plugin_ParticleFX.dll/>
15 <File Name=Plugin_PCZSceneManager.dll/>
16 <File Name=RenderSystem_Direct3D9.dll/>
17 <File Name=RenderSystem_GL.dll/>
18 </Component>
19 </DirectoryRef>

Debe observarse el uso del atributo FileSource para congurar la fuente de


los archivos a partir del valor de la variable de entorno OGRE_HOME. Esta variable
contiene el directorio de instalacin del SDK de OGRE si se han seguido los pasos
indicados en la documentacin.
A continuacin queda aadir los archivos de medios.

1 <DirectoryRef Id=MediaDir FileSource=media>


2 <Component Id=MediaFiles Guid=9088eac3-9a72-4942-ba5e-28
d870c90c36>
3 <File Name=Sinbad.mesh KeyPath=yes/>
4 <File Name=Sinbad.material/>
5 <File Name=sinbad_body.tga/>
6 <File Name=sinbad_clothes.tga/>
7 <File Name=sinbad_sword.tga/>
8 </Component>
9 </DirectoryRef>

Un ltimo componente nos permitir borrar la carpeta OgreHello 1.0 del


men de inicio en caso de desinstalacin.

1 <DirectoryRef Id=ProgramMenuDir>
2 <Component Id="ProgramMenuDir" Guid="b16ffa2a-d978-4832-a5f2
-01005e59853c">
3 <RemoveFolder Id=ProgramMenuDir On=uninstall />
4 <RegistryValue Root=HKCU Key=Software\[Manufacturer]\[
ProductName] Type=string Value= KeyPath=yes /> C25
5 </Component>
6 </DirectoryRef>

Cada producto software puede tener un conjunto de caractersticas que se instalan


obligatoriamente o bien segn la seleccin del usuario. En el caso ms simple el
paquete contiene una sola de estas caractersticas que instala todos los componentes
que hemos denido anteriormente.
[710] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN

1 <Feature Id=Complete Level=1>


2 <ComponentRef Id=MainExecutable />
3 <ComponentRef Id=OgreDLLs />
4 <ComponentRef Id=MediaFiles />
5 <ComponentRef Id=ProgramMenuDir />
6 </Feature>

Como colofn podemos denir el icono de la aplicacin. Normalmente se incluye


dentro del mismo ejecutable, por lo que no es necesario aadir archivos nuevos.

1 <Icon Id="OgreHello.exe" SourceFile="OgreHello.exe" />

Construccin del paquete

Para construir el paquete es preciso ejecutar candle.exe para generar el archivo


wixobj y posteriormente light.exe para generar el archivo msi. Por ejemplo,
suponiendo que el archivo wxs est en el subdirectorio wix del proyecto y que los
binarios estn compilados en el subdirectorio release deberemos ejecutar:

> cd release
> candle.exe ..\wix\ogre-hello.wxs
> light.exe ogre-hello.wixobj

Si todo va correctamente, en el directorio release estar disponible el archivo


gre-hello.msi con el paquete de instalacin.
Un doble click en el explorador es todo lo que se necesita para instalarlo.
Alternativamente podemos usar la herramienta msiexec de Windows Installer:

> msiexec /i ogre-hello.msi

25.1.2. Interaccin con el usuario


Hasta ahora los paquetes que hemos construido no tienen ningn tipo de interac-
cin con el usuario. WIX permite aadir fcilmente todo tipo de interaccin con el
usuario para personalizar la instalacin.
Para activar estas caractersticas es necesario ejecutar light.exe con la exten-
sin WixUIExtension:

> light.exe -ext WixUIExtension ogre-hello.wixobj

Por ejemplo, para aadir un formulario que pregunta al usuario dnde se desea
instalar tan solo tenemos que cambiar la seccin Feature de este modo:

1 <UIRef Id="WixUI_Mondo" />


2 <UIRef Id="WixUI_ErrorProgressText" />
3 <Feature Id=Complete Level=1
4 Title=OgreHello 1.0
5 Description=Componentes de OgreHello 1.0.
6 Display=expand
7 ConfigurableDirectory=INSTALLDIR>
8 <ComponentRef Id=MainExecutable />
9 <ComponentRef Id=OgreDLLs />
10 <ComponentRef Id=MediaFiles />
11 <ComponentRef Id=ProgramMenuDir />
25.1. Empaquetado y distribucin en Windows [711]

12 </Feature>

Tan solo hemos aadido dos referencias externas y algunos atributos, como
Title, Description, Display y ConfigurableDirectory. El resultado
es el que se muestra en la gura.


Figura 25.1: Ejemplo de dilogos generados por WIX.

25.1.3. Otras caractersticas


WIX es un producto complejo, con multitud de caractersticas avanzadas. Es
posible crear bases de datos, replicar sitios web, instalar pre-requisitos, aadir o
modicar permisos, alterar las reglas del rewall, etc. La utilizacin de XML permite
que se puedan desarrollar paquetes modulares, con componentes compartidos por
otros paquetes. Adems permite crear con facilidad paquetes con parches, que solo
alteran lo que ha sido modicado.
WIX dispone de documentacin razonablemente completa, existe un excelente C25
tutorial y ya se ha publicado un libro. Sin embargo las mejoras en WIX a veces van
ms rpido que la documentacin de Internet. A menudo se leen comentarios acerca
de la imposibilidad de crear paquetes con dependencias externas. Sin embargo los
bindles de WIX permiten instalaciones encadenadas, incluso desde fuentes en red.
Recomendamos la consulta peridica de su pgina web http://wixtoolset.
org.
[712] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN

25.2. Empaquetado y distribucin en GNU/Linux


Debian GNU/Linux es una de las distribuciones de software libre ms veteranas y
de mayor calidad que existen actualmente. Debian se distingue principalmente por las
siguientes caractersticas:

No depende de ninguna empresa u organismo privado. Est completamente


desarrollada y mantenida por voluntarios.
Es libre y universal. El contrato social 3 de Debian es una declaracin de valores
y principios que recoge su objetivo y una denicin detallada de las licencias
libres que acoge.
Asume un fuerte compromiso con la calidad, que se ve reejado en una estricta
y bien denida poltica4 para incorporacin de nuevo software y liberacin de
versiones.
Soporta 12 arquitecturas diferentes, con varios ncleos en algunas de ellas.
Un sosticado sistema de paquetera (.deb), y herramientas para su gestin y
actualizacin mediante APT (Advanced Packaging Tool). Probablemente sta
es la razn ms importante por la que existe tal cantidad de distribuciones
derivadas activas (unas 140) siendo Ubuntu la ms popular.

Por estos motivos, .deb es probablemente el formato de paquete ms adecuado


para distribuir nuestro software en plataformas GNU/Linux.
El sistema de paquetera de Debian est denido por una serie de normas y
procedimientos recogidos tambin en la Debian Policy. Uno de estos procedimientos
especica las habilidades que deben tener los desarrolladores (ms conocidos como
Debian Developers o DD). Solo los DD tiene permiso para subir nuevo software a
los repositorios de Debian. Sin embargo, cualquier persona puede convertirse en DD
si cumple una serie de exmenes. Es un proceso largo y muy burocrtico, puesto
que aparte de las habilidades tcnicas requiere demostrar conocimientos aplicados
de propiedad intelectual e industrial, y de licencias de software.
Afortunadamente, no es necesario realizar este proceso para colaborar con Debian.
Es posible ser mantenedor (maintainer) de paquetes propios o ajenos. Los mantene-
dores realizan un trabajo similar, pero requieren la supervisin de un DD (un sponsor)
para que su trabajo sea incluido en Debian de forma ocial.
Para aprender a empaquetar nuevo software o mantener paquetes existentes existe
un documento llamado Gua del nuevo mantenedor de Debian5 . Tanto la poltica
como la gua del mantenedor explican como crear y mantener muchos tipos de
paquetes: script de shell, programas de lenguajes interpretados, paquetes que dan
lugar a un solo paquete binario, a mltiples, libreras dinmicas, mdulos del kernel y
muchos otros.
Esta seccin se centra en describir el proceso para crear un paquete binario, que
contendr al menos un ejecutable ELF resultado de compilar un programa C++,
adems de cheros auxiliares, que es el caso ms habitual para distribuir un juego
sencillo.
3 http://www.debian.org/social_contract.es.html
4 http://www.debian.org/doc/debian-policy/
5 http://www.debian.org/doc/manuals/maint-guide/index.en.html
25.2. Empaquetado y distribucin en GNU/Linux [713]

Se habla de un paquete ocial cuando est disponible a travs de las copias


espejo (mirrors) ociales de Debian. Pero tambin es posible crear un paquete Debian
correcto, pero que vamos a distribuir por nuestros propios medios (no-ocial). Aunque
construir un paquete ocial es ms complejo y requiere considerar ms detalles, ser
se el caso que abordemos aqu, puesto que siendo capaces de manejar un paquete
ocial, se es capaz obviamente de manejar uno no ocial.

25.2.1. Pidiendo un paquete


Lo primero que debes averiguar antes de empezar es comprobar si el paquete existe
previamente. Si no es el caso, debes comprobar si ya hay alguien que lo haya solicitado
o que ya est trabajando en ello. Puedes empaquetar cualquier programa que permita
su distribucin conforme a las directrices DFSG (Debian Free Software Guidelines)
incluso sin pedir permiso al autor. Eso signica que aunque ests interesado en
empaquetar un programa propio, podra ocurrir que ya hubiera alguien hacindolo,
siempre claro que tu programa est disponible de algn modo y tenga una licencia
libre.
Cualquiera puede solicitar que se empaquete un programa. Estas solicitud (y
otras operaciones) se hacen reportando un error (bug) a un paquete especial llamado
WNPP (Work-Needing and Prospective Packages) mediante el sistema DBTS (Debian
Bug Tracking System), que est basado en comandos enviados a travs de mensajes
de correo electrnico. Vamos cules son esas operaciones:

RFP (Request For Package) Corresponden con las solicitudes anteriormente


mencionadas. Estas noticaciones aparecen en http://www.debian.org/
devel/wnpp/requested. Las peticiones deberan incluir el nombre del
programa, una breve descripcin, el copyright y una URL para descarga.
ITP (Intent to Package) Expresa la disposicin del emisor de dicho reporte para
empaquetar el programa por si mismo. Si vas a empaquetar un programa propio,
debes enviar un ITP. Si ya existe, puedes escribir a su emisor para ofrecerle
ayuda si tienes inters en que el paquete est listo lo antes posible. Los paquetes
en este estado apararecen en http://www.debian.org/devel/wnpp/
being_packaged.
RFH (Request For Help) Es una solicitud del mantenedor actual para que
otros voluntarios de Debian le ayuden, ya se porque el programa es complejo
o porque no dispone de tiempo para dedicarle. Aparecen listados en http:
//www.debian.org/devel/wnpp/help_requested.
RFA (Request For Adoption) Indica que el mantenedor ya no quiere o no
puede mantener el paquete, aunque se seguir encargando hasta que aparezca
un nuevo voluntario. Aparecen en http://www.debian.org/devel/
wnpp/rfa_bypackage.
O (Orphaned) Indica que el mantenedor actual ya no va a mantener el paquete.
Se requiere un nuevo mantenedor lo antes posible. Se listan en http://www. C25
debian.org/devel/wnpp/orphaned.

Para facilitar la creacin de estos reportes (que se formatean como mensajes de


correo) existe el programa reportbug. Se llama as porque en realidad este es el
mecanismo para informar de cualquier problema en cualquier paquete ocial.
Asumiendo que ya has enviado el ITP puedes empezar a trabajar en la tarea de
empaquetado propiamente dicha.
[714] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN

25.2.2. Obteniendo el fuente original


Lo habitual es que el autor (upstream author, usando la nomenclatura de Debian)
proporcione los cheros fuentes de su programa mediante un archivo .tar.gz
(llamado a menudo tarball) o similar un su sitio web, o bien en un sistema de control
de versiones pblico como github o bitbucket.
Vamos a empaquetar el programa ogre-hello, cuyo cdigo fuente se puede
descargar como vemos en el siguiente listado. Despus se procede a descomprimir el
archivo y a entrar en el directorio que aparece.

$ wget https://bitbucket.org/arco_group/ogre-hello/downloads/ogre-hello-0.1.tar.
gz
$ tar xvfz ogre-hello-0.1.tar.gz
$ cd ogre-hello-0.1
ogre-hello-0.1$

25.2.3. Estructura bsica


En este punto podemos utilizar el programa dh_make que nos ayuda a generar
las plantillas de cheros necesarios para conctruir el paquete Debian, pero antes de
eso debemos crear dos variables de entorno para el nombre y direccin de correo del
mantenedor:

DEBEMAIL="juan.nadie@gmail.com"
DEBFULLNAME="Juan Nadie"
export DEBEMAIL DEBFULLNAME

Estas variables son tiles para otras operaciones relacionadas con las construccin
de paquetes, por lo que es buena idea aadirlas al chero $HOME/.bashrc para
futuras sesiones. Despus de eso, ejecuta dh_make:
manpages
ogre-hello-0.1$ dh_make -f ../ogre-hello-0.1.tar.gz
Type of package: single binary, indep binary, multiple binary, library, kernel Todo chero ejecutable en el PATH
module, kernel patch? debera tener su pgina de ma-
[s/i/m/l/k/n] s
nual. Las pginas de manual es-
Maintainer name : Juan Nadie tn escritas en groff, pero es mu-
Email-Address : juan.nadie@gmail.com cho ms sencillo crearlas a par-
Date : Wed, 24 Apr 2013 12:59:40 +0200 tir de formatos ms sencillos como
Package Name : ogre-hello SGML (Standard Generalized Mar-
Version : 0.1
License : blank kup Language), XML, asciidoc o
Type of Package : Single reStructuredText y convertirlas en
Hit <enter> to confirm: el momento de la construccin del
Done. Please edit the files in the debian/ subdirectory now. You should also paquete.
check that the ogre-hello Makefiles install into $DESTDIR and not in / .

La ejecucin de dh_make ha creado un subdirectorio llamado debian con mu-


chos cheros. Muchos de ellos son ejemplos (extensin .ex) para distintos nes: p-
gina de manual en distintos formatos, acciones durante la instalacin o desinstalacin
del paquete, script para creacin de un servicio en background (daemon), integracin
con cron, y muchos otros usos. Entre todos ellos hay cuatro cheros esenciales que
todo paquete Debian debe tener. A continuacin de muestran y describen, una vez
modicados segn corresponde con el paquete en cuestin. Comprelos con los gene-
rados por dh_make.
25.2. Empaquetado y distribucin en GNU/Linux [715]

debian/control

El chero debian/control es una especie de maniesto del paquete. Incluye


informacin esencial para el sistema de gestin de paquetes. Veamos el chero
generado:

Listado 25.1: dh_make: debian/control


1 Source: ogre-hello
2 Section: unknown
3 Priority: extra
4 Maintainer: Juan Nadie <Juan.Nadie@gmail.com>
5 Build-Depends: debhelper (>= 8.0.0)
6 Standards-Version: 3.9.4
7 Homepage: <insert the upstream URL, if relevant>
8 #Vcs-Git: git://git.debian.org/collab-maint/ogre-hello.git
9 #Vcs-Browser: http://git.debian.org/?p=collab-maint/ogre-hello.git;
a=summary
10
11 Package: ogre-hello
12 Architecture: any
13 Depends: ${shlibs:Depends}, ${misc:Depends}
14 Description: <insert up to 60 chars description>
15 <insert long description, indented with spaces>

Este chero tiene dos secciones claramente diferenciadas. La seccin Source


(slo puede haber una) describe el paquete fuente y la informacin necesaria para
su construccin. Cada seccin Package (puede haber varias aunque en este caso
solo haya una) describe los paquetes binarios (los archivos .deb) resultado de la
construccin del paquete fuente. Veamos el signicado de cada uno de los campos del
chero:
Campos de la seccin fuente:

Source
Es el nombre del paquete fuente, normalmente el mismo nombre del programa
tal como lo nombr el autor original (upstream author).
Section
La categora en la que se clasica el paquete, dentro de una lista establecida,
vea http://packages.debian.org/unstable/.
Priority
La importancia que tiene el paquete para la instalacin. Las categoras son:
required, important, standard, optional y extra. Este paquete, al ser un juego y
no causar ningn conicto, es optional. Puede ver el signicado de cada una
de estas prioridades en http://www.debian.org/doc/debian-policy/
ch-archive.html.
Maintainer
Es el nombre y la direccin de correo electrnico de la persona que mantiene el
paquete actualmente. C25
Build-Depends
Es una lista de nombres de paquetes (opcionalmente con las versiones requeri-
das) que son necesarios para compilar el presente paquete. Estos paquetes deben
estar instalados en el sistema (adems del paquete build-essential) para
poder construir los paquetes binarios.
Standards-Version
La versin ms reciente de la policy que cumple el paquete.
[716] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN

Campos de la seccin binaria:

Package
El nombre del paquete binario, que dar lugar al archivo .deb. Este nombre
debe cumplir una serie de reglas que dependen del lenguaje en que est escrito
el programa, su nalidad, etc.
Architecture
Indica en qu arquitecturas hardware puede compilar el programa. Aparte de las
arquitecturas soportadas por Debian hay dos identicadores especiales:
all, indica que el mismo programa funciona en todas las plataformas.
Normalmente se trata de programas escritos en lenguajes interpretados o
bien cheros multimedia, manuales, etc, que no requieren compilacin. Se
dice que son paquetes independientes de arquitectura.
any, indica que el programa debe ser compilado pero est soportado en
todas las arquitecturas.
Depends
Es una lista de los nombres de los paquetes necesarios para instalar el paquete
y ejecutar los programas que contiene.

Debe quedar clara la diferencia entre el campo Build-Depends y


el campo Depends. El primero contiene dependencias necesarias pa-
ra la construccin del paquete, mientras que el segundo lista las de-
pendencias para su ejecucin. Es fcil comprobar la diferencia. Cuan-
do se requieren libreras, Build-Depends contiene las versiones -dev
(como libogre-1.8-dev en nuestro caso), que incluyen los che-
ros de cabecera, mientras que Depends contiene la versin de usuario
(libogre-1.8.0).

Despus de nuestras modicaciones podra quedar del siguiente modo:

Listado 25.2: debian/control adaptado a ogre-hello


1 ource: ogre-hello
2 Section: games
3 Priority: optional
4 Maintainer: Juan Nadie <Juan.Nadie@gmail.com>
5 Build-Depends: debhelper (>= 9.0.0), libogre-1.8-dev, libois-dev,
libgl1-mesa-dev, quilt
6 Standards-Version: 3.9.4
7
8 Package: ogre-hello
9 Architecture: any
10 Depends: ${shlibs:Depends}, ${misc:Depends}, libogre-1.8.0
11 Description: minimal packaged Ogre example
12 minimal Ogre example to demostrate Debian packaging
25.2. Empaquetado y distribucin en GNU/Linux [717]

debian/changelog

Contiene una descripcin breve de los cambios que el mantenedor hace a los
cheros especcos del paquete (los que estamos describiendo). En este chero no
se describen los cambios que sufre el programa en s; eso corresponde al autor del
programa y normalmente estarn en un chero CHANGES (o algo parecido) dentro del
.tgz que descargamos.
El siguiente listado muestra el chero changelog generado por dh_make:

Listado 25.3: debian/changelog para ogre-hello


1 ogre-hello (0.1-1) unstable; urgency=low
2
3 * Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP>
4
5 -- Juan Nadie <Juan.Nadie@gmail.com> Wed, 24 Apr 2013 12:59:40 +0200

Cada vez que el mantenedor haga un cambio debe crear una nueva entrada como
esa al comienzo del chero. Fjese que la versin del paquete est formada por el
nmero de versin del programa original ms un guin y un nmero adicional que
indica la revisin del paquete debian. Si el mantenedor hace cambios sobre el paquete
manteniendo la misma versin del programa ir incrementando el nmero tras el
guin. Sin embargo, cuando el mantenedor empaqueta una nueva versin del programa
(supongamos la 0.2 en nuestro ejemplo) el nmero tras el guin vuelve a empezar
desde 1.
Como se puede apreciar, la primera versin del paquete debera cerrar (solucionar)
un bug existente. Ese bug (identicado por un nmero) corresponde al ITP que el
mantenedor debi enviar antes de comentar con el proceso de empaquetado.
Cuando una nueva versin del paquete se sube a los repositorios ociales, las
sentencias Closes son procesadas automticamente para cerrar los bugs noticados
a travs del DBTS. Puedes ver ms detalles en http://www.debian.org/doc/
debian-policy/ch-source.html#s-dpkgchangelog

debian/copyright

Este chero debe contender toda la informacin sobre el autor del programa y las
licencias utilizadas en cada una de sus partes (si es que son varias). Tambin debera
indicar quin es el autor y la licencia de los cheros del paquete debian. El chero
generado por dh_make contiene algo similar a:

Listado 25.4: dh_make: debian/copyright


1 Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 Upstream-Name: ogre-hello
3 Source: <url://example.com>
4
5 Files: *
6
7
Copyright: <years> <put authors name and email here>
<years> <likewise for another author> C25
8 License: <special license>
9 <Put the license of the package here indented by 1 space>
10 <This follows the format of Description: lines in control file>
11 .
12 <Including paragraphs>
13
14 # If you want to use GPL v2 or later for the /debian/* files use
15 # the following clauses, or change it to suit. Delete these two lines
16 Files: debian/*
17 Copyright: 2013 Juan Nadie <Juan.Nadie@gmail.com>
18 License: GPL-2+
19 This package is free software; you can redistribute it and/or modify
[718] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN

20 it under the terms of the GNU General Public License as published by


21 the Free Software Foundation; either version 2 of the License, or
22 (at your option) any later version.
23 .
24 This package is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 GNU General Public License for more details.
28 .
29 You should have received a copy of the GNU General Public License
30 along with this program. If not, see <http://www.gnu.org/licenses/>
31 .
32 On Debian systems, the complete text of the GNU General
33 Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
34
35 # Please also look if there are files or directories which have a
36 # different copyright/license attached and list them here.
37 # Please avoid to pick license terms that are more restrictive than the
38 # packaged work, as it may make Debians contributions unacceptable upstream.

El chero debera contener una seccin (que comienza con Files:) por cada
autor y licencia involucrados en el programa. La seccin Files: debian/* ya
est completa y asume que el mantenedor va a utilizar la licencia GPL-2 y superior
para los cheros del paquete.
Debemos modicar este chero para incluir las licencias especcas del programa
que estamos empaquetando. Las secciones nuevas seran:

Listado 25.5: debian/copyright para ogre-hello


1
2 Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
3 Upstream-Name: ogre-hello
4 Source: http://www.ogre3d.org/tikiwiki/Sinbad+Model
5
6 Files: *
7 Copyright: 2013 ogre.developers@ogre.com
8 License: GPL-2
9 This package is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13 .
14 This package is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18 .
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>
21 .
22 On Debian systems, the complete text of the GNU General
23 Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
24
25 Files: media/*
26 Copyright: 2009-2010 Zi Ye <omniter@gmail.com>
27 License:
28 This work is licensed under the Creative Commons Attribution-Share
29 Alike 3.0 Unported License. To view a copy of this license, visit
30 http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to
31 Creative Commons, 171 Second Street, Suite 300, San Francisco,
32 California, 94105, USA.
33 .
34 This character is a gift to the OGRE community
35 (http://www.ogre3d.org). You do not need to give credit to the
36 artist, but it would be appreciated. =)
25.2. Empaquetado y distribucin en GNU/Linux [719]

debian/rules

Se trata de un chero Makefile de GNU Make que debe incluir un serie


de objetivos: clean, binary, binary-arch, binary-indep y build. Estos objetivos son
ejecutados por el programa dpkg-buldpackage durante la construccin del
paquete.
El listado muestra el chero rules generado:

Listado 25.6: dh_make: debian/rules


1 #!/usr/bin/make -f
2 # -*- makefile -*-
3 # Sample debian/rules that uses debhelper.
4 # This file was originally written by Joey Hess and Craig Small.
5 # As a special exception, when this file is copied by dh-make into a
6 # dh-make output file, you may use that output file without restriction.
7 # This special exception was added by Craig Small in version 0.37 of dh-make.
8
9 # Uncomment this to turn on verbose mode.
10 #export DH_VERBOSE=1
11
12 %:
13 dh $@ --with quilt

Por fortuna, actualmente el programa debhelper (o dh) hace la mayor parte del
trabajo aplicando reglas por defecto para todos los objetivos. Solo es necesario sobre-
escribir (reglas override_ dichos objetivos en el chero rules si se requiere un
comportamiento especial.
El listado anterior contiene una pequea diferencia respecto al generado por
dh_make que consiste en la adicin del parmetro -with quilt. El programa
quilt es una aplicacin especializada en la gestin de parches de un modo muy
cmodo.

25.2.4. Construccin del paquete

build-depends Una vez contamos con los cheros bsicos se puede proceder a una primera
compilacin del paquete. Para ello utilizamos el programa dpkg-buildpackage.
Para construir el paquete de- El siguiente listado muestra el comando y parte del resultado. Se omiten algunas partes
be tener instalados todos los dado que la salida es bastante verbosa:
paquetes listados en los cam-
pos Build-Depends y
Build-Depends-Indep, ogre-hello-0.1$ dpkg-buildpackage -us -us
adems del paquete dpkg-buildpackage: source package ogre-hello
build-essential. dpkg-buildpackage: source version 0.1-1
dpkg-buildpackage: source changed by Juan Nadie <Juan.Nadie@gmail.com>
dpkg-buildpackage: host architecture amd64
dpkg-source --before-build ogre-hello-0.1
dpkg-source: info: applying make-install
fakeroot debian/rules clean
dh clean --with quilt
dh_testdir
dh_auto_clean
make[1]: Entering directory /home/david/repos/ogre-hello/ogre-hello-0.1
rm -f helloWorld *.log *.o *~
make[1]: Leaving directory /home/david/repos/ogre-hello/ogre-hello-0.1 C25
dh_quilt_unpatch
dpkg-source -b ogre-hello-0.1
dpkg-source: info: using source format 3.0 (quilt)
dpkg-source: info: applying make-install
dpkg-source: info: building ogre-hello using existing ./ogre-hello_0.1.orig.tar.
gz
dpkg-source: info: building ogre-hello in ogre-hello_0.1-1.debian.tar.gz
dpkg-source: info: building ogre-hello in ogre-hello_0.1-1.dsc
debian/rules build
dh build --with quilt
[...]
dh_testdir
[720] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN

make[1]: Entering directory /home/david/repos/ogre-hello/ogre-hello-0.1


g++ -I. -Wall -O2 -D_RELEASE pkg-config --cflags OGRE pkg-config --libs OGRE
-lOIS -lstdc++ helloWorld.cpp -o helloWorld
make[1]: Leaving directory /home/david/repos/ogre-hello/ogre-hello-0.1
dh_auto_test
fakeroot debian/rules binary
dh binary --with quilt
[...]
dh_md5sums
dh_builddeb
dpkg-deb: building package ogre-hello in ../ogre-hello_0.1-1_amd64.deb.
dpkg-genchanges >../ogre-hello_0.1-1_amd64.changes
dpkg-genchanges: including full source code in upload
dpkg-source --after-build ogre-hello-0.1
dpkg-buildpackage: full upload (original source is included)

Fjese en los parmetros de la llamada: -us -uc. Piden que no se rmen


digitalmente ni el paquete fuente ni el chero .changes respectivamente.
Este proceso ha creado tres cheros en el directorio padre:

ogre-hello_0.1-1.dsc
Es una descripcin del paquete fuente, con una serie de campos extrados del -
chero debian/control adems de los checksums para los otros dos cheros
que forman el paquete fuente: el .orig.tar.gz y el .debian.tar.gz.

Listado 25.7: Fichero con la descripcin del paquete ogre-hello


1 Format: 3.0 (quilt)
2 Source: ogre-hello
3 Binary: ogre-hello
4 Architecture: any
5 Version: 0.1-1
6 Maintainer: Juan Nadie <Juan.Nadie@gmail.com>
7 Standards-Version: 3.9.4
8 Build-Depends: debhelper (>= 9.0.0), libogre-1.8-dev, libois-dev,
libgl1-mesa-dev, quilt
9 Package-List:
10 ogre-hello deb games optional
11 Checksums-Sha1:
12 651807d3ca4d07a84e80eb2a20f4fe48eb986845 1188396 ogre-hello_0.1.orig.
tar.gz
13 5e59ae17c6fd21a69573fc61bdd85bbb275ced68 1870 ogre-hello_0.1-1.debian.
tar.gz
14 Checksums-Sha256:
15 48f09390131bb0ea66d91a1cd27fbe6ccc9b29a465159b6202f0f2dc38702b10
1188396 ogre-hello_0.1.orig.tar.gz
16 79275174e5a2b358cd3b031db6dc46faca0a5865c1b22fcb778c6e05e594e4b9 1870
ogre-hello_0.1-1.debian.tar.gz
17 Files:
18 4d5e668550d95dc0614435c1480a44e1 1188396 ogre-hello_0.1.orig.tar.gz
19 83cf823860cc404ece965d88b565e491 1870 ogre-hello_0.1-1.debian.tar.gz

ogre-hello_0.1-1_amd64.changes
Este chero es utilizado por el archivo (el repositorio) de paquetes de Debian
y se utiliza en la subida (upload) de paquetes por parte de los DD (Debian
Developer). Contiene checksum para los otros cheros y, en una versin ocial,
debera estar rmado digitalmente para asegurar que el paquete no ha sido
manipulado por terceros.
ogre-hello_0.1-1.debian.tar.gz
Es un archivo que contiene todos los cambios que el mantenedor del paquete ha
hecho respecto al fuente del programa original, es decir, el directorio debian
principalmente.
ogre-hello_0.1-1_amd64.deb
. Es el paquete binario resultado de la compilacin (en concreto para la
arquitectura AMD-64) que puede ser instalado con dpkg o indirectamente con
los gestores de paquetes apt-get, aptitude u otros.
25.2. Empaquetado y distribucin en GNU/Linux [721]

Es posible descargar y construir el paquete binario (.deb) a partir de los


cheros .dsc, .debian.tar.gz y .orig.tar.gz. La forma ms
simple de hacer esto con un paquete disponible en el repositorio es el
comando apt-get -build source nombre-de-paquete

Aunque aparentemente todo ha ido bien y se ha generado el chero .deb vamos


a comprobar que hay algunos problemas graves por resolver. La forma ms ecaz de
ver estos problemas en utilizar el programa lintian:

ogre-hello-0.1$ lintian ../ogre-hello\_0.1-1\_amd64.changes


W: ogre-hello: empty-binary-package

Este aviso indica que el paquete no contiene nada aparte de los cheros aportados
por el propio sistema de construccin. Veamos qu contiene realmente:

ogre-hello-0.1$ debc ../ogre-hello_0.1-1_amd64.deb


new debian package, version 2.0.
size 1192818 bytes: control archive=760 bytes.
352 bytes, 10 lines control
836 bytes, 11 lines md5sums
Package: ogre-hello
Version: 0.1-1
Architecture: amd64
Maintainer: Juan Nadie <Juan.Nadie@gmail.com>
Installed-Size: 2814
Depends: libc6 (>= 2.2.5), libgcc1 (>= 1:4.1.1), libogre-1.8.0, libois-1.3.0,
libstdc++6 (>= 4.2.1)
Section: games
Priority: optional
Description: minimal packaged Ogre example
minimal Ogre example to demostrate Debian packaging

*** Contents:
drwxr-xr-x root/root 0 2013-04-29 14:05 ./
drwxr-xr-x root/root 0 2013-04-29 14:05 ./usr/
drwxr-xr-x root/root 0 2013-04-29 14:05 ./usr/share/
drwxr-xr-x root/root 0 2013-04-29 14:05 ./usr/share/doc/
drwxr-xr-x root/root 0 2013-04-29 14:05 ./usr/share/doc/ogre-hello/
-rw-r--r-- root/root 2348 2013-04-29 13:15 ./usr/share/doc/ogre-hello/
copyright
-rw-r--r-- root/root 175 2013-04-29 13:15 ./usr/share/doc/ogre-hello/
changelog.Debian.gz

Esto se debe a que no basta con compilar el programa, es necesario instalar


los cheros en el lugar adecuado. Esta es uno de los principales objetivos del
empaquetado.

25.2.5. Parches: adaptacin a Debian


Instalar el programa y sus cheros asociados requiere en este caso modicar el
chero Makefile. El siguiente listado muestra esos cambios respetando el lugar
adecuado segn la poltica de Debian:
C25
Listado 25.8: Parche para la instalacin de ogre-hello
1 install: helloWorld
2 install -vd $(DESTDIR)/usr/games
3 install -v -m 444 helloWorld $(DESTDIR)/usr/games/ogre-hello
4 install -vd $(DESTDIR)/usr/share/games/ogre-hello/
5 install -v -m 444 *.cfg $(DESTDIR)/usr/share/games/ogre-hello/
6 install -vd $(DESTDIR)/usr/share/games/ogre-hello/media
7 install -v -m 444 media/*.tga $(DESTDIR)/usr/share/games/ogre-
hello/media/
[722] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN

8 install -v -m 444 media/*.material $(DESTDIR)/usr/share/games/


ogre-hello/media/
9 install -v -m 444 media/*.mesh $(DESTDIR)/usr/share/games/ogre-
hello/media/

Es imporante destacar que toda la instacin debe hacerse respecto a un directorio


contenido en la variable DESTDIR. Se utiliza el programa install para crear los
directorios necesarios y copiar cada chero a sus lugar, indicando adems los permisos
de acceso y ejecucin que correspondan.
Sin embargo, si tratamos de construir el programa utilizando la herramienta
dpkg-buildpackage en este momento estado obtendremos un error:

dpkg-source: info: local changes detected, the modified files are:


ogre-hello-0.1/makefile
dpkg-source: info: you can integrate the local changes with dpkg-source --commit

Se debe a que no est permitido modicar los cheros extrados del tarball del
autor del programa. Es necesario crear un parche. Un parche es un chero que contiene
los cambios que es preciso realizar en otro chero para lograr un resultado concreto,
indicando el nmero de lnea y otro contenido que ayuda a las herramientas a localizar
el lugar correcto. Por suerte, el propio error nos ofrece una forma muy sencilla que
crear este parche:

ogre-hello-0.1$ dpkg-source --commit


dpkg-source: info: local changes detected, the modified files are:
ogre-hello-0.1/makefile
Enter the desired patch name: make-install

El programa dpkg-source pide un nombre para el parche (le damos make-install)


y como resultado:

Deja el chero makefile tal como estaba antes de nuestro cambio.


Crea el parche en debian/patches/make-install.
Crea el chero debian/patches/series que contendr los nombres de
todos los parches a aplicar (make-install) en este momento.

Estos parches sern aplicados por el programa quilt, que se invocar automti-
camente al usar dpkg-buildpackage. Veamos las diferencias:

ogre-hello-0.1$ dpkg-buildpackage -us -us -rfakeroot


[...]
dh build --with quilt
dh\_testdir
dh\_quilt\_patch
File series fully applied, ends at patch make-install
[...]
install -vd /home/david/repos/ogre-hello/ogre-hello-0.1/debian/ogre-hello/usr/
games
install: creating directory /home/david/repos/ogre-hello/ogre-hello-0.1/debian/
ogre-hello/usr
install: creating directory /home/david/repos/ogre-hello/ogre-hello-0.1/debian/
ogre-hello/usr/games
install -v -m 444 helloWorld /home/david/repos/ogre-hello/ogre-hello-0.1/debian/
ogre-hello/usr/games/ogre-hello
[....]

Veamos qu problemas detecta lintian:

ogre-hello-0.1$ lintian ../ogre-hello_0.1-1_amd64.changes


W: ogre-hello: binary-without-manpage usr/games/ogre-hello
W: ogre-hello: hardening-no-relro usr/games/ogre-hello
25.2. Empaquetado y distribucin en GNU/Linux [723]

Y el contenido del paquete binario:

ogre-hello-0.1$ debc ../ogre-hello\_0.1-1\_amd64.deb


[...]
*** Contents:
drwxr-xr-x root/root 0 2013-04-29 14:26 ./
drwxr-xr-x root/root 0 2013-04-29 14:26 ./usr/
drwxr-xr-x root/root 0 2013-04-29 14:26 ./usr/games/
-rwxr-xr-x root/root 37912 2013-04-29 14:26 ./usr/games/ogre-hello
drwxr-xr-x root/root 0 2013-04-29 14:26 ./usr/share/
drwxr-xr-x root/root 0 2013-04-29 14:26 ./usr/share/doc/
drwxr-xr-x root/root 0 2013-04-29 14:26 ./usr/share/doc/ogre-hello/
-rw-r--r-- root/root 2348 2013-04-29 13:15 ./usr/share/doc/ogre-hello/
copyright
-rw-r--r-- root/root 175 2013-04-29 13:15 ./usr/share/doc/ogre-hello/
changelog.Debian.gz
drwxr-xr-x root/root 0 2013-04-29 14:26 ./usr/share/games/
drwxr-xr-x root/root 0 2013-04-29 14:26 ./usr/share/games/ogre-hello/
-rw-r--r-- root/root 27 2013-04-29 14:26 ./usr/share/games/ogre-hello/
resources.cfg
-rw-r--r-- root/root 225 2013-04-29 14:26 ./usr/share/games/ogre-hello/
ogre.cfg
-rw-r--r-- root/root 112 2013-04-29 14:26 ./usr/share/games/ogre-hello/
plugins.cfg
drwxr-xr-x root/root 0 2013-04-29 14:26 ./usr/share/games/ogre-hello/
media/
-rw-r--r-- root/root 2519 2013-04-29 14:26 ./usr/share/games/ogre-hello/
media/Sinbad.material
-rw-r--r-- root/root 786476 2013-04-29 14:26 ./usr/share/games/ogre-hello/
media/sinbad\_body.tga
-rw-r--r-- root/root 1026978 2013-04-29 14:26 ./usr/share/games/ogre-hello/
media/Sinbad.mesh
-rw-r--r-- root/root 196652 2013-04-29 14:26 ./usr/share/games/ogre-hello/
media/sinbad\_sword.tga
-rw-r--r-- root/root 786476 2013-04-29 14:26 ./usr/share/games/ogre-hello/
media/sinbad\_clothes.tga

Aunque ahora el paquete contiene los cheros deseados y los instalar en su ruta
correcta, pero an tiene algunos problemas ya que el programa no estaba pensado para
trabajar con esta estructura de cheros:

Los cheros de conguracin de ogre se buscan en directorio actual, pero


queremos buscarlos en /usr/share/games/ogre-hello.
Para solucionarlo, editamos el chero ExampleApplication.h (lnea 168)
y, a continuacin, asignamos el valor /usr/share/games/ogre-hello/ a la
variable mResourcePath. Despus se ejecuta dpkg-source -commit y
escribimos resource-path como nombre del parche.
Los recursos grcos se buscan en media, pero en la versin instalada deberan
buscarse en /usr/share/games/ogre-hello/media. En este caso se debe
editar la variable FyleSystem del chero media.cfg dndole el valor
/usr/share/games/ogre-hello/media/.

Con esto ltimo tendremos un total de tres parches y el programa ser funcional
tras la instalacin. Quedan an dos problemas (no tan graves) por resolver segn
informa lintian. El primero (binary-without-manpage) indica que todo chero
ejecutable en el PATH debera tener una pgina de manual. El segundo (hardening-
no-relro) indica que el programa debera estar compilado con determinadas opciones C25
que evitan problemas comunes.
[724] CAPTULO 25. EMPAQUETADO Y DISTRIBUCIN

25.2.6. Actualizacin del paquete


Mantener un paquete no acaba con la construccin de un paquete correcto y
funcional. Lo normal ser que el autor del programa contine mejorando su aplicacin
y liberando versiones nuevas (releases). Adems, los usuarios de Debian pueden
encontrar e informar de problemas en el paquete que tambin deben ser reparados.
En ambos casos el mantenedor debe actualizar el paquete y crear una nueva versin
(en el primer caso) o revisin (en el segundo caso).
El mantenedor puede tener conocimiento de una nueva versin del programa
mediante una noticacin de los usuarios al DBTS. Sin embargo, existe un mtodo
automtico para lograrlo. El paquete puede contar con un chero especial en
debian/watch que contiene normalmente una URL con una expresin regular
para localizar los cheros fuente de todas las versiones que proporcione el autor. El
siguiente listado muestra el chero watch para el programa ogre-hello.
El chero watch es procesado automticamente por el sistema DEHS (Debian
External Health Status) de Debian. Este sistema lleva el control de todos los paquetes
de cada mantenedor y le permite comprobar fcilmente el estado de todos sus
paquetes6 .

Listado 25.9: Fichero watch para ogre-hello


1 version=3
2 http://bitbucket.org/arco_group/ogre-hello/downloads/ogre-hello-(.*)\.tar\.gz

Obviamente esto resulta til cuando mantenemos un programa de un tercero,


pero tambin es interesante incluso aunque estemos empaquetando una aplicacin
propia. El programa uscan puede procesar este chero y descargar automticamente
con los fuentes del programa. Incluso puede crear un enlace a l con el nombre
.orig.tar.gz que cumple las normas de Debian.

ogre-hello-0.1$ uscan --verbose --download-current-version \


--force-download --repack --destdir ..
-- Scanning for watchfiles in .
-- Found watchfile in ./debian
-- In debian/watch, processing watchfile line:
http://bitbucket.org/arco_group/ogre-hello/downloads/ogre-hello
-(.*)\.tar\.gz
-- Found the following matching hrefs:
/arco_group/ogre-hello/downloads/ogre-hello-0.1.tar.gz
/arco_group/ogre-hello/downloads/ogre-hello-0.1.tar.gz
Newest version on remote site is 0.1, local version is 0.1
=> Package is up to date
Newest version on remote site is 0.1, local version is 0.1
=> ogre-hello-0.1.tar.gz already in package directory
-- Scan finished

Las cuatro ltimas lneas de la salida de uscan conrman que tenemos empaque-
tada la ltima versin y que adems tenemos en disco el tarball del autor.
Una vez que disponemos de la nueva versin del programa, debemos crear una
nueva entrada en el chero debian/changelog (que se puede automatizar en
parte con el programa dch). Para aplicar los cambios del nuevo tarball puedes
utilizar el programa uupdate aunque uscan puede encargarse tambin de esto.
A continuacin debe comprobar que la construccin de la nueva versin es correcta
poniendo especial atencin a la aplicacin de los parches sobre la nueva versin del
cdigo fuente.
6 Como ejemplo vea http://qa.debian.org/developer.php?login=
pkg-games-devel%40lists.alioth.debian.ora
25.3. Otros formatos de paquete [725]

25.2.7. Subir un paquete a Debian


Enviar el paquete resultante a un repositorio ocial de Debian requiere el uso
de algunas herramientas adicionales (la principal es dupload que consideramos
exceden el mbito de este documento. Si ests interesado en que tus paquetes
aparezcan en los repositorios ociales lo ms sencillo es conseguir un sponsor y l
te ayudar con esa tarea, auditar tu trabajo de empaquetado y subir el paquete por
ti.

25.3. Otros formatos de paquete


Junto con .deb, el otro sistema de paquete de amplio uso es RPM (RPM Package
Manager). Este formato fue creado por la distribucin Red Hat y hoy en da es
utilizado por muchas otras: Fedora, SUSE, CentOS, Yellow Dog, Oracle Linux, etc.
El formato .rpm guarda muchas similitudes con .rpm. Existen paquetes de fuen-
tes (con extensin .srpm o src.rpm), paquetes binarios por cada arquitectura (por
ejemplo .i386.rpm) y tambin independientes de arquitectura (.noarch.rpm).
El procedimiento para la creacin de un paquete .rpm es en muchos sentidos ms
anrquico que el equivalente .deb e histricamente los gestores de paquetes manejas
peor las dependencias (en especial las circulares).
Sin embargo, es sencillo crear un paquete .rpm a partir del equivalente .deb
mediante el programa alien:

$ fakeroot alien --to-rpm ../ogre-hello_0.1-1_amd64.deb


ogre-hello-0.1-2.x86_64.rpm generated

C25
Representacin Avanzada
Captulo 26
Sergio Prez Camacho
Jorge Lpez Gonzlez
Csar Mora Castro

L
os sistemas de partculas suponen una parte muy importante del impacto
visual que produce una aplicacin 3D. Sin ellos, las escenas virtuales se
compondran bsicamente de geometra slida y texturas. Los elementos que
se modelan utilizando estos tipos de sistemas no suelen tener una forma denida,
como fuego, humo, nubes o incluso aureolas simulando ser escudos de fuerza. En las
siguientes secciones se van a introducir los fundamentos de los sistemas de partculas
y billboards, y cmo se pueden generar utilizando las mltiples caractersticas que
proporciona Ogre.

26.1. Fundamentos
Para poder comprender cmo funcionan los sistemas de partculas, es necesario
conocer primero el concepto de Billboard, ya que dependen directamente de estos. A
Figura 26.1: Ejemplo tpico de vi- continuacin se dene qu son los Billboard, sus tipos y los conceptos matemticos
deojuego que utiliza billboards para bsicos asociados a estos, para despues continuar con los sistemas de partculas.
modelar la vegetacin. Screenshot
tomado del videojuego libre Stunt
Rally. 26.1.1. Billboards
Billboard signica, literalmente, valla publicitaria, haciendo alusin a los grandes
carteles que se colocan cerca de las carreteras para anunciar un producto o servicio.
Aquellos juegos en los que aparece el nombre de un jugador encima de su personaje,
siempre visible a la cmara, utilizan billboards. Los rboles de juegos de carrera que
daban la sensacin de ser un dibujo plano, utilizan Billboards.

727
[728] CAPTULO 26. REPRESENTACIN AVANZADA

u u u' u
a) b) c) n'

n n
n
r r

Figura 26.2: En a) se puede observar un Billboard con el vector up y el vector normal. En b) y c) se muestran
ejemplos de cmo calcular uno de los vectores dependiendo del otro en el caso de no ser perpendiculares.

Un Billboard es un polgono con una textura, y con un vector de orientacin. A


medida que la posicin de la cmara cambia, la orientacin del Billboard cambiar.
Esta tcnica se combina con transparencia mediante un canal alfa y con texturas
animadas para representar vegetacin (especialmente hierba), humo, niebla, explosio-
nes...
Cada billboard est formado por un vector normal n y un vector up u, como se
aprecia en la Figura 26.2. Esto es suciente para denir la orientacin del polgono,
y poder extraer as la matriz de rotacin. El ltimo dato necesario es la posicin del
polgono en el espacio.
Para que el billboard sea correcto, el vector normal y el up deben ser perpendicula-
res. A menudo no lo son, por lo que hay que utilizar una serie de transformaciones para
conseguirlo. El mtodo consiste en tomar uno de los dos vectores como jo, mientras
que ser el otro el que se recalcule.
Una vez tomado un vector jo, se calcula el vector r, resultado de realizar el
producto vectorial entre u y n, por lo que ser perpendicular a ellos:

r =uv

El siguiente paso es normalizarlo, pues se tomar como vector cannico para la


matriz de rotacin del billboard.
En caso de haber tomado como vector jo el vector normal n (como representa el
caso b) de la Figura 26.2), se calcula el nuevo vector u mediante:

u = n r

En el caso de haber sido el vector up u el escogido como jo (caso c) de la


Figura 26.2), la ecuacin es la que sigue: Figura 26.3: Screenshot de bill-
board alineado con la pantalla. Ob-
tenido del videojuego libre Air Ri-
n = r u vals.

El nuevo vector es normalizado, y ya se podra obtener la matriz de rotacin del


billboard. El criterio para escoger un vector como jo para calcular la orientacin del
billboard depende del tipo de este, y del efecto que se quiera obtener. A continuacin
se explican los tres tipos bsicos de billboard existentes.
26.1. Fundamentos [729]

Billboard alineado a la pantalla

Estos son los tipos ms simples de billboard. Su vector up u siempre coincide con
el de la cmara, mientras que el vector normal n se toma como el inverso del vector
normal de la cmara (hacia donde la cmara est mirando). Estos dos vectores son
siempre perpendiculares, por lo que no es necesario recalcular ninguno con el mtodo
anterior. La matriz de rotacin de estos billboard es la misma para todos.
Por lo tanto estos billboard siempre estarn alienados con la pantalla, aun cuando
la cmara realice giros sobre el eje Z (roll), como se puede apreciar en la Figura 26.3.
Esta tcnica tambin puede utilizarse para sprites circulares como partculas.

Billboard orientado en el espacio

En el caso de tratarse de un objeto fsico, en el que el vector up debe corresponder


con el vector up del mundo, el tipo anterior no es el apropiado. En este caso, el vector
up del billboard no es de la cmara, si no el del mundo virtual, mientras que el vector
normal sigue siento la inversa del vector hacia donde mira la cmara. En este caso el
vector jo es el normal, mientras que el que se recalcula es el vector up.
Sin embargo, utilizar esta misma matriz de rotacin para todos los billboard puede
dar lugar a imprecisiones. Segn se ha explicado, estos tipos de billboard se mostraran
como en el caso a) de la Figura 26.5. Al estar lo sucientemente cerca de la cmara
puede sufrir una distorsin debido a la perspectiva del punto de vista.
La solucin a esta distorsin son los billboard orientados al punto de vista. El
vector up seguira siendo el mismo (el vector up del mundo), mientras que el vector
normal es el que une el centro del billboard con la posicin de la cmara. De esta
forma, cada billboard tendra su propia matriz de rotacin, y dicha distorsin no
existira. En el caso b) de la Figura 26.5 se aprecia este ltimo tipo.
Evidentemente, este tipo de billboard es menos eciente, ya que cada uno debe
calcular su propia matriz de rotacin. Para paliar este aumento de consumo de
Figura 26.4: Ejemplo de utiliza- tiempo de cmputo, podra implementarse dos niveles de billboard dependiendo de
cin de billboards orientados en el la distancia de estos a la cmara. Si estn lo sucientemente lejos, la distorsin es
espacio. mnima y pueden utilizarse los primeros, mientras que a partir de cierta distancia se
aplicaran los billboard orientados al punto de vista.
Los billboard orientados en el espacio son muy tiles para la representacin de
llamas, humo, explosiones o nubes. Una tcnica que se suele utilizar es aadir una
textura animada a un billboard, y luego crear de forma catica y aleatoria instancias
de este billboard, cambiando parmetros como el tamao o la transparencia. De esta
forma se elimina la sensacin de bucle repetitivo en la animacin. Este es un mtodo
muy comn para representar algunos sistemas de partculas. En [101] se describe la
tcnica utilizada para la implementacin de las nubes en Microsoft Flight Simulator.
Un inconveniente de esta forma de implementar sistemas de partculas es cuando
intersectan con objetos con geometra real. Al realizarse esta interseccin se pierde la
ilusin. Para ello se implementa un fade-out, que consiste en hacer que el billboard
sea ms transparente a medida que se acerca a cualquier objeto. Este tipo de billboards
se denominan soft particles.
C26

En [7] se pueden encontrar ms tcnicas utilizadas para dar ms sensacin de


realismo a estos billboard.
[730] CAPTULO 26. REPRESENTACIN AVANZADA

a) b)

Cmara Cmara

Figura 26.5: Billboards con el mismo vector normal que la cmara en a), mientras que en b) son orientados
al punto de vista.

Billboard axial

El ltimo tipo de billboard son los axiales. Estos no miran directamente a la


cmara, simplemente giran alrededor de un eje jo denido, normalmente su vector
up. Este tcnica suele utilizarse para representar rboles lejanos, como el de la
Figura 26.1. En este caso el vector jo es el vector up, mientras que el recalculado
es el normal.
El mayor problema con este tipo de billboard es que si la cmara se sita justo
encima del billboard (en algn punto de su eje de rotacin), la ilusin desapareca al no
mostrarse el billboard. Una posible solucin es aadir otro billboard horizontal al eje
de rotacin. Otra sera utilizar el billboard cuando el modelo est lo sucientemente
lejos, y cambiar a geometra tridimensional cuando se acerque.

26.1.2. Sistemas de partculas


Como se ha visto en la seccin anterior, con los billboard se pueden representar de
forma eciente y visualmente efectiva muchos tipos de elementos como nubes, humo
o llamas. A continuacin se va a explicar de forma ms concreta en qu consiste un
sistema de partculas y qu tcnicas se utilizan para implementarlos.
Segn [73], un sistema de partculas es un conjunto de pequeos objetos separados
en movimiento de acuerdo a algn algoritmo. Su objetivo principal es la simulacin
de fuego, humo, explosiones, ujos de agua, rboles, etc.
Las tareas tpicas de un sistema de partculas incluyen la creacin, puesta en
movimiento, transformacin y eliminado de dichas partculas durante sus diferentes
periodos de vida. Sin embargo, la que ms nos interesa es la representacin de dichas
partculas.
Dependiendo del elemento que se quiera representar, las partculas pueden ser
mostradas como simples puntos, lneas o incluso billboards. Algunas bibliotecas
grcas como DirectX da soporte para la representacin de puntos, y eliminar as
la necesidad de crear un billboard con un polgono.
Figura 26.6: Sistema de partculas
Algunos sistemas de partculas pueden implementarse mediante el vertex shader, simulando una llamarada.
para calcular la posicin de las distintas partculas y as delegar esa parte de cmputo
a la GPU. Adems puede realizar otras tareas como deteccin de colisiones.
Elementos de vegetacin como hierba o rboles pueden realizarse mediante estas
partculas, y determinar la cantidad de estas dependiendo de la distancia de la cmara,
todo mediante el geometry shader.
26.2. Uso de Billboards [731]

Figura 26.8: Modelado de un helicptero mediante nube de billboards.

A continuacin se explican con ms detalle dos conceptos bsicos utilizados en


los sistemas de partculas: los impostors y las nubes de billboards.

Impostors

Un impostor es un billboard cuya textura es renderizada en tiempo de ejecucin


a partir de la geometra de un objeto ms complejo desde la posicin actual de la
cmara. El proceso de rendering es proporcional al nmero de pxeles que el impostor
ocupa en la pantalla, por lo que es mucho ms eciente. Un buen uso para estos
impostors es para representar un elemento que est compuesto por muchos objetos
pequeos iguales, o para objetos muy lejanos. Adems, dependiendo de la distancia,
la frecuencia con la que se renderizan esos objetos es menor.
Una ventaja importante de los impostors es el poder aadir un desenfoque a la
textura de forma rpida para poder dar la sensacin de profundidad de campo.

Nubes de Billboards

El problema de los impostors es que estos deben continuar orientadas a la


Figura 26.7: Ejemplo de utiliza- cmara, por lo que si esta hace un movimiento el impostor debe ser renderizado de
cin de impostors utilizando Ogre. nuevo. Las nubes de billboards consiste en representar un modelo complejo como
un conjunto pequeo de billboards superpuestos. A cada uno de ellos se les puede
aplicar un material y propiedades diferentes, para conseguir un buen resultado con
un alto rendimiento. En la Figura 26.8 se puede apreciar el modelo geomtrico de un
helicptero (izquierda), su correspondiente representacin con una nube de billboards
(centro) y la descomposicin de cada billboard (derecha). Este ejemplo ha sido tomado
de [26].
Algunas aproximaciones propuestas sugieren utilizar parte del modelo como
geometra convencional, y para el resto utilizar una nube de billboards. Por ejemplo,
hacer el tronco de un arbol geomtricamente y aadir billboards para las hojas.

26.2. Uso de Billboards


C26

Billboards individuales
Tambin es posible controlar la En Ogre, no existe un elemento billboard por s slo que se pueda representar.
representacin individual de cada Estos deben pertenecer a un objeto de la clase BillboardSet. Esta clase se encarga de
Billboard dentro de un Billboard- gestionar los diferentes billboards que estn contenidos en ella.
Set, pero la penalizacin en rendi-
miento es mucho mayor. En la ma-
yora de los casos es ms eciente
crear distintos BillboardSets.
[732] CAPTULO 26. REPRESENTACIN AVANZADA

Todos los Billboard que pertenezcan a un BillboardSet deben ser idnticos


en cuanto a tamao y material. Este es un requerimiento semi-obligatorio por
cuestiones de eciencia. Una vez aadidos, es posible cambiar los Billboard
individualmente, aunque esto se desaconseja debido a la penalizacin en el
rendimiento, a no ser que haya una buena razn (por ejemplo, volmenes de
humo que se expanden).

Ogre tratar el BillboardSet como un elemento nico: o se representan todos


los Billboard contenidos, o no se representa ninguno. El posicionamiento de los
Billboards se realiza relativo a la posicin del SceneNode al que pertenece el
BillboardSet.
La forma ms comn de crear un BillboardSet es indicando el nmero de Billboard
que son necesarios en el constructor del BillboardSet. Cada vez que queramos crear
un Billboard mediante el mtodo createBillboard de BillboardSet, se nos devolver
uno. Una vez que se agote la capacidad el mtodo devolver NULL.
En el siguiente cdigo vemos un ejemplo sencillo de una escena con tres Capacidad dinmica
billboards. Como se ha explicado anteriormente, para poder crear Billboards es
necesario que estos pertenezcan a un BillboardSet. En la lnea 2 creamos uno, con Se puede indicar al BillboardSet
que aumente de forma dinmica su
nombre BillboardSet y con capacidad para tres Billboard. Por defecto, el tipo de

Billboard espoint.
capacidad. As, cada vez que se pi-
Ms adelante se explicarn los tres tipos bsicos que ofrece Ogre.
En la lnea 3 se asigna un material a esos Billboard. En este caso,
da un Billboard y no quede nin-
el material se
guno, se aumentar la capacidad al
encuentra descrito en un chero llamado Cloud.material. En la lnea 4 se especica
el doble automticamente. Este mto-
tamao del rectngulo que denirn cada uno de los Billboard. En la lnea 5 se activa
do es potencialmente peligroso, so-
bretodo si se utiliza en algn tipo de
la opcin de que Ogre ordene automticamente los Billboard segn su distancia a la bucle.
cmara, para que, en caso de que el material tenga transparencia, no se superpongan
unos encima de otros y causen un efecto indeseado.

Listado 26.1: Primer ejemplo con Billboards


1 void MyApp::createScene() {
2 Ogre::BillboardSet* billboardSet = _sceneManager->
createBillboardSet("BillboardSet",3);
3 billboardSet->setMaterialName("Cloud");
4 billboardSet->setDefaultDimensions(10.,10.);
5 billboardSet->setSortingEnabled(true);
6
7 billboardSet->createBillboard(Ogre::Vector3(0,0,0));
8 billboardSet->createBillboard(Ogre::Vector3(50,0,50));
9 billboardSet->createBillboard(Ogre::Vector3(-50,0,-50));
10
11 Ogre::SceneNode* node1 = _sceneManager->createSceneNode("Node1");
12 node1->attachObject(billboardSet);
13 _sceneManager->getRootSceneNode()->addChild(node1);
14 }


De las lneas 7-9 se crean tantos Billboard como capacidad se di al BillboardSet
en el momento de su creacin. A cada uno de ellos se le indica la posicin
relativa al
SceneNode al que pertenece el BillboardSet. Por ltimo, en las lneas 11-13 se crea
un nodo y se adjunta el BillboardSet a l.
26.2. Uso de Billboards [733]

26.2.1. Tipos de Billboard


Ogre, por defecto, al crear un Billboard lo crea utilizando el tipo point billboard.
Este tipo se puede cambiar a travs del mtodo BillboardSet::setBillboardType, y
recibe un argumento del tipo BillboardType. Los tres tipos bsicos existentes son:

Point Billboard: se indican con el valor BBT_POINT, y se corresponden con


los Billboards alineados en la pantalla. Se trata del tipo ms simple, y no es
necesario indicar ningn parmetro adicional.
Oriented Billboard: se indican con los valores:
BBT_ORIENTED_COMMON
BBT_ORIENTED_SELF
Se corresponden con los Billboard axiales. Es necesario indicar un eje sobre
el cul girar (en el ejemplo de los rboles, se corresponde con el tronco). En
caso de utilizarse la opcin Oriented Common, este vector se indica mediante el
mtodo BillboardSet::setCommonDirection, y todos los Billboard del conjunto
lo utilizarn. En el caso de utilizar Oriented Self, cada Billboard podr tener su
propio tronco, y se especica en la variable pblica Billboard::mDirection de
cada Billboard.
Perpendicular Billboard: se indican con los valores:
BBT_PERPENDICULAR_COMMON
BBT_PERPENDICULAR_SELF
Se corresponde con los Billboard orientados en el espacio. Siempre apuntan a la
cmara, pero al contrario que con los Billboard alineados en la pantalla, el vector
up no tiene por qu ser el mismo que el de ella. En cualquier caso es necesario
indicar un vector up mediante la llamada Billboard::setCommonUpVector. Tam-
bin se debe indicar un vector de direccin. En caso de haber escogido Perpendi-
cular Common se indica mediante la llamada BillboardSet::setCommonDirection.
En caso de haber escogido Perpendicular Self, se almacena en la variable pbli-
ca Billboard::mDirection. Este vector se escoge como jo, y se recalcula el vec-
tor up, segn el mtodo explicado en la primera seccin. Este vector suele ser el
inverso al vector de direccin de la cmara, o el vector con origen en la posicin
del Billboard y destino la posicin de la cmara. Es importante normalizarlo.

En el caso de los tipos BBT_PERPENDICULAR_COMMON,


BBT_PERPENDICULAR_SELF y BBT_ORIENTED_SELF es necesa-
rio actualizar los valores de los vectores segn la posicin actual de la cmara
en cada frame. Para ello se debe recuperar los Billboard desde el mtodo
frameStarted del FrameListener, y actualizarlos segn el valor escogido.

C26

26.2.2. Aplicando texturas


Hasta ahora se ha explicado que todos los Billboard de un mismo BillboardSet
deben tener el mismo tamao y el mismo material. Sin embargo, exite la opcin de
indicar a cada Billboard qu porcin de la textura del material puede representar. De
esta forma, se puede indicar una textura que est dividida en las y en columnas con
varias subtexturas, y asignar a cada Billboard una de ellas.
[734] CAPTULO 26. REPRESENTACIN AVANZADA

En la Figura 26.9 se observa un ejemplo de textura subdividida. Las coordenadas


estn contenidas en el rango de 0 a 1.
En el siguiente cdigo se va a utilizar esta tcnica para que, cada uno de los (0,0) (0.5,0) (1,0)

Billboard, muestren un trozo de la textura asociada. En 2-9 se crea el escenario y


un BillboardSet de tipo point y un material asociado llamado ABC, como la de la
Figura 26.9. De las lneas 11-19 se instancian los 4 Billboard. Se ha declarado el
A B
(0,0.5) (0.5,0.5) (1,0.5)
puntero a Billboard b para poder referenciar cada uno de los Billboard segn se crean
y poder indicar las coordenadas de las texturas asociadas. Esto se hace mediante el
mtodo Billboard::setTexcoordRect, al cual se le pasa un objeto del tipo FloatRec,
C D
indicando las coordenadas de la esquina superior izquierda y de la esquina inferior (0,1) (0.5,1) (1,1)

Losvalores de esa esquina estn en el rango de 0 a 1. Para terminar, de las


derecha.
lneas 21-23 se crea el nodo y se adjunta el BillboardSet. Figura 26.9: Ejemplo de subdivi-
sin de una textura.

Listado 26.2: Ejemplo de coordenadas de texturas.


1 void MyApp::createScene() {
2 /* Preparing simbad and ground */
3 /* Creating Sinbad and Ground ...*/
4
5 /* ABC BillboardSet*/
6 Ogre::BillboardSet* abcBillboardSet = _sceneManager->
createBillboardSet("AbcBillboardSet",4);
7 abcBillboardSet->setBillboardType(Ogre::BBT_POINT);
8 abcBillboardSet->setMaterialName("ABC");
9 abcBillboardSet->setDefaultDimensions(7.,7);
10
11 Ogre::Billboard* b;
12 b = abcBillboardSet->createBillboard(Ogre::Vector3(-15,0,0));
13 b->setTexcoordRect(Ogre::FloatRect(0.,0.,0.5,0.5));
14 b = abcBillboardSet->createBillboard(Ogre::Vector3(-5,0,0));
15 b->setTexcoordRect(Ogre::FloatRect(0.5,0.,1.,0.5));
16 b = abcBillboardSet->createBillboard(Ogre::Vector3(5,0,0));
17 b->setTexcoordRect(Ogre::FloatRect(0.,0.5,0.5,1.));
18 b = abcBillboardSet->createBillboard(Ogre::Vector3(15,0,0));
19 b->setTexcoordRect(Ogre::FloatRect(0.5,0.5,1.,1.));
20
21 Ogre::SceneNode* abcNameNode =_sceneManager->getRootSceneNode()->
createChildSceneNode("AbcNameNode");
22 abcNameNode->setPosition(0,10,0);
23 abcNameNode->attachObject(abcBillboardSet);
24 }

26.3. Uso de Sistemas de Partculas


Los sistemas de partculas en Ogre se implementan tpicamente mediante scripts,
aunque cualquier funcionalidad se puede realizar tambin por cdigo. La extensin de
los script que denen las plantillas de estos sistemas es .particle. Son plantillas porque
en ellas se denen sus caractersticas, y luego se pueden instanciar tanto sistemas como
se desee. Es decir, se puede crear un chero .particle para denir un tipo de explosin,
y luego instanciar cualquier nmero de ellas.
Los sistemas de partculas son entidades que se enlazan a SceneNodes, por lo
que estn sujetos a la orientacin y posicionamiento de estos. Una vez que se han Figura 26.10: Resultado del ejem-
emitido las partculas, estas pasan a formar parte de la escena, por lo que si se mueve plo de Billboard con coordenadas
el punto de emisin del sistema, las partculas no se vern afectadas, quedando en el de texturas.
26.3. Uso de Sistemas de Partculas [735]

mismo sitio. Esto es interesante si se quiere dejar una estela, por ejemplo, de humo.
Si se desea que las partculas ya creadas se trasladen con el nodo al que pertenece
el sistema, se puede indicar que el posicionamiento se haga referente al sistema de
coordenadas local.

Los sistemas de partculas deben tener siempre una cantidad lmite de estas, o
quota. Una vez alcanzado esta cantidad, el sistema dejar de emitir hasta que
se eliminen algunas de las partculas antiguas. Las partculas tienen un lmite
de vida congurable para ser eliminadas. Por defecto, este valor de quota es
10, por lo que puede interesar al usuario indicar un valor mayor en la plantilla.

Eciencia ante todo Ogre necesita calcular el espacio fsico que ocupa un sistema de partculas (su
Los sistemas de partculas pueden BoundingBox) de forma regular. Esto es computacionalmente costoso, por lo que
rpidamente convertirse en una par- por defecto, deja de recalcularlo pasados 10 segundos. Este comportamiento se
te muy agresiva que requiere mucho puede congurar mediante el mtodo ParticleSystem::setBoundsAutoUpdated(), el
tiempo de cmputo. Es importante
dedicar el tiempo suciente a opti-
cual recibe como parmetro los segundos que debe recalcular la BoundingBox. Si
mizarlos, por el bien del rendimien- se conoce de antemano el tamao aproximado del espacio que ocupa un sistema de
to de la aplicacin. partculas, se puede indicar a Ogre que no realice este clculo, y se le indica el tamao
jo mediante el mtodo ParticleSystem::setBounds(). De esta forma se ahorra mucho
tiempo de procesamiento. Se puede alcanzar un compromiso indicando un tamao
inicial aproximado, y luego dejando a Ogre que lo recalcule durante poco tiempo
pasados algunos segundos desde la creacin del sistema.
A continuacin se describen los dos elementos bsicos que denen los sistemas de
partculas en Ogre: los emisores y los efectores.

26.3.1. Emisores
Los emisores denen los objetos que literalmente emiten las partculas a la escena.
Los distintos emisores que proporciona Ogre son:

Puntos: point. Todas las partculas son emitidas desde un mismo punto.
Caja: box. Las partculas son emitidas desde cualquier punto dentro de un
volumen rectangular.
Cilindro: cylinder. Las partculas son emitidas desde un volumen cilndrico
denido.
Elipsoide: ellipsoid. Las partculas se emiten desde un volumen elipsoidal.
Supercie de elipsoide: hollow elipsoid. Las partculas se emiten desde la
supercie de un volumen elipsoidal.
Anillo: ring. Las partculas son emitidas desde los bordes de un anillo.

La velocidad, frecuencia y direccin de emisin de las partculas es completamen-


C26
te congurable. Estos emisores se posicionan de forma relativa al SceneNode al que
pertenecen.
Las partculas no son emitidas en una lnea recta. Se debe especicar un ngulo
mediante el parmetro angle para denir el cono de emisin. Un valor 0 indica que se
emiten en lnea recta, mientras que un valor de 180 signica que se emite en cualquier
direccin. Un valor de 90 implica que se emiten de forma aleatoria en el hemisferio
centrado en el vector de direccin.
[736] CAPTULO 26. REPRESENTACIN AVANZADA

Otros parmetros que se pueden congurar son la frecuencia de emisin (partcu-


las/segundo), la velocidad (puede ser una velocidad establecida o aleatoria para cada
partcula), el tiempo de vida o TTL (Time To Live) (denido o aleatorio), y el tiempo
de emisin del sistema. Ms adelante se mostrarn ejemplos de uso de estos parme-
tros.

26.3.2. Efectores
Los efectores o affectors realizan cambios sobre los sistemas de partculas. Estos
cambios pueden ser en su direccin, tiempo de vida, color, etc. A continuacin se
explican cada uno de los efectores que ofrece Ogre.

LinearForce: aplica una fuerza a las partculas del sistema. Esta fuerza
se indica mediante un vector, cuya direccin equivale a la direccin de la
fuerza, y su mdulo equivale a la magnitud de la fuerza. La aplicacin de
una fuerza puede resultar en un incremento enorme de la velocidad, por lo
que se dispone de un parmetro, force_application para controlar esto. El
valor force_application average ajusta el valor de la fuerza para estabilizar
la velocidad de las partculas a la media entre la magnitud de la fuerza y la
velocidad actual de estas. Por el contrario, force_application add deja que la
velocidad aumente o se reduzca sin control.
ColourFader: modica el color de una partcula mientras sta exista. El
valor suministrado a este modicador signica cantidad de cambio de una
componente de color en funcin del tiempo. Por lo tanto, un valor de red -0.5
decrementar la componente del color rojo en 0.5 cada segundo.
ColourFader2: es similar a ColourFader, excepto que el modicador puede ColourFader
cambiar de comportamiento pasada una determinada cantidad de tiempo. Por Un valor de -0.5 no quiere decir que
ejemplo, el color de una partcula puede decrementarse suavemente hasta el se reduzca a la mitad cada segundo,
50 % de su valor, y luego caer en picado hasta 0. y por lo tanto, nunca alcanzar el
valor de 0. Signica de al valor
de la componente (perteneciente al
ColourInterpolator: es similar a ColourFader2, slo que se pueden especicar intervalo de 0 a 1) se le restar 0.5.
hasta 6 cambios de comportamiento distintos. Se puede ver como una generali- Por lo tanto a un valor de blanco
zacin de los otros dos modicadores. (1) se reducir a negro (0) en dos
segundos.
Scaler: este modicador cambia de forma proporcional el tamao de la partcula
en funcin del tiempo.
Rotator: rota la textura de la partcula por bien un ngulo aleatorio, o a una
velocidad aleatoria. Estos dos parmetros son denidos dentro de un rango (por
defecto 0).
ColourImage: este modicador cambia el color de una partcula, pero estos
valores se toman de un chero imagen (con extensin .png, .jpg, etc.). Los
valores de los pxeles se leen de arriba a abajo y de izquierda a derecha. Por
lo tanto, el valor de la esquina de arriba a la izquierda ser el color inicial, y el
de abajo a la derecha el nal.
26.3. Uso de Sistemas de Partculas [737]

26.3.3. Ejemplos de Sistemas de Partculas

Para poder utilizar los sistemas de partculas en Ogre, es necesario editar


el chero plugins.cfg y aadir la lnea Plugin=Plugin_ParticleFX para que
pueda encontrar el plugin. En caso de utilizarse en Windows, hay que
asegurarse de tener la biblioteca Plugin_ParticleFX.dll en el directorio de
plugins del proyecto, o la biblioteca Plugin_ParticleFX.so en caso de sistemas
UNIX

Este primer ejemplo ilustra un anillo de fuego. A continuacin se explica paso


a paso cmo se instancia el sistema de partculas y qu signican cada uno de los
campos del script que lo dene.

Listado 26.3: Instancia de un sistema de partculas.


1 void MyApp::createScene() {
2 Ogre::ParticleSystem* ps = _sceneManager->createParticleSystem("
Ps","ringOfFire");
3
4 Ogre::SceneNode* psNode = _sceneManager->getRootSceneNode()->
createChildSceneNode("PsNode");
5 psNode->attachObject(ps);
6 }

crear un sistema de partculas en cdigo no es nada


Como se puede observar,
complicado. En la lnea 2 se crea un objeto de tipo ParticleSystem, indicando su
y el nombre del script que lo dene, en este caso ringOfFire. En las lneas
nombre
4-5 se crea un SceneNode y se aade a l.
El siguiente es el script que dene realmente las propiedades del sistema de
partculas.
parti-
Los scripts de sistemas de partculas comienzan con la palabra reservada
seguido del nombre del sistema, como se indica en la lnea 1 . De las
cle_system
lneas 3-6 se indican varios parmetros generales del sistema:
Figura 26.11: Captura de pantalla
del sistema de partculas RingOfFi-
re
quota: Indica el nmero mximo de partculas que pueden haber vivas en un
momento. Si el nmero de partculas alcanza esta cantidad, no se crearn ms
partculas hasta que mueran otras.
material: indica el material de cada partcula. Cada una de las partculas es, por
defecto, un Billboard como los estudiados anteriormente. Este material indica
qu textura se representar en cada uno de ellos.
particle_width: indica el ancho de cada partcula.
particle_height: indica el alto de cara partcula.


A continuacin se declaran tantos emisores y modicadores como se deseen. En
las lneas 8-19 se declara un emisor del tipo anillo. Los parmetros especicados para
C26

Y an hay ms!
este emisor son:
Existen multitud de parmetros pa-
ra congurar los sistemas de part- angle: dene el ngulo de apertura con el que las partculas salen disparadas.
culas, emisores y modicadores en
Ogre. En la API ocial se detallan direction: indica la direccin de salida d e las partculas, teniendo en cuenta
todos y cada uno de estos parme- el ngulo. En realidad, el par direccin-ngulo dene un cono por el cual las
tros.
partculas se crean.
[738] CAPTULO 26. REPRESENTACIN AVANZADA

Listado 26.4: Script para el sistema ringOfFire


1 particle_system ringOfFire
2 {
3 quota 1000
4 material explosion
5 particle_width 10
6 particle_height 10
7
8 emitter Ring
9 {
10 angle 10
11 direction 0 1 0
12 emission_rate 250
13 velocity_min 3
14 velocity_max 11
15 time_to_live 3
16 width 30
17 height 30
18 depth 2
19 }
20
21 affector ColourFader
22 {
23 red -0.5
24 green -0.5
25 blue -0.25
26 }
27 }

emission_rate: indica el ratio de partculas por segundo emitidas.


velocity_min: velocidad mnima inicial de las partclas.
velocity_max: velocidad mxima inicial de las partculas.
time_to_live tiempo de vida de una partcula.
width: determina el ancho del anillo.
height: determina el alto del anillo.
depth: determina la profundidad del anillo.

Adems se ha declarado un modicado del tipo ColourFader que cambia el color


de las partculas en el tiempo. Concretamente, por cada segundo que pasa decrementa
la componente del color rojo en 0.5, la del color verde en 0.5 y la del color azul en
0.25.
26.4. Introduccin a los Shaders [739]

Ao Tarjeta Grca Hito


1987 IBM VGA Provee un pixel framebuffer que la CPU debe encargarse de llenar.
1996 3dfx Voodoo Rasteriza y texturiza tringulos con vrtices pre-transformados.
1999 NVIDIA GeForce 256 Aplica tanto transformaciones, como iluminacin a los vrtices. Usa una xed
function pipeline.
2001 NVIDIA GeForce 3 Incluye pixel shader congurable y vertex shader completamente programable.
2003 NVIDIA GeForce FX Primera tarjeta con shaders completamente programables.

Cuadro 26.1: Evolucin del hardware de grco para PC.

26.4. Introduccin a los Shaders


A lo largo de las siguiente pginas se introducir, y se ensear a manejar,
una de las herramientas ms poderosas que existen a la hora de sacar partido de
nuestro hardware grco: los shaders. Descubriremos cmo gracias a estos pequeos
fragmentos de cdigo nos hacemos con todo el control sobre el dibujado de nuestras
escenas, a la vez que se abre ante nosotros la posibilidad de aadir una gran variedad
de efectos que, hasta no hace mucho, eran imposibles en aplicaciones grcas
interactivas.
Para comenzar a entender lo que son los shaders es conveniente acercarse primero
a la problemtica que resuelven y, para ello, en la siguiente seccin se har un pequeo
repaso a la historia de la generacin de grcos por computador, tanto en su vertiente
interactiva como en la no interactiva, puesto que la historia de los shaders no es ms
que la historia de la lucha por conseguir mejorar y controlar a nuestro gusto el proceso
de generacin de imgenes por ordenador.

26.4.1. Un poco de historia


Nuestra narraccin comienza en los aos 80, una poca que hoy en da a algunos
les puede parecer como un tiempo mejor, pero que denitivamente no lo era, sobre
todo si queras dedicarte al desarrollo de aplicaciones grcas interactivas.
En estos aos el desarrollo de este tipo de aplicacines era realmente complicado
y no slo por la poca capacidad de las mquinas de la poca, si no tambin por la
falta de estandarizacin que exista en los APIs grcos, lo que provocaba que cada
hardware necesitara de su propio software para poder ser usado.
Este contexto haca evidente que los grcos 3D tardaran todava un poco en
llegar a ser algo comn en los ordenadores personales y aun ms si hablamos de
moverlos en tiempo real. Es por tanto fcil comprender por qu los mayores avances
en este campo estuvieron enfocados sobre todo hacia el renderizado no interactivo.
En aquellos tiempos la principal utilidad que tena la informtica grca eran la
investigacin y el desarrollo de CGI (Computer Generated Imagery) para anuncios
y pelculas.
Es en este punto donde la historia de los shaders comienza, en una pequea
empresa conocida como LucasFilms a principios de los aos 80. Por esas fechas el
C26

estudio decidi contratar programadores grcos para que, comandados por Edwin
Catmull, empezaran a informatizar la industria de los efectos especiales. Casi nada.
Aquel grupo de pioneros se embarc en varios proyectos diferentes, logrando que
cada uno de ellos desembocara en cosas muy interesantes. Uno de estos proyectillos
acab siendo el germen de lo que ms adelante se conocera como Pixar Animation
Studios. Y fue en esta compaa donde, durante el desarrollo del API abierto RISpec
[740] CAPTULO 26. REPRESENTACIN AVANZADA

(RenderMan Interface Specication), se cre el concepto de shader. El propsito


de RISpec era la descripcin de escenas 3D para convertirlas en imgenes digitales
fotorealistas. En este API se incluy el RenderMan Shading Language, un lenguaje
de programacin al estilo C que permita, por primera vez, que la descripcin
de materiales de las supercies no dependiera slo de un pequeo conjunto de
parmetros, sino que pudiera ser especicada con toda libertad (no olvidemos que
tener la posibilidad de especicar cmo se hace o no se hace algo es siempre muy
valioso).
RISpec 3.0 (donde se introdujo el concepto de shader) fue publicado en Mayo
de 1988 y fue diseado con la vista puesta en el futuro para, de esta manera, poder
adaptarse a los avances en la tecnologa durante un numero signicativo de aos. A
las pelculas en que se ha usado nos remitimos para dar fe de que lo consiguieron
(http://www.pixar.com/featurefilms/index.html).
Hasta aqu el nacimiento del concepto shader, una funcionalidad incluida en
RISpec 3.0 para especicar sin cortapisas cmo se dibujan las supercies (materiales
en nuestra jerga) de los elementos de una escena 3D. Pero que ocurre con nuestro
campo de estudio?
El panorama para los grcos en tiempo real no era tan prometedor por aquel
entonces hasta que surgieron los primeros APIs estndar que abstraan el acceso al
hardware grco. En 1992 apareci OpenGL, en 1995 DirectX y, ya lanzados, surge
en 1996 la primera tarjeta grca, la 3Dfx Voodoo, que liberaba a la CPU de algunas
de las tareas que implicaban la representacin de grcos por ordenador.
Tarjeta grca y API estndarizado, estos dos ingredientes combinados ya empie-
zan a permitir la preparacin de un plato como Dios manda de grcos 3D en tiempo
real en ordenadores de escritorio. Si, 1996, este es el momento en que la cosa empieza
a ponerse seria y se establecen los actuales pipelines grcos (cuyo funcionamiento
ya se trat al principio del Mdulo 2, pero que, y hay que insistir, es vital comprender
para poder desarrollar cualquier aplicacin grca interactiva, por ello ms adelante
se vuelven a tratar).
Aun as, en los 90, tanto los APIs como el hardware ofrecan como nico pipeline Pipeline
de procesamiento grco el xed-function pipeline (FFP). El FFP permite varios tipos
de conguracin a la hora de establecer cmo se realizar el proceso de renderizado, En ingeniera del software, un pipe-
line consiste en una cadena etapas
sin embargo estas posibilidades estn predenidas y, por tanto, limitadas. De este de procesamiento, dispuestas de tal
modo, aunque el salto de calidad era evidente durante muchos aos el renderizado forma que la salida de una de estas
interactivo estuvo muchsimo ms limitado que su versin no interactiva. etapas es la entrada de la siguiente,
facilitando con ello la ejecucin en
paralelo de las etapas.

La programacin grca antes de los shaders usaba un conjunto jo de


algoritmos que, colectivamente, son conocidos como xed-function pipeline.
Este permita habilitar un conjunto prejado de caractersticas y efectos,
pudiendo manipular algunos parmetros pero, como es de esperar, con
opciones limitadas no se puede ejercer un gran control sobre lo que ocurre
en el proceso de renderizado.

En el lado no interactivo del espectro, las arquitecturas de alto rendimiento


de renderizado por software usadas para el CGI de las pelculas permita llegar
muchsimo ms lejos en cuanto a la calidad de las imagenes generadas. RenderMan
permita a los artistas y programadores grcos controlar totalmente el resultado del
renderizado mediante el uso de este simple, pero potente, lenguaje de programacin.
26.4. Introduccin a los Shaders [741]

La evolucin de la tecnologa para construir los hardware grcos y su correspon-


diente incremento en la capacidad de procesamiento acab permitiendo trasladar la
idea de RenderMan a los ordenadores domsticos en el ao 2001, el cual nos trae a
dos protagonistas de excepcin: la NVIDIA GeForce3 que introdujo por primera vez
un pipeline grco programable (aunque limitado) y DirectX 8.0 que nos di el API
para aprovechar esas capacidades.
Desde entonces, como suele pasar en la informtica, con el paso de los aos todo
fue a mejor, el hardware y los API grcos no han hecho sino dar enormes saltos hacia
delante tanto en funcionalidad como en rendimiento (un ejemplo de la evolucin de las
capacidades de las tarjetas grcas se presenta en la tabla 26.1). Lo cual nos ha llevado
al momento actual, en el que los FFP han sido casi sustituidos por los programmable-
function pipelines y sus shader para controlar el procesamiento de los grcos.
Hasta aqu el repaso a la historia de los shaders, que nos ha permitido introducir
algunos conceptos bsicos que siempre os acompaaran si os animis a seguir por este
campo: pipeline, programmable-function pipeline, xed-function pipeline. Pero...

26.4.2. Y qu es un Shader?
Una de las deniciones clsicas de shader los muestra como: piezas de cdigo,
que implementan un algoritmo para establecer como responde un punto de una
supercie a la iluminacin. Es decir sirven para establecer el color denitivo de
los pixeles que se mostrarn en pantalla.
Gestion-Recursos Como veremos esta denicin ya no es del todo correcta. Los shader actuales
En algn momento comprenderis
cumplen muchas ms funciones, ya que pueden encargarse de modicar todo tipo
que hacer videojuegos, al nal, no de parmetros (posicin de vrtices, color de pixeles e incluso generar geometra en
es ms que gestionar recursos y ba- los ms avanzados). Hoy por hoy una denicin de shader ms acertada podra ser:
lancear calidad de imagen con rapi- Un conjunto de software que especica la forma en que se renderiza un conjunto
dez de ejecucin. Por eso conocer el
funcionamiento de un motor gr-
de geometra, denicin extraida del libro GPU Gems [33], ideal para aprender
co, conocimientos altos de ingenie- sobre tcnicas avanzadas de programacin grca.
ra del software y gestin de recur-
sos en un programa software son de Desde un punto de vista de alto nivel los shader nos permiten tratar el estado de
las habilidades ms demandadas. renderizado como un recurso, lo cual los convierte en una herramienta extremadamen-
te poderosa, permitiendo que el dibujado de una aplicacin, es decir, la conguracin
del dispositivo que se encarga de ello sea casi completamente dirigido por recursos
(de la misma forma en que la geometra y las texturas son recursos externos a la apli-
cacin). Facilitando de esta manera su reusabilidad en mltiples proyectos.
Las deniciones, en mi opinin, suelen ser siempre algo esotricas para los
profanos en una materia, por ello la mejor manera de terminar de explicar qu son
y cmo funcionan estas pequeas piezas de cdigo es repasando el funcionamiento
del pipeline grco: desde la transmisin de los vrtices a la GPU, hasta la salida por
pantalla de la imagen generada.

26.4.3. Pipelines Grcos


En esta seccin se va a repasar el funcionamiento interno de los dos tipos de
pipelines grcos, para que de esta forma quede clara la forma en que trabajan los
C26

shader y cual es exctamente la funcin que desempean en el proceso de renderizado.


Adems es muy importante conocer el funcionamiento del pipeline a la hora de
desarrollar shaders y en general para cualquier aplicacin con grcos en tiempo real.
[742] CAPTULO 26. REPRESENTACIN AVANZADA

La informacin ofrecida aqu complementa a la que aparece en las secciones 1,


2 y 3 del capitulo Fundamentos de Grcos Tridimensionales del Mdulo 2, donde
se explica con mas detalle el pipeline grco y que se recomienda se repase antes de
continuar.

Por qu una GPU?

El motivo por el que se usan tarjetas grcas busca que la CPU delegue en la
medida de lo posible la mayor cantidad de trabajo en la GPU, y que as la CPU
quede liberada de trabajo. Delegar ciertas tareas en alguien o algo especializado en
algo concreto es siempre una buena idea. Existen adems dos claves de peso que
justican la decisin de usar un hardware especco para las tareas grcas. La
primera es que las CPU son procesadores de propsito general mientras que la tarea
de procesamiento de grcos tiene caractersticas muy especcas que hacen fcil
extraer esa funcionalidad aparte. La segunda es que hay mercado para ello, hoy en da
nos encontramos con que muchas aplicaciones actuales (videojuegos, simulaciones,
diseo grco, etc...) requieren de rendering interactivo con la mayor calidad posible.

Diseo de una GPU

La tarea de generar imagenes por ordenador suele implicar el proceso de un


gran nmero de elementos, por suerte se da la circunstancia de que la mayor parte
de las veces no hay dependencias entre ellos. El procesamiento de un vrtice, por
ejemplo, no depende del procesamiento de los dems o, tomando un caso en el que
quisieramos aplicar iluminacin local, se puede apreciar que la representacin de un
pixel iluminado no depende de la de los dems.
Adems el procesamiento de los grcos es ltamente paralelizable puesto que los
elementos involucrados suelen ser magnitudes vectoriales reales. Tanto la posicin,
como el color y otros elementos geomtricos se representan cmodamente mediante
vectores a los que se aplican diferentes algoritmos. Estos, generalmente, suelen ser
bastante simples y poco costosos computacionalmente (las operaciones ms comunes
en estos algoritmos son la suma, multiplicacin o el producto escalar).
En consecuencia, las GPUs se disean como Stream Processors. Estos procesa-
dores estn especializados en procesar gran cantidad de elementos en paralelo, distri-
buyndolos en pequeas unidades de procesamiento (etapas del pipeline), las cuales
disponen de operaciones para tratar con vectores de forma eciente y donde algunas
son programables o, al menos, congurables (Os suena?).

26.4.4. Fixed-Function Pipeline


Para seguir el proceso tomaremos como referencia el pipeline grco simplicado
de la gura 26.12, que representa las etapas bsicas de un xed-function pipeline. Este
tipo de pipeline no ofrece mucha libertad en la manipulacin de sus parmetros pero
aun se usa, sobre todo cuando uno quiere rapidez sobre calidad, y sirve como base
para entender el por qu son necesarias las etapas programables mediante shaders.
Para empezar la aplicacin descompone la escena en batches y se los manda al Primitiva
pipeline. As que como entrada, la primera etapa del pipeline, recibe el conjunto de
vrtices correspondientes a la geometra de la escena que va en cada batch. Una primitiva es una regin de una
supercie denida por sus vrtices.
Un vrtice en el contexto en el que nos encontramos se corresponde con la posicin La primitiva ms usada en inform-
tica grca es el tringulo, puesto
de un punto en el espacio 3D, perteneciente a una supercie, para el que se conocen que sus tres vrtices siempre de-
los valores exactos de ciertas propiedades (conocidos como componentes del vrtice). nen un plano.
Estas propiedades pueden incluir atributos como el color de la supercie (primario y
26.4. Introduccin a los Shaders [743]

Conectividad de vrtices

Fragmentos Fragmentos
Coloreados

Ensamblado Interpolacin,
Transformacin Operaciones
de Primitivas Texturizado
del Vrtice de Rasterizacin
y Rasterizacin y Coloreado

Vrtices Pxeles
Vrtices Actualizados
Transformados

Posiciones de los pxeles

Figura 26.12: Pipeline del hardware grco simplicado.

secundario, si tiene componente especular), uno o varios conjuntos de coordenadas de


textura, o su vector normal que indicar la direccin en que la supercie est orientada
con respecto al vrtice y que se usa para los clculos de iluminacin, entre otros (un
vrtice puede incluir mucha ms informacin, como se muestra en la seccin 10.1.2.2
Vertex Atributes del muy recomendable libro Game Engine Architecture [42]).
Batch Transformacin de Vrtices
Sera conveniente no olvidar jams El conjunto de vrtices primero atraviesa esta etapa de procesamiento del pipeline
que la GPU no suele recibir toda grco, en la que se realizan una serie de operaciones matemticas sobre los mismos.
la informacin de la escena de gol-
pe, sino que se le envan para dibu- Estas operaciones incluyen las transformaciones necesarias para convertir la posicin
jar varios grupos de primitivas cada del vrtice a la posicin que tendr en pantalla (Transformacin de Modelado,
vez, agrupados en la unidad de tra- Transformacin de Visualizacin y Transformacin de Proyeccin visto en seccin
bajo conocida como batch. 1.1.2 del Mdulo 2) y que ser usada por el rasterizador, la generacin de las
coordenadas de textura y el clculo de la iluminacin sobre el vrtice para conocer
su color.
Ensamblado de Primitivas y Rasterizacin
Los vrtices transformados uyen en secuencia hacia la siguiente etapa donde
el ensamblado de primitivas toma los vrtices y los une para formar las primitivas
correspondientes gracias a la informacin recibida sobre la conectividad de los
mismos (que indica cmo se ensamblan).
Textura Esta informacin se transmite a la siguiente etapa en unidades discretas conocidas
Es una matriz que almacena los va-
como batches.
lores de color de una supercie en El resultado del ensamblado da lugar a una secuencia de tringulos, lneas o
los puntos interiores de las primiti-
vas. Es una de las maneras, quizs puntos, en la cual no todos los elementos tienen porque ser procesados. A este
la que nos es ms familiar, para ob- conjunto, por lo tanto, se le pueden aplicar dos procesos que aligerarn la carga de
tener un mayor nivel de detalle en la trabajo del hardware grco.
representacin de los modelos 3D.
Por un lado las primitivas pueden ser descartadas mediante el proceso de clipping,
en el cual se selecciona slo a aquellas que caen dentro del volumen de visualizacin
C26
(la regin visible para el usuario de la escena 3D, conocido tambin como view frustum
o pirmide de visin). Y por otro lado, el rasterizador puede descartar tambin aquellas
primitivas cuya cara no est apuntando hacia el observador, mediante el proceso
conocido como culling.
[744] CAPTULO 26. REPRESENTACIN AVANZADA

Las primitivas que sobreviven a estos procesos son rasterizadas. La rasterizacin


es el proceso mediante el que se determina el conjunto de pixeles que cubren una
determinada primitiva. Polgonos, lneas y puntos son rasterizados de acuerdo a unas
reglas especicadas para cada tipo de primitiva. El resultado de la rasterizacin son
un conjunto de localizaciones de pixeles, al igual que un conjunto de fragmentos.
Es importante recalcar que no hay ninguna relacin entre el conjunto de fragmentos
generados en la rasterizacin y el nmero de vrtices que hay en una primitiva. Por
ejemplo, un tringulo que ocupe toda la pantalla provocar la creacin de miles de
fragmentos.
Framebuffer
Los framebuffer son dispositivos
Pixel o fragmento? Habitualmente se usan indistintamente los trminos grcos que ofrecen una zona de
pixel y fragmento para referirse al resultado de la fase de rasterizacin. memoria de acceso aleatorio, que
Sin embargo existen diferencias importantes entre estos dos trminos y representa cada uno de los pixeles
considero apropiado aclarar qu es cada uno. Pixel proviene de la abreviatura de la pantalla.
de picture element y representa el contenido del framebuffer en una
localizacin especca, al igual que el color, profundidad y algunos otros
valores asociados con esa localizacin. Un fragmento sin embargo representa
el estado potencialmente requerido para actualizar un pixel en particular. En
la etapa en la que nos encontramos del pipeline es mucho ms apropiado el
trmino fragmento ya que estamos ante pixeles potenciales, contienen toda la
informacin de un pixel pero son candidatos a pixel, no pixeles.

El trmino fragmento es usado porque la rasterizacin descompone cada


primitiva geomtrica, como puede ser un tringulo, en fragmentos del tamao de un
pixel por cada pixel que la primitiva cubre y al estar discretizndolo, lo fragmenta. Un
fragmento tiene asociada una localizacin para el pixel, un valor de profundidad, y un
conjunto de parmetros interpolados como son: el color primario, el color especular, y
uno o varios conjuntos de coordenadas de textura. Estos parmetros interpolados son
derivados de los parmetros incluidos en los vrtices transformados de la primitiva
que gener los fragmentos.
Como la rasterizacin ocurre con todas las primitivas y puede ser que detrs del
tringulo de ejemplo hubiera otro tringulo y se generarn fragmentos en la misma
posicin de pantalla que los que se han generado para el tringulo delantero. Pero
tranquilos, como buenos pixeles potenciales que son, guardan informacin de su
profundidad y transparencia, con lo que en las dos etapas siguientes se puede controlar
que un fragmento slo actualice su pixel correspondiente en el framebuffer si supera Scissors Test
los distintos tests de rasterizacin que tenemos disponibles. Este test permite restringir el rea
Interpolacin, texturizado y coloreado de dibujado de la pantalla, descar-
tando as, todos aquellos fragmen-
Una vez las primitivas han sido rasterizadas en una coleccin de cero o ms tos que no entren dentro.
fragmentos, la fase de interpolacin, texturizado y coloreado se dedica precisamente a
Alpha Test
eso, a interpolar los parmetros de los fragmentos como sea necesario, realizando una
secuencia de operaciones matemticas y de texturizado que determinan el color nal Permite descartar fragmentos com-
de cada fragmento. parando el valor de alpha de cada
fragmento, con un valor constante
Como complemento a la determinacin del color de los fragmentos, esta etapa especicado.
puede encargarse tambin de calcular la profundidad de cada fragmento pudiendo
descartarlos y as evitar la actualizacin del correspondiente pixel en pantalla. Debido Stencil Test
a que los fragmentos pueden ser descartados, esta etapa devuelve entre uno y cero A partir del uso del stencil buffer,
fragmentos coloreados por cada uno recibido. hace una comparacin con el frame-
buffer, y descarta aquellos fragmen-
Operaciones de Rasterizacin tos que no superen la condicin es-
pecicada. Como si usara una ms-
cara, o una plantilla, para especi-
car qu se dibuja y qu no.
26.4. Introduccin a los Shaders [745]

La fase en que se ejecutan las operaciones de rasterizacin pone en marcha una


secuencia de tests para cada cada fragmento, inmediatamente antes de actualizar
el framebuffer. Estas operaciones son una parte estndar de OpenGL y Direct3D,
e incluyen: el scissor test, alpha test, stencil test y el depth test. En ellos estn
involucrados el color nal del fragmento, su profundidad, su localizacin, as como su
valor de stencil.
Si cualquier test falla, esta etapa descarta el fragmento sin actualizar el valor de
color del pixel (sin embargo, podra ocurrir una operacin de escritura para el valor
stencil). Pasar el depth test puede reemplazar el valor de profundidad del pixel, por
el valor de profundidad del fragmento. Despues de los tests, la operacin de blending
combina el color nal del fragmento con el valor de color del pixel correspondiente.
Finlmente, con una operacin de escritura sobre el framebuffer se reemplaza el color
del pixel, por el color mezclado.
Conclusin
Depth Test Tras concluir esta serie de pasos obtenemos en el framebuffer, al fn, la imagen
generada a partir de nuestra escena 3D. La cual podr ser volcada a la pantalla, o
A partir del valor de profundidad
del fragmento establece qu frag- usada para algn otro propsito.
mento est ms cerca de la cma-
ra (podra haber fragmentos por de-
Como se puede apreciar, la libertad para inuir en el proceso de renderizado en este
lante) y, en funcin de la condicin tipo de pipeline est muy limitada. Cualquier transformacin sobre los vrtices debe
especicada lo descarta o no. hacerse mediante cdigo de la aplicacin (siendo la CPU la encargada de ejecutarlo)
y se limita a ofrecer los diferentes tests comentados, junto con algun parmetro
congurable ms, que son los que proveen algo de control sobre la forma en que
se renderizan las imagenes. Todo esto es ms que suciente para obtener resultados
decentes en muchos tipos de videojuegos. Pero acaso no nos gustan tambin los
grcos 3D alucinantes?

26.4.5. Programmable-Function Pipeline


Gracias al programmable-function pipeline muchas de las operaciones que antes
asuma la CPU, denitivamente pasan a manos de la GPU.
La gura 26.13 muestra las etapas de procesamiento de vrtices y fragmentos en
una GPU con un pipeline programable simplicado. En el, se puede apreciar que se
mantienen las mismas etapas que en el xed-function pipeline, pero se aaden tres
etapas nuevas en las que los shader se encargan de establecer cmo se procesan los
vrtices, primitivas y fragmentos.
A continuacin, en la siguiente seccin, se explica someramente cmo funcionan
estas nuevas etapas, que se corresponden con cada uno de los tipos de shader que
existen a da de hoy.

Tipos de Shader

Originalmente los shaders slo realizaban operaciones a nivel de pixel (o fragmen-


to). Los que hoy se conocen como fragment/pixel shaders. A lo largo del tiempo se
han introducido ms tipos de shader], por lo que ahora el trmino shader se ha vuelto
C26
mucho ms genrico, abarcando los tres tipos que se usan hoy en da en la generacin
de grcos en tiempo real.
Vertex Shader
[746] CAPTULO 26. REPRESENTACIN AVANZADA

Aplicacin 3D

Comandos del API 3D

API 3D:
OpenGL o
Direct3D
Lmite CPU - GPU

Comandos
Primitivas Primitivas
de la GPU Flujo de ndices Flujo de localizaciones Actualizaciones
ensambladas ensambladas
y flujo de datos. de vrtices de pxeles de pxeles

Procesador
GPU Ensamblado Rasterizacin Operaciones
Programable Framebuffer
Front End de Primitivas e Interpolacin de Raster
de Geometra

Vrtices Vrtices Fragmentos Fragmentos


pre-transformados transformados pre-transformados transformados
Procesador Procesador
Programable Programable
de Vrtices de Fragmentos

Figura 26.13: Pipeline del hardware grco programable simplicado.

El ujo de datos del procesamiento de vrtices comienza cargando los atributos


de cada vrtice (posicin, color, coordenadas de textura, etc...) en el procesador
de vrtices. Este va ejecutando las distintas operaciones secuencialmente para cada
vrtice hasta que termina. El resultado de esta etapa es un vrtice transformado
en funcin de las instruccines del shader. Despus del ensamblaje de la primitiva
geomtrica y de la rasterizacin, los valores interpolados son pasados al procesador
de fragmentos.
Como ya se ha comentado, los vertex shader tienen acceso y pueden modicar los
atributos de los vrtices. A su vez se permite a cada vrtice acceder a lo que se conoce
como variables uniformes. Estas son variables globales de slo lectura que permiten
acceder a informacin que no cambia a lo largo de una pasada del material, como
pueden ser por ejemplo, la matriz mundo/vista o la delta de tiempo (tiempo pasado
entre una vuelta del bucle principal y otra).
Un vertex shader recibe como entrada un vrtice y devuelve siempre un slo
vrtice, es decir, no puede crear nueva geometra.

Cada vertex shader afecta a un slo vrtice, es decir, se opera sobre vrtices
individuales no sobre colecciones de ellos. A su vez, tampoco se tiene acceso
a la informacin sobre otros vrtices, ni siquiera a los que forman parte de su
propia primitiva.

En el listado de cdigo 26.5 se muestra un ejemplo de este tipo de shaders. Cmo


se puede ver, la sintaxis es muy parecida a la de un programa escrito en C.
26.4. Introduccin a los Shaders [747]

Listado 26.5: Ejemplo de Vertex Shader


1 // Estructura de salida con la informacin del vertice procesado
2 struct tVOutput {
3 float4 position: POSITION;
4 float4 color : COLOR;
5 };
6
7 // Usamos la posicin y color del vrtice
8 tVOutput v_color_passthrough(
9 float4 position : POSITION,
10 float4 color : COLOR
11 uniform float4x4 worldViewMatrix) {
12 tVOutput OUT;
13 // Transformacin del vrtice
14 OUT.position = mul(worldViewMatrix, position);
15 OUT.color = color;
16 return OUT;
17 }

El fragmento de cdigo es bastante explicativo por si mismo porque, bsicamente,


no hace nada con el vrtice. Recibe algunos parmetros (color y posicin) y los
devuelve. La nica operacin realizada tiene que ver con transformar la posicin del
vrtice a coordenadas de cmara.
Fragment Shader
Los procesadores de fragmentos requieren del mismo tipo de operaciones que los
procesadors de vrtices con la salvedad de que tenemos acceso a las operaciones
de texturizado. Estas operaciones permiten al procesador acceder a la imagen, o
imagenes, usadas de textura y permiten manipular sus valores.
Los fragment shader tienen como propsito modicar cada fragmento individual
que les es suministrado desde la etapa de rasterizacion.
Estos tienen acceso a informacin como es: la posicin del fragmento, los datos
interpolados en la rasterizacion (color, profundidad, coordenadas de textura), as como
a la textura que use la primitiva a la cual pertenece (en forma de variable uniforme,
como los vertex shader), pudiendo realizar operaciones sobre todos estos atributos.

Al igual que los vertex shader, slo puede procesar un fragmento cada vez y
no puede inuir sobre, ni tener acceso a, ningn otro fragmento.

Listado 26.6: Ejemplo de Fragment Shader C26


1 // Estructura de salida con la informacin del fragmento procesado
2 struct tFOutput {
3 float4 color : COLOR;
4 };
5
6 // Devuelve el color interpolado de cada fragmento
7 tFOutput f_color_passthrough(
8 float4 color : COLOR)
9 {
[748] CAPTULO 26. REPRESENTACIN AVANZADA

10 tFOutput OUT;
11 // Se aplica el color al fragmento
12 OUT.color = color;
13
14 return OUT;
15 }

En el listado de cdigo 26.6 se muestra un ejemplo de este tipo de shaders.


Geometry Shader
Este es el ms nuevo de los tres tipos de shader. Puede modicar la geometra
e incluso generar nueva de forma procedural. Al ser este un tipo de shader muy
reciente todava no est completamente soportado por las tarjetas grcas y aun no
se ha extendido lo suciente.
La etapa encargada de procesar la geometra estara enclavada entre las etapas de
ensamblado de primitivas y la de rasterizacin e interpolacin (ver Figura 26.13).
Este tipo de shader recibe la primitiva ensamblada y, al contrario que los vertex
shader, si tiene conocimiento completo de la misma. Para cada primitiva de entrada
tiene acceso a todos los vrtices, as como a la informacin sobre cmo se conectan.
En esta seccin no se tratarn, pero cualquiera que lo desee puede encontrar ms
informacin al respecto en el captulo 3 del libro Real-Time Rendering [8].

26.4.6. Aplicaciones de los Shader


Existen mltiples y diferentes aplicaciones para los shaders. En esta seccin se
enumeran algunas de las funciones que pueden ayudar a cumplir.

Vertex Skinning

Los vrtices de una supercie, al igual que el cuerpo humano, son movidos a causa
de la inuencia de una estructura esqueltica. Como complemento, se puede aplicar
una deformacin extra para simular la dinmica de la forma de un msculo.
En este caso el shader ayuda a establecer cmo los vrtices se ven afectados por
el esqueleto y aplica las transformaciones en consecuencia.

Figura 26.14: Ejemplos de skinning (Fuente: Wikipedia).


26.4. Introduccin a los Shaders [749]

Vertex Displacement Mapping

Los vrtices pueden ser desplazados, por ejemplo verticalmente, en funcin de los
resultados ofrecidos por un algoritmo o mediante la aplicacin de un mapa de alturas
(una textura en escala de grises), consiguiendo con ello, por ejemplo, la generacin de
un terreno irregular.

Figura 26.15: Ejemplos de vertex displacement mapping (Fuente: Wikipedia).

Screen Effects

Los shader pueden ser muy tiles para lograr todo tipo de efectos sobre la imagen
ya generada tras el renderizado. Gracias a las mltiples pasadas que se pueden aplicar,
es posible generar todo tipo de efectos de post-procesado, del estilo de los que se usan
en las pelculas actuales.
Los fragment shader realizan el renderizado en una textura temporal (un frame-
buffer alternativo) que luego es procesada con ltros antes de devolver los valores de
color.

Figura 26.16: Ejemplo de glow o bloom (Fuente: Wikipedia).

C26
Light and Surface Models

Mediante shaders es posible calcular los nuevos valores de color aplicando


distintos modelos de iluminacin, lo cual involucra parametros como son las normales
de las supercies (N), angulo en el que incide la luz (L), angulo de la luz reejada (R)
y el ngulo de visin. De esta forma se pueden conseguir unos resultados nales con
[750] CAPTULO 26. REPRESENTACIN AVANZADA

una altsima calidad, aunque habra que tener cuidad con su uso. El conseguir, por
ejemplo, luz por pixel implicara unas operaciones bastante complejas por cada pixel
que se vaya a dibujar en pantalla, lo cual, contando los pixeles de una pantalla, pueden
ser muchas operaciones.

Figura 26.17: Diferencia entre Per-Vertex Lighting (izquierda) y Per-Pixel Lighting (derecha).

Visual representation improvement

Gracias a estas tcnicas es posible lograr que la visualizacin de los modelos sea
mucho mejor, sin incrementar la calidad del modelo. Ejemplo de esta tcnica sera el
Normal mapping, donde a partir de una textura con informacin sobre el valor de las
normales (codicada cada normal como un valor RGB, correspondiente al XYZ de
la misma) a lo largo de toda su supercie, permite crear la ilusin de que el modelo
cuenta con ms detalle del que realmente tiene.

Figura 26.18: Ejemplo de normal mapping.

Non-Photorealistic Rendering

Los modelos de iluminacin no tienen porque limitarse a imitar el mundo real,


pueden asignar valores de color correspondientes a mundos imaginarios, como puede
ser el de los dibujos animados o el de las pinturas al oleo.
26.5. Desarrollo de shaders en Ogre [751]

Figura 26.19: Ejemplo de Toon Shading (Fuente: Wikipedia).

26.4.7. Lenguajes de Shader


Existe una gran variedad de lenguajes para escribir shaders en tiempo real y no
parece que vayan a dejar de aparecer ms. Estos pueden agruparse en dos categoras:
los basados en programas individuales o los que se basan en cheros de efectos.
La primera aproximacin necesita que se cree una coleccin de cheros, cada uno
de los cuales implementa un Vertex Shader o un Fragment Shader en una pasada de
renderizado. Ejemplos de estos lenguajes son: Cg, HLSL o GLSL. Lo cual conlleva
una cierta complejidad en su gestin pues para cada pasada de renderizado, en los
efectos complejos, necesita de dos cheros diferentes.
Para solucionar este problema surgen el siguiente tipo de lenguajes que son
aquellos basados cheros de efectos, de los cuales, el ms conocido es el CgFX de
NVidia, y que a su vez es un super conjunto del Microsoft effect framework.
Los lenguajes basados en cheros de efectos permiten que los diferentes shader
se incluyan en un mismo chero y, adems, introducen dos nuevos conceptos: tcnica
y pasada. En los cuales pueden ser agrupados los diferentes shaders y permiten que el
estado del dispositivo grco pueda ser denido en cada pasada de renderizado.
Con esta aproximacin se consigue que la gestin sea ms manejable, mejor
dirigida por recursos y mucho ms poderosa.

26.5. Desarrollo de shaders en Ogre


Para crear y gestionar los diferentes shaders en Ogre se usa la aproximacin
basada en cheros de efectos. Los cuales ya se han comentado previamente.
Con el objetivo de hacer lo ms sencillo el aprendizaje, primero se explicar cmo
montar la escena a mostrar y ms tarde se explicar qu es lo que ha pasado.
En estos ejemplos, se usar el lenguaje Cg, del que se puede encontrar una
documentacin muy completa en el libro The Cg Tutorial: The Denitive Guide to
Programmable Real-Time Graphics [34], que, afortunadamente, puede encontrarse
online y de forma gratuita en la pgina de desarrolladores de NVIDIA:
http://developer.nvidia.com/object/cg_tutorial_home.html
Tambin podemos recurrir al captulo correspondiente a los materiales y shaders
de Ogre 3D 1.7 Beginners Guide [56] que puede ser un buen punto de partida para
C26
empezar con este tema.
[752] CAPTULO 26. REPRESENTACIN AVANZADA

Poniendo a punto el entorno

Antes de empezar debemos tener claro que para usar los shader en Ogre debemos
contar con dos tipos de cheros. Por un lado tendremos los programas, o archivos .cg,
en los que se encontrarn los shaders escritos en Cg, y por otro lado tendremos los
archivos de efectos, o materiales, que denirn cmo se usan nuestros programas
Una vez sabido esto, el siguiente paso debera ser dejar claro donde se han colocar Log de Ogre
los cheros correspondientes a los shader, para que as puedan ser usados por Ogre.
Estos se deben colocar en la ruta: En el chero de log podremos en-
contrar informacin sobre los per-
les y funcionalidad soportada por
/[directorio_proyecto_ogre]/media/materials/programs nuestra tarjeta grca, as cmo in-
formacin sobre posibles errores en
la compilacin de nuestros materia-
Si queremos usar otro directorio deberemos indicarlo en el archivo resources.cfg, les o shaders.
bajo la etiqueta [popular] por ejemplo (hay que tener cuidado sin embargo con lo que
se coloca bajo esa etiqueta en un desarrollo serio).

[Popular]
...
FileSystem=../../media/materials/programs/mis_shaders
...

Por otro lado, es conveniente tener a mano el chero de log que genera Ogre en
cada ejecucin, para saber por qu nuestro shader hace que todo aparezca blanco (o
lo que es lo mismo por qu ha fallado su compilacin?).

26.5.1. Primer Shader


Este primer shader servir de toma de contacto para familiarizarnos con algunos
de los conceptos que introduce. Es por eso que no hace prcticamente nada, slo deja
que el vrtice pase a travs de el, modicando nicamente su color y envindolo a la
siguiente etapa.
La escena 3D puede ser cualquiera, siempre y cuando alguna de las entidades
tenga asociado el material que se denir ahora.
Lo primero es indicarle a Ogre alguna informacin sobre los shader que queremos
que use, la cual debe estar incluida en el propio archivo de material.
Al denir el shader que usar nuestro material hay que indicar al menos:

El nombre del shader


En qu lenguaje est escrito
En qu chero de cdigo est almacenado
Cmo se llama el punto de entrada al shader
En qu perl queremos que se compile

Por ltimo, antes de denir el material en si, hay que indicar tambin a los shader
cuales son aquellos parmetros que Ogre les pasar. En este caso slo pasamos la
matriz que usaremos para transformar las coordenadas de cada vrtice a coordenadas
de cmara. Es importante denir esta informacin al principio del chero de material.
26.5. Desarrollo de shaders en Ogre [753]

Listado 26.7: Declaracin del vertex shader en el material


1 // Declaracion vertex shader y lenguaje usado
2 vertex_program VertexGreenColor cg
3 {
4 // Archivo con los programas
5 source firstshaders.cg
6
7 // Punto de entrada al shader
8 entry_point v_green_color
9
10 // Perfiles validos
11 profiles vs_1_1 arbvp1
12
13 // Parmetros usados
14 default_params
15 {
16 param_named_auto worldViewMatrix worldviewproj_matrix
17 }
18 }

Para este caso, el material quedara tal y como se ve en el listado 26.8. Y queda
claro cmo en la pasada se aplican los dos shader.

Listado 26.8: Primer Material con shaders


1 vertex_program VertexGreenColor cg
2 {
3 source firstshaders.cg
4 entry_point v_green_color
5 profiles vs_1_1 arbvp1
6
7 default_params
8 {
9 param_named_auto worldViewMatrix worldviewproj_matrix
10 }
11 }
12
13 fragment_program FragmentColorPassthrough cg
14 {
15 source firstshaders.cg
16 entry_point f_color_passthrough
17 profiles ps_1_1 arbfp1
18 }
19
20 material VertexColorMaterial
21 {
22 technique
23 {
24 pass
25 {
26 vertex_program_ref VertexGreenColor
27 {
28 }
29
30 fragment_program_ref FragmentColorPassthrough
31 {
32 }
33 }
C26
34 }
35 }

Declaracin shaders
Los dos programas que denen nuestros primeros fragment y vertex shader
Para ms informacin sobre la aparecen en los listados 26.9 y 26.10, y los dos deberan incluirse en el chero
declaracin de los shader, se-
ra buena idea dirigirse al ma- rstshaders.cg (o en el archivo que queramos), tal y como indicamos en el material.
nual en: http://www.ogre3d.org/-
docs/manual/manual_18.html
[754] CAPTULO 26. REPRESENTACIN AVANZADA

Listado 26.9: Primer vertex shader


1 // Estructura de salida con la informacin del vertice procesado
2 struct tVOutput {
3 float4 position: POSITION;
4 float4 color : COLOR;
5 };
6
7 // Cambia el color primario de cada vertice a verde
8 tVOutput v_green(
9 float4 position : POSITION,
10 uniform float4x4 worldViewMatrix)
11 {
12 tVOutput OUT;
13 // Transformamos la posicin del vrtice
14 OUT.position = mul(worldViewMatrix, position);
15 // Asignamos el valor RGBA correspondiente al verde
16 OUT.color = float4(0, 1, 0, 1);
17 return OUT;
18 }

El vertex shader recibe cmo nico parmetro del vrtice su posicin, esta
es transformada a coordenadas del espacio de cmara gracias a la operacin que
realizamos con la variable que representa la matriz de transformacin.
Por ltimo se asigna el color primario al vrtice, que se corresponde con su
componente difusa, y que en este caso es verde.

Listado 26.10: Primer fragment shader


1 // Estructura de salida con la informacin del fragmento procesado
2 struct tFOutput {
3 float4 color : COLOR;
4 };
5
6 // Devuelve el color interpolado de cada fragmento
7 tFOutput f_color_passthrough(
8 float4 color : COLOR)
9 {
10 tFOutput OUT;
11 // Se aplica el color al fragmento
12 OUT.color = color;
13
14 return OUT;
15 }

El fragment shader se limita smplemente a devolver el color interpolado (aunque


en este caso no se note) y el resultado debera ser como el mostrado en la Figura 26.20
Cualquiera con conocimientos de C o C++, no habr tenido mucha dicultad a la
hora de entender los dos listado de cdigo anteriores. Sin embargo si habr notado
la presencia de algunos elementos nuevos, propios de Cg. Y es que hay algunas
diferencias importantes, al tratarse de un lenguaje tan especializado.
En primer lugar, en Cg no hay necesidad de especicar qu tipos de elementos o
libreras queremos usar (como se hace en C o C++ con los #include). Automtica-
mente se incluye todo lo necesario para un programa escrito en este lenguaje.
Por otro lado, tenemos un conjunto completamente nuevo de tipos de datos, que Figura 26.20: Resultado del uso de
se encargan de representar vectores y matrices. los primeros shader.
26.5. Desarrollo de shaders en Ogre [755]

En los lenguajes de programacin clsicos, los tipos de datos representan magni-


tudes escalares (int, o float son ejemplos de ello). Pero, cmo hemos visto en la
seccin 26.4.5, las GPU estn preparadas para tratar con vectores (entiendolos como
una coleccin de magnitudes escalares). Los vectores en C o C++ pueden represen-
tarse fcilmente mediante arrays de valores escalares, sin embargo, como su proce-
samiento es fundamental en el tratamiento de vrtices o fragmentos, Cg ofrece tipos
predenidos para estas estructuras.
Para el tratamiento de vectores, podemos usar los siguientes tipos:
float2 uv_coord;
float3 position;
float4 rgba_color;
O sus equivalentes con la mitad de precisin:
half2 uv_coord;
half3 position;
half4 rgba_color;
Estos tipos de datos son mucho ms ecientes que el uso de un array, por lo
que no es recomendable sustituir un float4 X, por un float X[4], ya que no son
exactamente lo mismo. Cg se encarga de almacenar esta informacin en una forma,
mediante la cual, es posible sacar mucho mejor partido de las caractersticas de una
GPU a la hora de operar con estos elementos.
Por otro lado, Cg soporta nativamente tipos para representar matrices. Ejemplos
de declaraciones seran:
float4x4 matrix1;
float2x4 matrix2;
Al igual que los vectores, el uso de estos tipos garantiza una forma muy eciente
de operar con ellos.
Otra de las cosas que seguramente pueden llamar la atencin, es la peculiar forma
en que se declaran los tipos de los atributos pertenecientes a la estructura usada
tVOutput, o tFOutput.
float4 position : POSITION;
A los dos puntos, y una palabra reservada tras la declaracin de una variable (como
POSITION o COLOR), es lo que, en Cg, se conoce como semntica. Esto sirve para
indicar al pipeline grco, qu tipo de datos deben llenar estas variables.
Atributos especiales Cg Es decir, como ya sabemos, los shader son programas que se ejecutan en ciertas
etapas del pipeline. Por lo tanto, estas etapas reciben cierta informacin que nosotros
Para ms informacin sobre ellos se podemos usar indicando con la semntica cules son estos datos. Los nicos sitios
recomienda consultar los captulos
2 y 3 del muy recomendable The donde se pueden usar son en estructuras de entrada o salida (como las de los ejemplos)
Cg Tutorial [34] o en la denicin de los parmetros que recibe el punto de entrada (mtodo principal)
de nuestro shader.
Por ltimo, es necesario hablar brevemente sobre la palabra reservada uniform, su
signicado, y su conexin con lo denido en el chero de material.
C26

El pipeline debe proveer de algn mecanismo para comunicar a los shader los
valores de aquellos elementos necesarios para conocer el estado de la simulacin (la
posicin de las luces, la delta de tiempo, las matrices de transformacin, etc...). Esto
se consigue mediante el uso de las variable declaradas como uniform. Para Cg, estas
son variables externas, cuyos valores deben ser especicados por otro elemento.
[756] CAPTULO 26. REPRESENTACIN AVANZADA

En la declaracin del vertex shader en el material (listado 26.8) se puede ver


cmo se declara el parmetro por defecto param_named_auto worldViewMatrix
worldviewproj_matrix, que nos permite acceder a la matriz de transformacin
correspondiente mediante un parmetro uniform.

26.5.2. Comprobando la interpolacin del color


Con este segundo shader, se persigue el objetivo de comprobar cmo la etapa de
rasterizacin e interpolacin hace llegar al fragment shader los fragmentos que cubren
cada primitiva con su componente de color interpolado a partir de los valores de color
de los vrtices.
Para ello es necesario que creemos una escena especialmente preparada para
conseguir observar el efecto.

Listado 26.11: Escena denida en Ogre


1 void CPlaneExample::createScene(void)
2 {
3 // Creamos plano
4 Ogre::ManualObject* manual = createManualPlane();
5 manual->convertToMesh("Quad");
6
7 // Creamos la entidad
8 Ogre::Entity* quadEnt = mSceneMgr->createEntity("Quad");
9
10 // Lo agregamos a la escena
11 Ogre::SceneNode* quadNode = mSceneMgr->getRootSceneNode()->
createChildSceneNode("QuadNode");
12
13 quadNode->attachObject(ent);
14 }
15
16 Ogre::ManualObject* CPlaneExample::createManualPlane() {
17 // Creamos un cuadrado de forma manual
18 Ogre::ManualObject* manual = mSceneMgr->createManualObject("
Quad");
19
20 // Iniciamos la creacion con el material correspondiente al
shader
21 manual->begin("VertexColorMaterial", Ogre::RenderOperation::
OT_TRIANGLE_LIST);
22
23 // Situamos vrtices y sus correspondientes colores
24 manual->position(5.0, 0.0, 0.0);
25 manual->colour(1, 1, 1);
26
27 manual->position(-5.0, 10.0, 0.0);
28 manual->colour(0, 0, 1);
29
30 manual->position(-5.0, 0.0, 0.0);
31 manual->colour(0, 1, 0);
32
33 manual->position(5.0, 10.0, 0.0);
34 manual->colour(1, 0, 0);
35
36 // Establecemos los indices
37 manual->index(0);
38 manual->index(1);
39 manual->index(2);
40
41 manual->index(0);
42 manual->index(3);
43 manual->index(1);
44
45 manual->end();
26.5. Desarrollo de shaders en Ogre [757]

46 return manual;
47 }

El vertex shader lo nico que tendr que hacer es dejar pasar el vrtice, pero esta
vez, en vez de cambiarle el color dejamos que siga con el que se le ha asignado. Para
ello, en el mtodo que dene el punto de entrada del shader hay que indicarle que
recibe como parametro el color del vrtice.

Listado 26.12: Segundo vertex shader


1 // Estructura de salida con la informacin del vertice procesado
Figura 26.21: El cuadrado aparece 2 struct tVOutput {
con el color de sus cuatro vrtices 3 float4 position: POSITION;
4 float4 color : COLOR;
interpolado. 5 };
6
7 // Usamos la posicin y color del vrtice
8 tVOutput v_color_passthrough(
9 float4 position : POSITION,
10 float4 color : COLOR
11 uniform float4x4 worldViewMatrix) {
12 tVOutput OUT;
13 // Transformacin del vrtice
14 OUT.position = mul(worldViewMatrix, position);
15 OUT.color = color;
16 return OUT;
17 }

El fragment shader podemos dejarlo igual, no es necesario que haga nada. Incluso
es posible no incluirlo en el chero de material (cosa que no se puede hacer con los
vertex shader).
El resultado obtenido, debera ser el mostrado en la gura 26.21.

Ahora que sabes cmo modicar el color de los vrtices. Seras capaz
Figura 26.22: Ogro dibujado como de pintar la entidad tpica del Ogro como si de textura tuviera un mapa
si tuviera un mapa de normales en- de normales? Figura 26.22 Tip: Has de usar la normal del vrtice para
cima. conseguirlo.

26.5.3. Usando una textura


Pintar un modelo con el color de sus vrtices puede ser interesante, pero desde
luego no es muy impresionante. Por lo tanto con este tercer shader usaremos una
textura para darle algo de detalle a nuestro plano anterior. Para ello necesitamos
acceder a las coordenadas de textura de nuestra entidad cuadrada, por lo que se las
aadiremos en nuestro mtodo createManualPlane. C26

Listado 26.13: Creacin de un plano al que se le asignan coordenadas de textura


1 // Situamos vrtices y coordenadas de textura
2 manual->position(5.0, 0.0, 0.0);
3 manual->textureCoord(0, 1);
4 manual->position(-5.0, 10.0, 0.0);
5 manual->textureCoord(1, 0);
6 manual->position(-5.0, 0.0, 0.0);
7 manual->textureCoord(1, 1);
[758] CAPTULO 26. REPRESENTACIN AVANZADA

8 manual->position(5.0, 10.0, 0.0);


9 manual->textureCoord(0, 0);

Listado 26.14: Declaracin del material


1 material TextureMaterial {
2 technique
3 {
4 pass
5 {
6 vertex_program_ref VertexTexPassthrough
7 {
8 }
9
10 fragment_program_ref FragmentTexPassthrough
11 {
12 }
13
14 // Indicamos la textura
15 texture_unit
16 {
17 texture sintel.png
18 }
19 }
20 }
21 }

Para este ejemplo es necesario indicar la textura que se usar y aunque en el


captulo 8 del Mdulo 2 ya se habl del tema, ser necesario apuntar un par de cosas
sobre este nuevo material que aparece en el listado 26.14.
Una vez podemos acceder a las coordenadas de textura del plano, podemos usarlas
desde el vertex shader. Aunque en este caso nos limitaremos simplemente a pasarla,
sin tocarla, al fragment shader, tal y como se ve en el listado de cdigo 26.15.
Ahora el fragment shader mostrado en el listado 26.16 recibe la coordenada
interpolada de textura e ignora el color del vrtice. El resultado debera ser el que
se ve en la Figura 26.23.

Listado 26.15: Tercer vertex shader. Hacemos uso de las coordenadas UV


1 // Vertex shader
2 struct tVOutput {
3 float4 position: POSITION;
4 float2 uv : TEXCOORD0; Figura 26.23: El cuadrado aparece
5 }; con una textura.
6
7 // Usamos la posicin y la coordenada de textura
8 tVOutput v_uv_passthrough(
9 float4 position : POSITION,
10 float2 uv : TEXCOORD0,
11 uniform float4x4 worldViewMatrix)
12 {
13 tVOutput OUT;
14
15 // Transformacin del vrtice
16 OUT.position = mul(worldViewMatrix, position);
17 OUT.uv = uv;
18
19 return OUT;
20 }
26.5. Desarrollo de shaders en Ogre [759]

Listado 26.16: Segundo fragment shader. Mapeamos la textura en los fragmentos


1 struct tFOutput {
2 float4 color : COLOR;
3 };
4
5 // Devuelve el color correspondiente a la textura aplicada
6 tFOutput f_tex_passthrough(
7 float2 uv : TEXCOORD0,
8 uniform sampler2D texture)
9 {
10 tFOutput OUT;
11 // Asignamos el color de la textura correspondiente
12 // en funcin de la coordenada UV interpolada
13 OUT.color = tex2D(texture, uv);
14 return OUT;
15 }

En el anterior shader no se introdujo ningn concepto nuevo ms all de cmo


suministrar el color del vrtice al vertex shader, sin embargo, ahora nos encontramos
con una variable uniform sampler2D, un tipo nuevo que no habamos visto, y el
mtodo tex2D.
En Cg, un sampler se reere a un objeto externo que se puede muestrear, como
es una textura. El sujo 2D indica que es una textura convencional en 2 dimensiones
(existen texturas de 1D y 3D). Para ver los diferentes sampler que soporta Cg puedes
ir al captulo 3 de The Cg Tutorial [34].
El mtodo tex2D se encarga de devoler el color de la textura correspondiente a la
coordenada de textura que se le pase como parmetro.

Seras capaz de crear un fragment shader que mostrara la textura modicada


por los valores de color de los vrtices como en la Figura 26.24? (Unas pocas
lneas ms adelante est la respuesta)

26.5.4. Jugando con la textura


Esta subseccin se limitar a mostrar algunos posibles efectos que se pueden
conseguir mediante la modicacin del fragment shader.
Primero, podramos intentar devolver el color inverso de la textura que use nuestro
modelo, dando un efecto como en el negativo de las fotos.
Figura 26.24: La textura con una
cierta tonalidad rojiza. Esto es muy sencillo de conseguir, slamente debemos restarle a 1 los valores
correspondiente del vector RGBA. Veamos cmo:

Listado 26.17: Efecto negativo fotogrco


1 // Estructura de salida con la informacin del fragmento procesado
C26
2 struct tFOutput {
3 float4 color : COLOR;
4 };
5
6 // Devuelve el color inverso por cada fragmento
7 tFOutput f_tex_inverse(
Figura 26.25: La textura se mues- 8 float2 uv : TEXCOORD0,
tra como el negativo de una foto. 9 uniform sampler2D texture)
10 {
[760] CAPTULO 26. REPRESENTACIN AVANZADA

11 tFOutput OUT;
12 OUT.color = 1 - tex2D(texture, uv);
13 return OUT;
14 }

Y ya que estamos modicando el color, quizs en lugar de usar el efecto negativo,


tenemos intencin de potenciar algn color. En el siguiente ejemplo, daremos un tono
rojizo a la textura usada.

Listado 26.18: Modicando los colores de la textura con el color rojo


1 // Devuelve el color correspondiente a la textura aplicada,
modificandola para que predomine el rojo
2 tFOutput f_tex_red(
3 float2 uv : TEXCOORD0, Figura 26.26: Los colores de la tex-
4 uniform sampler2D texture) tura con su componente roja poten-
5 { ciada.
6 tFOutput OUT;
7 OUT.color = tex2D(texture, uv);
8 OUT.color.r *= 0.5f;
9 OUT.color.bg *= 0.15f;
10 return OUT;
11 }

Como se puede ver, lo nico que se ha hecho es multiplicar el componente de


color por una cantidad (mayor en el caso del rojo, y menor en el caso del verde y el
azl). A pesar de la sencillez del ejemplo, si has intentado ejecutar este ejemplo con
lo aprendido hasta ahora, es probable que no hayas podido.
El motivo es que se introducen dos conceptos nuevos. El primero de ellos se
conoce como swizzling y consiste en una forma de reordenar los elementos de los
vectores, o incluso de acortarlos. Por ejemplo:
float4 vec1 = float4(0, 2, 3, 5);
float2 vec2 = vec1.xz; // vec2 = (0, 3)
float scalar = vec1.w; // scalar = 5
float3 vec3 = scalar.xxx; // vec3 = (5, 5, 5)
float4 color = vec1.rgba; // color = (0, 2, 3, 5)
Por otro lado, entramos de lleno en el asunto de los perles. Los perles son una
forma de gestionar la funcionalidad de la que disponen los shader que programamos,
y de esta manera, saber en qu dispositivos podrn funcionar y qu funcionalidad
tenemos disponible.
Todos los ejemplos usados hasta ahora usaban los perles vs_1_1 y ps_1_1 para
DirectX 8, y arbvp1 y arbfp1 para OpenGL, que son los perles ms simples a la
vez que los ms ampliamente soportados. Sin embargo para disponer del swizzling es
necesario usar los ms avanzados vs_2_0 y ps_2_0, que son compatibles con DirectX
y OpenGL.
La declaracin del fragment shader en el material quedar, entonces, como se
muetra en el Listado 26.19.

Listado 26.19: Declaracin del fragment shader


1 fragment_program FragmentRedTex cg
2 {
3 source texshaders.cg
4 entry_point f_tex_red
5 profiles ps_2_0 arbfp1
26.5. Desarrollo de shaders en Ogre [761]

6 }

En el captulo 2 de The Cg Tutorial [34], se puede encontrar ms informacin


al respecto.
A continuacin se presentan algunos ejemplos ms de shaders que servirn para
familiarizarse con la forma en que se trabaja con ellos. Es muy recomendable intentar
ponerlos en accin todos y modicar sus parmetros, jugar con ellos, a ver qu sucede.

Listado 26.20: Las funciones trigonmetricas ayudan a crear un patrn de interferencia


1 // Devuelve el color de la textura modulado por una funciones
trigonometricas
2 tFOutput f_tex_interference(
3 float2 uv : TEXCOORD0,
4 uniform sampler2D texture)
5 {
6 tFOutput OUT;
7 OUT.color = tex2D(texture, uv);
8 OUT.color.r *= sin(uv.y*100);
9 OUT.color.g *= cos(uv.y*200);
10 OUT.color.b *= sin(uv.y*300);
11 return OUT;
12 }

Listado 26.21: Las funciones trigonmetricas ayudan a crear la ondulacin en la textura


1 // Muestra la imagen modulada por una funcion trigonometrica
2 tFOutput f_tex_wavy(
3 float2 uv : TEXCOORD0,
4 uniform sampler2D texture)
5 {
6 tFOutput OUT;
7 uv.y = uv.y + (sin(uv.x*200)*0.01);
Figura 26.27: Efecto interferencia. 8 OUT.color = tex2D(texture, uv);
9 return OUT;
10 }

Listado 26.22: La composicin del color de la textura en varias posiciones diferentes da lugar
a un efecto borroso
1 // Como si dibujaramos tres veces la textura
2 tFOutput f_tex_blurry(
3 float2 uv : TEXCOORD0,
4 uniform sampler2D texture)
5 {
6 tFOutput OUT;
Figura 26.28: Efecto ondulado. 7 OUT.color = tex2D(texture, uv);
8 OUT.color.a = 1.0f;
9 OUT.color += tex2D(texture, uv.xy + 0.01f);
10 OUT.color += tex2D(texture, uv.xy - 0.01f);
11 return OUT;
12 } C26

Listado 26.23: Este efecto se consigue con una combinacin del efecto blur con la conversin
del color a escala de grises
1 // Dibuja la textura como si estuviera grabada en piedra
2 tFOutput f_tex_emboss(
Figura 26.29: Efecto borroso 3 float2 uv : TEXCOORD0,
(blur). 4 uniform sampler2D texture)
[762] CAPTULO 26. REPRESENTACIN AVANZADA

5 {
6 float sharpAmount = 30.0f;
7
8 tFOutput OUT;
9 // Color inicial
10 OUT.color.rgb = 0.5f;
11 OUT.color.a = 1.0f;
12
13 // Aadimos el color de la textura
14 OUT.color -= tex2D(texture, uv - 0.0001f) * sharpAmount;
15 OUT.color += tex2D(texture, uv + 0.0001f) * sharpAmount;
16
17 // Para finalizar hacemos la media de la cantidad de color de
cada componente
18 // para convertir el color a escala de grises
19 OUT.color = (OUT.color.r + OUT.color.g + OUT.color.b) / 3.0f;
20
21 return OUT;
22 }

26.5.5. Jugando con los vrtices


Antes nos hemos dedicado a jugar un poco con los fragment shader, por lo que
ahora sera conveniente hacer lo mismo con los vertex shader.
Primero usaremos el conocimiento hasta ahora recogido para, a partir de un slo
modelo, dibujarlo dos veces en posiciones distintas. Para conseguirlo se necesitar un
material que dena dos pasadas. Una primera en la que el personaje se dibujar en su Figura 26.30: Efecto gravado en
lugar, y otra en la que ser desplazado. piedra.

Listado 26.24: Denicin de las dos pasadas


1 material CopyObjectMaterial
2 {
3 technique
4 {
5 pass
6 {
7 vertex_program_ref VertexColorPassthrough
8 {
9 }
10
11 fragment_program_ref FragmentTexPassthrough
12 {
13 }
14
15 texture_unit
16 {
17 texture terr_rock6.jpg
18 }
19 }
20
21 pass
22 {
23 vertex_program_ref VertexDisplacement
24 {
25 }
26
27 fragment_program_ref FragmentTexPassthrough
28 {
29 }
30
31 texture_unit
32 {
33 texture terr_rock6.jpg
26.5. Desarrollo de shaders en Ogre [763]

34 }
35 }
36 }
37 }

Listado 26.25: Vrtice desplazado cantidad constante en eje X


1 // Desplazamiento del vrtice 10 unidades en el eje X
2 tVOutput v_displacement(
3 float4 position : POSITION,
4 uniform float4x4 worldViewMatrix)
5 {
6 tVOutput OUT;
7 // Modificamos el valor de la posicin antes de transformarlo
8 OUT.position = position;
9 OUT.position.x += 10.0f;
10 OUT.position = mul(worldViewMatrix, OUT.position);
11 return OUT;
12 }

El anterior era un shader muy sencillo, por lo que ahora intentaremos crear
nuestras propias animaciones mediante shaders.
Para ello necesitamos tener acceso a la delta de tiempo. En la pgina ocial1
aparecen listados los diferentes parmetros que Ogre expone para ser accedidos
mediante variables uniform. En este caso, en la denicin del vertex shader realizada
en el material debemos indicar que queremos tener acceso a la delta de tiempo, como
se ve en el Listado 26.26.
Figura 26.31: Resultado de las dos
pasadas. Listado 26.26: Acceso a la variable que expone la delta de tiempo
1 vertex_program VertexPulse cg
2 {
3 source vertex_modification.cg
4 entry_point v_pulse
5 profiles vs_1_1 arbvp1
6
7 default_params
8 {
9 param_named_auto worldViewMatrix worldviewproj_matrix
10 param_named_auto pulseTime time
11 }
12 }

Una vez tenemos accesible el tiempo transcurrido entre dos vueltas del bucle
principal, slo necesitamos pasarlo como parmetro a nuestro shader y usarlo para
nuestros propsitos. En este caso, modicaremos el valor del eje Y de todos los
vrtices del modelo, siguiendo una funcin cosenoidal, como se puede ver en el
Listado 26.32.

Listado 26.27: Combinacin de funcin trigonomtrica y delta de tiempo para conseguir


simular un movimiento continuo
C26
1 // A partir de la delta de tiempo simulamos una seal pulsante para
2 // escalar el modelo en Y
3 tVOutput v_pulse(
4 float4 position : POSITION,
5 float2 uv : TEXCOORD0,
6 uniform float pulseTime,
Figura 26.32: Personaje deforma- 7 uniform float4x4 worldViewMatrix)
do en el eje Y.
1 http://www.ogre3d.org/docs/manual/manual_23.html#SEC128
[764] CAPTULO 26. REPRESENTACIN AVANZADA

8 {
9 tVOutput OUT;
10
11 OUT.position = mul(worldViewMatrix, position);
12 OUT.position.y *= (2-cos(pulseTime));
13 OUT.uv = uv;
14
15 return OUT;
16 }

Sabiendo cmo hacer esto, se tiene la posibilidad de conseguir muchos otros tipos
de efectos, por ejemplo en el siguiente se desplaza cada cara del modelo en la direccin
de sus normales.

Listado 26.28: Uso de la normal y la delta de tiempo para crear un efecto cclico sobre los
vrtices
1 tVOutput v_extrussion(
2 float4 position : POSITION,
3 float4 normal : NORMAL,
4 float2 uv : TEXCOORD0,
5 uniform float pulseTime,
6 uniform float4x4 worldViewMatrix)
7 { Figura 26.33: Figura con sus vrti-
8 tVOutput OUT; ces desplazados en direccin de sus
9 OUT.position = position + (normal * (cos(pulseTime)*0.5f)); normales.
10 OUT.position = mul(worldViewMatrix, OUT.position);
11 OUT.uv = uv;
12
13 return OUT;
14 }

Seras capaz de montar una escena con un plano y crear un shader que
simule un movimiento ondulatorio como el de la Figura 26.34?

Figura 26.34: Plano con movi-


miento ondulatorio.
26.5.6. Iluminacin mediante shaders
Como ejercicio nal de esta seccin se propone conseguir montar una escena con
una luz y un plano y, a partir de ella, conseguir usar un modelo de iluminacin por
pixel sencillo (como se ve en la Figura 26.17).
En el captulo 9 del Mdulo 2 se habla sobre el tema, a su vez, se puede encontrar
una implementacin, as como una explicacin muy buena sobre iluminacin en el
captulo 5 de The Cg Tutorial [34].
Como pista, o consejo: Se pueden hardcodear (poner como constantes) algunos
parmetros en el material con el objetivo de simplicar la tarea. En el listado 26.29
se muestran todos los que son necesarios para hacer funcionar el ejercicio de luz por
pixel, slo hay que declararlos usando param_named.

Listado 26.29: Ejemplo de declaracin de parmetros para ser usados como uniform por un
shader
1 default_params
2 {
26.6. Optimizacin de interiores [765]

3 param_named globalAmbient float3 0.1 0.1 0.1


4
5 param_named Ke float3 0.0 0.0 0.0
6 param_named Ka float3 0.0215 0.1745 0.0215
7 param_named Kd float3 0.07568 0.61424 0.07568
8 param_named Ks float3 0.633 0.727811 0.633
9 param_named shininess float 76.8
10
11 param_named_auto eyePosition camera_position_object_space
12
13 param_named_auto lightPosition
light_position_object_space 0
14 param_named_auto lightColorDiffuse light_diffuse_colour 0
15 }

26.6. Optimizacin de interiores

Ms velocidad? En las dos secciones siguientes se tratarn diferentes maneras de acelerar la


representacin de escenas. Cabe destacar que estas optimizaciones dieren de las que
Las optimizaciones no slo aumen- tienen que ver con el cdigo generado, o con los ciclos de reloj que se puedan usar
tan el nmero de frames por segun-
do, sino que puede hacer interactivo para realizar una multiplicacin de matrices o de vectores. Esta optimizaciones tienen
algo que no lo sera sin ellas. que ver con una mxima: lo que no se ve no debera representarse.

26.6.1. Introduccin
Quiz cabe preguntarse si tiene sentido optimizar el dibujado de escenas ya que
todo eso lo hace la GPU. No es una mala pregunta, teniendo en cuenta que muchos
monitores tienen una velocidad de refresco de 60Hz, para qu intentar pasar de
una tasa de 70 frames por segundo a una de 100?. La respuesta es otra pregunta:
por qu no utilizar esos frames de ms para aadir detalle a la escena? Quiz se
podra incrementar el nmero de tringulos de los modelos, usar algunos shaders
ms complejos o aadir efectos de post-proceso, como antialiasing o profundidad
de campo.
En este curso las optimizaciones se han dividido en dos tipos: optimizacin de
interiores y optimizacin de exteriores. En este tema vamos a ver la optimizacin de
interiores, que consiste en una serie de tcnicas para discriminar qu partes se pintan
de una escena y cules no.
Una escena de interior es una escena que se desarrolla dentro de un recinto cerrado,
como por ejemplo en un edicio, o incluso entre las calles de edicios si no hay
grandes espacios abiertos.
Ejemplos claros de escenas interiores se dan en la saga Doom y Quake, de Id
Software, cuyo ttulo Wolfenstein 3D fue precursor de este tipo de optimizaciones.
Sin el avance en la representacin grca que supuso la divisin del espacio de
manera eciente, estos ttulos jams hubieran sido concebidos. Fue el uso de rboles
BSP (2D y 3D, respectivamente) lo que permiti determinar qu parte de los mapas se
renderizaba en cada momento.
C26

En general, la optimizacin de interiores consiste en un algoritmo de renderizado


que determina (de forma muy rpida) la oclusiones en el nivel de geometra. Esto
diferencia claramente a este tipo de tcnicas a las utilizadas para la representacin
de exteriores, que se basarn principalmente en el procesado del nivel de detalle
(LOD (Level-Of-Detail)).
[766] CAPTULO 26. REPRESENTACIN AVANZADA

26.6.2. Tcnicas y Algoritmos


En su libro, Cormen et all [21] presentan a los algoritmos como una tecnologa Eleccin crucial
ms a tener en cuenta a la hora de disear un sistema. Es decir, no slo el hardware
Conocer diferentes algoritmos y sa-
ser determinante, sino que la eleccin de un algoritmo u otro resultar determinante ber elegir el ms apropiado en ca-
en el buen rendimiento del mismo. Los videojuegos no son una excepcin. da momento es de suma importan-
cia. Un buen algoritmo puede supo-
A continuacin se presentarn algunas de las tcnicas y algoritmos que recopila ner la diferencia entre poder disfru-
Dalmau [24] en su libro, que se ha seguido para preparar esta y la siguiente leccin, tar de una aplicacin interactiva o
para la representacin de escenas de interiores. no poder hacerlo.

Algoritmos basados en Oclusores

Si tuviramos que determinar qu tringulos dentro de frustum ocluyen a otros,


la complejidad de llevar a cabo todas las comprobaciones sera de O(n2 ), siendo n el
nmero de tringulos.

Listado 26.30: Algoritmo bsico basado en oclusores


1 vector<Triangle> occluders = createOccludersSet(sceneTrianles);
2 vector<Triangle> others = removeOccluders(sceneTriangles,
3 occluders);
4 vector<Triangle>::iterator it;
5
6 for (it = others.begin(); it != others.end(); ++it)
7 {
8 if (closerThanFarPlane(*it) &&
9 !testOcclusion(*it, occluders))
10 {
11 (*it)->draw(); // (*it)->addToRendeQueue();
12 }
13 }

Dado que esta complejidad es demasiado alta para una aplicacin interactiva, se
hace necesario idear alguna forma para reducir el nmero de clculos. Una forma
sencilla es reducir la lista de posibles oclusores. Los tringulos ms cercanos a la
cmara tienen ms posibilidades de ser oclusores que aquellos que estn ms alejados,
ya que estadsticamente estos van a ocupar ms rea de la pantalla. De este modo,
usando estos tringulos como oclusores y los lejanos como ocluidos, se podr reducir
la complejidad hasta casi O(n). Si adems se tiene en cuenta que el frustum de la
cmara tiene un lmite (plano far), se podrn descartar aun ms tringulos, tomando
como ocluidos los que estn ms all que este plano.
Este algoritmo podra beneciarse del clipping y del culling previo de los
tringulos. No es necesario incluir en el algoritmo de oclusores ningn tringulo que
quede fuera del frustum de la vista actual, tampoco los tringulos que formen parte de
las caras traseras se pintarn, ni tendrn que formar parte de los oclusores. La pega es
que hay que realizar clipping y culling por software. Tras esto, se tendran que ordenar
los tringulos en Z y tomar un conjunto de los n primeros, que harn de oclusores
en el algoritmo. Computacionalmente, llevar a cabo todas estas operaciones es muy Recursividad
caro. Una solucin sera hacerlas slo cada algunos frames, por ejemplo cuando la
Como casi todos los algoritmos de
cmara se moviese lo suciente. Mientras esto no pasase, el conjunto de oclusores no construccin de rboles, el BSP es
cambiara, o lo hara de manera mnima. un algoritmo recursivo. Se dice que
una funcin es recursiva cuando se
llama a s misma hasta que se cum-
Listado 26.31: Actualizacin de los oclusores ple una condicin de parada. Una
funcin que se llama a s misma dos
1 const size_t maxOccluders = 300; veces se podr representar con un
2 rbol binario; una que se llame n-
3 newPos = calculateNewCameraPosition(); veces, con un rbol n-ario.
26.6. Optimizacin de interiores [767]

4 if (absoluteDifference(oldPos, newPos) > delta)


5 {
6 performClipping(sceneTriangles);
7 performCulling(sceneTriangles);
8 oldPos = newPos;
9 vector<Triangles> occluders =
10 getZOrderedTriangles(sceneTriangles,
11 maxOccluders);
12 }

Algoritmo BSP

Una de las formas para facilitar la representacin de escenas interiores en tiempo


real es el uso de estructuras de datos BSP (Binary Space Partition). Estas estructuras
se han utilizado desde juegos como Doom, que usaba un arbol BSP de 2 dimensiones,
y Quake, que fue el primero que us uno de 3 dimensiones.
Un BSP es un rbol que se utiliza para clasicar datos espaciales, ms concreta-
mente tringulos en la mayora de los casos. La ventaja principal de esta estructura es
que se le puede preguntar por una serie de tringulos ordenados por el valor Z, des-
de cualquier punto de vista de la escena. Estos rboles se usan tambin para detectar
colisiones en tiempo real.

Construccin de estructuras BSP

Un BSP se construye a partir de un conjunto de tringulos, normalmente de la


parte esttica de una escena, esto es, el mapa del nivel. El algoritmo de construccin
es recursivo:
Figura 26.35: Posicin con respec-
to a un plano de normal n. 1. Tomar el conjunto completo de tringulos como entrada.
2. Buscar un tringulo que divida a ese conjunto en dos partes ms o menos
equilibradas.
3. Calcular el plano que corresponde a ese tringulo.
4. Crear un nodo de rbol y almacenar el tringulo y su plano asociado en el
mismo.
5. Crear nuevos tringulos a partir de los que queden cortados por este plano.
6. Dividir el conjunto total en dos nuevos conjuntos segn queden delante o detrs
Figura 26.36: Mapa de una escena de la divisin.
visto desde arriba.
7. Para cada uno de los nuevos conjuntos que aun tengan tringulos (ms que un
umbral mximo de divisin dado), volver al paso 2.

Para explicar este algoritmo, se usar una representacin de dos dimensiones


para simplicar la complejidad. En la gura 26.36 se ve la planta de un nivel. Cada
C26
segmento representa a un plano.
Primero se tomar el plano 7 como divisor del nivel, y este dividir al plano 5 y al
1 en dos (gura 26.37).
El nivel queda dividido en dos nuevos conjuntos, detrs de la divisin quedan cinco
Figura 26.37: Primera divisin planos (1.1, 6, 5.1, 8 y 9), y delante tambin (1.2, 2, 3, 4 y 5.2).
(BSP).
[768] CAPTULO 26. REPRESENTACIN AVANZADA

En el rbol se creara un nodo que contendra el plano 7, que es con el que se ha


realizado la divisin. Como los dos subconjuntos nuevos no estn vacos, se crearan
de nuevo otros dos nodos (ver gura 26.38).
El algoritmo de divisin se ir aplicando recursivamente hasta que se hayan
realizado todas la divisiones posibles (o se hayan llegado al umbral deseado), tal y
como se puede ver en la gura 26.39.
El rbol BSP que se obtiene despus de rellenar todos los niveles se muestra
en la gura 26.41. Notese cmo el rbol queda bastante equilibrado gracias a una
buena eleccin del plano de divisin. Un buen equilibrio es fundamental para que
pueda desempear su labor de manera eciente, puesto que si el rbol estuviera
desequilibrado el efecto sera parecido al no haber realizado una particin espacial.

Figura 26.38: rbol resultante de


la primera divisin (BSP).

Figura 26.39: Sucesivas divisiones del nivel (BSP)

La eleccin de un buen tringulo para utilizar como plano de divisin no es trivial.


Es necesario establecer algn criterio para encontrarlo. Un criterio podra ser tomar el
tringulo ms cercano al centro del conjunto que se tiene que dividir, que podra cortar
a muchos otros tringulos, haciendo que creciera rpidamente el nmero de ellos en
los siguientes subconjuntos. Otro criterio podra ser buscar el tringulo que corte a
menos tringulos. Uno mejor sera mezclar esta dos ideas.
Ericson [31] analiza algunos problemas relacionados con la eleccin del plano
divisor y propone algunas soluciones simples, parecidas al ltimo criterio propuesto
anteriormente.
Otro problema al que hay que enfrentarse es al de la divisin de los tringulos
que resultan cortados por el plano divisor. Cuando se corta un tringulo, este puede
dividirse en dos o tres tringulos, siendo mucho ms probable que se de la segunda
opcin. Esto es debido a que la divisin de un tringulo normalmente genera otro
tringulo y un cuadriltero, que tendr que ser dividido a su vez en otros dos tringulos
(gura 26.40).
Figura 26.40: Divisin de un trin-
gulo por un plano.
26.6. Optimizacin de interiores [769]

La solucin pasa por tomar los vrtices del tringulo atravesado como segmentos
y hallar el punto de interseccin de los mismos con el plano. Con esos puntos ser
trivial reconstruir los tringulos resultantes.
La estructura de rbol BSP podra estar representada en C++ como en el listado
siguiente:

Listado 26.32: Class BSP


1 class BSP {
2 public:
3 BSP(vector<Triangle> vIn);
4
5 private:
6 BSP* front;
7 BSP* back;
8 Plane p;
9 Triangle t; // vector<Triangle>vt;
10 };

Figura 26.41: Sucesivas divisiones del nivel (BSP)

Su construccin vendra dada por una funcin como esta:

Listado 26.33: createBSP


C26
1 BSP* createBSP(const vector<Triangle>& vIn) {
2
3 BSP* bsp = new BSP;
4
5 bsp->t = getBestTriangle(vt);
6 vtNew = removeTriangleFromVector(vt, t);
7
8 bsp->p = planeFromTriangle(t);
9
[770] CAPTULO 26. REPRESENTACIN AVANZADA

10 vector<Triangle>::iterator it;
11
12 for (it = vt.begin(); vt != vt.end(); ++it) {
13 if (cuts(bsp->p, *it))
14 split(*it, bsp->p);
15 }
16 vector<Triangle> frontSet = getFrontSet(vtNew, t);
17 vector<Triangle> backSet = getBackSet(vtNew, t);
18
19 bsp->front = createBSP(frontSet);
20 bsp->back = createBSP(backSet);
21
22 return bsp;
23 }

Orden dependiente de la vista

La principal ventaja de un BSP es que gracias a l es posible obtener una lista de


tringulos ordenados, sea cual sea la vista en la que nos encontremos.
Obsrvese el siguiente listado de cdigo:

Listado 26.34: paintBSP


1 void paintBSP(BSP* bsp, const Viewpoint& vp) {
2 currentPos = backOrFront(bsp, vp);
3 if (currentPos == front) {
4 paintBSP(back, vp);
5 bsp->t.addToRenderQueue();
6 paintBSP(front, vp);
7 } else {
8 paintBSP(front, vp);
9 bsp->t.addToRenderQueue();
10 paintBSP(back, vp);
11 }
12 }

La funcin anterior pinta los tringulos (incluidos los que quedan detrs de la
vista) en orden, desde el ms lejano al ms cercano.
Esto era muy til cuando el hardware no implementaba un Z-buffer, ya que est
funcin obtena los tringulos ordenados con un coste linear.
Si cambiamos el algoritmo anterior (le damos la vuelta) recorreremos las caras
desde las ms cercanas a las ms lejanas. Esto s puede suponer un cambio con el
hardware actual, ya que si pintamos el tringulo cuyo valor va a ser mayor en el Z-
buffer, el resto de los tringulos ya no se tendrn que pintar (sern descartados por el
hardware).

Clipping Jerrquico

Un BSP se puede extender para usarlo como un sistema de aceleracin de clipping,


quitando los tringulos que queden fuera del frustum de la cmara. Lo nico que
hay que aadir en el rbol durante su construccin en una bounding box por cada
nodo. Cuanto ms se profundice en el rbol, ms pequeas sern, y si el algoritmo
de equilibrado de la divisin es bueno, una bounding box contendr otras dos de un
volumen ms o menos parecido, equivalente a la mitad de la contenedora.
El algoritmo para recorrer el rbol es muy parecido al anterior, y bastara con
introducir una pequea modicacin.
26.6. Optimizacin de interiores [771]

Listado 26.35: BSPClipping


1 void paintBSP(BSP* bsp, const Viewpoint& vp, const Camera& cam) {
2 if ( isNodeInsideFrustum(bsp, cam.getCullingFrustum()) )
3 {
4 // Igual que en el ejemplo anterior
5 }
6 }

Deteccin de la oclusin

Tambin es posible utilizar un rbol BSP para detectar oclusiones. Este uso se
populariz gracias al motor de Quake, que utilizaba un nuevo tipo de rbol llamado
leafy-BSP, donde se utilizaron por primera vez para el desarrollo de un videojuego. Su
propiedad principal es la de dividir de manera automtica el conjuntos de tringulos
entrante en un array de celdas convexas.
Este nuevo tipo de rboles son BSPs normales donde toda la geometra se ha
propagado a las hojas, en vez de repartirla por todos los nodos a modo de tringulos
divisores. De este modo, en un BSP normal, las hojas slo almacenan el ltimo
tringulo divisor.
Para transformar un BSP en un leafy-BSP lo que hay que hacer es agitar el rbol
y dejar caer los tringulos de los nodos intermedios en las hojas (ver gura 26.42)

Figura 26.42: Transformacin de BSP a leafy -BSP. C26

Una vez que el rbol se haya generado, se podr almacenar la lista de tringulos
de cada nodo como una lista de celdas numeradas. Para el ejemplo anterior las celdas
se muestran en la gura 26.43.

Figura 26.43: Nivel dividido en


celdas (l-BSP).
[772] CAPTULO 26. REPRESENTACIN AVANZADA

Cada celda representa una zona contigua y convexa del conjunto de tringulos
inicial. Las paredes de las celdas pueden ser o bien reas ocupadas por geometra del
nivel o espacios entre las celdas. Cada espacio abierto debe pertenecer exactamente a
dos celdas.
Ntese que el algoritmo anterior convierte cualquier conjunto de tringulos en una
lista de celdas y de pasillos que las conectan, que es la parte ms complicada.
Es posible precalcular la visibilidad entre las celdas. Para ello se utilizan los
pasillos (o portales, aunque diferentes a los que se vern un poco ms adelante). Se
mandan rayos desde algunos puntos en un portal hacia los dems, comprobndose si
llegan a su destino. Si un rayo consigue viajar del portal 1 al portal 2, signica que
las habitaciones conectadas a ese portal son visibles mutuamente. Este algoritmo fue
presentado por Teller [97].
Esta informacin sobre la visibilidad se almacenar en una estructura de datos
conocida como PVS (Potential Visibility Set), que es slo una matriz de bits de N xN
que relaciona la visibilidad de la la i (celda i) con la de la columna j (celda j).

Rendering

Para representar los niveles de Quake III: Arena se utilizaba un algoritmo ms o


menos como el que se explica a continuacin.

Se comienza determinando en qu celda se encuentra la cmara (el jugador)


utilizando el BSP. Se recorre el rbol desde la raz, comparando la posicin con
la del plano divisor para bajar hasta una hoja y elegir una celda determinada.
Se utiliza el PVS para determinar qu celdas son visibles desde la celda actual, !Precalcular es la clave!
utilizando la matriz de bits (o de booleanos). Los pasos ms costosos han sido
precalculados, haciendo factible la
Se renderizan las celdas visibles. Se pintan desde el frente hasta el fondo, lo representacin en tiempo real.
que ayudar al Z-buffer a descartar tringulos lo antes posible. Se ordenan las
celdas por distancia, se usa su bounding box para determinar si quedan dentro
del frustum para hacer el clipping de pantalla y se mandan a renderizar.

Como se ha podido ver, gracias al uso de un rbol leafy-BSP se han resuelto


casi todos los problemas de determinacin de la visibilidad utilizando una estructura
precalculada. Esto hace que en el bucle principal del juego no se dedique ningn
esfuerzo a computarla. Adems, este tipo de estructuras son tiles para determinar
colisiones en tiempo real y para ayudar a recorrer los niveles a la IA.

Portal Rendering

Otra de las tcnicas utilizadas para optimizar la representacin de interiores son


los portales (Portals). Es un enfoque diferente a los rboles BSP, pero que ofrece
una aceleracin similar. El motor grco Unreal demostr su validez utilizando una
versin del mismo, y ha ganado adeptos entre los desarrolladores desde entonces.
Permite, al igual que los BSPs, representar slo lo que se ve. En el caso de esta tcnica,
no se precalcula la visibilidad sino que es computada en tiempo real.
Esta tcnica se basa en que los niveles de interiores de un juego estn construidos a
base de habitaciones interconectadas entres s por puertas, por ventanas, o en general,
por portales. Es de aqu de donde viene su nombre. Para representar estas conexiones
entre las diferentes habitaciones ser necesario una estructura de datos de grafo no
26.6. Optimizacin de interiores [773]

dirigido. Cada nodo del grafo es una habitacin y cada vrtice del grafo es un portal.
Ya que es posible que en una habitacin se encuentren varios portales, es necesario
que la estructura de datos permita conectar un nodo con varios otros, o que dos nodos
estn conectados por dos vrtices.

Figura 26.45: Grafo que representa las conexiones del mapa de habitaciones.
Figura 26.44: Mapa de habitacio-
nes conectadas por portales
Al contrario que los BSP, la geometra de un nivel no determina de manera
automtica la estructura de portales. As, ser necesario que la herramienta que se use
como editor de niveles soporte la divisin de niveles en habitaciones y la colocacin
de portales en los mismos. De esto modo, la creacin de la estructura de datos
es un proceso manual. Estas estructuras no almacenan datos precalculados sobre la
visibilidad; esta se determinar en tiempo de ejecucin.
El algoritmo de renderizado comienza por ver dnde se encuentra la cmara en un
momento dado, utilizando los bounding volumes de cada habitacin para determinar
dentro de cul est posicionada. Primero se pintar esta habitacin y luego las que
estn conectadas por los portales, de forma recursiva, sin pasar dos veces por un
mismo nodo (con una excepcin que se ver en el siguiente apartado). Lo complejo
del algoritmo es utilizar los portales para hacer culling de la geometra.
Es necesario detectar qu tringulos se pueden ver a travs de la forma del portal,
ya que normalmente habr un gran porcentaje no visible, tapados por las paredes que
rodean al mismo. Desde un portal se puede ver otro, y esto tendr que tenerse en
cuenta al calcular las oclusiones. Se utiliza una variante de la tcnica de view frustum
(que consiste en descartar los tringulos que queden fuera de un frustum, normalmente
el de la cmara), que Dalmau llama portal frustum. El frustum que se utilizar para
realizar el culling a nivel de portal tendr un origen similar al de la cmara, y pasar
por los vrtices del mismo. Para calcular las oclusiones de un segundo nivel en el
grafo, se podr obtener la interseccin de dos o ms frustums.
Portal Un portal puede tener un nmero de vrtices arbitrario, y puede ser cncavo o
Tendr algo que ver esta tcnica
convexo. La interseccin de dos portales no es ms que una interseccin 2D, en la
con algn juego de ValveTM ? Gra- que se comprueba vrtice a vrtice cules quedan dentro de la forma de la recursin
cias a ella el concepto de una de sus anterior. Este algoritmo puede ralentizar mucho la representacin, puesto que el
franquicias ha sido posible. nmero de operaciones depende del nmero de vrtices, y la forma arbitraria de los
portales no ayuda a aplicar ningn tipo de optimizaciones.
Luebke y Jobes [59] proponen que cada portal tenga asociada un bounding
volume, que simplicar enormemente los clculos. Este bounding volume rodea a
todos los vrtices por portal, lo que har que el algoritmo de como visibles algunos
C26

tringulos que no lo son. La prdida de rendimiento es mnima, y ms en el hardware


actual donde probablemente cada habitacin est representada como un array de
tringulos.
[774] CAPTULO 26. REPRESENTACIN AVANZADA

Efectos pticos utilizando portales

Una de las ventajas principales de utilizar la tcnica de portales en comparacin


con la de BSP es que se pueden implementar efectos de reexin y de transparencia,
usando el algoritmo central de la misma. Para llevar a cabo este tipo de efectos, lo
nico que hay que aadir a cada portal es su tipo, por ejemplo los portales podran ser
del tipo normal, espejo, transparencia, o de cualquier otro efecto que se pueda llevar a
cabo a travs de este tipo de estructuras.
Espejos
Listado 26.36: Ejemplo de representacin de un portal
Usando portales, poner espejos en
1 enum portalType { la escena tiene un coste gratuito,
2 NORMAL, excepto porque supone representar
3 MIRROR, dos veces la misma habitacin.
4 TRANPARENT,
5 INTERDIMENSIONAL,
6 BLACK_VOID
7 };
8
9 struct portal {
10 vector<Vertex3D*> vertexes_;
11 portalType type_;
12 Room* room1;
13 Room* room2;
14 };

A la hora de representar un portal, podra discriminarse por el tipo, utilizando la


tcnica adecuada segn corresponda.

Listado 26.37: Ejemplo de eleccin de tipos de portales


1 switch(type_) {
2 case NORMAL:
3 // Algoritmo Normal
4 break;
5
6 case MIRROR:
7 // Calcular la cmara virtual usando el plano de soporte del
portal
8 //
9 // Invertir la view-matrix
10 //
11 // Pintar la habitacin destino
12 //
13 // Pintar la geometra del portal de forma
14 // translcida con algo de opacidad si se desea
15 break;
16
17 case TRANSPARENT:
18 // Pintar de forma normal la habitacin que corresponda
19 //
20 // Pintar la geometra del portal de forma
21 // translcida con algo de opacidad si se desea
22 break;
23
24 case INTERDIMENSIONAL:
25 // Modificar los vrtices del array con una funcin sinuidal
26 // Pintarlo
27 // Aadir colores chillones a la opacidad del portal.
28 break;
29
30 case BLACK_VOID:
31 // Modificar la geometra para que la habitacin
32 // destino parezca estirada hacia un agujero negro
33 //
26.6. Optimizacin de interiores [775]

34 // Pintar un borde brillante en los vrtices de la forma del


portal.
35 break;
36 }

Se le ocurre algn tipo de efecto ms o alguna otra forma de aprovechar las


caractersticas de los portales?

Mapas de Oclusin Jerrquicos (HOM)

Esta tcnica, al igual que la de portales, computa la oclusiones en tiempo real


durante la ejecucin del bucle principal. La ventaja principal de esta tcnica es que no
es necesario preprocesar la geometra del nivel de ningn modo. Adems, otra ventaja
de HOM (Hierarchical Occlusion Maps) frente a BSP o portales es que permite utilizar
geometra tanto esttica como dinmica de manera indiferente.
HOM [106] est basado en una jerarqua de mapas de oclusin. Cada uno de
ellos ser de la mitad de tamao que el anterior. Se comienza con una representacin
a pantalla completa de nuestra escena en blanco y negro. Tras esto se calculan una
serie de mipmaps, donde cada 2x2 pxeles se transforman en uno de la nueva imagen.
Este algoritmo es muy sencillo si se comienza con una imagen cuadrada potencia de
dos. Estas imgenes son las que forman la jerarqua de mapas de oclusin. El mapa
de oclusin no contiene la escena completa, slo un conjunto de posibles oclusores,
elegidos con algn mtodo parecido al explicado en el primer algoritmo.
En el bucle principal se pintar objeto por objeto, utilizando algn tipo de
estructura no lineal, como un octree o un quadtree. Para cada objeto se calcula un
bounding rectangle alineado con la pantalla. Despus se toma la imagen (el nivel
HOM) con un tamao de pxel aproximadamente similar al mismo. Este rectngulo
Figura 26.46: HOM. Jerarqua de puede caer en una zona completamente blanca, y habr que hacer ms comprobaciones
imgenes (Z HANG).
(existe un full-overlap, el objeto que comprobamos est completamente delante o
detrs), puede caer en una zona negra, y se tendr que pintar, o puede caer en una
zona gris, caso en el que habr que consultar con una imagen de mayor resolucin.
Cuando el rectngulo cae en una zona con blanca, es necesario hacer una
comprobacin sobre los valores Z para comprobar si el objeto est delante o detrs.
Esto se consigue con un DEB (Deep Estimation Buffer)2 , que no es ms que un Z-
buffer construido por software, utilizando los posibles oclusores. El DEB almacena
la informacin resultante de crear las bounding boxes de los oclusores y almacenar
a modo de pxel el valor ms lejano (al contrario que un Z-buffer normal) para cada
posicin de la pantalla.
El algoritmo completo podra describirse como sigue:

Seleccionar un buen conjunto de oclusores. Se descartarn objetos pequeos,


C26

o con muchos polgonos, y los objetos redundantes. Es posible colocar objetos


falsos que no se pintarn como oclusores a mano, para mejorar el conjunto de
manera premeditada.
En ejecucin, se seleccionan los primeros N oclusores ms cercanos.
2 Buffer de Estimacin de Profundidad
[776] CAPTULO 26. REPRESENTACIN AVANZADA

Calcular el HOM en funcin de estos objetos. Con la funcin de render-to-


texture se crea la primera imagen. Las dems, por software o utilizando otras
texturas y alguna funcin de mipmapping de la GPU.
Mientras se recorre el scene-graph se comparan estos objetos con el mapa de
oclusin. Si caen en una zona blanca, se comprueban contra el DEB; si caen
en una zona negra, se pinta; y si caen en una zona gris, ser necesario usar una
imagen de mayor resolucin.

Dalmau arma que con esta tcnica se evita pintar de media entre un 40 % y un
60 % de toda la geometra entrante.

Enfoques hbridos

En los videjuegos se suele utilizar la combinacin de tcnicas que ms benecio


brinde al tipo de representacin en tiempo real a la que se est haciendo frente. De
nuevo, Dalmau propone dos aproximaciones hbridas.
Equilibrio
Portal-Octree
Utilizar lo mejor de cada una de las
tcnicas hace que se puedan suplir
En un juego donde el escenario principal est repleto de habitaciones y cada una sus debilidades.
de ellas est llena de objetos, una aproximacin de travs de un BSP quiz no sera la
mejor idea. No slo porque este tipo de estructuras est pensado principalmente para
objetos estticos, sino porque un rbol BSP suele extenderse muy rpido al empezar a
dividir el espacio.
Si adems el juego requiere que se pueda interactuar con los objetos que hay en
cada habitacin, el BSP queda descartado para almacenar los mismos. Quiz utilizar
la tcnica de portales pueda usarse para las habitaciones, descartando as algo de
geometra del nivel. Aun as la gran cantidad de objetos hara que fueran inmanejables.
Una posible solucin: utilizar portales para representar las habitaciones del nivel,
y en cada habitacin utilizar un octree.

Quadtree-BSP

Hay juegos que poseen escenarios gigantestos, con un rea de exploracin muy
grande. Si se enfoca la particin de este tipo de escenas como la de un rbol
BSP, el gran nmero de planos de divisin har que crezca la geometra de manera
exponencial, debido a los nuevos tringulos generados a partir de la particin.
Una forma de afrontar este problema es utilizar dos estructuras de datos. Una
de ellas se usar para realizar una primera divisin espacial de la supercie (2D, un
Quadtree, por ejemplo) y la otra para una divisin ms exhaustiva de cada una de esas
particiones. De esto modo, se podr utilizar un Quadtree donde cada nodo contiene un
BSP.
De este modo, se pueden utilizar las caractersticas especiales de cada uno de
ellos para acelerar la representacin. En un primer paso, el Quadtree facilitar la
determinacin de la posicin global de una manera muy rpida. Una vez que se sepa en
qu parte del escenario se encuentra la accin, se tomar el BSP asociado a la misma
y se proceder a su representacin como se mostr en el apartado anterior.
Este tipo de representaciones espaciales ms complejas no son triviales, pero a
veces son necesarias para llevar a cabo la implementacin exitosa de un videojuego.
En el siguiente captulo ese introducirn los quadtrees.
26.6. Optimizacin de interiores [777]

Recuerda alguno de estos tipo de escenas en los ltimos videojuegos a los


que ha jugado?

Tests asistidos por hardware

Las tarjetas grcas actuales proveen de mecanismos para llevar a cabo los
clculos de deteccin de la oclusin por hardware. Estos mecanismos consisten en
llamadas a funciones internas que reducirn la complejidad del cdigo. El uso de
estas llamadas no evitar la necesidad de tener que programar pruebas de oclusin,
pero puede ser una ayuda bastante importante.
Hardware La utilizacin del hardware para determinar la visibilidad se apoya en pruebas
sobre objetos completos, pudiendo rechazar la inclusin de los tringulos que los
El uso del hardware para realizar
los tests de oclusin es el futuro, forman antes de entrar en una etapa que realice clculos sobre los mismos. As, las
pero eso no quita que se deban co- GPUs actuales proveen al programador de llamadas para comprobar la geometra de
nocer las tcnicas en las que se ba- objetos completos contra el Z-buffer. Ntese como estas llamadas evitarn mandar
sa para poder utilizarlo de manera estos objetos a la GPU para se pintados, ahorrando las transformaciones que se
efectiva.
producen antes de ser descartados. Adems, como retorno a dichas llamadas se puede
obtener el nmero de pxeles que modicra dicho objeto en el Z-buffer, lo que
permitira tomar decisiones basadas en la relevancia del objeto en la escena.
Cabe destacar que si se usa la geometra completa del objeto, mandando todos
los tringulos del mismo al test de oclusin de la GPU, el rendimiento global podra
incluso empeorar. Es algo normal, puesto que en una escena pueden existir objetos con
un gran nmero de tringulos. Para evitar este deterioro del rendimiento, y utilizar esta
capacidad del hardware en benecio propio, lo ms adecuado es utilizar bounding-
boxes que contengan a los objetos. Una caja tiene tan solo 12 tringulos, permitiendo
realizar tests de oclusin rpidos y bastante aproximados. Es fcil imaginarse la
diferencia entre mandar 12 tringulos o mandar 20000.
Adems, si las pruebas de oclusin para todos los objetos se llevan a cabo de forma
ordenada, desde los ms cercanos a los ms lejanos, las probabilidades de descartar
algunos de ellos aumentan.
Como ejemplo, uno tomado de las especicaciones de occlusion query de las
extensiones de OpenGL [69].

Listado 26.38: Oclusin por hardware


1 GLuint queries[N];
2 GLuint sampleCount;
3 GLint available;
4 GLuint bitsSupported;
5
6 // Comprobar que se soporta la funcionalidad
7 glGetQueryiv(GL_QUERY_COUNTER_BITS_ARB, &bitsSupported);
8 if (bitsSupported == 0) {
// Representar sin test de oclusion...
C26
9
10 }
11
12 glGenQueriesARB(N, queries);
13 ...
14
15 // Antes de este punto, renderizar los oclusores mayores
16 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
17 glDepthMask(GL_FALSE);
18
[778] CAPTULO 26. REPRESENTACIN AVANZADA

19 // tambien deshabilitar el texturizado y los shaders inutiles


20 for (i = 0; i < N; i++) {
21 glBeginQueryARB(GL_SAMPLES_PASSED_ARB, queries[i]);
22 // renderizar la bounding box para el objecto i
23 glEndQueryARB(GL_SAMPLES_PASSED_ARB);
24 }
25
26 glFlush();
27
28 // Hacer otro trabajo hasa que la mayoria de las consultas esten
listas
29 // para evitar malgastar tiempo
30 i = N*3/4; // en vez de N-1, para evitar que la GPU se ponga en
dle"
31 do {
32 DoSomeStuff();
33 glGetQueryObjectivARB(queries[i],
34 GL_QUERY_RESULT_AVAILABLE_ARB,
35 &available);
36 } while (!available);
37
38 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
39 glDepthMask(GL_TRUE);
40
41 // habilitar otro estado, como el de texturizado
42 for (i = 0; i < N; i++) {
43 glGetQueryObjectuivARB(queries[i], GL_QUERY_RESULT_ARB,
44 &sampleCount);
45 if (sampleCount > 0) {
46 // representar el objecto i
47 }
48 }

26.6.3. Manejo de escenas en OGRE


La representacin de escenas en OGRE tiene su base en un grafo de escena
(Scene Graph ) un tanto particular. En otros motores grcos, el grafo de escena est
completamente ligado al tipo nodo del mismo. Esto hace que exista un completo
acoplamiento entre grafo y nodos, haciendo muy difcil cambiar el algoritmo de
ordenacin una vez realizada la implementacin inicial.
Para solucionar este problema, OGRE interacta con el grafo de escena slo me-
diante su rma (sus mtodos pblicos), sin importar cmo se comporte internamente.
Adems, OGRE tan slo utiliza este grafo como una estructura, ya que los nodos no
contienen ni heredan ningn tipo de funcionalidad de control. En vez de eso, OGRE
utiliza una clase renderable de donde se derivar cualquier tipo de geometra que pue-
da contener una escena. En el captulo de fundamentos, se vio la relacin entre un tipo
renderable y un Movable Object, que estar ligado a un nodo de escena. Esto quiere
decir que un nodo de escena puede existir sin tener ninguna capacidad representativa,
puesto que no es obligatorio que tenga ligado ningn MovableObject. Con esto
se consigue que los cambios en la implementacin de los objetos rendarables y en BSP
la implementacin del grafo de escena no tengan efectos entre ellos, desacoplando la El soporte de OGRE para BSP es
implementacin de los mismos. histrico. Se usa slo para cargar
mapas de Quake3 y este es el ni-
Es posible incluso ligar contenido denido por el programador a un nodo de co casa para el que se recomienda
escena, implementando una interfaz muy sencilla. Gracias a esto, se podran por usarlos.
ejemplo aadir sonidos ligados a ciertas partes de una escena, que se reproduciran El futuro de OGRE
en una parte determinada de la misma.
Los gestores de escenas de OGRE
estn evolucionando. Utilizar el
gestor por defecto es una buena ga-
ranta de que ser compatible con
las siguientes versiones.
26.6. Optimizacin de interiores [779]

El grafo de escena que se utiliza en OGRE se conoce como scene manager (gestor
de escena) y est representado por la clase Scene-Manager. El interfaz de la misma
lo implementan algunos de los gestores de escena que incluye OGRE, y tambin
algunos otros desarrollados por la comunidad o incluso comerciales.
En OGRE es posible utilizar varios gestores de escena a la vez. De esto modo,
es posible utilizar uno de interiores y de exteriores a la vez. Esto es til por ejemplo
cuando desde un edicio se mira por una ventana y se ve un terreno.
Un gestor de escena de OGRE es responsable entre otras cosas de descartar los
objetos no visibles (culling) y de colocar los objetos visibles en la cola de renderizado.

Interiores en OGRE

El gestor de escena para interiores de OGRE est basado en BSP. De hecho, este
gestor se utiliza con mapas compatibles con Quake 3. Hay dos formas de referirse a
este gestor, la primera como una constante de la enumeracin Ogre::SceneType
(ST_INTERIOR) y otra como una cadena ("BspSceneManager") que se reere
al nombre del Plugin que lo implementa. La forma ms moderna y la preferida es la

segunda.
En la lnea 9 se crea el SceneManager de BSP, y en la lnea 30 se carga el
mapa y se usa como geometra esttica.
Cabe destacar que para poder navegar por los mapas BSP de manera correcta hay
que rotar 90 grados en su vector pitch y cambiar el vector up de la cmara por el eje
Z.

Listado 26.39: Ejemplo de carga de BSP en OGRE


1 _root = new Ogre::Root();
2
3 if(!_root->restoreConfig()) {
4 _root->showConfigDialog();
5 _root->saveConfig();
6 }
7
8 Ogre::RenderWindow* window = _root->initialise(true,"BSP");
9 _sceneManager = _root->createSceneManager("BspSceneManager");
10
11 Ogre::Camera* cam = _sceneManager->createCamera("MainCamera");
12 cam->setPosition(Ogre::Vector3(0,0,-20));
13 cam->lookAt(Ogre::Vector3(0,0,0));
14 cam->setNearClipDistance(5);
15 cam->setFarClipDistance(10000);
16 // Cambiar el eje UP de la cam, los mapas de Quake usan la Z
17 // Hacer los ajustes necesarios.
18 cam->pitch(Ogre::Degree(90));
19 cam->setFixedYawAxis(true, Ogre::Vector3::UNIT_Z);
20
21 Ogre::Viewport* viewport = window->addViewport(cam);
22 viewport->setBackgroundColour(Ogre::ColourValue(0.0,0.0,0.0));
23 double width = viewport->getActualWidth();
24 double height = viewport->getActualHeight();
25 cam->setAspectRatio(width / height);
C26
26
27 loadResources();
28
29 [...]
30
31 _sceneManager->setWorldGeometry("maps/chiropteradm.bsp");
[780] CAPTULO 26. REPRESENTACIN AVANZADA

Se propone crear una escena utilizando un mapa BSP. Se sugiere utilizar el


archivo pk0 que se distribuye con OGRE. Pruebe a navegar por l con y sin
el x de la cmara. Aada alguna forma de mostrar los FPS. En qu partes
del mapa y en qu condiciones aumenta esta cifra? Por qu?

26.7. Optimizacin de Exteriores

26.7.1. Introduccin
Las diferencias entre una escena de interiores y una de exteriores son evidentes.
Mientras una escena de interiores se dar en entornos cerrados, con muchas paredes o
pasillos que dividen en espacio en habitaciones, una escena de exteriores normalmente
no tiene ningn lmite que no est impuesto por la naturaleza. Si bien es cierto que es
una escena de este tipo, por ejemplo, pudiesen existir colinas que se tapasen unas a
las otras, si estamos situados frente a algunas de ellas en una posicin muy lejana, el
nmero de tringulos que se deberan representar sera tan elevado que quiz ningn
hardware podra afrontar su renderizado.
Est claro que hay que afrontar la representacin de exteriores desde un enfoque
diferente: hacer variable el nivel de detalle LOD). De este modo, los detalles de una
montaa que no se veran a cierta distancia no deberan renderizarse. En general, el
detalle de los objetos que se muestran grandes en la pantalla (pueden ser pequeos
pero cercanos), ser mayor que el de los objetos menores.
Si bien el nivel de detalle es importante, tampoco se descarta el uso de oclusiones
en algunos de los algoritmos que se presentarn a continuacin, siguiendo de nuevo
la propuesta de Dalmau. Se comenzar haciendo una pasada rpida por algunas de las
estructuras de datos necesarias para la representacin de exteriores eciente.

26.7.2. Estructuras de datos


Uno de los principales problemas que se tienen que resolver para la representacin
de exteriores es la forma de almacenar escenas compuestas por grandes extensiones
de tierra.
Las estructuras de datos utilizadas tendrn que permitir almacenar muchos datos,
computar de manera eciente el nivel de detalle necesario y permitir que la transicin
entre diferentes dichos niveles sea suave y no perceptible.
Figura 26.47: Mapa de altura
Mapas de altura (W IKIPEDIA - P UBLIC D OMAIN)

Los mapas de altura (heigtelds o heightmaps) han sido utilizados desde hace
mucho tiempo como forma para almacenar grandes supercies. No son ms que
imgenes en las que cada uno de sus pxeles almacenan una altura.
Cuando se empez a usar esta tcnica, las imgenes utilizaban tan solo la escala
de grises de 8-bit, lo que supona poder almacenar un total de 256 alturas diferentes.
Los mapas de altura de hoy en da pueden ser imgenes de 32-bit, lo que permite
que se puedan representar un total de 4.294.967.296 alturas diferentes, si se usa el Figura 26.48: Mapa de altura ren-
derizado (W IKIMEDIA C OMMONS)
canal alpha.
26.7. Optimizacin de Exteriores [781]

Para transformar uno de estos mapas en una representacin 3D es necesario hacer


uso de un vector de 3 componentes con la escala correspondiente. Por ejemplo, si se
tiene un vector de escala s = (3, 4, 0,1) quiere decir que entre cada uno los pxeles del
eje X de la imagen habr 3 unidades de nuestra escena, entre los de Y habr 4, y que
incrementar una unidad el valor del pxel signicar subir 0,1 unidades. La posicin
vara segn cmo estn situados los ejes.
Las ventajas de utilizar un mapa de altura es que se pueden crear con cualquier
herramienta de manipulacin de imgenes y que se pueden almacenar directamente
en memoria como arrays de alturas que se transformarn en puntos 3D cuando sea
necesario, liberando de este modo mucha memoria.
Las principal desventaja viene de que cada pxel representa una sola altura,
haciendo imposible la representacin de salientes o arcos. Normalmente todo este
tipo de detalles tendrn que aadirse en otra capa.

Quadtrees

Figura 26.49: Representacin de un Quadtree (W IKIMEDIA C OMMONS - DAVID E PPSTEIN)

Un quadtree es un rbol donde cada nodo tendr exactamente cuatro hijos, as, se
dice que es un rbol 4-ario. Un quadtree divide un espacio en cuatro partes iguales por
cada nivel de profundidad del rbol (gura 26.49).
Un quadtree permite que ciertas reas del terreno se puedan representar con ms
detalle puesto que es posible crear rboles no equilibrados, utilizando ms niveles
C26

donde sea necesario. Una aproximacin vlida es comenzar con un mapa de altura y
crear un quadtree a partir del mismo. Las partes del escenario donde exista ms detalle
(en el mapa de altura, habr muchas alturas diferentes), se subdividirn ms veces.

Figura 26.50: Divisin de un te-


rreno con un quadtree (Q IUHUA
L IANG)
[782] CAPTULO 26. REPRESENTACIN AVANZADA

Adems al representar la escena, es posible utilizar un heurstico basado en la


distancia hasta la cmara y en el detalle del terreno para recorrer el rbol. Al hacerlo
de este modo, ser posible determinar qu partes hay que pintar con ms detalle,
seleccionando la representacin ms simple para los objetos ms distantes y la ms
compleja para los ms cercanos y grandes.
En el momento de escribir esta documentacin, InniteCode permite descargar de
su web [47] un ejemplo de Quadtrees (apoyados en mapas de altura).

rboles binarios de tringulos (BTT)

Un BBT (Balanced Binary Tree) es un caso especial de un rbol binario. En cada


subdivisin se divide al espacio en dos tringulos. El hecho de que cada nodo tenga
menos descendientes que un quadtree e igualmente menos vecinos, hace que esta
estructura sea mejor para algoritmo de nivel de detalle continuo.
Dallaire [23] explica con detalle en Gamasutra cmo generar este tipo de
estructuras para indexar fragmentos de terreno.

26.7.3. Determinacin de la resolucin


En una escena de exteriores, a parte de utilizar la estructura de datos correcta, es Figura 26.51: Divisin de una su-
crucial disponer de un mtodo para determinar la resolucin de cada uno de los objetos percie con un BTT.
de la escena.
Cada objeto podr aparecer con diferentes resoluciones y para ello sern necesarias
dos cosas: la forma de seleccionar la resolucin correcta y la manera de representarla,
esto es, un algoritmo de renderizado que permita hacerlo de manera correcta.
Determinar la resolucin de manera exacta no es un problema abordable en un
tiempo razonable, puesto que son muchas las variables que inuyen en dicha accin.
Lo mejor es afrontar este problema utilizando algn heurstico permita aproximar una
buena solucin.
Un primer enfoque podra ser utilizar la distancia desde la cmara hasta el objeto,
y cambiar la resolucin del mismo segn objeto se acerca (ms resolucin) o se aleja
(menos resolucin). Puede que haya un objeto muy lejano pero que sea tan grande que
requiera un poco ms del detalle que le corresponda segn la distancia. Un heurstico
aadido que se podra utilizar es el nmero de pxeles que ocupa aproximadamente en
el espacio de pantalla, utilizando una bouding-box y proyectndola sobre la misma.
Incluso se podra utilizar el hardware, como se ha visto en el tema anterior, para
realizar estas comprobaciones.
Una vez que se ha determinado cmo se selecciona la resolucin el siguiente
paso ser aplicar la poltica de dibujado. Existe una gran divisin, dependiendo de la
continuidad de los modelos a pintar. Si se almacenan diferentes modelos de un mismo
objeto, con niveles diferentes de detalle, hablaremos de una poltica discreta de LOD.
Si el detalle de los modelos se calcula en tiempo real segn el criterio de resolucin,
hablaremos de una poltica de LOD continua.
26.7. Optimizacin de Exteriores [783]

Polticas Discretas de LOD

Si se utiliza una poltica discreta, se tendrn varias representaciones del mismo


objeto, con diferentes nivel de detalle. Este nivel de detalle ir desde la versin
original, con el mayor detalle posible, a una versin con muy pocos tringulos. Se
crear la versin de alto detalle y se generarn versiones simplicadas reduciendo el
nmero de tringulos gradualmente con alguna herramienta de diseo 3D.
Teniendo una tabla con diferentes modelos donde elegir, el algoritmo de pintado
simplemente tiene que elegir el que corresponda y ponerlo en la cola de renderizado.
El problema de estas polticas es que existen un momento en el que se produce un
cambio notable en el objeto, y es completamente perceptible si no se disimula de
alguna forma.
Una de las tcnica que se usa para ocultar el salto que se produce al cambiar
de modelo es utilizar alpha blending entre el modelo origen y destino. Este efecto
se puede ver como un cross-fade entre los mismos, cuya intensidad depender del
heurstico utilizado. As en la mitad de la transicin, cada modelo se renderizar
con un alpha de 0.5 (o del 50 %). Justo antes de empezar la transicin, el modelo
origen tendr un valor alpha de 1 y el destino de 0, y al nalizar tendrn los valores
intercambiados. Un inconveniente muy importante de esta tcnica es que durante un
tiempo durante el cual antes slo se representaba un objeto, ahora se representarn
dos, lo que supone una sobrecarga de la GPU.

Polticas Continuas de LOD

Si se quiere evitar del todo el salto producido por el intercambio de modelos, se


podra implementar una forma de reducir el nmero de tringulos en tiempo real, y
generar un modelo dependiendo de la resolucin requerida.
Este tipo de clculos en tiempo real son muy costosos, porque hay que determinar
qu aristas, vrtices o tringulos se pueden eliminar y adems aplicar esa modicacin
al modelo.
Hoppe [44] propone una implementacin eciente de lo que llama mallas progre-
sivas (Progressive Meshes). La tcnica se basa en la eliminacin de aristas de la malla,
convirtiendo dos tringulos en slo uno por cada arista eliminada (edge-collapsing).
Hoppe determina que esta tcnica es suciente para simplicar mallas y propone al-
gunos heursticos para eliminar las aristas.
Hay dos posibles aproximaciones a la hora de quitar una arista, la primera, crear
un nuevo vrtice en el centro de la misma, y la otra eliminarla completamente (ms
eciente). La primera aproximacin es vlida para todas las aristas, la segunda es slo
vlida para aristas que corten a tringulos y no a cuadrilteros, puesto que al eliminarla
se debera obtener un polgono con tres vrtices.
Un posible heurstico para utilizar es el ngulo que forman los dos tringulos
conectados por dicha arista. Si el ngulo es menor que un umbral, se quitar esa arista.
Como optimizacin, esta tcnica no debera utilizarse en cada frame, sino slo cuando
cambie la distancia o el rea que ocupa el objeto en pantalla lo suciente.
Este tipo de polticas permite obtener el mejor resultado, a costa de aadir un coste
C26

computacional. Adems, otra desventaja muy importante es que la informacin de


mapeado de las texturas del objeto se podr ver afectada por la reduccin del nmero
de tringulos.
Figura 26.52: Edge-Collapsing.
Arriba, el strip original. Abajo, el
nuevo strip despus de aplicar la
tcnica.
[784] CAPTULO 26. REPRESENTACIN AVANZADA

26.7.4. Tcnicas y Algoritmos


Las estructuras de datos presentadas anteriormente se utilizan en las tcnicas y
algoritmos que se presentan a continuacin.

GeoMipmapping

De Boer [25] presenta el GeoMipmapping como una tcnica para representar de


manera eciente grandes terrenos. En su artculo, De Boer divide el algoritmo en
tres fases diferentes: la representacin en memoria de los datos del terreno y a qu
corresponder en la representacin, el frustum culling con los pedazos de terreno
disponibles (chunks) y por ltimo, describe los GeoMipMaps haciendo una analoga
con la tcnica de mipmapping usada para la generacin de texturas.

Representacin del terreno

La representacin del terreno elegida es la de una malla de tringulos cuyos


vrtices estn separados por la misma distancia en el eje X y en el eje Z.
El nmero de vrtices horizontales y verticales de la malla tendr que ser de la
forma 2n +1, lo que signica tener mallas con 2n cuadrilteros, que tendrn 4 vrtices
compartidos con sus vecinos. Cada cuadriltero est compuesto de dos tringulos, que
sern los que se mandarn a la cola de representacin. Figura 26.53: Malla con los datos
del terreno. Cada crculo es un vr-
Cada vrtice tendr un valor jo de X y de Z, que no cambiar durante el tice.
desarrollo del algoritmo. El valor de Y (altura) ser ledo de un mapa de altura de
8-bit, que tendr exactamente las misma dimensiones que la malla. Posteriormente
esta se cortar en pedazos de tamao 2n + 1. Estos pedazos se usarn en un quadtree
para realizar el frustum culling, y como primitivas de nivel 0 para los GeoMipMaps.
En la gura 26.53 se muestra una de estas mallas, donde n vale 2.
Una ventaja de utilizar este tipo de representacin es que los pedazos de malla se
pueden mandar como una sola primitiva (strips) el hardware. La desventaja es que los
vrtices de los 4 bordes de dichos trozos se comparten con los bloques que lo rodean,
y se transformarn dos veces.

View-Frustum Culling

Ser necesario descartar los pedazos de terreno que no caigan dentro del frustum
de la vista (cmara) puesto que no sern visibles. Para ello, lo ideal es utilizar un
quadtree.
El quadtree ser precalculado antes de comenzar la parte interactiva de la
aplicacin y consistir tan solo en bounding boxes de tres dimensiones, que a su vez
contendrn otras correspondientes a los subnodos del nodo padre. En cada hoja del
rbol quedar un pedazo del nivel 0 de divisiones que se ha visto al principio. Es
suciente utilizar quadtrees y no octrees ya que la divisin se realiza de la supercie,
y no del espacio.
Para descartar partes del terreno, se recorrer el rbol desde la raz, comprobando
si la bounding box est dentro del frustum al menos parcialmente, y marcando dicho
nodo en caso armativo. Si una hoja est marcada, quiere decir que ser visible y que
se mandar a la cola de representacin. A no ser que el terreno sea muy pequeo,
se terminarn mandando muchos tringulos a la cola, y esta optimizacin no ser Figura 26.54: Ejemplo de una
suciente. Es aqu donde De Boer introduce el concepto de Geomipmapping. bounding-box (M ATH I MAGES
P ROJECT ).
26.7. Optimizacin de Exteriores [785]

Geomipmaps y nivel de detalle

Esta tcnica se basa en el hecho de que los bloques que estn ms lejos de la
cmara no necesitan representarse con tan nivel de detalle como los ms cercanos.
De este modo, podrn ser representados con un nmero mucho menor de tringulos,
lo que reducir enormemente el nmero de tringulos del terreno que se mandarn a
la cola de renderizado. Otro algortmos utilizan una aproximacin en la que hay que
analizar cada tringulo para poder aplicar una poltica de nivel de detalle. Al contrario,
esta tcnica propone una poltica discreta que se aplicar en un nivel ms alto.
La tcnica clsica de mipmapping se aplica a las texturas, y consiste en la
generacin de varios niveles de subtexturas a partir de la original. Este conjunto de
texturas se utilizan en una poltica de nivel de detalle para texturas, usando unas u
otras dependiendo de la distancia a la cmara. Esta es la idea que se va a aplicar a la
mallas 3D de terrenos.
Cada bloque de terreno tendr asociado varios mipmaps, donde el original corres-
ponde al bloque del mapa de altura. Estos GeoMipMaps pueden ser precalculados y
Figura 26.55: Construccin de los almacenados en memoria para poder ser utilizados en tiempo de ejecucin directa-
mipmaps. En cada nivel, la textura mente.
se reduce a un cuarto de su rea.
(A KENINE -M OLLER)

Figura 26.56: Diferentes niveles de detalle de la malla desde los GeoMipMaps : niveles 0, 1 y 2.

Para elegir qu geomipmap es adecuado para cada distancia y evitar saltos


(producto de usar distancias jas para cada uno) habr que utilizar un mtodo un poco
ms elaborado. En el momento en que se se pase del nivel 0 al 1 (ver gura 26.56)
existir un error en la representacin del terreno, que vendr dado por la diferencia
en altura entre ambas representaciones. Debido a la perspectiva, la percepcin del
error tendr que ver con los pxeles en pantalla a la que corresponda esa diferencia.
Ya que cada nivel tiene muchos cambios en altura, se utilizar el mximo, que podr
almacenarse durante la generacin para realizar decisiones ms rpidas. Si el error en
pxeles cometido es menor que un umbral, se utilizar un nivel ms elevado.
Hay que tener en cuenta que la mayora de las ocasiones el terreno estar formado
por bloques con deferentes niveles, lo que puede hacer que existan vrtices no
conectados. Ser necesario reorganizar las conexiones entre los mismos, creando
nuevas aristas.
En la gura 26.57 se muestra una propuesta que consiste en conectar los vrtices
de la malla de nivel superior con los de la de nivel inferior pero saltando un vrtice
cada vez.
C26

Figura 26.57: Unin de diferentes


niveles de GeoMipmaps. En rojo la
frontera comn.
[786] CAPTULO 26. REPRESENTACIN AVANZADA

ROAM

Duchaineau [29] propone ROAM (Real-time Optimally Adapting Meshes) como


un enfoque de nivel de detalle continuo al problema de la representacin de exteriores.
El algoritmo que propone combina una buena representacin del terreno (lo que
facilitar el culling) con un nivel de detalle dinmico que cambiar la resolucin del
terreno segn la disposicin de la cmara. ROAM es un algoritmo complejo, como el
de los BSPs, que est dividido en dos pasadas y que permite una representacin muy
rpida de terrenos.
En este algoritmo, la malla se calcula en tiempo real, utilizando preclculos sobre
la resolucin necesaria en cada caso. En la primera pasada se rellena un BTT con
la informacin geogrca, aadiendo informacin sobre el error producido al entrar
en un subnodo (que ser usada para detectar zonas que necesiten un mayor nivel de
detalle). En la segunda pasada se construir otro BTT, que ser el encargado de crear
la maya y de representar el terreno.

Primera pasada: rbol de Varianza.

En la primera pasada se construir un rbol que almacenar el nivel de detalle


que existe en el terreno. Una buena mtrica es la varianza. En cada hoja del rbol (un
pxel de un mapa de altura), la varianza almacenada ser por ejemplo la media de los
pxeles que lo rodean. La varianza de los nodos superiores ser la mxima varianza de
los nodos hijos.

Segunda pasada: Malla.

Se utilizar un BTT, donde el nodo raz representa un terreno triangular (si se


quiere representar un terreno rectangular se necesitarn los nodos como este). Se
almacenar la informacin de la altura para cada una de las esquinas del tringulo
almacenado en cada nodo.
Si entre los vrtices de este tringulo grande la informacin del terreno no es
coplanar, se consultar el rbol de varianza para determinar si es conveniente explorar
una nueva divisin para aadir un nivel ms de detalle. Se rellenar el BTT hasta que
no queden pxeles por aadir del mapa de altura, o hasta que el estadstico sea menor Figura 26.58: Distintos niveles de
que un umbral elegido. divisin en un BTT.

De este modo se construye un rbol que representa la malla, donde cada nivel
del mismo corresponde a un conjunto de tringulos que pueden ser encolados para
su renderizado. El rbol podr expandirse en tiempo real si fuera necesario. Bajar un
nivel en el rbol equivale a una operacin de particin y subir a una de unin.
El problema viene de la unin de regiones triangulares con diferente nivel de
detalle, ya que si no se tienen en cuenta aparecern agujeros en la malla representada.
Mientras que en el geomipmapping se parchea la malla, en ROAM, cuando se detecta
una discontinuidad se utiliza un oversampling de los bloques vecinos para asegurar
que estn conectados de manera correcta. Para ello, se aade informacin a cada lado
de un tringulo y se utilizan alguna reglas que garantizarn la continuidad de la malla.
Se conoce como vecino base de un tringulo al que est conectado a este a travs
de la hipotenusa. A los otros dos tringulos vecinos se los conocer como vecino
izquierdo y derecho (gura 26.59). Analizando diferentes rboles se deduce que el
vecino base ser del mismo nivel o del anterior (menos no) nivel de detalle, mientras Figura 26.59: Etiquetado de un
que los otros vecinos podrn ser del mismo nivel o de uno ms no. tringulo en ROAM. El tringulo
junto a su vecino base forman un
diamante.
26.7. Optimizacin de Exteriores [787]

Las reglas propuestas para seguir explorando el rbol y evitar roturas en la malla
sern las siguientes:

Si un nodo es parte de un diamante, partir el nodo y el vecino base.


Si se est en el borde de una malla, partir el nodo.
Si no forma parte de un diamante, partir el nodo vecino de forma recursiva hasta
encontrar uno antes de partir el nodo actual (gura 26.60).

Figura 26.60: Particin recursiva El algoritmo en tiempo de ejecucin recorrer el rbol utilizando alguna mtrica
hasta encontrar un diamante para el para determinar cundo tiene que profundizar en la jerarqua, y cmo ha de realizar
nodo de la izquierda. las particiones cuando lo haga depender de las reglas anteriores.
Realizar en tiempo real todos estos clculos es muy costoso. Para reducir este
coste, se puede dividir el terreno en arrays de BTTs y slo recalcular el rbol cada
ciertos frames y cuando la cmara se haya movido lo suciente para que cambie la
vista por encima de un umbral.
Otra aproximacin para optimizar el algoritmo es utilizar el frustum para deter-
minar qu nodos hay que reconstruir de nuevo, marcando los tringulos que quedan
dentro o fuera, o parcialmente dentro. Slo estos ltimos necesitarn atencin para
mantener el rbol actualizado. A la hora de representarlo, lo nico que habr que ha-
cer es mandar los nodos marcados como dentro, total o parcialmente.

Chunked LODs

Ulrich [100] propone un mtodo para representar grandes extensiones de tierra. En


la demo del SIGGRAPH 2002 incluye un terreno que cubre 160Km2 . Este mtodo
tiene su partida en una imagen muy grande, por ejemplo obtenida de un satlite, lo
que hace el mtodo ideal para la implementacin de simuladores de vuelo.
Para almacenar la informacin se utilizar un quadtree. Se comenzar con una
imagen potencia de dos en el nodo raz, que corresponder a una imagen de muy baja
resolucin del terreno completo. Segn se vaya profundizando, los 4 subnodos hijos
contendrn imgenes del mismo tamao pero con la calidad resultante de hacer un
zoom a los cuatro cuadrantes de la misma (gura 26.61).

Figura 26.61: Nodo y subnodos de las texturas en un quadtree para Chunked LODs.
C26

A cada uno de los nodos se le aade informacin acerca de la prdida del nivel de
detalle se produce al subir un nivel en la jerarqua. Si las hojas contienen imgenes de
32x32 pxeles, los pedazos de terreno contendrn 32x32 vrtices. El rbol de mallas
tambin forma parte del preproceso normalmente, partiendo de una malla muy grande
y con mucha resolucin y partindola en trozos ms pequeos.
[788] CAPTULO 26. REPRESENTACIN AVANZADA

Para renderizar el quadtree bastar con recorrerlo realizando clipping jerrquico,


utilizando algn umbral para comparar con un valor obtenido de computar la distancia
a la cmara, el error que produce un nivel determinado y la proyeccin de perspectiva
que se est utilizando. Si la comparacin sugiere que el nivel de detalle debera
incrementarse en esa regin, se proceder a encolar un nivel ms bajo del rbol
recursivamente.
Ulrich propone unir los chunks simplemente prolongando un poco sus fronteras
con unos faldones. Como la proyeccin de la textura depender slo de la X y la Z,
las uniones sern prcticamente invisibles. Este algoritmo se benecia de un quadtree
para texturizar el terreno. Como contrapartida, el principal problema del mismo es
la gran cantidad de memoria que necesita (la demo del SIGGRAPH, ms de 4GB), lo
que hace que sea necesario prestar especial atencin a la misma, cargando en memoria
los nuevos pedazos cuando sean necesarios, descartando los que no se necesitan hace
tiempo.

Terrenos y GPU

En su libro, Dalmau propone una aproximacin diferente a la representacin de


terrenos utilizando simplemente la GPU. La premisa de la que parte el algoritmo es
mantener a la CPU completamente desocupada, pudiendo ser utilizada esta para otra
labores como para la inteligencia articial o para el clculo de las colisiones o las
fsicas.
De esto modo sugiere que la geometra del terreno tendr que almacenarse en
algn modo en el que la GPU pueda acceder a ella sin la intervencin de la CPU,
seleccionando bloques de 17x17 vrtices (512 tringulos), que sern analizados
e indexados para maximizar el rendimiento. Adems, como diferentes bloques
compartirn vrtices, estos se almacenarn slo una vez y se indexarn de forma
eciente. As, la CPU slo tendr que determinar los bloques visibles y mandar esta
informacin a la GPU para que los pinte.
A esta tcnica se le puede sumar el uso de bounding boxes para cada bloque
de terreno y la construccin de un PVS o incluso implementar alguna poltica de
LOD (que Dalmau dene como no necesaria excepto en GPU con limitaciones muy
restrictivas de nmero de tringulos por segundo).

Scenegraphs de Exteriores

Una escena de exteriores es mucho ms grande que una de interiores. La cantidad


de datos que es necesario manejar es en consecuencia gigantesca en comparaciones
con escenas mucho ms pequeas. Aunque se tenga una buena poltica de nivel de
detalle, hay que tener en cuenta que el nmero de tringulos totales que se tendrn
que manejar es enorme.
Para implementar un grafo de escena de exteriores es importante tener en cuenta
que:

Cada objeto slo tendr una instancia, y lo nico que se almacenar aparte de
esta sern enlaces a la misma.
Es necesario implementar alguna poltica de nivel de detalle, puesto que es
imposible mostrar por pantalla (e incluso mantener en memoria) absolutamente
todos los tringulos que se ven en una escena.
Se necesita una rutina muy rpida para descartar porciones no visibles del
terreno y del resto de objetos de la escena.
26.7. Optimizacin de Exteriores [789]

Mientras que para almacenar un terreno y las capas estticas que lleva encima es
muy buena opcin utilizar un quadtree, ser mejor utilizar alguna tabla de tipo rejilla
para almacenar la posicin de los objetos dinmicos para determinar cules de ellos
se pintar en cada fotograma de manera rpida.

Reconoce alguna de estas tcnicas en algn juego o aplicacin que haya


utilizado recientemente?

26.7.5. Exteriores y LOD en OGRE


OGRE da soporte a diferentes estilos de escenas. El nico scene manager que
es de uso exclusivo para interiores es ST_INTERIOR (el gestor de portales parece
estar abandonado en los ejemplos de ogre 1.7.3). El resto gestores est ms o menos
preparado para escenas de exteriores. Estos son:

ST_GENERIC - Gestor de propsito general, adecuado para todo tipo de


escenas, pero poco especializado. Se utiliza un octree para almacenar los
objetos.
ST_EXTERIOR_CLOSE - Gestor de terrenos antiguo de OGRE. Soportado
hasta la versin 1.7, deprecado en la 1.8.
ST_EXTERIOR_REAL_FAR - Gestor que permite dividir la escena en un con-
junto de pginas. Slo se cargarn las pginas que se usen en un momento de-
terminado, permitiendo representar escenas muy grandes de cualquier tamao.
Cada pgina tiene asociado un mapa de altura, y se pueden aplicar diferentes
texturas a la malla generada segn la altura.

Terrenos

OGRE soporta la creacin de terrenos utilizando mapas de altura, que se pueden


cargar en diferentes pginas y cubrir grandes extensiones en una escena.

En estos momentos OGRE est cambiando de gestor de terrenos. El gestor


nuevo utiliza shaders que dependen del plugin CgProgramManager, vlido
nicamente para tarjetas grcas NVIDIA. Esta dependencia se da slo
utilizando en sistema de render basado en OpenGL, porque lo mientras en
las demos de OGRE de Windows funciona perfectamente con DirectX, en
GNU/Linux har falta cargar este plugin. En Debian, ya no se distribuye este
plugin con lo que ser necesario compilarlo desde cero.
C26

Un terreno de OGRE implementa una poltica de nivel de detalle continua. El


nuevo gestor implementa una variante de Chunked-LODS y permite cargar mapas
CG Plugin
muy grandes, compuestos por diferentes mapas de altura.
Para compilar el plugin CgManager A continuacin se muestra un ejemplo de uso del nuevo terreno de OGRE. El
de OGRE en Debian, habr que ins-
talar antes nvidia-cg-toolkit, que es Scene Manager que utiliza es el genrico. El mapa de altura que cargar por secciones
privativo. en el mismo.
[790] CAPTULO 26. REPRESENTACIN AVANZADA

Figura 26.62: Pantallazo de terreno en OGRE


En la lineas 3-5 se congura niebla en
la escena, usando un color oscuro para
hacer que parezca oscuridad. En las lneas 7-14 se congura una luz direccional que
en el terreno. A partir de ella se calcularn las sombras
se utilizar en el mismo. En
la lnea 16 se congura la luz ambiental de la escena. En la 19 se crea un nuevo
objeto de conguracin del terreno, que se rellenar ms adelante. En la 20 se crea
un objeto agrupador de terrenos. Este objeto es el responsable de agrupar diferentes
del terreno juntos para que puedan ser representados como proceda. En la
pedazos
lnea 26 se congura el error mximo medido en pxeles de pantalla que se permitir
al representar el terreno. En las lneas 28-31 se congura la textura que compodr con
l, dibujando las sombras. Primero se determina la distancia de representacin de las
luces, luego se selecciona la direccin de las sombras, que parte de una luz direccional
y luego se conguran el valor ambiental y difuso de iluminacin.

Listado 26.40: Ejemplo de terreno en OGRE. Basado en O GRE 3D W IKI


1 void MyApp::createScene()
2 {
3 _sMgr->setFog(Ogre::FOG_LINEAR,
4 Ogre::ColourValue(0.1, 0.1, 0.1),
5 0.5, 2000, 5000);
6
7 Ogre::Vector3 lightdir(0.55, -0.3, 0.75);
8 lightdir.normalise();
9
10 Ogre::Light* light = _sMgr->createLight("DirLight");
11 light->setType(Ogre::Light::LT_DIRECTIONAL);
12 light->setDirection(lightdir);
13 light->setDiffuseColour(Ogre::ColourValue::White);
14 light->setSpecularColour(Ogre::ColourValue(0.4, 0.4, 0.4));
15
16 _sMgr->setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2));
26.7. Optimizacin de Exteriores [791]

17
18
19 _tGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();
20 _tGroup = OGRE_NEW Ogre::TerrainGroup(_sMgr,
21 Ogre::Terrain::ALIGN_X_Z,
22 513, 12000.0f);
23
24 _tGroup->setOrigin(Ogre::Vector3::ZERO);
25
26 _tGlobals->setMaxPixelError(8);
27
28 _tGlobals->setCompositeMapDistance(3000);
29 _tGlobals->setLightMapDirection(light->getDerivedDirection());
30 _tGlobals->setCompositeMapAmbient(_sMgr->getAmbientLight());
31 _tGlobals->setCompositeMapDiffuse(light->getDiffuseColour());
32
33 Ogre::Terrain::ImportData& di;
34 di = _tGroup->getDefaultImportSettings();
35
36 di.terrainSize = 513;
37 di.worldSize = 12000.0f;
38 di.inputScale = 600;
39 di.minBatchSize = 33;
40 di.maxBatchSize = 65;
41
42 di.layerList.resize(3);
43 di.layerList[0].worldSize = 100;
44 di.layerList[0].textureNames.push_back("dirt_diff_spec.png");
45 di.layerList[0].textureNames.push_back("dirt_normal.png");
46 di.layerList[1].worldSize = 30;
47 di.layerList[1].textureNames.push_back("grass_diff_spec.png");
48 di.layerList[1].textureNames.push_back("grass_normal.png");
49 di.layerList[2].worldSize = 200;
50 di.layerList[2].textureNames.push_back("growth_diff_spec.png");
51 di.layerList[2].textureNames.push_back("growth_normal.png");
52
53
54 Ogre::Image im;
55 im.load("terrain.png",
56 Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
57
58 long x = 0;
59 long y = 0;
60
61 Ogre::String filename = _tGroup->generateFilename(x, y);
62 if (Ogre::ResourceGroupManager::getSingleton().resourceExists(
_tGroup->getResourceGroup(), filename))
63 {
64 _tGroup->defineTerrain(x, y);
65 }
66 else
67 {
68 _tGroup->defineTerrain(x, y, &im);
69 _tsImported = true;
70 }
71
72 _tGroup->loadAllTerrains(true);
73
74 if (_tsImported)
75 {
76 Ogre::TerrainGroup::TerrainIterator ti;
77 ti = _tGroup->getTerrainIterator();
C26
78 while(ti.hasMoreElements())
79 {
80 Ogre::Terrain* t = ti.getNext()->instance;
81 initBlendMaps(t);
82 }
83 }
84
85 _tGroup->freeTemporaryResources();
86
[792] CAPTULO 26. REPRESENTACIN AVANZADA

87 Ogre::Plane plane;
88 plane.d = 100;
89 plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
90
91 _sMgr->setSkyPlane(true, plane,"Skybox/SpaceSkyPlane",
92 500, 20, true, 0.5, 150, 150);
93
94 }
95
96 void MyApp::initBlendMaps(Ogre::Terrain* t)
97 {
98 Ogre::TerrainLayerBlendMap* blendMap0 = t->getLayerBlendMap(1);
99 Ogre::TerrainLayerBlendMap* blendMap1 = t->getLayerBlendMap(2);
100
101 Ogre::Real minHeight0 = 70;
102 Ogre::Real fadeDist0 = 40;
103 Ogre::Real minHeight1 = 70;
104 Ogre::Real fadeDist1 = 15;
105
106 float* pBlend1 = blendMap1->getBlendPointer();
107 for (Ogre::uint16 y = 0; y < t->getLayerBlendMapSize(); ++y) {
108 for (Ogre::uint16 x = 0; x < t->getLayerBlendMapSize(); ++x){
109 Ogre::Real tx, ty;
110
111 blendMap0->convertImageToTerrainSpace(x, y, &tx, &ty);
112 Ogre::Real height = t->getHeightAtTerrainPosition(tx, ty);
113 Ogre::Real val = (height - minHeight0) / fadeDist0;
114 val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1);
115 val = (height - minHeight1) / fadeDist1;
116 val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1);
117 *pBlend1++ = val;
118 }
119 }
120 blendMap0->dirty();
121 blendMap1->dirty();
122 blendMap0->update();
123 blendMap1->update();
124 }


En 34 se obtiene la instancia que congura algunos parmetros del terreno.
El primero es el tamao del terreno, que corresponde al nmero de pxeles del
mapa de altura. El segundo el tamao total del mundo, en unidades virtuales. El
tercero (inputScale) corresponde a la escala aplicada al valor del pxel, que se
transformar en la altura del mapa. Las dos siguientes corresponden al tamao de
los bloques de terrenos que se incluirn en la jerarqua (diferentes LOD). Estos tres
ltimos valores tendrn que ser del tipo 2n + 1. El atributo layerList contiene un
vector de capas, que se rellenar con las texturas (color y mapa de normales en este
worldSize corresponde a la relacin de tamao entre la textura y
caso). El atributo
el mundo. En 55 se carga la imagen con el mapa de altura.
Las siguientes lneas son las que asocian el mapa de altura con el terreno que se va

a generar. En este ejemplo slo se genera elsubterreno (0,0) con lo que slo se cargar
una imagen. En las siguientes lneas 61-70 se dene el terreno, en este caso, slo para
el slot (0,0), aunque el ejemplo est preparado para ser extendido fcilmente y aadir
la denicin algunos ms. La llamada defineTerrain() expresa la intencin de
que contiene el mapa de altura. La ejecucin
crear ese pedazo de terreno con la imagen
de este deseo se realiza en la linea 72 . Sin esta llamada, el ejemplo no funcionara
puesto que se ejecutara el siguiente paso sin haber cargado (generado) el terreno.
26.7. Optimizacin de Exteriores [793]


En el bloque 74-83 se crea la capa de blending, esto es, la fusin entre las tres
texturas del terreno (habr dos de blending). Esta operacin se realiza en el
mapas
mtodo initBlendMaps 96-124 . Dependiendo de la altura a la que corresponda
un pxel de las imgenes a fusionar (el tamao ha de ser coherente con el mapa de
altura) as ser el valor de blending aplicado, con lo que se conseguir que cada altura

presente una textura diferente.
En las lneas 87-89 se aade un plano, y justo debajo, en la 91 se usa como un
Skyplane.

Skyboxes, skydomes y skyplanes

Una de las caractersticas de una escena de exteriores es que muy probablemente


se llegue a ver el horizonte, una gran parte del cielo, o si estamos en un entorno marino
o espacial, el abismo o las estrellas. Representar con alto de nivel de detalle los objetos
del horizonte es practicamente imposible. Una de las razones es que probablemente
alguna de las polticas de culling o de nivel de detalle har que no se pinte ningn
objeto a partir de una distancia determinada de la cmara.
La solucin adopotada por muchos desarrolladores y que est disponible en OGRE
es utilizar imgenes estticas para representar lo que se ve en el horizonte. En OGRE
existen tres formas de representar el entorno que contiene a todos los objetos de
la escena. Es decir, no habr ningn objeto que quede fuera de las imgenes que
representan el horizonte. Los objetos que brinda OGRE son de tipo:

skybox - Como su nombre indica es una caja. Normalmente son cubos


situados a una distancia ja de la cmara. As, ser necesario proporcionar seis
texturas, una para cada uno de sus caras.
skydome - Corresponde con una cpula de distancia ja a la cmara. Con
una textura es suciente. Como contrapartida, una skydome slo cubre la mitad
de una esfera sin distorsionarse y, mientras una skybox posee un suelo, una
skydome no.
skyplane - El cielo est representado por un plano jo a la cmara, con lo
que parecer innito. Con una sla textura es suciente.

Ejemplo de Skybox

Para utitlizar una skybox primero es necesario denir un material:

material SkyBoxCEDV
{
technique
{
pass
{
lighting off
depth_write off
C26

texture_unit
{
cubic_texture cubemap_fr.jpg cubemap_bk.jpg cubemap_lf.jpg\
cubemap_rt.jpg cubemap_up.jpg cubemap_dn.jpg separateUV
tex_address_mode clamp
}
}
[794] CAPTULO 26. REPRESENTACIN AVANZADA

}
}

En este material se deshabilita la escritura en el buffer de profundidad (a travs de setSkyBox()


depth_write off) para que la representacin del cubo no afecte a la visibilidad
del resto de los objetos. Tambin se ha de representar sin iluminacin (lighting Esta llamada acepta ms parme-
off). Con cubic_texture se determina que la textura ser cbica. Existen dos tros, si fuera necesario denir una
formas de declarar qu imgenes se usarn para la misma. La del ejemplo anterior orientacin tendra que usarse un
quinto parmetro (el cuarto es un
consiste en enumerar las seis caras. El orden es: booleano que indica que la caja se
dibuja antes que el resto de la esce-
<frontal> <posterior> <izquierda> <derecha> <arriba> <abajo> na.

nombre de los archivos que queda delate de _. En el caso anterior, sera cubemap.jpg. El
ltimo parmetro puede ser combinedUVW, si en una misma imagen estn contenidas las seis
caras, o separateUV si se separan por imgenes. La primera forma utilizar coordenadas de
textura 3D, la segunda usar coordenadas 2D ajustando cada imagen a cada una de las caras.
Usando tex_address_mode clamp har que los valores de coordenadas de texturizado
mayores que 1,0 se queden como 1,0.
Para utilizar una Skybox simplemente habr que ejecutar esta lnea:

1 sceneManager->setSkyBox(true, "SkyBoxCEDV", 5000);

Como cabra esperar, la skybox se congura para el gestor de escenas. sta se encontrar
a una distancia ja de la cmara (el tercer parmetro), y podr estar activada o no (primer
parmetro).

Ejemplo de SkyDome

Aunque slo se utiliza una textura, realmente una SkyDome est formada por las cinco
caras superiores de un cubo. La diferencia con una skybox es la manera en la que se proyecta
la textura sobre dichas caras. Las coordenadas de texturizado se generan de forma curvada y
por eso se consigue tal efecto de cpula. Este tipo de objetos es adecuado cuando se necesita
un cielo ms o menos realista y la escena no va a contener niebla. Funcionan bien con texturas
repetitivas como las de nubes. Una curvatura ligera aportar riqueza a una escena grande y una
muy pronunciada ser ms adecuada para una escena ms pequea donde slo se vea pedazos
de cielo de manera intermitente (y el efecto exagerado resulte atractivo).
Un ejemplo de material es el siguiente:

material SkyDomeCEDV
{
[...]
texture_unit
{
texture clouds.jpg
scroll_anim 0.15 0
}
[...]
}

El resto del mismo sera idntico al ejemplo anterior. Con scroll_anim se


congura un desplazamiento jo de la textura, en este caso slo en el eje X.
La llamada para congurar una skydome en nuestra escena en un poco ms
compleja que para una skybox.

1 sceneManager->setSkyDome(true, "SkyDomeCEDV", 10, 8, 5000);


26.7. Optimizacin de Exteriores [795]

Donde el tercer parmetro es la curvatura (funciona bien para valores entre 2 y


65), y el cuarto es el nmero de veces que se repite la textura en la cpula.

Ejemplo de SkyPlane

Un skyplane es a priori la ms simple de las representaciones. Como su nombre


indica est basado en un plano, y la creacin de uno es necesaria. Aun as tambin es
posible aplicarle algo de curvatura, siendo ms adecuado para escenas con niebla que
una cpula. El material podra ser el mismo que antes, incluso sin utilizar animacin.
Se ve un ejemplo del uso del mismo en el ejemplo anterior del terreno.

Se propone crear una escena con cada uno de las tres tcnicas anteriores. Si
fuera posible, utilice el ejemplo de terreno anterior.

LOD : Materiales y Modelos

OGRE da soporte a la representacin con diferentes niveles de detalle tanto en


materiales como en modelos.

Materiales

En los materiales no slo se remite a las texturas sino a todas las propiedades
editables dentro del bloque technique. Para congurar un material con soporte para
LOD lo primero es congurar la estrategia (lod_strategy) que se va a utilizar de
las dos disponibles:

Distance - Basado en la distancia desde la cmara hasta la representacin del


material. Se mide en unidades del mundo.
PixelCount - Basado en el nmero de pxeles del material que se dibujan en
la pantalla para esa instancia.

Tras esto, lo siguiente es determinar los valores en los que cambiar el nivel de de-
talle para dicha estrategia. Se usar para tal labor la palabra reservada lod_values
seguida de dichos valores. Es importante destacar que tendrn que existir al menos
tantos bloques de technique como valores, ya que cada uno de ellos estar relacio-
nado con el otro respectivamente. Cada uno de los bloques technique debe tener
asociado un ndice para el nivel de detalle (lod_index). Si no aparece, el ndice se-
r el 0 que equivale al mayor nivel de detalle, opuestamente a 65535 que corresponde
con el menor. Lo normal es slo tener algunos niveles congurados y probablemente
jams se llegue a una cifra tan grande. Aun as, es importante no dejar grandes sal-
C26
tos y hacer un ajuste ms o menos ptimo basado en las pruebas de visualizacin de
la escena. Es posible que varias tcnicas tengan el mismo ndice de nivel de detalle,
siendo OGRE el que elija cul es la mejor de ellas segn el sistema en el que se est
ejecutando.
Un ejemplo de material con varios niveles de detalle:

material LOD_CEDV
[796] CAPTULO 26. REPRESENTACIN AVANZADA

{
lod_values 200 600
lod_strategy Distance

technique originalD {
lod_index 0
[...]
}
technique mediumD {
lod_index 1
[...]
}

technique lowD {
lod_index 2
[...]
}

technique shaders {
lod_index 0
lod_index 1
lod_index 2
[...]
}
}

Ntese que la tcnica shaders afecta a todos los niveles de detalle.

Como ejercicio se propone construir una escena que contenga 1000 objetos
con un material con tres niveles de detalle diferente. Siendo el nivel 0 bastante
complejo y el nivel 3 muy simple. Muestre los fotogramas por segundo y
justique el uso de esta tcnica. Se sugiere mostrar los objetos desde las
diferentes distancias conguradas y habilitar o deshabilitar los niveles de
detalle de baja calidad.

Modelos

OGRE proporciona tres formas de utilizar LOD para mallas. La primera es una
donde modelos con menos vrtices se generan de forma automtica, haciendo uso
de progressiveMesh internamente. Para utilizar la generacin automtica ser
necesario que la malla que se cargue no tenga ya de por s LOD incluido (por ejemplo,
la cabeza de ogro de las demos ya contiene esta informacin y no se podr aplicar).
Una vez que la entidad est creada, se recuperar la instancia de la malla contenida
dentro.

Podra analizar los niveles de detalle incluidos en la cabeza de ogro de


las demos de OGRE? Use OgreXMLConverter para obtener una versin
legible si es necesario.
26.7. Optimizacin de Exteriores [797]

Listado 26.41: Generacin de niveles de detalle para una malla


1 Entity *entidad = sceneManager->createEntity("objeto", "objeto.mesh
");
2
3 Ogre::MeshPtr mesh = entidad->getMesh();
4
5 Ogre::ProgressiveMesh::LodValueList lodDvec;
6
7 lodDvec.push_back(50);
8 lodDvec.push_back(100);
9 lodDvec.push_back(150);
10 lodDvec.push_back(200);
11
12 Ogre::ProgressiveMesh::generateLodLevels(mesh, lodDList,
ProgressiveMesh::VRQ_PROPORTIONAL, 0.1);


En la lnea 3 del ejemplo anterior se crea una lista de distancias para el
nivel de detalle. Esta lista no es ms que un vector de reales que determina
los valores que se utilizarn segn la estrategia elegida para esa entidad. Por
defecto corresponde con la distancia y en ese caso los valores corresponden a las
races cuadradas de la distancia en la que se producen cambios. La estrategia se
puede cambiar con setLodStrategy() usando como parmetro un objeto de
tipo base lodStrategy, que ser DistanceLodStrategy o PixelCount-
LodStrategy. Estas dos clases son singletons, y se deber obtener su instancia
utilizando getSingleton(). La segunda de ellas corresponde con la poltica
basada en el nmero de pxeles que se dibujan en la pantalla de una esfera que contiene
al objeto (bounding-sphere). En la lnea 12 se llama a la funcin que genera los
diferentes niveles a partir de la malla original. El segundo parmetro es corresponde
con el mtodo que se usar para simplicar la malla:

VRQ_CONSTANT - Se elimina un nmero dijo de vrtices cada iteracin.


VRQ_PROPORTIONAL - Se elimina una cantidad proporcional al nmero de
vrtices que queda en la malla.

El tercero corresponde con el valor asociado al mtodo y en el caso de ser un


porcentaje tendr que estar acotado entre 0 y 1.
La segunda forma de aadir LOD a una entidad de OGRE es hacerlo de manera
completamente manual. Ser necesario crear diferentes mallas para el mismo objeto.
Esto se puede llevar a cabo en un programa de edicin 3D como Blender. Se empieza
con el modelo original, y se va aplicando algn tipo de simplicacin, guardando
cada uno de los modelos obtenidos, que sern usados como mallas de diferente nivel
de detalle para la entidad que represente a dicho modelo.

Listado 26.42: Generacin de niveles de detalle para una malla


1 Ogre::MeshPtr m =
2 Ogre::MeshManager::getSingleton().load("original.mesh",
Figura 26.63: Ejemplo de boun- 3 Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
ding -sphere (M ATH I MAGES P RO - 4
JECT) 5 m->createManualLodLevel(250, "lod2.mesh");
C26
6 m->createManualLodLevel(500, "lod3.mesh");
7 m->createManualLodLevel(750, "lod3.mesh");
8
9 Ogre::Entity* objeto = sceneManager->createEntity("objetoConLOD",
10 m->getName() );
11
12 Ogre::SceneNode* n = sceneManager->getRootSceneNode()->
createChildSceneNode();
13 n->attachObject(objeto);
[798] CAPTULO 26. REPRESENTACIN AVANZADA

La tercera forma es que el archivo que se carga ya tenga la informacin de nivel de


detalle. Esta forma es bastante habitual, ya que el programa OgreXMLConverter
acepta los siguientes parmetros relacionados con en nivel de detalle cuando se
convierte un XML en un archivo mesh de OGRE:

-l <lodlevels> - El nmero de niveles de detalle.


-v <lodvalue> - El valor asociado a la reduccin de detalle.
-s <lodstrategy> - La estrategia a usar. Puede ser Distance o PixelCount.
-p <lodpercent> - Porcentaje de tringulos reducidos por iteracin.
-f <lodnumtrs> - Nmero jo de vrtices para quitar en cada iteracin.

Cuando se cargue un archivo mesh creado de este modo, se utilizar de forma


automtica el nmero de niveles de detalle congurados.

Se propone la construccin de una escena con unos 1000 objetos utilizando


cualquiera de las dos primeras formas programticas de aadir LOD. Justi-
que mediante el nmero de frames por segundo el uso de este tipo de tcnicas.
Compare la velocidad usando y sin usar LOD.

26.7.6. Conclusiones
En los dos ltimas secciones se ha realizado una introduccin a algunas de las
tcnicas utilizadas para aumentar el rendimiento de las representaciones en tiempo
real. Sin algunas de ellas, sera del todo imposible llevar a cabo las mismas. Si
bien es cierto que cualquier motor grco actual soluciona este problema, brindando
al programador las herramientas necesarias para pasar por alto su complejidad, se
debera conocer al menos de forma aproximada en qu consisten las optimizaciones
relacionadas.
Saber elegir la tcnica correcta es importante, y conocer cul se est utilizando
tambin. De esto modo, ser posible explicar el comportamiento del juego en
determinadas escenas y saber qu acciones hay que llevar a cabo para mejorarla.
Por desgracia, OGRE est cambiando alguno de los gestores de escena y se est
haciendo evidente en la transicin de versin que est sucediendo en el momento de
escribir esta documentacin.
La forma de realizar optimizaciones de estos tipos ha cambiado con el tiempo,
pasando de utilizar el ingenio para reducir las consultas usando hardware genrico, a
usar algoritmos fuertemente apoyados en la GPU.
Sea como sea, la evolucin de la representacin en tiempo real no slo pasa
por esperar a que los fabricantes aceleren sus productos, o que aparezca un nuevo
paradigma, sino que requiere de esfuerzos constantes para crear un cdigo ptimo,
usando los algoritmos correctos y eligiendo las estructuras de datos ms adecuadas
para cada caso.
Captulo

Plataformas Mviles
27
Miguel Garca Corchero

U
Motores de videojuego n motor de videojuegos es un termino que hace referencia a una serie de
herramientas que permiten el diseo, la creacin y la representacin de un
Existen otros motores de videojue-
gos ms utilizados en la industria videojuego. La funcionalidad bsica de un motor es proveer al videojuego
como CryEngine o Unreal Engine renderizacin, gestin de fsicas, colisiones, scripting, animacin, administracin de
y tienen ms funcionalidades que memoria o gestin del sonidos entre otras cosas.
Unity3D pero tambin es ms com-
plejo trabajar con ellos. En este captulo se trata el caso especco del motor de videojuegos Unity3D y se
realizar un repaso supercial por la forma de trabajar con un motor de videojuegos
mientras se realiza un videjuego de ejemplo para dispositivos mviles con el sistema
operativo iOS o Android.
El videojuego ser un shootem up de aviones con vista cenital.

27.1. Mtodo de trabajo con un motor de videojuegos

27.1.1. Generacin de contenido externo al motor


Diseo del videojuego: Esta fase suele hacerse con papel y boli. En ella
deniremos las mecnicas necesarias a implementar, haremos bocetos de los
personajes o situaciones implicadas y creamos listas de tareas a realizar
asignando diferentes prioridades.

Figura 27.1: Visualizacin del mo-


delo 3D del jugador.

799
[800] CAPTULO 27. PLATAFORMAS MVILES

Generacin del material grco: El videojuego necesitar grcos, texturas,


fuentes, animaciones o sonidos. Este material se conoce como assets. Podemos
utilizar diferentes programas de modelado y texturizado 3D o 2D para esta
tarea, ya que todos los assets son exportados a un formato de entrada de los
que reconoce el motor grco.

27.1.2. Generacin de contenido interno al motor


Escritura de scripts: La escritura de scripts se realiza con un editor de texto
externo a las herramientas del motor grco pero se considera contenido
ntimamente relacionado del motor de videojuego.
Escritura de shaders: Los shaders tambin se escriben con un editor externo. Scripts

Importacin de assets: Uno de los pasos iniciales dentro del entorno integrado Los scripts que utilizamos se pue-
den escribir en los lenguajes C#, Ja-
de Unity3D es aadir al proyecto todo el material generado anteriormente y vascript o BOO.
ajustar sus atributos; como formatos, tamaos de textura, ajuste de propiedades,
calculo de normales, etc.
Creacin de escenas: Crearemos una escena por cada nivel o conjunto de
mens del videojuego. En la escena estableceremos relaciones entre objetos y
crearemos instancias de ellos.
Creacin de prefabs: Los prefabs son agrupaciones de objetos que se salvan
como un objeto con entidad propia.
Optimizacin de la escena: Uno de los pasos fundamentales se lleva a cabo
al nal del desarrollo de la escena y es la optimizacin. Para ello emplearemos
tcnicas de lightmapping y occlusion culling con las herramientas del entorno.

Con Unity3D no tenemos que preocuparnos de la gestin de las fsicas,


colisiones, renderizacin o controles a bajo nivel. Nos dedicamos nicamente
a la programacin de scripts y shaders.

Figura 27.2: Visualizacin de un


shader de normal mapping.

27.2. Creacin de escenas


Una escena est constituida por instancias de objetos de nuestros assets y
las relaciones entre ellos. Podemos considerar una escena como la serializacin
de el objeto escena. Este objeto contiene jerrquicamente otros objetos que son
almacenados cuando se produce esa serializacin, para posteriormente cargar la
escena con todas esas instancias que contendrn los mismos valores que cuando fueron
almacenados. Hay dos enfoques diferentes a la hora de crear escenas:

1. Una escena por cada nivel del juego: Utilizaremos este enfoque cuando cada
nivel tenga elementos diferentes. Podrn repetirse elementos de otros niveles,
pero trataremos que estos elementos sean prefabs para que si los modicamos
en alguna escena, el cambio se produzca en todas.
27.2. Creacin de escenas [801]

Figura 27.3: En nuestro ejemplo se han utilizado capturas de pantalla de google map para obtener texturas
de terreno y se han mapeado sobre un plano en Blender. Posteriormente se ha utilizado el modo scuplt para
dar relieve al terreno y generar un escenario tridimensional.

2. Una nica escena con elementos modicados dinmicamente: Puede que en


nuestro videojuego todos los niveles tengan el mismo tipo de elementos pero lo
diferente sea la dicultad o el nmero de enemigos, en este caso podemos crear
una nica escena pero variar sus condiciones dinmicamente en funcin de en
que nivel estemos.
C27
[802] CAPTULO 27. PLATAFORMAS MVILES

Figura 27.4: Interface de Unity3D. Dividida en las vistas ms utilizadas: Jerarqua de escena, Assets del
proyecto, Vista del videojuego, Vista de escena y Vista de propiedades del asset seleccionado.

Como las escenas pueden cargarse desde otra escena. Podemos realizar un cambio
de escena cuando se ha llegado al nal de un determinado nivel por ejemplo. Este
cambio de escena mantendr en memoria los elementos comunes como texturas o
modelos 3D o sonidos que pertenezcan a los dos escenas, la escena que se descarga y
la escena que se carga, por lo que dependiendo del caso esta carga suele ser bastante
rpida. Tambin podemos realizar los mens en una escena y desde ah cargar la
escena del primer nivel del juego.

Figura 27.5: Visualizacin de la je-


rarqua de assets de nuestro proyec-
to.
27.3. Creacin de prefabs [803]

Figura 27.6: Para la escena de nuestro ejemplo hemos aadido el modelo 3D del escenario, los enemigos,
el jugador y hemos establecido las relaciones de jerarqua necesarias entre estos elementos.

27.3. Creacin de prefabs


Como se ha descrito en el apartado anterior, cada escena contiene instancias de
objetos de nuestros Assets. Cada uno de los objetos de nuestra escena es un nodo, y
cada nodo puede contener jerrquicamente a otros nodos.
Uso de prefabs Podemos agrupar esa jerarqua y darle un nombre propio para despus serializarla
e instanciarla en el futuro. A ese concepto se le conoce con el nombre de prefab.
Cuando un elemento se repita en los Podemos crear tantos prefabs como queramos a partir de jerarquas de objetos de una
diferentes niveles o en la misma es-
cena debe de ser un prefab. De esta escena y son una parte fundamental para entender el mtodo de trabajo con un motor.
forma slo se tiene una referencia
de ese objeto y es ptimo en rendi-
En nuestra escena hemos creado prefabs para cada uno de los tipos de enemigos, y
miento y organizacin de assets. tambin hemos creado prefabs para el disparo, una explosin y un efecto de partculas
de llamas.
C27
[804] CAPTULO 27. PLATAFORMAS MVILES

Figura 27.7: El nodo Camera contiene otros nodos de manera jerrquica.

Figura 27.8: Prefabs de nuestro videojuego de ejemplo.


27.4. Programacin de scripts [805]

27.4. Programacin de scripts


Una script es un chero de cdigo que contiene instrucciones sobre el compor-
tamiento de un determinado actor de nuestra escena. Podemos aadir uno o varios
scipts a cada uno de los elementos de nuestra escena y adems los scripts tienen la
posibilidad de hacer referencia a estos objetos o scripts de otros objetos.
Documentacin En los scripts podemos utilizar las clases y APIs que nos proporciona el motor de
Podemos consultar la documenta-
videojuegos. Algunas de estas clases son:
cin de cada una de las APIs para
los tres lenguajes de scripting desde GameObject: Esta clase tiene informacin sobre el objeto. Todos los nodos de
la pgina ocial de Unity3D
una escena son GameObjects.
Transform: Esta clase representa la posicin, rotacin y escala de un elemento
en el espacio tridimensional.
AudioSource: Esta clase almacena un sonido y permite gestionar su reproduc-
cin.
Texture 2D: Esta clase contiene una textura bidimensional.

Los scripts tienen algunos mtodos especiales que podemos implementar como:

Update: Este mtodo es invocado por el motor grco cada vez que el objeto
va a ser renderizado.
Start: Este mtodo es invocado por el motor grco cuando se instancia el
objeto que contiene a este script.

27.4.1. Algunos scripts bsicos


Algunos de nuestros enemigos son helicpteros. Podemos aadir el siguiente
script a el objeto de las hlices para que realice una rotacin sobre su eje perpendicular.
Figura 27.9: En nuestro modelo del
helicptero se ha separado la hlice Listado 27.1: HeliceHelicoptero.js
del resto del modelo para poder
1 #pragma strict
aadirle este script de movimiento. 2
3 public var delta = 4.0;
4
5 function Update () {
6 //Rotar la helice en el eje y
7 transform.Rotate(0,Time.deltaTime * delta,0);
8 }

En nuestro escenario se han aadido nubes modeladas mediante planos y con una
textura de una nube con transparencias. Para darle mayor realismo a este elemento se
va a programar un script para mover las coordenadas u,v del mapeado de este plano
provocando que la textura se mueve sobre el plano y simulando un movimiento de
nubes.
C27

Listado 27.2: Nube.js


1 #pragma strict
2
3 public var delta = 0.1;
4 public var moveFromLeftToRight : boolean = false;
[806] CAPTULO 27. PLATAFORMAS MVILES

5 private var offset : float = 0.0;


6
7 function Update () {
8 //Mover la coordenada u o v de la textura de el material del
objeto que contiene este script
9 if (!moveFromLeftToRight) {
10 renderer.material.SetTextureOffset ("_MainTex", Vector2(
offset,0));
11 } else {
12 renderer.material.SetTextureOffset ("_MainTex", Vector2(0,
offset));
13 }
14 offset+=Time.deltaTime * delta;
15 if (offset>1.0) { offset-=1.0; }
16 }

En los scripts se utiliza el valor de Time.deltaTime para interpolar el valor de otros


elementos con respecto al tiempo que ha pasado desde el ltimo frame renderizado. De
esta forma el videojuego va igual de rpido en todas las mquinas, pero se visualizar
de manera ms uida en mquinas ms rpidas debido a que al renderizar mayor
numero de frames por segundo se producirn ms posiciones intermedias de cada uno
de los valores que dependen de Time.deltaTime.

27.4.2. Triggers Figura 27.10: Visualizacin de las


nubes.
Un trigger es una porcin del espacio denida por un objeto geomtrico como
una caja o una esfera que utilizaremos para colocar sobre la escena y de esta forma
saber cuando un determinado objeto entra o sale de una zona concreta. De esta forma
podremos invocar diferentes comportamientos en ese momento. En nuestra escena
utilizaremos un trigger para determinar cuando hemos llegado al enemigo nal.

Listado 27.3: TriggerFinal.js


1 #pragma strict
2
3 function OnTriggerEnter (other : Collider) {
4 if (other.tag=="Player"){
5 //Si entra un objeto dentro de el trigger y el objeto es el
jugador, pasarle el mensaje a ControlJuego de que hemos
llegado a la parte final Figura 27.11: Trigger de la zona
6 var ControlJuegoPointer : Transform = (GameObject. nal.
FindWithTag("ControlJuego")).transform;
7 var ControlJuegoStatic : ControlJuego = (
ControlJuegoPointer.GetComponent("ControlJuego") as
ControlJuego);
8 ControlJuegoStatic.EntrarEnZonaFinal();
9 }
10 }

27.4.3. Invocacin de mtodos retardada


A veces necesitaremos programar un evento para que pase trascurrido un deter-
minado tiempo. En nuestro ejemplo esto ocurre con la explosin, que invocaremos su
destruccin 5 segundos despus de haberse instanciado. Variables pblicas
Las variables pblicas nos permiti-
Listado 27.4: DestruirPasadoUnTiempo.js rn modicar esos parmetros des-
de el interface de Unity3D cuando
1 public var timeOut = 1.0; tengamos el objeto que contiene el
2 script seleccionado.
27.4. Programacin de scripts [807]

3 function Start(){
4 //Realizar una llamada al mtodo destruir pasados los segundos
de timeOUT
5 Invoke ("Destruir", timeOut);
6 }
7
8 function Destruir(){
9 //Destruir el objeto que contiene este script
10 DestroyObject (gameObject);
11 }

27.4.4. Comunicacin entre diferentes scripts


La mayora de los scripts se comunicarn con otros scripts ms complejos. Para
ello hay que crear un puntero al objeto que contiene el script y utilizarlo para a su vez
crear un puntero a la instancia de el script de ese objeto. Despus, sobre el puntero de
esa instancia de script, podremos invocar los mtodos denidos en el mismo.
En el siguiente script que controla el disparo se utiliza este procedimiento
de comunicacin para invocar un mtodo de el script Sonidos, encargado de la
reproduccin de los sonidos y msicas.
Sonido 3D
Listado 27.5: Disparo.js
Es posible reproducir sonido en una
posicin del espacio para que el 1 public var delta = 8.0;
motor de juego calcule la atenua- 2 public var timeOut = 5.0;
cin, reberberacin o efecto dop- 3 public var enemigo : boolean;
pler del mismo. 4
5 function Start() {
6 //Invocar PlaySonidoDisparo del script Sonidos del objeto
Sonidos
7 var SonidosPointer : Transform = (GameObject.FindWithTag("
Sonidos")).transform;
8 var SonidosStatic : Sonidos = (SonidosPointer.GetComponent("
Sonidos") as Sonidos);
9 SonidosStatic.PlaySonidoDisparo();
10 Invoke ("Destruir", timeOut);
11 }
12
13 function Update () {
14 //Actualizar la posicin del disparo
15 if (enemigo){
16 transform.position.z-=Time.deltaTime * delta*0.85;
17 } else {
18 transform.position.z+=Time.deltaTime * delta;
19 }
20 }
21 function OnCollisionEnter(collision : Collision) {
22 Destruir();
23 }
24 function Destruir() {
25 DestroyObject (gameObject);
26 }

Listado 27.6: Sonidos.js


C27

1 #pragma strict
2
3 var SonidoDisparo : AudioSource;
4 var SonidoExplosionAire : AudioSource;
5 var SonidoExplosionSuelo : AudioSource;
6 var SonidoVuelo : AudioSource;
7 var MusicaJuego : AudioSource;
[808] CAPTULO 27. PLATAFORMAS MVILES

8 var MusicaFinal : AudioSource;


9
10 //Realizamos una fachada para que los dems objetos invoquen la
reproduccin de sonidos o msica
11
12 function PlaySonidoExplosionSuelo(){
13 SonidoExplosionSuelo.Play();
14 }
15
16 function PlaySonidoExplosionAire(){
17 SonidoExplosionAire.Play();
18 }
19
20 function PlaySonidoDisparo(){
21 SonidoDisparo.Play();
22 }
23
24 function PlayMusicaJuego(){
25 MusicaJuego.Play();
26 }
27
28 function StopMusicaJuego(){
29 MusicaJuego.Stop();
30 }
31
32 function PlayMusicaFinal(){
33 MusicaFinal.Play();
34 }
35
36 function StopMusicaFinal(){
37 MusicaFinal.Stop();
38 }
39
40 function PlaySonidoVuelo(){
41 SonidoVuelo.Play();
42 }

27.4.5. Control del ujo general de la partida


Normalmente se suele utilizar un script que controla el ujo general de la partida.
Este script se utiliza como nexo de unin entre el resto de los scripts y se le
pasarn mensajes por ejemplo cuando ha terminado la partida. Podemos modelar el
comportamiento de este script como si de un autmata se tratara.
Autmatas nitos
Listado 27.7: ControlJuego.js La gran mayora de comportamien-
1 #pragma strict tos de los actores de un videojuego
2 pueden ser modelados como un au-
3 public var velocidadCamara :float = 2.0; tmata nito determinista.
4 public var enZonaFinal : boolean = false;
5 public var Camara : Transform; Lmite de FPS
6 public var ScoreGUI : GUIText; En dispositivos mviles el limite de
7 public var LifeGUI : GUIText; frames por segundo est ajustado
8
por defecto a 30 FPS pero podemos
9 public var BotonIZ : GUITexture; cambiarlo modicando el atributo
10 public var BotonDE : GUITexture;
Application.targetFrameRate.
11 public var BotonDISPARO : GUITexture;
12
13 public var FondoFinal : GUITexture; Retardos
14 public var TexturaFinalBien : Texture2D; Utilizaremos la instruccin Wait-
15 public var TexturaFinalMal : Texture2D; ForSeconds para introducir retardos
16
en los scripts.
17 private var SonidosStatic : Sonidos;
18 private var Score : int;
19
27.4. Programacin de scripts [809]

20 function Awake(){
21 //Hacemos que el juego corra a 60 FPS como mximo
22 Application.targetFrameRate = 60;
23 }
24
25 function Start(){
26 //Obtenemos el puntero a Sonidos y ajustamos algunos valores
iniciales
27 var SonidosPointer : Transform = (GameObject.FindWithTag("
Sonidos")).transform;
28 SonidosStatic = (SonidosPointer.GetComponent("Sonidos") as
Sonidos);
29 SonidosStatic.PlayMusicaJuego();
30 SonidosStatic.PlaySonidoVuelo();
31 ScoreGUI.text="Score : "+Score;
32 }
33
34 function Update () {
35 if (enZonaFinal && velocidadCamara>0.0){
36 //Si estamos en la zona final paramos el movimiento de
manera gradual
37 velocidadCamara*=0.95;
38 if (velocidadCamara<0.1) { velocidadCamara=0; }
39 }
40
41 if (velocidadCamara>0.0){
42 //Movemos la cmara en su componente z para hacer scroll
43 Camara.position.z+=Time.deltaTime * velocidadCamara;
44 }
45 }
46
47 function EntrarEnZonaFinal(){
48 //Se ha entrado en el trigger de la zona final
49 enZonaFinal=true;
50
51 SonidosStatic.StopMusicaJuego();
52 SonidosStatic.PlayMusicaFinal();
53 }
54
55 function FinDeJuegoGanando(){
56 //Fin de partida cuando hemos completado la misin
57 FondoFinal.texture = TexturaFinalBien;
58 Restart();
59 }
60
61 function FinDeJuegoPerdiendo(){
62 //Fin de partida cuando hemos fallado la misin
63 FondoFinal.texture = TexturaFinalMal;
64 Restart();
65 }
66
67 function AddScore(valor : int){
68 //Aadimos puntos, por lo que hay que hacer la suma y
actualizar el texto
69 Score+=valor;
70 ScoreGUI.text="Score : "+Score;
71 }
72
73 function Restart(){
74 //Ocultamos los textos y botones
75 LifeGUI.enabled=false;
76 ScoreGUI.enabled=false;
77 BotonDISPARO.enabled=false;
78 BotonIZ.enabled=false;
C27

79 BotonDE.enabled=false;
80 FondoFinal.enabled=true;
81
82 //Esperamos 5 segundos y hacemos un reload de la escena
83 yield WaitForSeconds (5);
84 Application.LoadLevel(Application.loadedLevel);
85 }
[810] CAPTULO 27. PLATAFORMAS MVILES

Figura 27.12: Diagrama de control de juego.

27.4.6. Programacin de enemigos


Vamos a tener un nico script para denir el comportamiento de todos los
enemigos, incluidos el enemigo nal. En este script vamos a denir una serie de
atributos pblicos que despus ajustaremos con unos valores especcos para cada
uno de los prefabs de enemigos.
Cada enemigo vendr determinado un rango de disparo y un tiempo de recarga, Deteccin de colisiones
y modicando estos parmetros crearemos enemigos ms peligrosos que otros. Estos Cuando un objeto tridimensional
valores inuirn en el calculo de puntuacin que proporciona ese enemigo concreto al tiene aadidos los elementos colli-
ser destruido. der y rigidbody permite detectar co-
lisiones mediante el mtodo OnCo-
llisionEnter.
Listado 27.8: Enemigo.js
1 #pragma strict
2
3 public var explosionPrefab : Transform;
4 public var llamasPrefab : Transform;
5 public var disparoPrefab : Transform;
6 public var player : Transform;
7 public var rangoDisparo : float;
8 public var tiempoRecarga = 0.5;
9 public var jefeFinal : boolean = false;
10
11 private var siguienteTiempoDisparo = 0.0;
12 private var enRangoDisparo : boolean=true;
27.4. Programacin de scripts [811]

13
14 private var llamasInstancia : Transform;
15 private var cantidadRotationCaida : Vector3 = Vector3(0,0,0);
16
17 function Update () {
18 if (transform.gameObject.rigidbody.useGravity){
19 //Si el enemigo est callendo, el avin rota sobre sus ejes
porque entra en barrena
20 transform.Rotate(Time.deltaTime * cantidadRotationCaida.x,
Time.deltaTime * cantidadRotationCaida.y,Time.deltaTime
* cantidadRotationCaida.z);
21 } else {
22 if (player!=null){
23 var distancia : float = transform.position.z-player.
position.z;
24 if (distancia<=rangoDisparo && distancia>0) {
25 //Si estamos en rango de disparo el avin dispara
al frente
26 if (Time.time > siguienteTiempoDisparo){
27 siguienteTiempoDisparo = Time.time +
tiempoRecarga;
28 Instantiate(disparoPrefab, transform.position,
Quaternion.identity);
29 }
30 }
31 }
32 }
33 }
34
35 function OnCollisionEnter(collision : Collision) {
36 //Determinar posicin y rotacin del punto de contacto de la
colisin
37 var contact : ContactPoint = collision.contacts[0];
38 var rot : Quaternion = Quaternion.FromToRotation(Vector3.up,
contact.normal);
39 var pos : Vector3 = contact.point;
40
41 var SonidosPointer : Transform = (GameObject.FindWithTag("
Sonidos")).transform;
42 var SonidosStatic : Sonidos = (SonidosPointer.GetComponent("
Sonidos") as Sonidos);
43
44 if (transform.gameObject.rigidbody.useGravity || collision.
collider.tag=="Player"){
45 //Si estamos callendo y hemos vuelto a colisionar entonces
explota
46 var ControlJuegoPointer : Transform = (GameObject.
FindWithTag("ControlJuego")).transform;
47 var ControlJuegoStatic : ControlJuego = (
ControlJuegoPointer.GetComponent("ControlJuego") as
ControlJuego);
48
49 SonidosStatic.PlaySonidoExplosionSuelo();
50
51 //Instanciamos la explosin final en la posicin del
impacto
52 Instantiate(explosionPrefab, pos, rot);
53 if (llamasInstancia!=null){
54 Destroy (llamasInstancia.gameObject);
55 }
56
57 if (jefeFinal) {
58 ControlJuegoStatic.AddScore(500);
59 ControlJuegoStatic.FinDeJuegoGanando();
C27

60 } else {
61 var cantidadScore : float = (rangoDisparo * (1.0/
tiempoRecarga))*5;
62 ControlJuegoStatic.AddScore(cantidadScore);
63 }
64
[812] CAPTULO 27. PLATAFORMAS MVILES

65 //Eliminamos el objeto enemigo


66 Destroy (transform.gameObject);
67 } else if (collision.collider.tag=="Disparo"){
68 //Si no estamos callendo y hemos sido tocados por un
disparo, empezamos a caer y a arder
69 SonidosStatic.PlaySonidoExplosionAire();
70
71 //Instanciamos llamas para la posicin del impacto y las
aadimos jerrquicamente al enemigo
72 llamasInstancia=Instantiate(llamasPrefab, transform.
position, Quaternion.identity);
73 llamasInstancia.parent = transform;
74
75 //Activamos la gravedad del rigidBody del objeto
76 transform.gameObject.rigidbody.useGravity=true;
77
78 //Calculamos la cantidad de movimiento en cada para los
ejes de manera aleatoria
79 cantidadRotationCaida.x=Random.Range(0, 20.0);
80 cantidadRotationCaida.y=Random.Range(0, 20.0);
81 cantidadRotationCaida.z=Random.Range(0, 20.0);
82 }
83 }

27.4.7. Programacin del control del jugador


El jugador controlar su avin con los botones: izquierda, derecha y disparo.
Adems hay que tener en cuenta que cuando el jugador colisiona con un disparo
enemigo o un enemigo debe reducir su vida, y cuando esta llega a cero explotar.
Teclas de control
Listado 27.9: Player.js
Aunque el juego nal se controle
1 #pragma strict mediante botones virtuales dibuja-
2 dos sobre la pantalla tctil del dis-
3 public var explosionPrefab : Transform; positivo, tambin permitiremos su
4 public var disparoPrefab : Transform; control con un teclado para cuando
5 public var posicionDisparoIZ : Transform; probemos el juego en el emulador
6 public var posicionDisparoDE : Transform; integrado en Unity3D.
7 public var tiempoRecarga = 0.5;
8 public var cantidadMovimiento = 0.1;
9 public var camara : Transform;
10 public var vida : int = 5;
11 public var LifeGUI : GUIText;
12 public var topeIZ : Transform;
13 public var topeDE : Transform;
14
15 private var siguienteTiempoDisparo = 0.0;
16 private var anteriorDisparoIZ : boolean = false;
17 private var botonIzquierda : boolean = false;
18 private var botonDerecha : boolean = false;
19 private var botonDisparo : boolean = false;
20
21 function Start(){
22 //Inicializamos el marcador de vida con el valor de vida
inicial
23 LifeGUI.text="Life : "+vida;
24 }
25
26 function Update() {
27 if ((botonDisparo || Input.GetButton("Fire1")) && Time.time >
siguienteTiempoDisparo){
28 //Si hay que disparar, instanciamos prefabs de disparo en
las posiciones alternativamente izquierda y derecha de
el avin del jugador
29 siguienteTiempoDisparo = Time.time + tiempoRecarga;
30 if (anteriorDisparoIZ){
27.4. Programacin de scripts [813]

31 Instantiate(disparoPrefab, posicionDisparoDE.position,
posicionDisparoDE.rotation);
32 } else {
33 Instantiate(disparoPrefab, posicionDisparoIZ.position,
posicionDisparoIZ.rotation);
34 }
35 anteriorDisparoIZ=!anteriorDisparoIZ;
36 }
37
38 if (botonIzquierda || Input.GetButton("Left")){
39 //Si hay moverse a la izquierda se actualiza la posicin
del jugador
40 //Tambin se mueve un poco la cmara para simular un poco
de efecto parallax
41 if (transform.position.x>topeIZ.position.x) {
42 transform.position.x-= Time.deltaTime *
cantidadMovimiento;
43 camara.position.x-= Time.deltaTime * cantidadMovimiento
/2;
44 }
45 } else if (botonDerecha || Input.GetButton("Right")) {
46 //Si hay moverse a la derecha se actualiza la posicin del
jugador
47 //Tambin se mueve un poco la cmara para simular un poco
de efecto parallax
48 if (transform.position.x<topeDE.position.x) {
49 transform.position.x+= Time.deltaTime *
cantidadMovimiento;
50 camara.position.x+= Time.deltaTime * cantidadMovimiento
/2;
51 }
52 }
53 }
54
55 function OnCollisionEnter(collision : Collision) {
56 if (collision.collider.tag=="DisparoEnemigo" || collision.
collider.tag=="Enemigo"){
57 //Si el jugador colisiona con un disparo o un enemigo la
vida disminuye
58 vida--;
59 LifeGUI.text="Life : "+vida;
60
61 if (vida<=0){
62 //Si la vida es 0 entonces acaba la partida
63 var ControlJuegoPointer : Transform = (GameObject.
FindWithTag("ControlJuego")).transform;
64 var ControlJuegoStatic : ControlJuego = (
ControlJuegoPointer.GetComponent("ControlJuego") as
ControlJuego);
65 ControlJuegoStatic.FinDeJuegoPerdiendo();
66
67 //Reproducimos sonido de explosin
68 var SonidosPointer : Transform = (GameObject.
FindWithTag("Sonidos")).transform;
69 var SonidosStatic : Sonidos = (SonidosPointer.
GetComponent("Sonidos") as Sonidos);
70 SonidosStatic.PlaySonidoExplosionSuelo();
71
72 //Instanciamos un prefab de explosin en la posicin
del avin del jugador
73 Instantiate(explosionPrefab, transform.position,
Quaternion.identity);
74 //Eliminamos el avin del jugador
75 Destroy(gameObject);
C27

76 }
77 }
78 }
79
80
81 //Mtodos para controlar la pulsacin de los botones virtuales
[814] CAPTULO 27. PLATAFORMAS MVILES

82
83 function ActivarBotonIzquierda(){
84 botonIzquierda=true;
85 }
86
87 function ActivarBotonDerecha(){
88 botonDerecha=true;
89 }
90
91 function ActivarBotonDisparo(){
92 botonDisparo=true;
93 }
94
95 function DesactivarBotonIzquierda(){
96 botonIzquierda=false;
97 }
98
99 function DesactivarBotonDerecha(){
100 botonDerecha=false;
101 }
102
103 function DesactivarBotonDisparo(){
104 botonDisparo=false;
105 }

27.4.8. Programacin del interface


Utilizaremos botones dibujados sobre la pantalla tctil para controlar el video- OnGUI
juego. Para colocar cada uno de los botones virtuales utilizaremos un script que en
El mtodo OnGui ser llamado
funcin del valor del atributo tipoGUI lo posicionar en una zona determinada de la cuando se redimensiona la ventana
pantalla. o se cambia de resolucin de modo
que se calcule la posicin de los ele-
mentos con respecto a las dimensio-
Listado 27.10: ColocarGUI.js nes de la pantalla para que siempre
estn bien colocados.
1 #pragma strict
2
3 enum TipoGUI { Life, Score, BotonLeft, BotonRight, BotonShoot };
4 public var tipoGUI : TipoGUI;
5
6 function OnGUI () {
7 // Hacemos que el ancho del botn ocupe un 10 por ciento
8 // Alto del botn mantiene la proporcin respecto a la imagen
9 var anchoBoton : float = Screen.width*0.1;
10 var altoBoton : float = anchoBoton * 94.0/117.0;
11 var margen : int = 10;
12
13 //Dependiendo del tipo de guiTexture o guiText; colocamos
14 switch(tipoGUI){
15 case tipoGUI.Life:
16 guiText.pixelOffset = Vector2 (Screen.width/2 - 55,
Screen.height/2 - margen);
17 break;
18 case tipoGUI.Score:
19 guiText.pixelOffset = Vector2 (-Screen.width/2 + margen
, Screen.height/2 - margen);
20 break;
21 case tipoGUI.BotonLeft:
22 guiTexture.pixelInset = Rect (-Screen.width/2 + margen,
-Screen.height/2 + margen, anchoBoton, altoBoton);
23 break;
24 case tipoGUI.BotonRight:
25 guiTexture.pixelInset = Rect (-Screen.width/2 +
anchoBoton+ 2*margen, -Screen.height/2 +margen,
anchoBoton, altoBoton);
26 break;
27.4. Programacin de scripts [815]

27 case tipoGUI.BotonShoot:
28 guiTexture.pixelInset = Rect (Screen.width/2 -
anchoBoton - margen, - Screen.height/2 + margen,
anchoBoton, altoBoton);
29 break;
30 }
31 }

Para darle funcionalidad a estos botones utilizaremos un nico script, que en


funcin de el valor de el atributo tipoGUI se comportar de un modo u otro cuando se
pulse.
Touch
El objeto touch representa un toque Listado 27.11: BotonGUI.js
sobre la pantalla y contiene infor- 1 #pragma strict
macin de si se est tocando o sol- 2
tado, adems de la posicin x e y 3 public var tipoGUI : TipoGUI;
donde se realiz el toque. 4 public var Boton : GUITexture;
5 public var TextureON : Texture2D;
6 public var TextureOFF : Texture2D;
7
8 private var wasClicked : boolean = false;
9 private var PlayerStatic : Player;
10
11 function Update(){
12 //Recorre los toques de pantalla
13 for (var touch : Touch in Input.touches) {
14 if (Boton.HitTest (touch.position)){
15 //Si algn toque est dentro de la zona del botn
16 if (touch.phase == TouchPhase.Began) {
17 //Activar el botn cuando comienza el toque
18 wasClicked = true;
19 Activate();
20 } else if (touch.phase == TouchPhase.Ended || touch.
phase == TouchPhase.Canceled) {
21 //Desactivar el botn cuando comienza el toque
22 wasClicked = false;
23 Deactivate();
24 }
25 }
26 }
27 }
28
29 function Activate() {
30 //Ponemos la textura botn pulsado
31 Boton.texture= TextureON;
32 //Dependiendo del tipo de botn que pulsamos enviamos el
mensaje correspondiente a Player
33 switch(tipoGUI){
34 case tipoGUI.BotonLeft:
35 PlayerStatic.ActivarBotonIzquierda();
36 break;
37 case tipoGUI.BotonRight:
38 PlayerStatic.ActivarBotonDerecha();
39 break;
40 case tipoGUI.BotonShoot:
41 PlayerStatic.ActivarBotonDisparo();
42 break;
43 }
44 }
45
46 function Deactivate() {
C27

47 //Ponemos la textura botn sin pulsar


48 Boton.texture= TextureOFF;
49 //Dependiendo del tipo de botn que soltamos enviamos el
mensaje correspondiente a Player
50 wasClicked = false;
51 switch(tipoGUI){
[816] CAPTULO 27. PLATAFORMAS MVILES

52 case tipoGUI.BotonLeft:
53 PlayerStatic.DesactivarBotonIzquierda();
54 break;
55 case tipoGUI.BotonRight:
56 PlayerStatic.DesactivarBotonDerecha();
57 break;
58 case tipoGUI.BotonShoot:
59 PlayerStatic.DesactivarBotonDisparo();
60 break;
61 }
62 }
63
64 function Start () {
65 //Obtenemos el puntero a Player y ajustamos algunos valores
iniciales
66 var PlayerPointer : Transform = (GameObject.FindWithTag("Player
")).transform;
67 PlayerStatic = (PlayerPointer.GetComponent("Player") as Player)
;
68 wasClicked = false;
69 Boton.texture= TextureOFF;
70 }

27.5. Optimizacin
El motor grco nos proporciona dos herramientas imprescindibles para optimizar
nuestros videojuegos: lightmapping y occlusion culling. Aplicando estas tcnicas
reduciremos mucho la carga de renderizado y nos permitir que nuestros videojuegos
puedan correr a buena velocidad en dispositivos de poca potencia grca como
smartphones o tablets.

27.5.1. Light mapping


Esta tcnica consiste en calcular previamente las sombras que reciben y provocan Figura 27.13: Visualizacin de la
los objetos estticos de las escenas para generar unos mapas de sombreado que se sombra provocada por una torre de
aplican mediante multitextura sobre la maya de los modelos 3D. Los modelos sobre electricidad.
los que se aplica esta tcnica son renderizados como polgonos con textura sin ningn
sombreado, lo que evita el calculo de iluminacin de la maya, ahorrando mucho
tiempo de computo.

27.5.2. Occlusion culling


Esta tcnica consiste en calcular previamente desde todas las posibles posiciones
que puede tomar la cmara que objetos son visibles y cuales son ocluidos por otros.
Despus se utiliza esta informacin en tiempo de renderizado para no representar los
objetos que despus no van a ser visibles.
El clculo de todas las posiciones que puede tomar la cmara se hace discretizando
el espacio mediante una matriz tridimensional de la cual podremos elegir el nivel de
granularidad. De este modo se calcula que objetos estticos debern ms tarde ser
renderizados cuando la cmara se encuentre en esta regin del espacio. Figura 27.15: Imagen de lo que vi-
La combinacin de esta tcnica junto con otras como frustrum culling permitirn sualiza el jugador cuando el motor
que podamos tener escenas con millones de polgonos, pero en cada frame de grco est descartando objetos pa-
renderizado slo se representar una pequea fraccin de estos polgonos. ra no ser renderizados cuando la c-
mara pasa por la seccin que se pue-
de contemplar en la gura 27.18.
27.5. Optimizacin [817]

Figura 27.14: Ejemplo de las sombras que proyectan sobre el terreno los modelos tridimensionales de unas
ruinas colocados en la escena.

C27

Figura 27.16: Ejemplo de mapa generado mediante esta tcnica que despus se mapear sobre el modelo
3D de la escena. Este mapa de sombreado es de la zona que se aprecia en la gura 27.18.
[818] CAPTULO 27. PLATAFORMAS MVILES

27.6. Resultado nal


El resultado nal es una pieza jugable de unos dos minutos y medio de duracin
que podra ser un nivel de un videojuego shoot em up de aviones con vista cenital.
Este videojuego est listo para compilarse para dispositivos con iOS o Android
y funcionar en terminales de gama media-baja, pudiendo alcanzar los 60 FPS en
terminales de gama alta.

Figura 27.17: Imagen del enemigo


nal.

Figura 27.18: Imagen del resultado nal.


27.6. Resultado nal [819]

Figura 27.19: En nuestro ejemplo se ha dividido el escenario en porciones para poder aplicar la tcnica, de
este modo en un momento determinado slo se renderiza una pequea parte del escenario.

C27

Figura 27.20: Nivel de granularidad elegido para la matriz de discretizacin del espacio en nuestro ejemplo.
Desarrollo de Componentes
El objetivo de este mdulo, titulado Desarrollo de Componentes
dentro del Curso de Experto en Desarrollo de Videojuegos, consiste
en profundizar en tcnicas especficas vinculadas al desarrollo de
videojuegos, como por ejemplo el uso de tcnicas de Inteligencia
Artificial o la programacin multijugador en red. Para ello, una de
las principales metas es la de complementar la visin general de la
arquitectura de un motor de juegos con cuestiones especficas que
resultan fundamentales para su desarrollo. Dentro del contexto de la
Inteligencia Artificial, en este mdulo se estudian tcnicas
fundamentales como la Lgica Difusa o los algoritmos genricos,
entre otras. As mismo, se realiza una discusin del diseo orientado
a agentes como pilar esencial en el desarrollo del componente
inteligente de un videojuego. En la parte relativa al juego
multijugador se exploran las posibilidades que ofrecen los sockets y,
posteriormente, se discute cmo el uso de herramientas de ms alto
nivel, como los middlewares de comunicaciones pueden contribuir a
facilitar el desarrollo del mdulo de networking. Finalmente, este
mdulo tambin contempla aspectos relativos a la edicin de audio,
la gestin de vdeo y la importancia de la integracin de nuevos
dispositivos de interaccin. En el contexto del desarrollo de
videojuegos, tcnicas como la visin por computador o la realidad
aumentada pueden contribuir a mejorar la experiencia del jugador.
Captulo

Inteligencia Articial
28
Javier Alonso Albusac Jimnez
Jos Jess Castro Snchez
David Vallejo Fernndez
Luis Jimnez Linares
Manuel Palomo Duarte

L
a Inteligencia Articial (IA) es un elemento fundamental para dotar de realismo
a un videojuego. Uno de los retos principales que se plantean a la hora
de integrar comportamientos inteligentes es alcanzar un equilibrio entre la
sensacin de inteligencia y el tiempo de cmputo empleado por el subsistema de
IA. Dicho equilibrio es esencial en el caso de los videojuegos, como exponente ms
representativo de las aplicaciones grcas en tiempo real.
Este planteamiento gira, generalmente, en torno a la generacin de soluciones que,
sin ser ptimas, proporcionen una cierta sensacin de inteligencia. En concreto, dichas
soluciones deberan tener como meta que el jugador se enfrente a un reto que sea
factible de manera que suponga un estmulo emocional y que consiga engancharlo al
juego.
En los ltimos aos, la IA ha pasado de ser un componente secundario en el
proceso de desarrollo de videojuegos a convertirse en uno de los aspectos ms
importantes. Actualmente, lograr un alto nivel de IA en un juego sigue siendo uno
de los retos ms emocionantes y complejos y, en ocasiones, sirve para diferenciar un
juego normal de uno realmente deseado por los jugadores.
Tal es su importancia, que las grandes desarrolladores de videojuegos mantienen
en su plantilla a ingenieros especializados en la parte de IA, donde los lenguajes de
scripting, como Lua o Python, y la comunicacin con el resto de programadores del
juego resulta esencial.
Figura 28.1: El robot-humanoide En este captulo se discuten cuestiones con gran relevancia en el mbito de la IA
Asimo, creado por Honda en el ao aplicada a videojuegos, haciendo especial hincapi en las tcnicas ms consolidadas,
2000, es uno de los exponentes ms
como por ejemplo el uso de mquinas de estados nitas, el diseo basado en agentes,
reconocidos de la aplicacin de tc-
nicas de IA sobre un prototipo fsi-
la aplicacin de algoritmos de bsqueda o la lgica difusa. En la ltima seccin del
co real. captulo se discute un caso de estudio prctico enfocado a la IA.

823
[824] CAPTULO 28. INTELIGENCIA ARTIFICIAL

28.1. Introduccin a la IA para videojuegos

28.1.1. Aplicando el Test de Turing


La Inteligencia Articial es un rea fascinante y relativamente moderna de la
Informtica que gira en torno a la construccin de programas inteligentes. Existen
diversas interpretaciones para el trmino inteligente (vea [79] para una discusin en
profundidad), las cuales se diferencian en funcin de la similaritud con conceptos
importantes como racionalidad y razonamiento.
De cualquier modo, una constante en el campo de la IA es la relacin entre un
programa de ordenador y el comportamiento del ser humano. Tradicionalmente, la IA
se ha entendido como la intencin de crear programas que actuasen como lo hara una
persona ante una situacin concreta en un contexto determinado.
Hace ms de medio siglo, en 1950, Alan Turing propuso la denominada Prueba
de Turing, basada en la incapacidad de una persona de distinguir entre hombre o
mquina a la hora de evaluar un programa de ordenador. En concreto, un programa
pasara el test si un evaluador humano no fuera capaz de distinguir si las respuestas a
una serie de preguntas formuladas eran o no de una persona. Hoy en da, esta prueba Figura 28.2: Alan Turing (1912-
1954), matemtico, cientco, crip-
sigue siendo un reto muy exigente ya que, para superarlo, un programa tendra que ser tgrafo y lsofo ingls, es con-
capaz de procesar lenguaje natural, representar el conocimiento, razonar de manera siderado uno de los Padres de la
automtica y aprender. Computacin y uno de los precur-
Adems de todas estas funcionalidades, esta prueba implica la necesidad de sores de la Informtica Moderna.
interactuar con el ser humano, por lo que es prcticamente imprescindible integrar
tcnicas de visin por computador y de robtica para superar la Prueba Global de
Turing. Todas estas disciplinas cubren gran parte del campo de la IA, por lo que Turing
merece un gran reconocimiento por plantear un problema que hoy en da sigue siendo
un reto muy importante para la comunidad cientca.
En el mbito del desarrollo de videojuegos, la Prueba de Turing se podra utilizar
para evaluar la IA de un juego. Bsicamente, sera posible aplicar esta prueba a los
Non-Player Characters (NPCs) con el objetivo de averiguar si el jugador humano es
capaz de saber si son realmente bots o podran confundirse con jugadores reales.
Aunque actualmente existen juegos que tienen un grado de IA muy sosticado, en
trminos generales es relativamente fcil distinguir entre NPC y jugador real. Incluso
en juegos tan trabajados desde el punto de vista computacional como el ajedrez, en
ocasiones las decisiones tomadas por la mquina delatan su naturaleza.
Desafortunadamente, los desarrolladores de videojuegos estn condicionados por
el tiempo, es decir, los videojuegos son aplicaciones grcas en tiempo real que han
de generar una determinada tasa de frames o imgenes por segundo. En otras palabras,
este aspecto tan crtico hace que a veces el tiempo de cmputo dedicado al sistema de
IA se vea reducido. La buena noticia es que, generalmente, el mdulo responsable de
la IA no se tiene que actualizar con la misma frecuencia, tan exigente, que el motor de
rendering.
Aunque esta limitacin se ir solventando con el incremento en las prestaciones Figura 28.3: Terminator pasara sin
ninguna duda la Prueba Global de
hardware de las estaciones de juego, hoy en da es un gran condicionante que afecta a Turing, al menos hasta que tuviera
los recursos dedicados al mdulo de IA. Una de las consecuencias de esta limitacin que ir a la consulta del mdico...
es que dicho mdulo se basa en proporcionar una ilusin de inteligencia, es decir,
est basado en un esquema que busca un equilibro entre garantizar la simplicidad
computacional y proporcionar al jugador un verdadero reto.
28.1. Introduccin a la IA para videojuegos [825]

28.1.2. Ilusin de inteligencia


De una manera casi inevitable, el componente inteligente de un juego est
vinculado a la dicultad o al reto que al jugador se le plantea. Sin embargo, y debido
a la naturaleza cognitiva de dicho componente, esta cuestin es totalmente subjetiva.
Por lo tanto, gran parte de los desarrolladores opta por intentar que el jugador se sienta
inmerso en lo que se podra denominar ilusin de inteligencia.
Por ejemplo, en el videojuego Metal Gear Solid, desarrollado por Konami y
lanzado para PlayStationTM en 1998, los enemigos empezaban a mirar de un lado a
otro y a decir frases del tipo Quin anda ah? si el personaje principal, Solid Snake,
se dejaba ver mnimamente o haca algn ruido en las inmediaciones de los NPCs.
En este juego, el espionaje y la inltracin predominaban sobre la accin, por lo que
este tipo de elementos generaban una cierta sensacin de IA, aunque en realidad su
implementacin fuera sencilla.
Un caso ms general est representado por modicar el estado de los NPCs,
tpicamente incrementando su nivel de stamina o vida. De este modo, el jugador
puede tener la sensacin de que el enemigo es ms inteligente porque cuesta ms
abatirlo. Otra posible alternativa consiste en proporcionar ms habilidades al enemigo,
por ejemplo haciendo que se mueva ms rpido o que dispare con mayor velocidad.
En [16], el autor discute el caso de la IA del juego Halo, desarrollado por Bungie
Studios y publicado por Microsoft Games Studio en 2001, y cmo los desarrolladores
consiguieron engaar a los testers del juego. En concreto, los desarrolladores
asociaban el nivel de IA con la altitud de los puntos de impacto sobre los NPCs. As,
los jugadores perciban un grado bajo de IA cuando este nivel no era elevado, es decir,
cuando los impactos en la parte inferior del NPC eran relevantes para acabar con el
Figura 28.4: El jarrn de Rubin mismo. Sin embargo, al incrementar dicho nivel y forzar a los jugadores a apuntar a
es una de las ilusiones pticas ms partes ms elevadas del NPC, stos perciban que el juego tena una IA ms elevada.
famosas. Jarrn o dos caras?

Desde un punto de vista general, el principal reto del componente de IA de un


juego consiste en que el jugador se sienta inteligente, planteando una lucha
equilibrada pero, al mismo tiempo, factible para el jugador.

Este planteamiento se puede implementar utilizando polimorsmo, es decir,


haciendo uso de una implementacin u otra en funcin del nivel de complejidad. La
gura 28.5 muestra el diagrama de clases de una solucin muy simplicada de este
problema. La clase Player mantiene como estado la stamina y la altura del personaje.
Este nivel de stamina se ver reducido por los impactos de proyectiles sobre el
personaje. Si la dicultad del juego es normal, todos los proyectiles tendrn el mismo
impacto sobre la salud del personaje. Sin embargo, si la dicultad es elevada, entonces
se har una distincin explcita entre proyectiles que impactan en la parte superior del
personaje y aquellos que lo hacen en la inferior.
C28
[826] CAPTULO 28. INTELIGENCIA ARTIFICIAL

El siguiente listado de cdigo muestra una implementacin muy simple de la clase


Player.
Player
Listado 28.1: Clase Player
1
1 #ifndef __PLAYER__
2 #define __PLAYER__ 1
3
4 class Player {
5 public: AIBehaviour
6 Player (int stamina, float height);
7 ~Player () {}
8
9 void reduceStamina (int value) {
10 if ((_stamina - value) > 0)
11 _stamina -= value;
12 else
13 destroy(); ormalAI HardAI
Behaviour Behaviour
14 }
15 int getStamina () const { return _stamina; }
16 int getHeight () const { return _height; }
17
Figura 28.5: Diagrama de clases
18 private: simplicado del ejemplo de Halo.
19 int _stamina; // Vida del personaje.
20 float _height; // Altura.
21 void destroy () {}
22 };
23
24 #endif

La parte ms interesante de este ejemplo est en la clase abstracta AIBehaviour,


la cual obliga a sus clases derivadas a implementar la funcin miembro hit, con el
objetivo de evaluar su impacto en funcin del nivel de complejidad.

Listado 28.2: Clase AIBehaviour


1 #ifndef __AIBEHAVIOUR__
2 #define __AIBEHAVIOUR__
3
4 #include <OgreVector3.h>
5 #include "Player.h"
6
7 class AIBehaviour {
8 public:
9 AIBehaviour (Player* player) { _player = player; }
10
11 // El impacto de hit depender del nivel de AI.
12 virtual void hit (const Ogre::Vector3& bullet) = 0;
13 protected:
14 Player* _player;
15 };
16
17 class NormalAIBehaviour: public AIBehaviour { 3 5 7 8 9
18 public: 10
9 8 7 5 3
19 NormalAIBehaviour (Player* player): AIBehaviour(player) {}
20 void hit (const Ogre::Vector3& bullet);
21 };
22
23 class HardAIBehaviour: public AIBehaviour {
24 public:
25 HardAIBehaviour (Player* player) : AIBehaviour(player) {}
26 void hit (const Ogre::Vector3& bullet); Figura 28.6: En los shooters, la zo-
27 }; na de impacto sobre el enemigo es
28
especialmente relevante y se puede
29 #endif
usar para ajustar el nivel de dicul-
tad.
28.1. Introduccin a la IA para videojuegos [827]

La implementacin de la funcin miembro hit() de las clases derivadas NormalAI-


Behaviour y HardAIBehaviour diere en funcin del punto de impacto del proyectil
sobre el personaje y, consecuentemente, en la reduccin de su nivel de stamina.

Listado 28.3: Implementacin de la funcin miembro hit


1 #include <AIBehaviour.h>
2
3 // SE OBVIA LA DETECCIN DE COLISIONES.
4 void
5 NormalAIBehaviour::hit
6 (const Ogre::Vector3& bullet)
7 {
8 _player->reduceStamina(25.0);
9 }
10
11 // Considera la altura del impacto de la bala.
12 void
13 HardAIBehaviour::hit
14 (const Ogre::Vector3& bullet)
15 {
16 float aux = _player->getHeight() / 3.0;
17 float top = _player->getHeight() - aux;
18
19 if (bullet.z > top)
20 _player->reduceStamina(25.0);
21 else
22 _player->reduceStamina(10.0);
23 }

Como se puede apreciar, la reduccin de vida sobre el personaje se mantiene


constante para cualquier impacto de proyectil en el nivel de dicultad normal. Sin
embargo, dicha reduccin variar en el nivel de dicultad hard, dependiendo de la
altura del impacto.
La idea de aplicar el Test de Turing a los NPCs de un juego plantea otro reto
complejo y realmente atractivo para los desarrolladores de IA: la cooperacin. Piense,
por ejemplo, en una situacin en la que el jugador se enfrenta a varios oponentes de
manera simultnea en un rst-person shoooter. El jugador que busca un reto ante dicha
situacin no espera que los NPCs ataquen de uno en uno por el mismo punto. Por el
contrario, un jugador habilidoso esperara un ataque coordinado, basado tpicamente
en una emboscada, tal y como un oponente real podra plantear en la mayora de
ocasiones.
IA cooperativa Evidentemente, una entidad siempre estar en desventaja ante un grupo, por lo
que, tal y como se introdujo anteriormente, el desarrollador de IA ha de ser capaz de
La coordinacin de comportamien-
tos en los NPCs es una tarea com- balancear la complejidad y saber adecuar una situacin complicada en una situacin
pleja pero que, bien efectuada, pue- que ofrezca un reto factible para el jugador, al menos en la mayora de ocasiones.
de incrementar notablemente la IA
de un juego.
28.1.3. NPCs o Agentes?
En gran parte de la bibliografa del desarrollo de videojuegos, especialmente en la
relativa a la IA, el concepto de agent (agente) se utiliza para referirse a las distintas
entidades virtuales de un juego que, de alguna manera u otra, tienen asociadas un
Academic VS Game AI comportamiento. Dicho comportamiento puede ser trivial y basarse, por ejemplo, en
un esquema totalmente preestablecido o realmente complejo y basarse en un esquema
Recuerde que en el mbito acad- que gire entorno al aprendizaje. De cualquier modo, estas dos alternativas comparten
mico, las tcnicas de IA tienden
C28

a buscar soluciones ptimas, mien- la idea de mostrar algn tipo de inteligencia, aunque sea mnima.
tras que en el contexto de los video-
juegos la tendencia general consiste
en encontrar buenas soluciones.
[828] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Tradicionalmente, el concepto de agente en el mbito de la IA se ha denido


como cualquier entidad capaz de percibir lo que ocurre en el medio o contexto en el
que habita, mediante la ayuda de sensores, y actuar en consencuancia, mediante la
ayuda de actuadores, generando normalmente algn tipo de cambio en dicho medio o
contexto. La gura 28.7 muestra esta idea tan general. Note cmo de nuevo la idea de
la Prueba de Turing vuelve a acompaar al concepto de agente.
El concepto de agente se ha ligado a ciertas propiedades que se pueden trasladar
perfectamente al mbito del desarrollo de videojuegos y que se enumeran a continua-
cin:

Autonoma, de manera que un agente acta sin la intervencin directa de


terceras partes. Por ejemplo, un personaje de un juego de rol tendr sus propios
deseos, de manera independiente al resto.
Habilidad social, los agentes interactan entre s y se comunican para alcanzar
un objetivo comn. Por ejemplo, los NPCs de un shooter se comunicarn para
cubrir el mayor nmero de entradas a un edicio.
Reactividad, de manera que un agente acta en funcin de las percepciones
del entorno. Por ejemplo, un enemigo reaccionar, normalmente, atacando si es
atacado.
Proactividad, de manera que un agente puede tomar la iniciativa en lugar de
ser puramente reactivo. Por ejemplo, un enemigo feroz atacar incluso cuando
no haya sido previamente atacado.

Agente Sensores
Percepciones
Medio o contexto

Actuadores
Acciones

Figura 28.7: Visin abstracta delconcepto de agente

De manera adicional a estas propiedades, los conceptos de razonamiento y


aprendizaje forman parte esencial del ncleo de un agente. Actualmente, existen
juegos que basan parte de la IA de los NPCs en esquemas de aprendizaje y los usan
para comportarse de manera similar a un jugador real. Este aprendizaje puede basar
en la deteccin de patrones de comportamiento de dicho jugador.
Un ejemplo tpico son los juegos deportivos. En este contexto, algunos juegos de
ftbol pueden desarrollar patrones similares a los observados en el jugador real que
los maneja. Por ejemplo, si la mquina detecta que el jugador real carga su juego por
la parte central del terreno de juego, entonces podra contrarrestarlo atacando por las
bandas, con el objetivo de desestabilizar al rival.
28.1. Introduccin a la IA para videojuegos [829]

Aunque los agentes se pueden implementar haciendo uso de una losofa


basada en el diseo orientado a objetos, informalmente se suele armar que
los objetos lo hacen gratis, mientras que los agentes lo hacen porque quieren
hacerlo.

Comnmente, los agentes basan su modelo de funcionamiento interno en una


mquina de estados, tal y como se muestra de manera grca en la gura 28.8.
Este esquema se ha utilizado durante muchos aos como herramienta principal para
proporcionar esa ilusin de inteligencia por parte del desarrollador de IA. De hecho,
aunque en algunos proyectos se planteen arquitecturas mucho ms sosticadas, en la
prctica la mayor parte de ellas girarn en torno a la idea que se discute en la siguiente
seccin.

Agente

ver accin

siguiente estado

Entorno

Figura 28.8: Visin abstracta del funcionamiento interno de un agente.

28.1.4. Diseo de agentes basado en estados

Accin, reaccin Una mquina de estados dene el comportamiento que especica las secuencias
de estados por las que atraviesa un objeto durante su ciclo de ejecucin en respuesta
El modelo de diseo de las mqui- a una serie de eventos, junto con las respuestas a dichos eventos. En esencia, una
nas de estados est basado en el tra-
dicional esquema de accin y reac- mquina de estados permite descomponer el comportamiento general de un agente en
cin. Bsicamente, ante el cumpli- pedazos o subestados ms manejables. La gura 28.9 muestra un ejemplo concreto de
miento de una condicin (accin) se mquinas de estados, utilizada para denir el comportamiento de un NPC en base a
produce una transicin (reaccin). una serie de estados y las transiciones entre los mismos.
Como el lector ya habr supuesto, los conceptos ms importantes de una mquina
de estados son dos: los estados y las transiciones. Por una parte, un estado dene una
condicin o una situacin durante la vida del agente, la cual satisface alguna condicin
o bien est vinculada a la realizacin de una accin o a la espera de un evento. Por
Fuzzy Logic otra parte, una transicin dene una relacin entre dos estados, indicando lo que ha
La lgica difusa o borrosa es una de ocurrir para pasar de un estado a otro. Los cambios de estado se producen cuando
tcnica que permite tratar la incerti- la transicin se dispara, es decir, cuando se cumple la condicin que permite pasar de
dumbre y la vaguedad presenta en la un estado a otro.
mayora de los problemas del mun-
C28

do real. sta se puede utilizar para


proporcionar ms exibilidad a un
esquema basado en mquinas de es-
tados.
[830] CAPTULO 28. INTELIGENCIA ARTIFICIAL

after (30s)

ruido
Inactivo Patrullando Rastreando

sin enemigos
enemigo
detectado
Atacando

Figura 28.9: Mquina de estados que dene el comportamiento de un NPC.

Aunque la idea general de las mquinas de estado es tremendamente sencilla, su


popularidad en el rea de los videojuegos es enorme debido a los siguientes factores
[16]:

Son fciles de implementar y muy rpidas. Aunque existen diversas alternati-


vas, todas ellas tienen una complejidad baja.

Su depuracin es sencilla, ya que se basan en el principio de descomposicin


en subestados que sean manejables.
Tienen una mnima sobrecarga computacional, ya que giran en torno a un
esquema if-then-else.
Son muy intuitivas, ya que se asemejan al modelo de razonamiento del ser
humano.
Son muy exibles, debido a que permiten la integracin de nuevos estados sin
tener un impacto signicativo en el resto y posibilitan la combinacin de otras
tcnicas clsicas de IA, como la lgica difusa o las redes neuronales.

Una mquina de estados se puede implementar utilizando distintas aproximacio-


nes, como por ejemplo la que se estudi en el mdulo 1, Arquitectura del Motor, para
dar soporte al sistema de gestin de estados. En este contexto, es bastante comn hacer
uso del polimorsmo para manejar distintos estados que hereden de uno ms general,
proporcionando distintas implementaciones en funcin de dicha variedad de estados.
En esencia, la idea consiste en mantener la interfaz pero concretando la implementa-
cin de cada estado. Normalmente, tambin se suele hacer uso del patrn singleton
para manejar una nica instancia de cada estado.
28.1. Introduccin a la IA para videojuegos [831]

28.1.5. Los lenguajes de script


A medida que un proyecto de tanta envergadura como un motor de juegos o un
juego comienza a crecer, el simple hecho de llevar a cabo una compilacin para
evaluar un cambio mnimo puede resultar desesperante debido a la cantidad de tiempo
dedicada para generar un archivo ejecutable. La solucin inmediata consiste en separar
los datos de la implementacin, es decir, plantear algn tipo de esquema que evite
recompilar cuando los datos cambien.
Tpicamente, esta solucin se ha basado en hacer uso de archivos de congura-
cin sobre los que leer, en tiempo de ejecucin, valores globales o especcos de la
conguracin de algn mdulo en particular. Este esquema tan sencillo es la esen-
cia de los lenguajes de scripting. En lugar de independizar solamente los datos, este
tipo de lenguajes, en el mbito del desarrollo de videojuegos, tambin permiten in-
dependizar gran parte de la lgica del juego. En este contexto, la lgica determina el
comportamiento de las distintas entidades del juego a desarrollar, como por ejemplo
los agentes o NPCs.
Este planteamiento representa la principal ventaja de los lenguajes de scripting,
emplazndolos como una herramienta fundamental y muy potente en el desarrollo
de proyectos software no triviales. Algunos ejemplos representativos de este tipo de
lenguajes en el mbito del desarrollo de videojuegos son Lua o Python.
Los lenguajes de scripting suelen ser interpretados. A diferencia de los lenguajes
compilados, en los que se genera algn tipo de cdigo mquina, los lenguajes
interpretados se suelen leer, parsear e interpretar lnea a lnea en tiempo de ejecucin,
proceso que reduce el rendimiento de la aplicacin. Ante esta problemtica, algunos
lenguajes de scripting son compilados.
Adems de permitir independizar la lgica del juego, los lenguajes de scripting
tambin se utilizan masivamente como herramientas de prototipado rpido. El
principal objetivo que se persigue es el de obtener un producto o servicio que muestre
la funcionalidad bsica del sistema nal, reduciendo drsticamente el tiempo y, por
lo tanto, el coste del desarrollo. Posteriormente, se podra desarrollar dicho prototipo
Figura 28.10: Actualmente, Pyt- haciendo uso de un lenguaje compilado, como por ejemplo C++.
hon es uno de los lenguajes de
script ms populares debido a su
gran potencia y a su facilidad de
aprendizaje. El uso de un lenguaje de scripting implica llevar a cabo un proceso de
integracin en el motor de juegos o en el juego en cuestin, normalmente
desarrollados en C++. En el mdulo M3, Tcnicas Avanzadas de Desarrollo,
se abordar esta problemtica desde un punto de vista prctico.

El siguiente listado de cdigo muestra una posible implementacin del juego de


las chinas1 en Python. Como se puede apreciar, su sintaxis es muy legible y sencilla.
Por ejemplo, slo se necesita una lnea de cdigo (lnea 11) para mostrar un mensaje
por pantalla, que el usuario introduzca un dato y que dicho dato se almacene en
una variable. El manejo de estructuras esenciales, como las listas, es tremendamente
prctico (lneas 6 y 24), al igual que el tratamiento de excepciones (lneas 10 y 26).
Python es un lenguaje de tipado dinmico, est soportado en un gran nmero de
plataformas y es escalable gracias a una losofa basada en mdulos de desarrollo.
1 http://es.wikipedia.org/wiki/Chinos
C28
[832] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Listado 28.4: Implementacin del juego de las chinas en Python


1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 import sys, random
5
6 jugadas = []
7
8 def jugar ():
9 while True:
10 try:
11 total_humano = int(input(\nTotal? ))
12
13 n_maquina, total_maquina = predecir(total_humano)
14 n_humano = int(input(Cuantas tenias? ))
15
16 print \nHumano: + str(n_humano)
17 print Maquina: + str(n_maquina)
18 if (n_maquina + n_humano) == total_humano:
19 print Ganaste!
20 elif (n_maquina + n_humano) == total_maquina:
21 print Perdiste!
22
23 # Para el mdulo de IA...
24 jugadas.append((n_humano, total_humano))
25
26 except KeyboardInterrupt:
27 print \nSaliendo...
28 sys.exit(0)
29
30 def predecir (total_humano):
31 n_maquina = random.randint(0, 3)
32 total_maquina = random.randint(n_maquina, n_maquina + 3)
33 print La maquina predice + str(total_maquina)
34 return n_maquina, total_maquina
35
36 jugar()

El anterior listado de cdigo contiene una funcin predecir que debera implemen-
tar una IA ms sosticada, ms all de devolver valores aleatorios. Una posible opcin
sera utilizar la estructura de datos jugadas para generar algn tipo de patrn que mo-
delara el comportamiento del humano despus de varias rondas de juego. Se plantea
como ejercicio al lector la implementacin de la funcin predecir() con el objetivo
de estudiar la secuencia previa de predicciones por parte del jugador humano, con el
objetivo de ganar un mayor nmero de partidas.

28.1.6. Caso de estudio. Un Tetris inteligente


En esta seccin se discute cmo afrontar el mdulo de IA del Tetris en el modo
Human VS CPU, es decir, cmo se puede disear el comportamiento de la mquina
cuando se enfrenta a un jugador real.
Tradicionalmente, el modo versus del Tetris se juega a pantalla dividida, al igual
que el modo de dos jugadores. En la parte izquierda juega el jugador real y en la
derecha lo hace la mquina. En esencia, el perdedor es aqul que es incapaz de colocar
una cha debido a que ha ocupado la prctica totalidad del tablero sin limpiar lneas.
28.1. Introduccin a la IA para videojuegos [833]

5 5 x
5 5 x
4 4 4 4 x
3
22 2 2 2 2 2
11 1 1 1 1 1 1 1 1 1

(a) (b) (c)

Figura 28.11: Planteando un mdulo de IA para el Tetris. a) Limpieza de una lnea, b) Cuanticando la
altura del montn de chas, c) Problema de huecos perdidos (los puntos representan los huecos mientras
que las x representan los potenciales bloqueos).

Para llevar a cabo la gestin de la dicultad de este modo de juego o, desde


otro punto de vista, modelar la habilidad de la mquina, se pueden plantear diversas
alternativas. Por ejemplo, si se desea modelar un nivel de complejidad elevado,
entonces bastara con incrementar la velocidad de cada de las chas. En este caso,
la mquina no se vera afectada ya que tendra tiempo ms que suciente para
colocar la siguiente cha. Sin embargo, el jugador humano s que se vera afectado
signicativamente.
Otra posibilidad para ajustar el nivel de dicultad consistira en que el jugador y
la mquina recibieran distintos tipos de chas, computando cules pueden ser ms
adecuadas para completar una lnea y, as, reducir la altura del montn de chas.
Tambin sera posible establecer un handicap, basado en introducir deliberadamente
piezas en la pantalla del jugador real.
No obstante, la implementacin de todas estas alternativas es trivial. El verdadero
reto est en modelar la IA de la mquina, es decir, en disear e implementar el
comportamiento de la mquina a la hora de ir colocando chas.
La solucin inicial planteada en esta seccin consiste en asignar una puntuacin
a cada una de las posible colocaciones de una cha. Note que las chas se pueden rotar
y, al mismo tiempo, se pueden colocar en distintas posiciones del tablero. El objetivo
perseguido por el mdulo de IA ser el de colocar una cha all donde obtenga una
mejor puntuacin. El siguiente paso es, por lo tanto, pensar en cmo calcular dicha
puntuacin.
Para ello, una opcin bastante directa consiste en distinguir qu aspectos resultan
fundamentales para ganar o perder una partida. En principio, el mdulo de IA debera
evitar formar torres de chas de gran altura, ya que lo aproximaran a perder la partida
de forma inminente, tal y como muestra la gura 28.11.b. Este factor debera suponer
una penalizacin para la puntuacin asociada a colocar una cha. Por el contrario, la
mquina debera limpiar lneas siempre que fuera posible, con el objetivo de obtener
puntos y evitar que el montn de chas siga creciendo. Este factor representara una
bonicacin.
C28

Figura 28.12: Cuatro posibles rota-


ciones de una cha de Tetris (2D).
[834] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Adems, idealmente el mdulo de IA debera evitar generar espacios que no se


puedan aprovechar por las chas siguientes, es decir, debera evitar que se perdieran
huecos, tal y como se reeja en la gura 28.11.c. Evidentemente, la generacin de
huecos perdidos es, a veces, inevitable. Si se produce esta situacin, el mdulo de IA
debera evitar la colocacin de chas sobre dichos huecos, con el objetivo de liberarlos
cuanto antes y, en consecuencia, seguir limpiando lneas.
Estos cuatro factores se pueden ponderar para construir una primera aproxima-
cin de la frmula que se podra utilizar para obtener la puntacin asociada a la colo-
cacin de una cha:

P = w1 salt + w2 nclears + w3 nh + w4 nb (28.1)

donde:

salt representa la suma de las alturas asociada a la pieza a colocar en una


determinada posicin,
nclears representa el nmero de lneas limpiadas,
nh representa el nmero de huecos generados por la colocacin de la cha en
una determinada posicin,
nb representa el nmero de bloqueos como consecuencia de la potencial
colocacin de la cha.
wi representa el peso asociado al factor i.

Evidentemente, nclears tendr asociado un peso positivo, mientras que el resto


de factores tendrn asociados pesos negativos, ya que representan penalizaciones a la
hora de colocar una cha.
Una inconveniente inmediato de esta primera aproximacin es que no se contem-
pla, de manera explcita, la construccin de bloques consistentes por parte del mdulo
de IA de la mquina. Es decir, sera necesario incluir la lgica necesaria para premiar
el acoplamiento entre chas de manera que se premiara de alguna forma la colocacin
lgica de las mismas. La gura 28.13 muestra un ejemplo grco de esta problemti- 1 2
ca, cuya solucin resulta fundamental para dotar a la mquina de un comportamiento
inteligente.

28.2. Tcnicas Fundamentales


Figura 28.13: Idealmente, la posi-
En esta seccin se describirn brevemente algunas de las tcnicas ms extendidas cin 2 debera ser premiada en de-
trimento de la posicin 1, ya que fa-
en el mbito de la Inteligencia Articial que se emplean en el desarrollo de videojue-
cilita la colocacin de futuras pie-
gos, como son la lgica difusa, los algoritmos genticos y las redes neuronales. La zas.
lgica difusa permite presentar al computador una forma alternativa de solucionar los
problemas de una forma similar a como lo hacen los humanos, mediante el uso de tr-
minos y conceptos lingsticos; as como el tratamiento de la imprecisin y vaguedad
presentes en multitud de problemas reales.
Por otro lado, los algoritmos genticos estn basados en la teora de la evolucin
y permiten encontrar de forma eciente soluciones efectivas (no tiene por qu ser la
ptima) en poblaciones de individuos amplias (o espacio de soluciones). Para ello, los
algoritmos genticos emplean varias tcnicas de combinacin, seleccin y mutacin
que permiten llegar a soluciones nales mediante la combinacin de soluciones
parciales.
28.2. Tcnicas Fundamentales [835]

Una de las principales caractersticas potenciales de este tipo de algoritmos es que


son adaptativos; permiten encontrar soluciones en entornos dinmicos o cambiantes,
incluso si existe un gran desconocimiento inicial sobre el problema a tratar. Esta
caracterstica los hace especialmente adecuados en el desarrollo de videojuegos de
ltima generacin en donde se busca el mayor realismo posible, y se intenta modelar
entornos y comportamientos poco predecibles.
Por otro lado, las redes neuronales tambin estn basadas en la propia naturaleza.
Se trata de algoritmos de aprendizaje y razonamiento basados en el sistema nervioso
de los animales (interconexin de neuronas en una red que colabora para producir
un estmulo de salida). Las redes neuronales y los algoritmos genticos se suelen
combinar con frecuencia, ya que los segundos permiten construir el espacio de casos
o entrenamiento que las redes neuronales necesitan para su correcto funcionamiento.

28.2.1. Lgica Difusa


La lgica difusa es un modelo matemtico creado por el profesor Lot Zadeh de la
Universidad de Berkeley en California (EEUU). El profesor Zadeh present la teora
de conjuntos difusos por primera vez en el ao 1965 mediante un artculo publicado
en la revista Information and Control [105]. Segn su autor, la lgica difusa se basa
en dos principios fundamentales [14]:

Permite presentar al computador una forma alternativa de solucionar los


problemas, de una forma similar a como lo hacen los humanos.
La mayora de elementos se representan en la lgica difusa mediante grados de
pertenencia.

Para aquellos que no estn familiarizados con esta tcnica, los dos principios
anteriores pueden resultar un tanto confusos; desarrollemos con mayor detalle la
principal idea en la que se basa cada uno de ellos.
En cuanto al primero, los computadores son mquinas que trabajan de forma
metdica y que resuelven los problemas con total exactitud. Normalmente, un
computador necesita unos parmetros de entrada, los procesa y genera unos datos
de salida como solucin a un problema. Sin embargo, los humanos suelen analizar las
situaciones o resolver los problemas de una manera ms imprecisa, sin la necesidad de
conocer todos los parmetros o que la informacin relativa a los mismos est completa.
Por ejemplo, supongamos el caso en el que dos personas se estn pasando una
pelota. El lanzador no es consciente en ningn momento, del ngulo que forma su
brazo con el cuerpo, ni la fuerza exacta que realiza para lanzar la bola. Uno de ellos
podra pedir al otro que le lanzara la pelota ms fuerte. El trmino lingstico ms
fuerte es impreciso en s; en ningn momento se especica cuanto debe aumentar
el valor de la fuerza para cumplir el objetivo deseado. La persona encargada de
lanzar, puede resolver el problema rpidamente, aumentando la fuerza con respecto
Figura 28.14: Profesor Lofti Za-
al lanzamiento anterior sin la necesidad de realizar clculos precisos. Esta forma de
deh, padre de la lgica difusa, pre- trabajar, en la que se manejan trminos lingsticos, conceptos vagos e imprecisos
sent su teora en el ao 1965 en la es un tanto opuesta a la forma en la que el computador trabaja. Sin embargo,
revista Information and Control. puede ofrecer soluciones rpidas y ecientes a problemas reales, con un bajo coste
computacional.
Por otro lado, el segundo principio enuncia que la lgica difusa se basa principal-
mente en el estudio de los grados de pertenencia a los conjuntos difusos denidos.
C28

Normalmente, un sistema difuso consta de un conjunto de variables de entrada; cada


una de estas variables posee un dominio de denicin (rango de valores que las va-
[836] CAPTULO 28. INTELIGENCIA ARTIFICIAL

riables pueden tomar) en el que se incluyen los conjuntos difusos. Segn el valor de
entrada de la variable, ste pertenecer a uno o varios conjuntos del dominio con un
cierto grado de pertenencia. Esta es una de las principales caractersticas que diferen-
cian a la lgica difusa de la lgica tradicional.
En la lgica bivaluada existen dos valores de verdad posibles: verdadero y falso.
En la lgica difusa pueden existir valores intermedios, es decir, un hecho podra ser
no del todo cierto y no del todo falso. En cuanto a la teora de conjuntos tradicional,
un elemento pertenece absolutamente a un conjunto o no pertenece. En el caso de
la lgica difusa, un elemento puede pertenecer a diferentes conjuntos con distintos
grados. Los grados de pertenencia pertenecen al intervalo [0,1], donde 1 representa
pertenencia absoluta y 0 el caso contrario. Por tanto, podemos decir que la lgica
difusa es una generalizacin de la lgica clsica.
Veamos un ejemplo; supongamos que un sistema posee una variable de entrada
llamada altura. El objetivo de este sistema podra ser catalogar a un conjunto de
personas adultas como muy bajo, bajo, mediano, alto o muy alto. Supongamos que la
altura puede variar entre 1.40 y 2.20. En el dominio de denicin de la variable altura
habr que indicar de algn modo la correspondencia entre los trminos muy bajo,
bajo, mediano, alto y muy alto, y el rango posible de valores numricos [1.40-2.20].
Supongamos que en un sistema clsico, se considera personas bajas hasta 1.69 y las
personas medianas desde 1.70 hasta 1.75. En este sistema, una persona que mida 1.69
sera clasicada como baja, mientras que una persona que mida 1.70 sera clasicada
como mediana.
Tal como se puede apreciar, la discriminacin en el proceso de clasicacin entre
una persona y otra es excesiva teniendo en cuenta que la diferencia es mnima (slo
un centmetro). La lgica difusa contempla la pertenencia a varios conjuntos al mismo
tiempo, es decir, una persona que mida 1.70 podra pertenecer al conjunto de los
medianos con un grado 0.6 mientras que al conjunto de los bajos podra ser 0.4. Este
es un ejemplo orientativo para concebir una idea general sobre la esencia en la que
se basa la lgica difusa; en secciones posteriores se explicar con mayor detalle la
denicin de conjuntos difusos y el clculo de grados de pertenencia.

La lgica difusa permite resolver problemas reales de forma imprecisa con el


uso de trminos lingsticos.

Aplicaciones generales de la lgica difusa

Desde el ao 1965, la lgica difusa se ha implantado con xito en multitud


de mbitos, teniendo una mayor acogida inicial en Japn en la fabricacin de
electrodomsticos y el control industrial. Algunos ejemplos de aplicacin son los
siguientes:

Sistema de control inteligente de aires acondicionados.


Enfoque automtico en cmaras digitales.
Electrodomsticos (frigorcos, lavadoras, ...).
Control industrial.
Comercio electrnico.
28.2. Tcnicas Fundamentales [837]

Sistemas de escritura.
Mejora de la eciencia en el consumo de combustibles en vehculos.
Sistemas expertos que simulan el comportamiento humano.
Tecnologa informtica.

De los mbitos anteriores, el control inteligente de aparatos de aire acondicionado


es uno de los ms frecuentes en donde se aplica con xito la lgica difusa. Estos
aparatos disponen de un termostato que mide la temperatura ambiente y, en funcin
de sta, el aparato se activa con mayor o menor potencia para obtener la temperatura
deseada. La aplicacin de la lgica difusa en este caso evita activaciones mltiples
cuando la temperatura oscila frecuentemente entre los lmites establecidos en el
termostato. De esta forma, se permite un mayor ahorro energtico y, en consecuencia,
un ahorro econmico importante. Este tipo de sistemas no slo permiten un control
suavizado de la activacin del suministro de aire, sino tambin del grado y potencia
Figura 28.15: La lgica difusa se con la que se emite el aire para conseguir la temperatura deseada.
ha empleado con frecuencia para
controlar el movimiento de aeropla- La lgica difusa tambin es realmente til para la toma de decisiones [64]; un
nos en simuladores de vuelo. ejemplo de ello es el comercio electrnico. En numerosas ocasiones, un cliente no
tiene claro las caractersticas del producto que desea adquirir; de esta forma, en el
proceso de negociacin se manejan trminos imprecisos, subjetivos o vagos. La lgica
1 E1 E2 E3 E4 difusa es un modelo que, por su propia naturaleza, se adapta correctamente a este tipo
de situaciones y facilita la toma de decisiones.
En general, el uso de la lgica difusa es conveniente cuando se trata un proceso
complejo no lineal y en el que se maneja conocimiento que no est estrictamente
0 denido.
V

Figura 28.16: Denicin de domi- La lgica difusa en el desarrollo de videojuegos


nio difuso de la variable V mediante
conjuntos triangulares. La lgica difusa tambin se ha aplicado desde su creacin en el desarrollo
de videojuegos de diversas formas; algunas de las ms destacadas son: control de
E1 E2 E3 elementos mviles, evaluacin de estrategias y clasicacin [14].
1
El primer caso, uso de la lgica difusa para el control de elementos mviles, ha
permitido modelar con xito el movimiento de vehculos en los videojuegos para con-
seguir movimientos mucho ms realistas. Mediante esta tcnica se consiguen movi-
mientos suavizados, mucho ms cercanos a los reales, y se evitan cambios bruscos
0 en la direccin o velocidad de movimiento. Adems del movimiento de vehculos, la
V lgica difusa tambin se ha empleado en el desarrollo de videojuegos para la anima-
cin realista de humanoides, cuyos elementos corporales estn estructurados en un
Figura 28.17: Denicin de domi- esqueleto.
nio difuso de la variable V mediante
conjuntos trapezoidales. Estas tcnicas se pueden emplear a nivel individual, o bien, a nivel grupal, con el
objetivo de animar grupos de objetos en movimiento que se dirigen hacia un objetivo
comn. Los integrantes del grupo pueden variar sus movimientos y conductas uno con
E1 E2 E3 respecto de otros sin alejarse del objetivo que tienen en comn. De esta forma dejan
1 de comportarse como un bloque que acta exactamente de la misma forma y se otorga
mayor realismo a la escena.
En el segundo caso, evaluacin y determinacin de estrategias mediante el uso
0 de la lgica difusa, se consiguen comportamientos "ms humanos". A la hora de
V modelar el comportamiento inteligente de los adversarios a los que se enfrenta un
jugador, se puede tener en cuenta la posibilidad de que ste no cuenta con toda la
C28

Figura 28.18: Denicin de domi- informacin sobre el personaje principal, o bien, la informacin de la que dispone es
nio difuso de la variable V mediante incompleta. Esto hace tambin que el comportamiento de los adversarios sea mucho
conjuntos curvilneos. menos previsible y rompe la monotona del videojuego.
[838] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Por ltimo, la lgica difusa se emplea con mucha frecuencia en procesos de


clasicacin. A partir de ciertos parmetros como pueden ser: fuerza del personaje,
potencia del arma, puntos dbiles, etc, se puede realizar una clasicacin para la
obtencin de un ranking que luego puede ser empleado en otros procesos relacionados
con la Inteligencia Articial del juego; como por ejemplo, en la elaboracin de
estrategias.

Fundamentos de la lgica difusa

En general, cualquier sistema difuso consta de cuatro partes fundamentales: (I)


una primera parte de modelado en el que se dene el conjunto de variables de
entrada y sus dominios de denicin, (II) proceso de fuzzicacin en el que se
convierten las entradas crisp o valores numricos del sistema en entradas difusas,
(III) motor de inferencia para el proceso de razonamiento y, nalmente, (IV) proceso
de defuzzicacin en el que se realiza la conversin inversa al punto (I) (ver
Figura 28.19). Este ltimo paso no siempre es necesario y se puede mantener una
salida difusa si as se desea.
Fase de modelado
La fase de modelado consiste en especicar el conjunto de variables de entrada
que utilizar posteriormente el sistema en el proceso de inferencia. Para cada una de
las variables ser necesario denir su dominio, constituido por el rango de valores
numricos que la variable puede tomar y los conjuntos difusos. Ejemplos de variables
podran ser: altura, peso, velocidad, presin, etc. Los conjuntos difusos abarcan un
rango de valores y se les asocia una etiqueta lingstica. Por ejemplo, para la variable
altura los conjuntos podran ser: muy bajo, bajo, medio, alto y muy alto. Los conjuntos
pueden adoptar diferentes formas: curvilneas (Figura 28.18), triangulares (Figura
28.16) o trapezoidales (Figura 28.17) entre otras. En funcin de las caractersticas
del problema a modelar ser conveniente elegir una forma u otra.

Modelado: Definicin de variables y sus dominios

Proceso de Fuzzificacin
Entrada crisp Entrada difusa

Motor de inferencia
Proceso de inferencia Salida difusa

Proceso de defuzzificacin
Salida difusa Salida crisp

Figura 28.19: Esquema general de un sistema difuso.


28.2. Tcnicas Fundamentales [839]

En la Figura 28.20 se puede observar un ejemplo en el que se dene un espacio


difuso para la variable altura. Cada uno de los conjuntos difusos se dene mediante
un trapezoide y tiene asociado una etiqueta lingstica. Para denir cada uno de los
trapecios y sus lmites son necesarios cuatro valores numricos. Tal como se puede
apreciar, el valor de la altura vara desde 1.40 hasta 2.20 y todos los casos son
abarcados por los conjuntos difusos denidos.
Los dominios de una variable se pueden variar fcilmente modicando los lmites
de los conjuntos; esta caracterstica dota a los sistemas difusos de gran exibilidad. El
ejemplo que se muestra en la Figura 28.20 podra servir para clasicar los estudiantes
de un curso en una universidad espaola. Suponga que quisiramos clasicar los
alumnos de una universidad noruega en la que la mayora de alumnos supera la medida
de 1.80 metros. En este caso, una persona que mida 1.80 puede ser considerado en ese
contexto con una estatura media y no alta. Para tal caso, bastara con modicar los
lmites de los conjuntos sin la necesidad de alterar el resto de partes del sistema.

Altura

Muy bajo bajo medio alto muy alto


1

0
1.40 1.45 1.55 1.60 1.65 1.70 1.75 1.80 1.85 2.20

Figura 28.20: Dominio de denicin o espacio difuso denido para la variable altura.

Una vez denidas las variables y sus dominios de denicin, el siguiente paso
consiste en la fuzzicacin de variables a medida que reciban valores de entrada. Este
paso consiste bsicamente en el estudio de los valores de pertenencia a los conjuntos
difusos en funcin de los valores crisp de entrada. Por ejemplo, supongamos que una
persona mide 1.64, es decir, altura = 1.64. Dicho valor de altura estara comprendido
entre los conjuntos bajo y medio, con una pertenencia mayor al conjunto medio.
Un espacio difuso est correctamente denido, si la suma de todas las pertenencias
a los conjuntos siempre es 1, independientemente del valor de entrada. Formalmente,
la denicin de la funcin de pertenencia asociada a un trapecio o conjunto difuso
trapezoidal es de la siguiente manera:

0 u<a

(ua)

(ba) au<b
(u; a, b, c, d) = 1 buc (28.2)

(du)

(dc) c<ud

0 u>d
C28

donde u representa el valor de entrada de la funcin, y a, b, c y d los cuatro parmetros


a partir de los cuales se dene el trapecio, siendo 1 la altura mxima. Cada DDVi
(dominio de denicin de la variable vi ) debe vericar las siguientes propiedades:
[840] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Altura

Muy bajo bajo medio alto muy alto


1

0
1.40 1.45 1.55 1.60 1.65 1.70 1.75 1.80 1.85 2.20

Figura 28.21: Estudio de los valores de pertenencia a los conjuntos difusos a partir de una altura de 1.64.

1. Lx DDVi , altura(Lx ) = 1. Lx representa la etiqueta lingstica del


conjunto x.
2. Lx , Ly DDVi , nucleo(Lx ) nucleo(Ly ) = . Pertenecen al ncleo de
un conjunto difuso, representado mediante una etiqueta lingstica L, aquellos
valores x Xi que maximizan la funcin de pertenencia L , es decir, L (x) =
1.
|DDVi |
3. x Xi , j=1 Lj (x) = 1, siendo Xi el dominio donde se dene vi .

A continuacin se muestra una posible implementacin de las funciones que


estudian el grado de pertenencia a conjuntos trapezoidales y triangulares:

Listado 28.5: Grado de pertenencia a un conjunto trapezoidal


1 double FuzzyTrapezoid(double u, double a, double b, double c,
double d)
2
3 {
4 double result = 0;
5
6 if (a <= u && u < b )
7 result = (u-a)/(b-a);
8 else if (b <= u && u <= c)
9 result = 1;
10 else if (c < u && u <= d)
11 result = (d-u)/(d-c);
12
13 return result;
14
15 }

Listado 28.6: Grado de pertenencia a un conjunto triangular


1 double FuzzyTriangle(double u, double a, double b, double c)
2 {
3 double result = 0;
4
5
6 if (a <= u && u < b )
7 result = (u-a)/(b-a);
28.2. Tcnicas Fundamentales [841]

8 else if (u == b)
9 result = 1;
10 else if (b < u && u <= c)
11 result = (c-u)/(c-b);
12
13 return result;
14 }

Motor de inferencia
Una vez que se ha realizado el proceso de fuzzicacin, para determinar el
grado de pertenencia de las variables a los conjuntos difusos, comienza el proceso
de inferencia o razonamiento para la toma de decisiones. Uno de los motores de
inferencia ms comunes es el basado en reglas difusas de tipo SI-ENTONCES. Este
tipo de reglas est formado por un conjunto de antecedentes conectados mediante
operadores lgicos (AND, OR, NOT) y uno o varios consecuentes. Cada antecedente
a su vez consiste en una variable difusa y el rango de etiquetas lingsticas que sta
debera tomar para que se produzca la activacin de la regla. Veamos un ejemplo:

SI distancia {muy corta, corta} y fuerza_enemigo es {baja, muy baja, media}


ENTONCES Ataque {agresivo}

Para que se active la regla anterior la distancia entre el enemigo y el personaje


principal debe tener un grado de pertenencia superior a cero en los conjuntos, corta
o muy corta; lo mismo sucede con la fuerza del enemigo, en donde al menos se debe
dar uno de los tres casos siguientes: baja, muy baja o media. En caso armativo, la
regla se activa y el consecuente es Atacar con un determinado grado de pertenencia a
ataque agresivo.
Como se puede apreciar una regla difusa abarca mltiples casos y, estos casos a su
vez, tienen en cuenta un amplio rango de valores para las variables de entrada. Segn
Timothy Masters en su libro Practical Neural Networks Recipes in C++ [61], un
sistema formado por reglas difusas necesita entre un 50 % y un 80 % de reglas menos
que necesitara un sistema tradicional, para realizar las mismas tareas; por tanto, el
mantenimiento es mucho ms sencillo.
Evaluacin de las reglas difusas
Para evaluar el grado de activacin de una regla difusa, primero es necesario
establecer como se resolvern los operadores lgicos empleados en los antecedentes.
Existen diversas formas, pero una de las ms comunes es emplear el mximo para el
operador OR, el mnimo para AND y 1-valor de pertenencia, para el operador NOT.
Si se aplican estos criterios, el grado de activacin de una regla viene determinado por
el mnimo de los mximos.
Si tenemos en cuenta la regla anterior, supongamos que el grado de pertenencia a
los conjuntos muy corta y corta de la variable distancia es 0.2 y 0.8 respectivamente;
por otro lado, supongamos que en el caso de la fuerza del enemigo el grado de
pertenencia a muy baja es 1 y al resto es cero. La forma de determinar el grado de
activacin es el siguiente:

distancia: MAX (0.2 y 0.8) = 0.8


C28

fuerza del enemigo MAX (1, 0, 0) = 1


[842] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Mtodo Mtodo Directo


Directo de Mandani

Razonamiento Modelado Difuso


Aproximado Takagi y Sugeno

Mtodo Mtodo
Indirecto Simplificado

Figura 28.22: Clasicacin de los Mtodos de Razonamiento Aproximado.

Tanto la distancia como la fuerza del enemigo estn conectadas mediante un


operador AND, por tanto: MIN( 0.8, 1) = 0.8, determina el grado de activacin
de la regla.

Listado 28.7: Funciones asociadas a los operadores lgicos


1 double FuzzyAND(double A, double B)
2 {
3 return MIN(A, B);
4 }
5
6 double FuzzyOR(double A, double B)
7 {
8 return MAX(A, B);
9 }
10
11 double FuzzyNOT(double A) {
12 return 1.0 - A;
13 }

Veamos con mayor detalle uno de los mtodos de inferencia ms populares en la


lgica difusa: Mtodo de Mandani.
Proceso de razonamiento mediante el mtodo de Mandani
Segn Tanaka [96], los sistemas de razonamiento aproximado se pueden clasicar
como se muestra en la gura 28.22.
Uno de los mtodos ms utilizado es el Mtodo Directo de Mandani. Veamos un
ejemplo de funcionamiento con dos reglas:

Regla 1: IF x es A1 e y es B1 THEN z es C1
Regla 2: IF x es A2 e y es B2 THEN z es C2

Donde A1 , A2 , B1 , B2 , C1 y C2 son conjuntos difusos. La gura 28.23 muestra


el proceso de razonamiento que se expondr a continuacin. Supongamos x0 e y0
como entradas para las variables x e y de las premisas (parte antecedente de la regla).
Denotaremos la entrada como (x0 , y0 ).
28.2. Tcnicas Fundamentales [843]

A1 B1 C1
1 1 1

w1
0 x 0 y 0 z

A2 B2 C2
1 1 1

w2

0 x 0 y 0 z
x0 y0

C1 C2
1

0 z
z0 (centroide)

Figura 28.23: Proceso de razonamiento mediante el mtodo de Mandani.

El proceso de razonamiento para esta entrada ser el siguiente:

Paso 1: Obtener el grado en que se satisfacen los antecedentes. En cada regla


hay dos proposiciones en el antecedente. Para calcular el grado de satisfaccin
de cada regla se utilizarn, en este caso concreto, los operadores estndar sobre
conjuntos difusos. En este ejemplo se emplearn los operadores estndar para
T-normas (interseccin de conjuntos - operador AND).

Regla 1: W1 = mn [A1 (x0 ), B1 (y0 )]


Regla 2: W2 = mn [A2 (x0 ), B2 (y0 )]

Paso 2: Aplicar los resultados obtenidos en el paso anterior a los conjuntos


difusos en la parte del consecuente, para as obtener la conclusin de cada regla.

Conclusin de la Regla 1: C1 (x0 ) = mn [W 1 , C1 (z)] z Z


Conclusin de la Regla 2: C2 (x0 ) = mn [W 2 , C2 (z)] z Z

Paso 3: Construir la conclusin nal mediante la agregacin de todas las


conclusiones parciales. En este caso, se ha empleado el operador T-conorma
estndar (Unin de conjuntos - operador OR).
C28

Conclusin nal: C (z) = m


ax [C1 (z), C2 (z)]
[844] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Proceso de defuzzicacin
El proceso de defuzzicacin es necesario slo si se desea un valor real o crisp
de salida en nuestro sistema (valor z0 en la gura 28.23). El proceso ms sencillo y
ampliamente utilizado se conoce como mtodo de centro de gravedad, en el que se
obtiene el punto central del rea generada en el consecuente de la regla.
Si tenemos en cuenta de nuevo el ejemplo anterior, donde se plantea el mtodo de
razonamiento de Mandani, los mtodos de defuzzicacin ms comunes son:

Tomar el centro de masas del conjunto difuso conclusin:

c (z)z dz
z0 =
c (z) dz

Tomar el valor de la mxima pertenencia del conjunto difuso conclusin:

z0 = m
ax c (z)
z

Caso de estudio: seleccin de estrategia de combate

En esta seccin se plantea un ejemplo sencillo en el que se puede emplear un


sistema difuso para determinar el comportamientos de los personajes que participan
en un campo de batalla. Para no complicar el sistema de navegacin de los personajes,
podemos suponer que el campo de batalla es abierto, es decir, sin obstculos. Dicho
campo se puede modelar fcilmente con el uso de una matriz bidimensional, donde la
la y la columna en la que se encuentra un personaje determina su posicin actual.
Inicialmente el frente enemigo se encuentra situado en un extremo (ya sea la E - Enemigo E E E
J - Jugador
primera la o la primera columna) y el bando en el que nuestro personaje se encuentra A - Aliado
E

en el extremo opuesto. El personaje que el usuario maneja cuenta con el apoyo de


otros personajes aliados; el sistema difuso debe permitir modelar el comportamiento
tanto de los enemigos como de los aliados. J
A A A
Con el objetivo de no introducir una excesiva complejidad en el problema,
supongamos que la estrategia que puede adoptar cada personaje depende de un nmero Figura 28.24: Campo de batalla
reducido de variables: representado como una matriz bi-
dimensional. Elementos participan-
Energa del objetivo y del personaje que valora la estrategia tes: Enemigos, aliados y el propio
jugador.
Distancia entre ambos
Grado de ocupacin, es decir, si est libre o, por el contrario, se encuentra
envuelto en una batalla con una o varias entidades.

Para dichas variables se pueden denir los siguientes conjuntos difusos:

Energa: baja, muy baja, media, alta y muy alta.


Distancia: muy cerca, cerca, distancia media, lejos y muy lejos
Grado de ocupacin: libre, muy poco ocupado, poco ocupado, ocupado, muy
ocupado.
28.2. Tcnicas Fundamentales [845]

Tal como se vio en las las secciones anteriores, existen diferentes alternativas para
denir los conjuntos difusos correspondientes a las etiquetas lingsticas especicadas
en el listado anterior: trapezoidales, triangulares, mediante curvas, etc. Queda a
eleccin del lector, elegir una de las opciones en funcin del comportamiento del
sistema deseado.
Por otro lado, las variables de salida son las que se incluirn en el consecuente
de las reglas y determinarn las acciones bsicas de cada individuo: acercarse, huir,
atacar y defenderse. A continuacin se detalla una breve descripcin sobre cada una
de ellas:

Atacar: Si un enemigo ataca y el otro no se encuentra en posicin de defensa,


recibir el 100 % de dao producido por el ataque. A medida que la energa
del individuo disminuya, el ataque debera producir un dao menor. Por el
contrario, si el individuo se encuentra en posicin de defensa, podra evitar el
dao producido por el ataque (no siempre).
Defenderse: Esta accin permite al individuo que la realiza recuperar parte de
su energa. Adems, podra evitar daos en el caso de recibir un ataque.
Huir: Desplazarse una casilla en el tablero incrementando la distancia con
respecto al enemigo.
Acercarse: Desplazarse una casilla en el tablero disminuyendo la distancia con
respecto al enemigo.

Una vez especicadas las variables de entrada/salida y denido el dominio


difuso para cada una de ellas, el siguiente paso consiste en construir una base
de conocimiento formada por reglas difusas que van a determinar las conductas.
En funcin de la estrategia que queramos adoptar, se debe dar preferencia en el
antecedente de las reglas a unas variables u otras. Por ejemplo, podramos optar por
atacar siempre a aquellos enemigos que tengan una energa ms baja o bien, podramos
optar por atacar al ms cercano. Algunos ejemplos de reglas difusas podran ser los
siguientes:

SI Energia_Enmigo{muy baja, baja} y Ocupacion {baja} y


Mi_energia{media, alta, muy alta} y Distancia{muy cerca} ENTON-
CES Atacar
SI Mi_Energia{muy baja, baja} y Distancia{muy cerca} ENTONCES
Defenderse

Si nalmente optamos por realizar una de las cuatro acciones posibles en cada
turno: acercarse, huir, atacar o defenderse; tan slo sera necesario estudiar la
activacin de las reglas y actuar segn aquella que tenga un grado de pertenencia
mayor.
Una vez implementando el sistema difuso, sera interesante realizar modicacio-
nes del dominio de denicin de cada variable para poder observar como vara el
comportamiento de los personajes sin la necesidad de variar la lgica de programa.
La inclusin de nuevas reglas o modicacin de las existentes, tambin contribuye al
C28

cambio de conductas.
[846] CAPTULO 28. INTELIGENCIA ARTIFICIAL

28.2.2. Algoritmos genticos


Los algoritmos genticos (AG) estn basados en la propia naturaleza, en concreto,
en la teora de la evolucin en donde los seres vivos evolucionan de generacin en
generacin adaptndose al entorno que habitan. Los geneticistas y bilogos evolutivos
arman que la evolucin no optimiza necesariamente, sino que ms bien se adapta y
optimiza localmente en el espacio y el tiempo; es decir, la evolucin no necesariamente
signica progreso y los individuos de una generacin no tienen porque ser mejores a
los de generaciones anteriores, simplemente, se adaptan mejor al entorno actual.
Imaginemos por un momento, que los alimentos se encontraran nicamente en lo
alto de rboles de una altura elevada. Las jirafas, animales de cuello muy largo, u otros
animales con la capacidad de trepar desarrollada, seran los nicos que tendran acceso
a estos alimentos y, por tanto, los nicos que podran sobrevivir. Este hecho no hace
a las jirafas o animales trepadores mejores que los de otra especie, simplemente, los
convierte en seres mejores adaptados al medio.
En los AG existe un espacio (normalmente amplio) de soluciones candidatas al
problema que se est tratando (conocidas tambin como individuos). Esta poblacin
tiende a evolucionar hacia mejores soluciones mediante un proceso iterativo. Cada
una de estas soluciones se puede entender como un cromosoma y, estos cromosomas,
a su vez estn constituidos por una cadena de genes. Estos genes representan la Figura 28.25: Charles Darwin (12
conguracin gentica del individuo o solucin, y cada uno de ellos es una parte de febrero de 1809 19 de abril de
importante en la solucin global; dependiendo de como estn combinados se obtiene 1882), propulsor de la teora de la
soluciones ms o menos ptimas. De forma general, el proceso evolutivo y de evolucin mediante seleccin natu-
ral.
adaptacin consiste en una seleccin de los mejores individuos de cada generacin (de
esta forma se orienta la bsqueda a las reas ms prometedoras); as como el cruce
entre stos o la mutacin para dar lugar a los individuos de la siguiente generacin, Adaptabilidad
ms evolucionados que los anteriores. Es la principal caracterstica de los
La evolucin es una forma de adaptacin ms potente que el simple aprendizaje. algoritmos genticos; permiten en-
contrar soluciones que se adecuan a
En el desarrollo de videojuegos puede ser una herramienta realmente til si se desea un entorno.
modelar entornos dinmicos y generar soluciones que se adapten a estos cambios.
De esta forma, es posible desarrollar vdeojuegos mucho ms realistas en donde las Operadores evolutivos
situaciones y comportamientos dejan de ser montonos y previsibles; se producen Los operadores evolutivos bsicos
un amplio abanico de posibilidades y el entorno de manera global se adapta a estas son la seleccin, cruce y mutacin.
situaciones.
Los AG son tambin realmente apropiados para conseguir conguraciones pti-
mas. Imagnese, por ejemplo, el juego Gran Turismo 4 en el que para cada circuito
existen coches y caractersticas mecnicas que se adaptan mejor para lo obtencin
de mejores tiempos. Si se tiene en cuenta que existen ms de 500 coches y doce-
nas de caractersticas congurables, encontrar una conguracin ptima es realmente
complicado. Un AG podra simular mltiples conguraciones y calcular el benecio
proporcionado por cada una de ellas.

Figura 28.26: Conguracin de la


Esquema general de un algoritmo gentico relacin de marchas en el juego
GT4
Como se coment anteriormente, los AG constan de un conjunto de procedimien-
tos de bsqueda adaptativos. El primer requisito para un AG es la constitucin de una
poblacin inicial formada por un conjunto de cromosomas. La generacin de dicha
poblacin se puede producir de forma absolutamente aleatoria, o bien, combinndola
con posibles soluciones candidatas que sean conocidas. Independientemente del m- Diversidad Estructural
todo de generacin, la poblacin inicial debe poseer diversidad estructural para tener
una representacin de la mayor parte de la poblacin y no caer en una convergen- En un algoritmo gentico es fun-
damental que la poblacin inicial o
cia prematura. La formacin de la primera generacin es vital para llegar con xito a primera generacin posea diversi-
soluciones prometedoras en las futuras generaciones. dad estructural para converger a so-
luciones adecuadas
28.2. Tcnicas Fundamentales [847]

Una vez que se ha constituido la primera poblacin o primera generacin, se


procede a la ejecucin de un proceso iterativo. Este proceso se basa en el estudio
de la calidad de cada una de las soluciones mediante el uso de una funcin de aptitud;
es decir, esta funcin informar sobre lo buena que es una solucin. Despus de
estudiar la aptitud de cada solucin o cromosoma, comienza un proceso de seleccin,
de tal forma que los cromosomas elegidos sern combinados para dar lugar a los
individuos de la siguiente generacin. Los cromosomas con mejor aptitud tienen
siempre ms probabilidades de ser seleccionados.
Funcin de aptitud Entre los cromosomas elegidos se aplican operadores genticos - reproduccin
para la generacin de nuevos individuos: mutacin, cruce, evaluacin, etc. Finalmente,
Permite evaluar la bondad de una el AG debe nalizar el proceso iterativo cuando se alcance la solucin ptima. En caso
solucin
de que no sea posible obtener la mejor, se pueden aplicar otros criterios de parada: i)
determinar al comienzo un nmero mximo de iteraciones y detener el procedimiento
cuando se alcance esa iteracin, ii) nalizar cuando no se produzcan cambios en la
poblacin o stos sean mnimos.

Formacin de la poblacin inicial

Valoracin de la aptitud de cada


solucin de la poblacin

Seleccin de individuos

Reproduccin: operadores genticos

NO Solucin optima?/
Condicin de parada

SI

Espacio de soluciones final

Figura 28.27: Esquema general de un algoritmo gentico.


C28
[848] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Representacin del problema

Antes de constituir la poblacin inicial y comenzar el proceso iterativo descrito en


la seccin anterior, es fundamental determinar la forma en la que se van a representar
las soluciones. Cuando se elige o disea un modo de representacin se est deniendo
tambin el espacio de estados en el que el AG va a realizar la bsqueda de soluciones.
Por tanto, es fundamental ser cuidadoso a la hora de denir la estructura gentica
ya que de ello depende en gran medida el xito o fracaso del AG en la bsqueda de
soluciones ptimas, as como la eciencia y coste computacional invertido.
Una forma de representacin comn es el uso de cadenas binarias, formadas por
1s y 0s, en donde cada dgito representa el valor de algn aspecto de la solucin.
Cuantos ms bits tengamos en la cadena, ms posibles estados podremos denir
(2n ). En funcin del nmero de estados que queramos representar, se emplearn
cadenas binarias de mayor o menor longitud. Por ejemplo, un juego clsico de
comecocos en el que el personaje principal tiene cuatro posibles opciones en cada
turno: movimiento hacia arriba, abajo, izquierda o derecha. Son cuatro estados los
que podemos representar con dos bits:

00: mover arriba Figura 28.28: Juego clsico de co-


mecocos en el que los cuatro movi-
01: mover abajo mientos o estados posibles del per-
sonaje principal son: movimiento
10: mover izquierda hacia arriba, abajo, izquierda y de-
recha
11: mover derecha

Cuando el personaje principal se encuentra en alguno de los estados, la mutacin


o cambio en alguna de las partes de la estructura gentica produce un cambio de
estado. La funcin de aptitud determinar cul es el mejor de los cambios de estado
teniendo en cuenta que tras el movimiento, el personaje puede encontrarse con un
muro, una casilla libre, un fantasma al que puede comer, o bien, un fantasma que
podra eliminarlo.
En algunas ocasiones el modo de representacin binario es limitado y no permite
afrontar con garantas el problema a tratar. En este caso, se puede optar por emplear
valores numricos en las cadenas; as se permite una mayor precisin y complejidad.
Una segunda opcin es emplear cadenas de letras, en donde cada una de las letras
representa un aspecto especco de la solucin.
Binaria 1 0 1 1 0 0 1
Los tres mtodos anteriores facilitan el uso de operadores genticos que alteren
las cadenas de genes: el primero de ellos intercambiando los ceros por unos y Enteros 7 3 3 5 7 1 2

viceversa; el segundo de ellos incrementando o decrementando los valores numricos Reales 2.1 3.4 5.6 4.5 3.2 9.1 8.8
con una cantidad adicional; nalmente, el ltimo mtodo de representacin permite el Caracteres A C A A B C A
intercambio de letras.
Figura 28.29: Modos de represen-
tacin para secuencias genticas
Operadores Genticos

Los operadores genticos se pueden clasicar en tres grandes clases: seleccin,


mutacin y cruce. En cada una de estas categoras existen mltiples variantes y se
pueden emplear y combinar varias de ellas en un AG.
28.2. Tcnicas Fundamentales [849]

Operadores de seleccin

Seleccin elitista: se escogen los miembros ms aptos en cada generacin.


Este mtodo se puede entender como un proceso de clonacin, en el que se
copian directamente los mejores miembros de una generacin a la siguiente, de
tal forma que se garantiza que no se pierdan en el proceso de bsqueda. Sin
embargo, abusar de este mtodo no es conveniente ya que la bsqueda puede
converger a soluciones que representan mximos locales y podran evitar la
generacin de una solucin que represente un mximo global.
Seleccin proporcional a la aptitud: Los individuos ms aptos tienen mayor
probabilidad de ser seleccionados pero no la certeza. Esta probabilidad se podra
calcular en funcin del valor de aptitud y de la suma totales de aptitudes:
pi = fi / j fj . Para determinar si un cromosoma se selecciona para una
posterior reproduccin, se puede generar un nmero aleatorio entre 0 y 1; si
el valor generado es inferior a pi , entonces el individuo es seleccionado.

0.125 Seleccin por rueda de ruleta: En este caso, el individuo tambin tiene
0.25 una probabilidad de ser seleccionado. Sin embargo, pi es proporcional a la
0.125 diferencia entre su aptitud y la del resto de soluciones. Se puede entender como
una ruleta en la que cada sector representa la probabilidad de cada individuo
para ser seleccionado. Cuanto mayor sea la probabilidad, mayor ser el sector
0.5 para ese individuo. Cada vez que se gira la ruleta y se detiene en alguno de
los sectores se realiza la seleccin de uno de los miembros. A medida que se
obtengan nuevas generaciones, las soluciones deberan ir convergiendo hacia
un punto en comn de tal forma que la diferencia de aptitud entre los individuos
debe ser menor y la probabilidad de ser seleccionados muy similar.
Figura 28.30: Seleccin gentica
mediante el mtodo de ruleta. Cada Seleccin escalada: este mtodo soluciona el problema planteado en el punto
sector representar la probabilidad anterior, en el que los miembros de una poblacin poseen valores de aptitud
de un individuo de ser seleccionado similares. Esto suele suceder en generaciones posteriores y no es conveniente
hasta entonces utilizarlo. Con este tipo de seleccin se permite que la funcin
de aptitud sea ms discriminatoria.
Seleccin por torneo: En este mtodo se eligen subgrupos de la poblacin y se
enfrentan unos con otros. El vencedor de la competicin es seleccionado para
el proceso de reproduccin.
Seleccin por rango: En este mtodo se realiza un ranking de individuos en
base a un rango numrico asignado a cada uno de ellos en funcin de la aptitud.
La ventaja principal de este mtodo es que evita que individuos con un grado de
aptitud demasiado elevado ganen dominancia sobre los que tienen un valor ms
bajo. Esto es crtico sobre todo al principio, ya que soluciones que podran ser
descartadas en las primeras generaciones podran evolucionar progresivamente
hasta soluciones ptimas globales.
Seleccin generacional: Solo pasa a la siguiente generacin la descendencia de
la generacin anterior. En ningn momento se copian individuos.
Seleccin por estado estacionario: En las primeras generaciones se realiza una
seleccin menos compleja, poco rigurosa y menos discriminatoria, mientras que
en las sucesivas ocurre justo lo contrario. De esta forma se reduce el tiempo total
de clculo en la bsqueda de soluciones.
C28
[850] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Mutacin
La mutacin consiste en alterar de forma aleatoria una o varias partes de la
solucin. En el caso de la naturaleza las mutaciones pueden llegar a ser letales, sin
embargo, contribuyen a la diversidad gentica de la especie. No es conveniente utilizar
con una frecuencia alta este operador para no reducir el AG a una bsqueda aleatoria;
tan slo es aconsejable cuando el algoritmo est estancado. Una buena frecuencia de
mutacin es 1/100 o 1/1000.
Para cada uno de los genes que constituyen una solucin se genera un nmero Original 1 0 1 1 0 0 1

aleatorio, entre 0 y 1; si ese nmero es inferior a 0.01 o 0.001 se realiza el intercambio


del valor. Mutado 1 0 1 1 1 0 1

Cruce (Crossover)
Este es el operador principal en un AG y el que se utiliza con mayor frecuencia. Figura 28.31: Ejemplo de muta-
Consiste en el intercambio gentico entre dos cromosomas; para ello se escogen dos cin en una cadena binaria
de los miembros elegidos en el proceso de seleccin y se intercambian algunas de las
partes de la cadena formada por los genes. Existen diferentes tipos de operadores de
cruce:

Cruce de 1 punto: Se divide la cadena de genes en un punto en concreto y


se intercambian entre los dos miembros. En la gura 28.32 se puede ver un
ejemplo.
Cruce de n-puntos: Igual que el caso anterior, pero las cadenas se dividen n
veces en puntos diferentes (ver gura 28.33).
Cruce uniforme: Se genera un patrn y se realiza el intercambio de acuerdo
a ese patrn. Por ejemplo, en el caso de la imagen 28.34 se realizan varios
cortes en diferentes puntos de la cadena y slo se intercambian aquellas que se
encuentran en posiciones impares. En el caso de emplear cadenas binarias como
mtodo de representacin, se puede generar un patrn fcilmente con ceros y
unos obtenidos de forma aleatoria. Aquellos lugares en los que se encuentre un
uno, representan los fragmentos de la cadena que deben ser intercambiados.
Cruce especializado: El problema de los mtodos de cruce anteriores es que
pueden generar casos que representan soluciones no vlidas. En este tipo de
cruce se evala si un cambio genera una solucin no vlida, y en tal caso, no
realiza el intercambio.

Adems de elegir los operadores genticos que se van a emplear hay que
determinar tambin su frecuencia de uso. Dependiendo de la fase o etapa de la
bsqueda en la que se encuentre un AG, es conveniente emplear unos tipos u otros.
En un principio, los mtodos ms ecaces son la mutacin y el cruce; posteriormente,
cuando la poblacin o gran parte de sta converge el cruce no es demasiado til ya
que nos vamos a encontrar con individuos bastantes similares. Por otro lado, si se
produce un estancamiento, y no se consiguen soluciones en las nuevas generaciones
que mejoren el valor de aptitud, la mutacin tampoco es aconsejable ya que se reduce
el AG a una bsqueda aleatoria. En tal caso, es aconsejable utilizar otro tipo de
operadores ms especializados.

Procesamiento paralelo
Se exploran varias soluciones de
forma simultnea
28.2. Tcnicas Fundamentales [851]

Padre

Madre

Descendientes

Figura 28.32: Cruce de un punto entre dos cromosomas.

Padre

Madre

Descendientes

Figura 28.33: Cruce de n puntos entre dos cromosomas. En este caso n = 2.

Padre

Madre

Descendientes

Figura 28.34: Cruce uniforme de dos cromosomas. En este caso n = 2.

Ventajas de los algoritmos genticos


C28

Una de las grandes ventajas de los AG es que estos tienen descendencia mltiple
y pueden explorar el espacio de soluciones de forma paralela. A diferencia de los
algoritmos que buscan una solucin siguiendo un nico camino, los AG no se ven
realmente penalizados en el caso de que uno de los caminos no llegue a una solucin
satisfactoria. Gracias a esta ventaja, los AG tienen un gran potencial para resolver
problemas cuyo espacio de soluciones es grande o para resolver problemas no lineales.
[852] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Otra ventaja importante es que los AG son apropiados para manejar mltiples
parmetros simultneamente. Es posible, que el AG no encuentre una solucin ptima
global y pueden existir varias soluciones al problema en cuestin y que algunas de
ellas optimicen algunos de los parmetros de entrada y otras soluciones, optimicen
otros distintos.
Adems, los AG no tienen porque conocer nada sobre el problema a resolver. Mltiples parmetros
De hecho, el espacio inicial de soluciones puede ser generado aleatoriamente en su
Manejo simultneo de mltiples pa-
totalidad, y que estos individuos converjan hacia soluciones vlidas para el problema. rmetros de entrada, hasta encon-
Esta caracterstica es muy importante y hace que los AG sean realmente tiles para trar la conguracin adecuada
resolver problemas en el que se posee un gran desconocimiento sobre las posibles
soluciones y no sera posible denir una de ellas de forma analtica.
Desconocimiento
Limitaciones de los algoritmos genticos Permite llegar a las soluciones de
forma no analtica en caso de que
exista desconocimiento sobre como
Una de las posibles limitaciones de los AG reside en el modo de representacin resolver el problema
de las cadenas de genes. Como se coment anteriormente, el mtodo utilizado debe
tolerar cambios aleatorios que no produzcan una gran cantidad de soluciones o
cadenas que supongan resultados sin sentido. Normalmente, los tres mtodos de
representacin expuestos con anterioridad (representacin mediante cadenas binarias,
cadenas formadas por nmeros enteros o reales, cadenas de letras) ofrecen la
posibilidad de seleccin, mutacin y cruce.
Otro problema con el que nos encontramos en los AG es la denicin de una
funcin de aptitud apropiada. Si la denicin no es correcta puede que el AG sea
incapaz de encontrar las soluciones al problema. Adems, de elegir una funcin de
aptitud apropiada, es necesario tambin congurar el resto de parmetros del AG
correctamente, como por ejemplo, tamao inicial de la poblacin, frecuencia de cruce,
frecuencia de mutacin, etc.
Si el conjunto inicial es demasiado pequeo, es posible que el AG no explore el Representacin
espacio de soluciones correctamente; por otro lado, si el ritmo de cambio gentico es
El modo de representacin elegido
demasiado alto o los procesos de seleccin no hacen su trabajo correctamente, puede debe facilitar la aplicacin de los
que no se alcancen las soluciones apropiadas. operadores genticos
Cuando un individuo destaca los suciente en un principio con respecto al resto
Funcin de aptitud
de elementos de la poblacin, se puede producir una convergencia prematura. El
problema es que este individuo se puede reproducir tan abundantemente que merma Es fundamental disear una funcin
la diversidad de la poblacin. De esta forma el AG converge hacia un ptimo local de aptitud adecuada para encontrar
demasiado pronto y se limita la posibilidad de encontrar ptimos globales. Esto las soluciones deseadas
suele suceder sobre todo en poblaciones pequeas. Para solucionar este problema, los
mtodos de seleccin empleados deben reducir o limitar la ventaja de estos individuos;
los mtodos de seleccin por rango, escalada y torneo son aptos para solucionar este
problema.
Por ltimo, cabe destacar que no es muy aconsejable el uso de AG para problemas Mximos locales
que se pueden resolver de forma analtica, sobre todo por el alto coste computacional Se producen cuando ocurre una
que implican. El uso de AG es apropiado en problemas no lineales, donde el espacio convergencia prematura por la do-
de soluciones es grande y no se tiene una idea clara sobre la/las posibles soluciones. Si minancia de uno de los individuos
esa solucin adems depende de la conguracin de mltiples parmetros que deben
ser ajustados, los AG son idneos para encontrarla.
28.2. Tcnicas Fundamentales [853]

Ejemplo de aplicacin de un algoritmo gentico: f (x) = x2

En esta seccin se describir un ejemplo sencillo en el que se aplica un AG


para maximizar la funcin f (x) = x2 y donde x es una variable que toma valores
numricos en el intervalo [0,31] [87]. Lgicamente, este problema se puede resolver
de forma analtica y no es necesario el uso de un AG. Sin embargo, el ejemplo ayudar
a comprender con facilidad la forma en la que se aplica un AG.
2
f(x) = x
Dado que x puede tomar valores entre 0 y 31, elegimos un modo de representacin
de cadenas binarias de 5 bits, donde 00000 representa el nmero 0 y 11111 el 31. En la
tabla 28.1 se muestra la poblacin inicial formada por 6 posibles soluciones (escogidas
de forma aleatoria): 21, 8, 18, 19, 30 y 9. En la segunda columna se representan los
Figura 28.35: Optimizacin de la cromosomas equivalentes a la representacin binaria de estos nmeros. Finalmente,
funcin f (x) = x2 en el interva- en la cuarta columna se muestra el valor de aptitud de cada una de las soluciones,
lo [0,31] mediante el uso de algorit-
mos genticos
siendo el nmero 30 la que tiene un valor mayor de aptitud.

ndice Cromosoma Entero x Aptitud f (x)


1 10101 21 441
2 00100 8 64
3 10010 18 18
4 10011 19 361
5 11110 30 900
6 01001 9 81
Promedio 361
Mximo 900

Cuadro 28.1: Poblacin inicial de individuos

Una vez que el conjunto de soluciones inicial est disponible, debe comenzar
el proceso de seleccin y generar los descendientes. En este ejemplo, se ha elegido
un modo de seleccin por torneo, en el que los cromosomas se enfrentan por pares.
La forma de organizar los enfrentamientos es aleatoria y, al menos, cada cromosoma
debe participar en alguno de los torneos. En este caso, al tener 6 cromosomas, sern
necesarios 6/2 = 3 enfrentamientos. En la tabla 28.2 se puede apreciar como en una
primera ronda, de forma aleatoria, se han enfrentado los pares de cromosomas (2,5) ,
(1,6) y (3,4). Para mantener una poblacin de seis individuos, se ha vuelto a realizar
un segundo torneo (las 3-6) en el que se han enfrentado los pares: (1,5) , (2,3) y (4,6).
Despus de la seleccin se aprecia un incremento notable de la aptitud media en los
miembros de la generacin, aunque se mantiene el valor mximo.

ndice Torneos Cromosoma Entero x Aptitud f (x)


1 2,5 11110 30 900
2 1,6 10101 21 441
3 3,4 10011 19 361
4 1,5 11110 30 900
5 2,3 10010 18 324
6 4,6 10011 19 361
Promedio 547
Mximo 900
C28

Cuadro 28.2: Seleccin por torneo. Enfrentamiento entre los cromosomas del conjunto inicial
[854] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Realizada la seleccin se pueden aplicar operadores genticos de cruce y muta-


cin. En la tabla 28.3 se aplican operaciones de cruce en un slo punto entre los cro-
mosomas 1-2, 3-4 y 5-6. Tal como se puede apreciar los cruces no se realizan siempre
en el mismo punto, en el primer caso se realiza en el cuarto punto, en el segundo pun-
to para los cromosomas 3-4 y en el tercer punto para el par de cromosomas 5-6. Por
otro lado, la mutacin solo se produce en el ltimo bit del cromosoma 4 y como se
puede observar, en este caso, la mutacin es daina ya que disminuye la aptitud de
la solucin. En este ltimo grupo la aptitud promedio disminuye pero nalmente se
encuentra el mximo global y, por tanto, no es necesario continuar.

Caso de estudio: juego de las 8 reinas

El problema de las 8 reinas consiste en ubicar las reinas en un tablero de 8x8 de


tal forma que no se ataquen entre s. Dos reinas no se atacan si no se encuentran en la
misma diagonal. Un AG es apropiado para este problema y puede ayudar fcilmente
a determinar las diferentes combinaciones existentes en la ubicacin de cada reina en
el tablero. Para un tablero de 8x8 y 8 reinas existen 12 patrones o soluciones correctas
posibles. En la tabla 28.4 se muestra uno de ellos.

ndice Cromo. Punto Cruce Muta. Entero x Aptitud f


1 1111|0 4 11111 11111 31 961
2 1010|1 4 10100 10100 20 400
3 10|011 2 10110 10110 22 484
4 11|110 2 11011 11010 26 676
5 100|10 3 10011 10011 19 361
6 100|11 3 10010 10100 18 324
Promedio 534
Mximo 961

Cuadro 28.3: Operadores de cruce y mutacin

Q
Q
Q
Q
Q
Q
Q
Q

Cuadro 28.4: Una de las soluciones al problema de las 8 reinas

Para representar las soluciones se pueden emplear cadenas de valores enteros


con nmeros pertenecientes al intervalo [1,8]. Cada solucin o cromosoma ser una
tupla de 8 valores (q1 , q2 , ..., q8 ) en donde la posicin que ocupa cada qi en la tupla
representa la columna y el nmero indicado en la tupla la la. De esta forma, la
solucin que se muestra en la tabla 28.4 se representara de la siguiente forma:
(5, 3, 1, 6, 8, 2, 4, 7)
28.2. Tcnicas Fundamentales [855]

La funcin de adaptacin es la que nos debe indicar la bondad de una solucin.


En este caso una solucin perfecta es aquella en la que no existe conictos entre las
reinas. En dicha funcin se podran contabilizar los conictos, es decir, cuantas reinas
se encuentran en la misma diagonal. Por tanto, cuando menor sea el valor devuelto por
esta funcin, mejor ser la solucin.
El procedimiento que se puede seguir para resolver el problema es el siguiente:

Generacin de una poblacin inicial


Evaluar la poblacin. Obtener el valor de adaptacin para cada una de las
soluciones de la poblacin.
Generar una poblacin a partir de la anterior. Para ello se pueden emplear
varios operadores genticos, por ejemplo, el elitista en el que copiamos a la
siguiente generacin algunas de las mejores soluciones de la generacin actual.
Figura 28.36: En este caso la fun- Seleccin por ruleta y torneo, y combinar las soluciones elegidas para generar
cin de adaptacin devolvera el va- descendencia mediante cruce y mutacin.
lor 1 debido al conicto entre la rei-
na situada en la esquina superior iz- Volver al paso 2 hasta llegar a un nmero mximo de generaciones o hasta
quierda, y la reina situada en la es- alcanzar el objetivo (cero conictos entre las reinas).
quina inferior derecha. La solucin
no sera ptima
28.2.3. Redes neuronales
Ventajas
Dos de las grandes ventajas que Las redes neuronales son un paradigma de aprendizaje y procesamiento automti-
proporcionan las redes neuronales co. Se basa en el modo de procesamiento del cerebro humano en el que existe multitud
son: procesamiento en paralelo y de neuronas interconectadas entre s y colaboran para producir estmulos de salida. La
conocimiento distribuido neurona esta formada por el cuerpo o soma, en donde se encuentra el ncleo. Del
cuerpo de la clula surgen diversas ramicaciones conocidas como dendritas y sale
Salida
una bra an ms larga denominada axn. La conexin entre neuronas se realiza a
travs del axn y las dendritas; a esta unin se le conoce como sinapsis. Las neuronas
+1 emiten seales y stas se propagan a lo largo de la red gracias a reacciones electroqu-
micas. Las sinapsis liberan sustancias qumicas transmisoras y entran en las dendritas,
de tal forma que se eleva o se reduce el potencial elctrico de la clula. Cada clula
t Entrada tiene un nivel o valor umbral de activacin; cuando este se rebasa se enva al axn un
impulso elctrico que llega al resto de neuronas. Las sinapsis que aumentan el poten-
Figura 28.38: Funcin escaln cial son conocidas como excitadoras mientras que las que producen el efecto inverso
son conocidas como inhibidoras.
Salida Una red neuronal articial est constituida por una serie de nodos conectados en-
+1 tre s. A cada una de las conexiones se le asigna un peso numrico wi que determina
la inuencia de dicha conexin en la propia neurona. Las redes neuronales articiales
representan mecanismos potentes para el procesamiento paralelo y distribuido; parale-
-1
lo porque en la red existen varias neuronas al mismo tiempo procesando informacin
Entrada y distribuido porque el conocimiento est distribuido a lo largo de toda la red y no
focalizado en un punto concreto. En la fura 28.41 se muestra el esquema general de
Figura 28.39: Funcin signo una unidad mnima de procesamiento en una red neuronal.
Tal como se puede apreciar, la neurona recibe una o varias entradas. En cada una de
Salida las conexiones existe asociado un peso wi que determina el grado de inuencia de la
+1
entrada conectada a la neurona. En la propia neurona, existe una funcin base h que se
encarga de combinar los valores de entrada teniendo en cuenta los pesos. Normalmente
la funcin base es una suma ponderada j wj entradaj . Una vez combinados los
valores de entrada se enva el valor calculado a la funcin de activacin g. Dicha
C28

Entrada funcin determinar el valor de salida de la neurona. Existen multitud de funciones


de activacin, las ms frecuentes son: funcin escaln (gura 28.38), funcin signo
Figura 28.40: Funcin sigmoidea (gura 28.39) y funcin sigmoidea (gura 28.40).
[856] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Sinapsis

Axn de otra clula

Dendrita Axn

Ncleo

Figura 28.37: Partes de una clula nerviosa o neurona.

Entrada 1 w1

Entrada 2
w2
ai
Salida

w3 h g
...
Entrada n

Figura 28.41: Unidad mnima de procesamiento en una red neuronal.

En la funcin de activacin de escaln, se establece un valor umbral t. Si el valor


numrico resultante de la combinacin de las entradas con los pesos es superior a t
entonces se activa la neurona pasando a valer su salida 1. En caso contrario sera cero.

1 si x t
escalon(x) = 0 si x < t

En la funcin signo, el limite o valor umbral se encuentra en el punto cero. Si el


valor procedente de la funcin base h es inferior a cero, el valor de salida es -1, en
caso contrario la salida es 1:

1 si x 0
signo(x) = -1 si x < 0

Por ltimo, la funcin sigmoidea se emplea cuando se desea una activacin ms


suavizada y no una funcin escalonada como en los casos anteriores. El valor de salida
viene dado por la siguiente expresin:
sigmoide(x) = 1
1+ex
28.2. Tcnicas Fundamentales [857]

Las unidades neuronales tambin pueden funcionar como puertas lgicas [79], tal
como se muestra en la gura 28.42. De esta forma, es posible construir redes que
calculen cualquier funcin booleana de las entradas.

W=1 W=1
W=-1
t = 1.5 t = 0.5 t = -0.5

W=1 W=1
Puerta AND Puerta OR Puerta NOT

Figura 28.42: Unidades con funcin escaln que actan como puertas lgicas.

Por ltimo, cabe mencionar una de las principales caractersticas de las redes
neuronales: su alto nivel de adptabilidad. Mediante procesos de aprendizaje es posible
ajustar los pesos de las conexiones y adaptarse correctamente a cualquier situacin.
Dicho aprendizaje puede ser supervisado o automtico. En el caso del aprendizaje
supervisado se conocen las entradas de la red y como deberan ser las salidas para cada
una de esas entradas. El ajuste de los pesos se realiza a partir del estudio de la salida
real obtenida a partir de las entradas y de la salida esperada, intentando minimizar el
error en la medida de los posible. Por otro lado, el aprendizaje automtico dispone
nicamente de los valores de entrada. Este tipo de algoritmos trata de ajustar los pesos
hasta encontrar una estructura, conguracin o patrn en los los datos de entrada.

Aplicaciones de las redes neuronales

Las redes neuronales se emplean principalmente para el aprendizaje de patrones


y clasicacin; en algunas ocasiones pueden ser una buena alternativa a sistemas
complejos de reglas y mquinas de estados. A continuacin se detalla una lista de
aplicaciones generales y algunas de las aplicaciones en el desarrollo de videojuegos:

1. Aplicaciones generales
Reconocimiento facial
Anlisis de imagen
Modelos nancieros
Perles de mercado-cliente
Aplicaciones mdicas (sntomas y diagnstico de enfermedades)
Optimizacin de procesos industriales
Control de calidad
Deteccin de SPAM en correo electrnico
Reconocimiento de voz
Reconocimiento de smbolos en escritura
C28

2. Aplicaciones en el desarrollo de videojuegos


Bsqueda de caminos
[858] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Clasicacin de personajes
Sistemas de control y bsqueda de equilibrio: minimizacin de prdidas o
maximizacin de ganancias
Toma de decisiones
Elaboracin de estrategias
Simulaciones fsicas

Estructuras de red

Existen multitud de tipos de estructuras de red y cada una de ellas otorga diferentes
caractersticas de cmputo [79]. Todos ellos se pueden clasicar en dos grandes
grupos: redes de alimentacin progresiva y redes recurrentes. En el primer caso,
las conexiones son unidireccionales y no se forman ciclos. Las neuronas de un nivel
o capa slo pueden estar conectadas con las del nivel siguiente. En la gura 28.43 se
muestra un ejemplo de una red de alimentacin progresiva con dos niveles; dicha red
consta de dos entradas, dos neuronas en la capa oculta y una nica salida. Al no existir
ciclos en las conexiones, el clculo se realiza de forma uniforme desde las unidades
de entrada hasta las unidades de salida.

W13
Entrada 1
W14 H3 W35 Salida

W23 O5
H4 W45
Entrada 2 W24

Figura 28.43: Red de alimentacin progresiva de dos niveles: dos entradas, dos neuronas en la capa oculta
y un nodo de salida

El cerebro no se puede considerar una red de alimentacin progresiva porque si

Precisin de la red
no, no tendra memoria a corto plazo; una red de recurrente, a diferencia de las de
N de Neuronas

alimentacin progresiva, permite mantener un estado interno. El problema de este tipo


de redes es que el clculo es ms complejo, se pueden volver inestables y llegar a una
conducta catica.

Figura 28.44: Relacin entre n-


Diseo de una red neuronal mero de neuronas y precisin de la
red. La precisin suele aumentar a
A la hora de disear una red neuronal hay que resolver una serie de cuestiones medida que se incrementa el nme-
importantes: ro de neuronas.

Nmero de entradas
N de Neuronas

Generalidad

Nmero de salidas
Nmero de niveles
Cantidad de neuronas en cada una de las capas o niveles intermedios. Figura 28.45: Relacin entre n-
mero de neuronas y generalidad de
Cantidad de ejemplos de entrada para entrenar la red la red. Cuanto menor es el nmero
de neuronas ms fcil es conseguir
la generalidad.
28.2. Tcnicas Fundamentales [859]

A las dos primeras cuestiones es fcil encontrar una respuesta. Para la mayora
de problemas conocemos el nmero de entradas y las salidas deseadas. En el caso de
encontrar ciertas dicultades a la hora de elegir el nmero de parmetros de entrada
existe una relacin directa con los patrones que pretendemos aprender en la red.
Parmetros = log2 P
donde P es el nmero de patrones. Por otro lado, si queremos calcular un nmero de
patrones estimado, se puede emplear la siguiente frmula:
P = W 1/
donde W es el nmero de pesos en la capa intermedia y el error mnimo deseado en
la red, dicho error se podra jar en el siguiente intervalo: 0 1/8
Sin embargo, establecer una conguracin adecuada en cuanto al nmero de
niveles y neuronas es ms complicado. Normalmente, la conguracin se establece
mediante un proceso de aprendizaje supervisado por prueba y error. En dicho proceso
se conocen las entradas y las salidas que se deberan obtener ante tales entradas.
Los errores producidos en la salida producen un ajuste de pesos para el correcto
funcionamiento de la red. Si despus del proceso de entrenamiento, no se obtiene
el comportamiento deseado, es necesario modicar la conguracin de la red.
Al disear una red neuronal siempre hay que tratar de encontrar un equilibrio
entre precisin y generalizacin. Precisin se reere a obtener el menor error posible
en la salida ante los casos de entrada y generalizacin a la posibilidad de abarcar el
mayor nmero de casos posibles. Cuando el nmero de neuronas en la capa oculta es
alto, normalmente se consigue una mayor precisin a costa de un incremento de la
complejidad (red ms compleja implica mayor tiempo de entrenamiento).
Bsqueda de Equilibrio Por el contrario, cuando el nmero de neuronas es ms bajo, obtenemos una red
ms simple que nos permite alcanzar una mayor generalizacin. El uso de funciones de
En el diseo de una red neuronal activacin sigmoidales tambin fomenta la generalizacin de la red. Adems se debe
hay que buscar siempre un equi-
librio entre precisin y generaliza- tener especial cuidado a la hora de elegir el nmero de parmetros de entrada; cuanto
cin mayor sea la cantidad de parmetros, mayores distinciones podremos hacer entre
los individuos de una poblacin, pero ms complejo ser alcanzar esa generalidad
deseada.
Evidentemente, cuanto ms complejo sea el problema a tratar, mayor ser tambin
el nmero de neuronas necesarias en la capa oculta. En casi la totalidad de los casos,
la capa oculta necesita un menor nmero de neuronas que la capa de entrada. Segn
el teorema de Kolgomorov [58] "El nmero de neuronas en la capa oculta debe ser
como mximo dos veces el nmero de entradas".
Para la conguracin del nmero de neuronas en la capa oculta se pueden utilizar
algoritmos constructivos. Bsicamente, un algoritmo de este tipo parte de una
conguracin inicial en el que el nmero de neuronas es reducido. Se entrena la red
hasta que el error se mantenga constante. Posteriormente, si no se ha alcanzado el
error mnimo deseado, se puede agregar una neurona ms a la capa intermedia con
un peso pequeo y se repite el proceso anterior. Es importante tener en cuenta, que la
agregacin de neuronas no va a solucionar el problema si: i) el conjunto de datos de
entrada es insuciente, ii) hay datos que no se pueden aprender.
Para el clculo del error de salida en funcin de las entradas se puede utilizar el
error cuadrtico medio:
etotal = e21 + e22 + ... + e2n
donde ei es el error obtenido en la salida para el ejemplo o patrn de entrada i. Para
calcular el error cometido en uno de los patrones:
C28

1/n j=0 (tj aj ) 2


[860] CAPTULO 28. INTELIGENCIA ARTIFICIAL

donde n es el nmero de neuronas, t el valor esperado en la salida y aj el valor de


salida real.
Finalmente, el proceso de entrenamiento y conguracin de una red neuronal se
debe detener cuando:

1. El error promedio alcanza el error mnimo deseado


2. El error no diminuye o lo hace de forma insignicante
3. El error comienza a incrementarse de forma gradual y constante.

Ajuste de los pesos de una red mediante aprendizaje Hebbiano

En esta seccin se explica como se pueden ajustar los pesos de una red mediante
la regla de aprendizaje de Hebb. Para ello supongamos una red sencilla constituida por
dos entradas y una capa de salida formada por una nica neurona. El valor umbral de
activacin de la neurona se va a ajustar de forma dinmica y para ello se considerar
como una entrada ms con peso negativo. El esquema de dicha red se muestra en la
gura 28.46

-W0
X0 1
W1
X1 Salida
O
X2 W2

Figura 28.46: Esquema de la red empleada en el ejemplo de aprendizaje Hebbiano

La condicin de activacin de la neurona de salida vendr dada por la siguiente


expresin: X0 W0 + X1 W1 + X2 W2 > 0. Ahora supongamos que deseamos modelar
una red neuronal que represente una caja negra que simule el mismo comportamiento
que una puerta lgica AND:
Casos X1 X2 Salida
caso 1 0 0 0
caso 2 0 1 0
caso 3 1 0 0
caso 4 1 1 1

Cuadro 28.5: Comportamiento deseado en la red neuronal

En el ao 1949, Donald Hebb observ que la sinapsis se reforzaba si la neurona


de entrada y la de salida eran activadas de manera continua. As, las conexiones en las
que se produce este fenmeno son las que se refuerzan. Si trasladamos esta idea a una
red neuronal articial, las conexiones en las que la entrada y la salida se activan vern
reforzados sus pesos, mientras que en el caso contrario se quedan tal cual. Por tanto,
para entrenar la red se seguirn las siguientes reglas:
28.2. Tcnicas Fundamentales [861]

1. Si la salida es la esperada en funcin de las entradas, no se realizan ajustes en


los pesos.
2. Si la salida es 1, pero debera ser 0, se reducen los pesos de las conexiones
activas de acuerdo a un valor constante.
3. Si la salida es 0, pero debera ser 1, se aumentan los pesos de las conexiones
activas de acuerdo a la misma constante.

Para entrenar la red se toman de forma aleatoria los casos expuestos anteriormente
y se introducen en las entradas. Supongamos que inicialmente todos los pesos valen
cero (tal como se coment anteriormente, el valor inicial de los pesos debe ser bajo).
Supongamos tambin, que el valor de incremento/decremento empleado para los pesos
es 1.
Si el caso 1: X1 = 0, X2 = 0 fuera el primero en utilizarse para entrenar la red,
X0 W0 + X1 W1 + X2 W2 = 0, debido a que todos los pesos valen cero. Por tanto,
la neurona de salida no se activa. En realidad es el valor esperado y, por tanto, no se
producen modicacin de los pesos. En realidad, el nico ejemplo o caso que puede
producir un error con todos los pesos a cero, es el caso 4.
Supongamos que el caso 4 es el siguiente ejemplo empleado para entrenar la red.
X1 = 1, X2 = 1 y la salida esperada es 1; sin embargo X0 W0 + X1 W1 + X2 W2 = 0.
Se esperaba una activacin de la salida que no se ha producido y, as, es necesario
modicar los pesos incrementando los pesos de las neuronas activadas (en este caso
todas las entradas):

W0 = W0 + C = 0 + 1 = 1
W1 = W1 + C = 0 + 1 = 1
W2 = W2 + C = 0 + 1 = 1

De forma iterativa se van aplicando los casos de entrada hasta que no se producen
errores en la salida. Finalmente, para este ejemplo, el ajuste apropiado de los pesos es
el siguiente: W0 = 2, W1 = 1, W2 = 2.

Aprendizaje supervisado mediante retropropagacin (backpropagation)

Cuando tenemos ms de un nivel en la red (ver gura 28.47), el mtodo anterior


resulta insuciente y es necesario utilizar otros como el de retropropagacin. Una de
las condiciones indispensables para poder aplicar este mtodo, es que las funciones de
activacin empleadas en las neuronas deben ser derivables. Bsicamente se calcula el
error producido en la salida y lo propaga hacia las capas intermedias para ajustar los
pesos. El algoritmo consta de los siguientes pasos:

1. Denir una conguracin inicial de la red.


2. Inicializar los pesos con valores aleatorios pequeos
3. REPETIR
Elegir un patrn y ubicarlo en la entrada de la red
C28

Simular el funcionamiento de la red y observar la activacin de neuronas


en la capa oculta y la salida
Calcular el error producido en la salida
[862] CAPTULO 28. INTELIGENCIA ARTIFICIAL

X0
H1
X1 O1

X2 H2

X3 O2
H3
X4
Figura 28.47: Esquema de un perceptrn o red neuronal multicapa

Calcular las derivadas parciales del error con respecto a los pesos de salida
(unin de la capa oculta y la de salida)
Calcular las derivadas parciales del error con respecto a los pesos iniciales
(unin de las entradas con la capa oculta)
Ajustar los pesos de cada neurona para reducir el error.
4. FIN REPETIR
5. Continuar hasta minimizar el error.

En el caso de tener un solo nivel, la actualizacin de los pesos en funcin del error
es sencilla:
Error = valor esperado (T ) - valor real obtenido (O)
Si el error es positivo, el valor esperado es mayor que el real obtenido y por tanto ser
necesario aumentar O para reducir el error. En cambio, si el error es negativo habr
que realizar la accin contraria: reducir O.
Por otro lado, cada valor de entrada contribuir en la entrada de la neurona segn
el peso: Wj Ij (peso W y valor de entrada I). De esta forma, si Ij es positivo, un
aumento del peso contribuir a aumentar la salida y, si por el contrario, la entrada Ij
es negativa, un aumento del peso asociado reducir el valor de salida O. El efecto
deseado se resume en la siguiente regla:

Wj = Wj + Ij Error

donde Error = T O y es la constante de velocidad de aprendizaje (velocidad de


convergencia). Normalmente esta constante oscila entre 0.05 y 0.25. El valor de se
debe disminuir a medida que se reduce el error.
28.2. Tcnicas Fundamentales [863]

En el caso de tener varios niveles, la actualizacin de pesos es un poco ms


compleja (ver gura 28.48). Los pesos se actualizan a partir del error cometido, dicho
error se propaga desde las primeras capas hasta las nales. La idea general es evaluar
las consecuencias del error y dividirlas entre todos aquellos que hayan contribuido
a dicho error. En primer lugar se actualizan los pesos de las conexiones entre las
neuronas de la capa oculta y la de salida. Las salidas aj de las neuronas de la capa
oculta supondrn la entrada de las neuronas de salida.

Salida Oi
Wj,i
dad
aj
cultas

Entrada

Figura 28.48: Red de alimentacin progresiva de dos niveles

Wj,i = Wj,i + aj i

en donde i es igual a Errori g (entradai ), y g es la derivada de la funcin de


activacin g.
Una vez actualizadas las conexiones entre la capa oculta y la de salida, queda por
actualizar las conexiones entre las entradas y la capa oculta. Para la actualizacin se
necesita un valor de error y es aqu donde tiene lugar la propagacin posterior. La idea
es que el nodo oculto j es responsable de una parte del error producido en i en cada
uno de los nodos de salida con los que conecta. Es decir, todos los nodos de la capa
oculta tienen parte de culpa en el error producido en las salidas; de esta forma, los
valores de i son divididos de acuerdo a la intensidad de conexin entre los nodos
ocultos y las salida, y se propaga hacia atrs para calcular el valor j del nodo oculto.
j se calcula de la siguiente forma:


j = g (entradaj ) i Wj,i i
C28

La regla de actualizacin de pesos quedara denida de la siguiente manera:


[864] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Wk,j = Wk,j + Ik j

Ejercicio propuesto: Dada una red neuronal con una nica neurona oculta,
con patrones de entrada P1 (1 0 1), P2 (1 1 0), P3 (1 0 0), llevar acabo un
proceso de entrenamiento mediante backpropagation para ajustar los pesos
de la red y que sta se comporte como la funcin XOR

Aplicacin de redes neuronales al juego de los asteroides

Las redes neuronales se han aplicado con frecuencia en el juego de los asteroides,
en el que una nave debe evitar el impacto con asteroides que se mueven de forma
aleatoria es un espacio comn. La red neuronal permite modelar el comportamiento
de la nave para evitar el impacto con los asteroides ms cercanos. En [83] se plantea
una conguracin de red neuronal para solucionar el problema. Dicha conguracin
consta de cuatro entradas, una capa oculta con 8 neuronas y una capa de salida de tres
neuronas, donde cada una representa una posible accin.
Las dos primeras entradas corresponden con la componente x e y del vector entre
la nave y el asteroide ms cercano. La tercera entrada corresponde a la velocidad con
la que la nave y el asteroide ms cercano se aproximan. Finalmente, la cuarta entrada
representa la direccin que est siguiendo la nave.
La salida de la red neuronal son tres posibles acciones: i) aceleracin puntual
(turbo), ii) giro a la izquierda, iii) giro a la derecha. En [83] se encuentra publicada
una posible implementacin al problema.

Ejercicio propuesto: Disear una red neuronal que regule el comportamiento


de la nave en el problema planteado, y evite la colisin con los asteroides
Figura 28.49: Juego de asteroides.
. La nave debe evitar el impacto con
los asteroides en movimiento, con
diferentes formas, tamaos y velo-
cidades.

28.3. Algoritmos de bsqueda

28.3.1. Problemas y soluciones.


Como ya se ha visto en la seccin 28.1.4, una de las formas ms habituales de
representar el comportamiento de un agente 28.1.3 es mediante un sistema de estados
y transiciones. Este formalismo permite la descripcin perfecta de cualquier actividad
que tenga que desplegar un determinado agente en todo momento del videojuego.
28.3. Algoritmos de bsqueda [865]

La actividad desarrollada por cada agente, viene dada por la secuencia de


transiciones o acciones realizadas hasta dicho momento. Pongamos por ejemplo un
problema lgico clsico como es siguiente:
Sea una par de islas llamadas IslaA e IslaB. En IslaA hay una oveja, una col, un
lobo, una barca y un barquero. La cuestin a resolver es cmo llevar al lobo, la oveja
y la col a IslaB sin perder a nadie en el intento. Si eres el barquero has de considerar
que: slo puedes trasladar un objeto o animal en cada momento en la barca, que la
oveja no se puede quedar sola con la col, ya que se la comera, y que igual destino
correra la oveja a solas con el lobo.
LOCB# En un primer momento para trabajar el problema propuesto denimos los elemen-
>O <O >B <B
tos que van a congurar un estado, as como las acciones que podramos hacer en cada
LC#BO LOC#B
uno de ellos. La forma de describir una foto instantnea de una situacin es mediante
<B >B
la enumeracin de los elementos que hay en cada isla. Llamemos al lobo L, a la oveja
LCB#O
O, a la col C y al barquero junto con la barca B, y determinemos que IslaA est a la
<C >C
izquierda y IslaB est a la derecha. De esta manera representarmos cualquier situacin
L#BOC
del problema mediante una cadena de caracteres del tipo LOCB# que reeja la situa-
cin inicial donde el lobo, oveja, la col y el barquero estn en IslaA, o por ejemplo
esta otra situacin LC#OB donde el barquero y la oveja se encuentran en IslaB y el
>O <O

lobo y la col en IslaA.


LOB#C

<L >L <L >L

O#BLC Una vez jados los estados de nuestro problema de la isla, tendremos que enumerar
>B <B >C <C las acciones que se pueden desarrollar en un determinado estado. Pongamos por
OB#LC OCB#L ejemplo el estado LOCB#, en esta situacin las acciones que podemos realizar son:
<O >O <O >O
coger la barca e ir de vacio a IslaB, que representamos mediante la secuencia de
caracteres >B, llevar el lobo a IslaB >L, llevar la oveja >O o llevar la col >C.
#BLOC C#BLO Considerando solamente las acciones vlidas, que no provocan la prdida de algn
elemento, tendremos que en el estado LOCB# slo son correctas las acciones: >B y
<B >B
>O.
B#LOC

Para denir este conjunto de acciones establemos la funcin sucesor de un estado


como el conjunto de pares (accin,estado) como los estados que son accesibles
Figura 28.50: Espacio de estados desde el mismo mediante una accinn concreta. Por ejemplo sucesor(LOCB#)
del problema de la isla = ((>B,LOC#B),(>O,LC#BC)). En estos momentos hemos denido el espacio de
estados del problema (28.50).

El espacio de estados de un problema queda denido mediante el conjunto de


estados posibles del problema y la funcin sucesor de cada uno. Este espacio
de estado se puede representar mediante un grafo donde los nodos son los
estados y los arcos son las acciones.

Establecido un espacio de estados, un problema [79] es llegar de un estado inicial


a un estado meta deseado mediante una secuencia de acciones vlidas. En el ejemplo
de la isla ser bscar la secuencia de acciones que llevase del estado inicial LOCB# al
nal #BLOC.

Los componentes de un problema son:


1. Un espacio de estados.
2. Un estado inicial.
C28

3. Una funcin meta, que identique estados nales.


[866] CAPTULO 28. INTELIGENCIA ARTIFICIAL

El chero problema.py contiene, en lenguaje python, una realizacin de estos con-


ceptos mediante la especicacin de una clase abstracta Problema, que representa
cualquier problema en terminos de espacio de estados, y una clase derivada Proble-
maIsla para el problema de la isla.

Listado 28.8: class Problema


1 class Problema:
2 def __init__(self,estadoInit,nombre="NoDef"): LOCB#
3 self.id=nombre
4 self.estInit=estadoInit
5
6 def estadoValido(self,estado): >O
7 pass
8
9 def sucesor(self,estado): LC#BO
10 pass
11
12 def meta(self,estado): <B
13 pass
14
15 def estadoInit(self):
16 return self.estInit LCB#O

En los trminos jados, una solucin a un problema es, precisamente, un camino >C
o secuencia de acciones vlidas que dirige al sistema de un estado inicial a un
estado meta. Al proceso de buscar entre todos los caminos posibles, que parten de
un estado inicial establecido, aquellos que son soluciones se denomina resolucin de L#BOC
problemas mediante tcnicas de bsqueda. Una solucin para el problema de la isla
se puede ver en la gura 28.51.
<O

Listado 28.9: class ProblemaIsla


LOB#C
1 class ProblemaIsla(Problema):
2 """Problema de la Isla.
3 Un estado es un string del tipo LOCB#.
4 L representa LOBO >L
5 O representa OVEJA
6 C representa COL
7 B representa al Barquero y la Barca. O#BLC
8 La izquierda de # es lo que hay en la isla A.
9 La derecha de # es lo que hay en la isla B."""
10
11 def __init__(self,estado): <B
12 Problema.__init__(self,estado,"Isla")
13 self.g={}
14 self.g["LOCB#"]=[(">O","LC#BO"),(">B","LOC#B")] OB#LC
15 self.g["LOC#B"]=[("<B","LOCB#")]
16 self.g["LC#BO"]=[("<O","LOCB#"),("<B","LCB#O")]
17 self.g["LCB#O"]=[("<C","L#BOC"),(">L","C#BLO")] >O
18 self.g["L#BOC"]=[("<C","LCB#O"),("<O","LOB#C")]
19 self.g["LOB#C"]=[(">L","O#BLC"),(">O","L#BOC")]
20 self.g["O#BLC"]=[("<L","LOB#C"),("<B","OB#LC"),("<C","OCB#L
")]
21 self.g["OCB#L"]=[(">C","O#BLC"),(">O","C#BLO")]
22 self.g["OB#LC"]=[(">B","O#BLC"),(">O","#BLOC")] #BLOC
23 self.g["C#BLO"]=[("<L","LCB#O"),("<O","OCB#L")]
24 self.g["#BLOC"]=[("<O","OB#LC"),("<B","B#LOC")]
25 self.g["B#LOC"]=[(">B","#BLOC")]
26
27 def estadoValido(self,estado):
28 return estado in self.g.keys()
29
30 def sucesor(self,estado):
31 if self.estadoValido(estado): Figura 28.51: Una solucin al pro-
32 return self.g[estado] blema de la isla.
28.3. Algoritmos de bsqueda [867]

33 else:
34 return None
35
36 def meta(self,estado):
37 return estado=="#BLOC"

28.3.2. Organizar la bsqueda de soluciones


La solucin a un problema es un camino por el espacio de estados, que permite
ID0* transformar la realidad de una situacin inicial a una situacin deseada o meta. El
proceso de bsqueda deber explorar el conjunto de caminos posibles y centrarse
LOCB# en aquellos que son solucin. Esto marca un primer objetivo que es la obtencin de
forma organizada y sistemtica de todos los posible caminos que pudieran existir en
un espacio de estados.
Una estructura de datos, que permite realizar este propsito es el rbol. Un rbol
puede incorporar la informacin relativa a un estado en sus nodos, as como las
Figura 28.52: rbol inicial. acciones realizadas, y etiquetar los enlaces entre un padre y sus hijos. Un rbol cuyo
nodo raz represente el estado inicial de un problema y la relacin lial entre nodos
hijos y padres venga explicitada por el conjunto de acciones posibles segn el espacio
ID0 de estados se denomina rbol de bsqueda, y cada una de sus ramas representa un
LOCB# camino posible.

>B >O

ID1* ID2* El rbol de bsqueda es una estructura que permite generar y almacenar todos
LOC#B LC#BO los posibles caminos de un espacio de estado.

Figura 28.53: Primera expansin.


Un proceso iterativo de generacin del rbol de bsqueda es el siguiente algoritmo.

ID0 Listado 28.10: Crear rbol de bsqueda


LOCB#
1 while exista un nodo hoja sin explorar:
>B >O
2 tomar n como nodo a explorar de los nodos hoja no explorados
3 if n representa un estado meta:
ID1* ID2
4 solucion es la rama desde la raiz al nodo n
5 salir.
LOC#B LC#BO 6 else:
7 explorar el n
<B <O 8 marcar n como explorado.
ID3* ID4*
LCB#O LOCB# Siguiendo este algoritmo, como se muestra en la gura 28.52, el problema de la
isla comenzara con un rbol con un nico nodo raz y hoja. Observando el espacio de
estados de la gura 28.50 se explora el nodo ID0 obteniendo el rbol de la gura 28.53.
Figura 28.54: Segunda expansin. En la tercera iteracin y seleccionando, por ejemplo, el nodo ID2 tendremos el rbol
de bsqueda que muestra la gura 28.54.
Padre ID ID0 ID1 Antes de continuar desarrollando el esbozo de algoritmo expuesto, deniremos los
Estado Accion Inf LC#BO >O Inf1
elementos que debern constituir un nodo del rbol de bsqueda como se muestra en
None ID0
la gura 28.55. Estos son:
LOCB# None Inf0
C28

1. ID Un identicador nico para el nodo.


Figura 28.55: Nodo del rbol de
bsqueda 2. Padre Una referencia u enlace al nodo padre.
[868] CAPTULO 28. INTELIGENCIA ARTIFICIAL

3. Estado La representacin del estado.


4. Accin La accin realizada para generarse.
5. Inf Informacin diversa referente a la situacin de la bsqueda, valoraciones,
etc.

Una posible realizacin del concepto de nodo del rbol de bsqueda se muestra
en la clase que se lista a continuacin. En esta clase class NodoArbol se crean un
nodo con los atributos que hemos indicado. Tambin se facilitan los mtodos para la
construccin del camino desde el nodo raiz del rbol a un nodo concreto (lnea 19),
y la creacin de un hijo (lnea 28). Se le ha aadido una valoracin general del nodo
val, que se usar posteriormente.

Listado 28.11: Clase para el nodo del rbol de bsqueda


1 class NodoArbol:
2 id=0
3 def __init__(self,padre,acc,estado,inf=None,val=0):
4 self.padre=padre
5 self.accion=acc
6 self.estado=estado
7 self.inf=inf
8 self.id=NodoArbol.id
9 self.val=val
10 NodoArbol.id=NodoArbol.id+1
11
12 def __str__(self):
13 if self.padre==None:
14 idpadre=-1
15 else:
16 idpadre=self.padre.id
17 return "[ %i| %i, %s, %s, %s]" %(idpadre,self.id,self.accion,self
.estado,self.inf)
18
19 def camino(self):
20 """Retorna la lista de nodos hasta el nodo raiz."""
21 p=[]
22 n=self
23 while n<>None:
24 p.insert(0,n)
25 n=n.padre
26 return p
27
28 def GeneraNodoHijo(self,ac,est):
29 n=NodoArbol(self,ac,est,{Prof:n.inf[Prof]+1})
30 n.val=n.inf[Prof]
31 return n

El algoritmo descrito selecciona, del conjunto de nodos hoja del rbol de bsque-
da, un nodo para analizar. Primero verica que no es una solucin y posteriormente lo
analiza. Este anlisis consiste en obtener a partir del problema, con la funcin sucesor,
el conjunto de nuevos nodos hijos e introducirlos en el rbol de bsqueda como nuevas
hojas al mismo tiempo que el nodo estudiado deja de ser hoja.
De lo descrito anteriormente el proceso de seleccin del nodo hoja a expandir es
de una importancia , ya que de una buena o mala seleccin depender que la bsqueda
de un nodo meta sea ms o menos eciente.
28.3. Algoritmos de bsqueda [869]

Al mecanismo seleccionado para la eleccin del nodo hoja que hay que
expandir se denomina estrategia de bsqueda.

Si no se tiene ms informacin, para un problema, que el espacio de estado, las


estrategias de bsqueda estn limitadas a dos:

1. Anchura Las hojas son seleccionadas por antiguedad, es decir, los nodos de
menor profundidad.
2. Profundida Las hojas son seleccionadas por novedad, es decir, los nodos de
mayor profundidad.

Una implentacin de las estructuras necesarias para realizar estas dos estrategias
son mostradas en las siguientes clases: AlmacenNodos, PilaNodos y ColaNodos.

10 7
#BLOC
Listado 28.12: Clases de Cola Pila
1 class AlmacenNodos:
[>O,1]
2 """Modela la frontera."""
9 6 3 def __init__(self,nombre="NoName"):
OB#LC 4 self.id=nombre
5 def add(self,elem):
[<B,1] 6 """Introduce un elemento."""
7 pass
8 5
8 def esVacia(self):
O#BLC
9 """Retorna True si no tiene ningun elemento."""
[>L,1]
10 pass
11 def get(self):
6 4 7 4 12 """Retorna un elemento."""
LOB#C OCB#L 13 pass
14 def esta(self,id):
[<O,1] [<O,1] 15 """Retorna True si el elemento con Id esta en la estructura
."""
4 3 5 3
16 pass
L#BOC C#BLO
17
[<C,1] [>L,1]
18 class PilaNodo(AlmacenNodos):
19 """Modela un Almacen como una Pila: Ultimo en entrar, primero
3 2 en salir."""
LCB#O 20 def __init__(self,nombre):
21 AlmacenNodos.__init__(self,nombre)
[<B,1] 22 self.p=[]
23 self.lest=[]
1 1 2 1
24 def esVacia(self):
LC#BO LOC#B
25 return len(self.p)==0
[>O,1] [>B,1]
26 def add(self,elem):
27 self.p.append(elem)
0 0 28 self.lest.append(elem.estado)
LOCB# 29 def get(self):
30 return self.p.pop()
31 def esta(self,estado):
32 return estado in self.lest
Figura 28.56: rbol de bsqueda 33
completo Anchura. 34 class ColaNodo(AlmacenNodos):
35 """ Modela un Almacen como una Cola: Primero en entrar, primero
en salir."""
36 def __init__(self,nombre):
37 AlmacenNodos.__init__(self,nombre)
C28

38 self.c=[]
39 self.lest=[]
40 def esVacia(self):
41 return len(self.c)==0
[870] CAPTULO 28. INTELIGENCIA ARTIFICIAL

42 def add(self,elem):
43 self.c.insert(0,elem)
44 self.lest.append(elem.estado)
45 def get(self):
46 return self.c.pop()
47 def esta(self,estado):
48 return estado in self.lest

En el siguiente cdigo se muestra el algoritmo de bsqueda, y como dependiendo


del parmetro introducido produce la salida con una estrategia de anchura o de
profundidad. Aplicado al problema de la Isla los resultados obtenidos segn las
estrategias utilizadas son:

Anchura. (None,LOCB#), (>0,LC#BO), (<B,LCB#O), (<C,L#BOC), (<O,LOB#C),


(>L,O#BLC), (<B,OB#LC), (>O,#BLOC). (gura 28.56).
Profundidad. (None,LOCB#), (>O,LC#BO), (<B,LCB#O), (>L,C#BLO), (<O,OCB#L),
(>C,O#BLC), (<B,OB#LC), (>O,#BLOC). (gura 28.57).

10 7
Listado 28.13: Algoritmo de bsqueda #BLOC

1 def BuscaSolucionesSinInf(prob,estrategia): [>O,1]

2 if estrategia==PROFUNDIDAD:
8 6 9 6
3 frontera=PilaNodo(Profundidad) LOB#C OB#LC
4 elif estrategia==ANCHURA:
5 frontera=ColaNodo(Anchura) [<L,1] [<B,1]

6 n=NodoArbol(None,None,prob.estadoInit(),{Prof:0})
7 5
7 solucion=None O#BLC
8 frontera.add(n)
9 while (not frontera.esVacia()) and (solucion==None): [>C,1]
10 n=frontera.get()
11 if prob.meta(n.estado): 6
OCB#L
4

12 solucion=n
13 else: [<O,1]
14 for ac,est in prob.sucesor(n.estado):
15 if not frontera.esta(est): 4 3 5 3
L#BOC C#BLO
16 h=GeneraNodo(n,ac,est)
17 frontera.add(h) [<C,1] [>L,1]
18 return solucion
3 2
LCB#O

Sobre la eciencia de estas estrategias hay que destacar que la estrategia de [<B,1]

bsqueda en anchura es una estrategia completa, esto es que es capaz de encontrar la 1 1 2 1


solucin si existe; pero en el peor de los casos se ha de sarrollar el rbol completo con LC#BO LOC#B

una complejidad temporal (tiempo de ejecucin) del orden O(bn ) para un problema [>O,1] [>B,1]

con b acciones en cada estado y una logitud de n acciones hasta un estado meta. La 0 0
estrategia de profundidad tiene el problema que se puede perder si el rbol de bsqueda LOCB#

es innito, y no podra garantizar encontrar solucin alguna; pero si la encuentra en el


camino seleccionado la necesidad de almacenamiento es la menor posible O(n) que
es la longitud de la solucin. Figura 28.57: rbol de bsqueda
completo Profundidad.
Para evitar los problemas de perderse por las profundidades de los rboles de
bsqueda en estrategias de profundidad, existe la opcin de incorporar un lmite de
profundidad y denir una estrategia profundidad acotada. Si en una determina cota
de la estrategia de profundidad acotada y habiendo expandido el rbol completo hasta
dicha cota no se encontrase ninguna solucin, por que sta se encontrase por debajo
de dicha cota, se podra volver a comenzar aumentado dicha cota en alguna cantidad.
La estrategia que modica la cota lmite de una estrategia en profundidad se denomina
estrategia de profundidad iterativa.
28.3. Algoritmos de bsqueda [871]

28.3.3. Bsqueda con informacin


Las estrategias de anchura, profundidad y sus derivadas encuentran una solucin
al problema planteado. Esta solucin puede que no sea nica, esto es, que existan una
multitud de posibles soluciones; pero estas estrategias obtiene como solucin aquella
que primero encuentran. En este tipo de problemas slo son elevantes las soluciones y
no su conguracin, cualquier solucin ser vlidad siempre que lleve a una situacin
meta.
La situacin cambia si tuviramos que abonar una cantidad de dinero por cada
accin que realizamos, en este caso est claro que entre las soluciones obtenidas para
un problema optaramos por la solucin en anchura frente a la de profundidad. Esta
decisin se toma en base al conocimiento que una solucin obtenida mediante una
estrategia en anchura ser la de menor profundidad encotrada, esto es, la que menos
pasos tendrn que realizar.
Esta situacin donde cada accin cuesta igual no es la ms normal, por contra en la
gran mayora de las situaciones reales cada una de las acciones que podamos realizar
en un estado concreto depender del tipo de accin, as como de otras caractersticas
asociadas al problema concreto. En denitiva, el coste de cada accin normalmente es
difente.
En situaciones con diferentes costos, una estrategia de anchura no garatiza que la
solucin obtenida sea la mejor con un coste menor. Para incorporar estos criterios al
algoritmo de bsqueda, se denir una valoracin entre las distintas soluciones que
permita seleccionar aquella que la maximize.
Una de las valoraciones ms sencillas a considerar para una solucin es la suma
de cada uno de los costes de las acciones que la conguran. Para poder establecer
esta valoracin aadimos en cada accin posible en un estado el coste asociado a
su realizacin como un valor numrico positivo. La nueva funcin sucesor devolver
una tupla (Acc,val,estado) y deniremos el costo de un nodo n obtenido mediante la
aplicacin de la accin Acc a un nodo n como una funcin no decreciente C(n):

({Acc, val}, n ) sucesor(n)C(n ) = C(n) + val (28.3)

Inicial
Ahora ya tenemos un valor para decidir que nodo hoja coger, el menor valor de
costo de los nodos hoja, o que el conjunto de nodos hojas estn ordenados de forma
n C(n)
creciente por el valor del costo.
(Acc,val) Con el objeto de generalizar esta estrategia y poder utilizar las estructuras
n1 C(n1)=C(n)+val
posteriormente, se ha denido una nueva estructura de almacen como una lista
ordenada por una valor del nodo. La estructura elegida para realizar la cola ordenada
ha sido el montculo, una estructura vectorial donde la posicin inicial del vector se
Figura 28.58: Costo asociado a un encuentran siempre el elemento menor.
nodo.

Listado 28.14: Montculo como lista ordenada


1 import heapq
2 class ListaOrdenada(Almacen):
3 def __init__(self,nombre):
4 Almacen.__init__(self,nombre)
5 self.p=[]
6
7 def esVacia(self):
8 return len(self.p)==0
9
C28

10 def add(self,elem):
11 print elem
12 valor,n=elem
13 heapq.heappush(self.p,(valor,n))
[872] CAPTULO 28. INTELIGENCIA ARTIFICIAL

14
15 def get(self):
16 return heapq.pop(self.p)

A la estrategia que selecciona un nodo hoja con valor de costo menor se denomina
estrategia de costo uniforme.
Como ejemplo denimos un nuevo problema. El problema es el conocido como 8
puzzle donde se tiene un artilugio con 8 piezas numeradas del 1 al 8 y un espacio sin
pieza. Las piezas se puden deslizar al espacio en blanco y as cambiar la conguracin
del puzzle. Si se representa una conguracin concreta del 8 puzzle con una cadena
de 9 caracteres donde los 3 primeros representan la primera la, y as sucesivamente
y el espacio en blanco por el caracter B, y consideramos que los movimientos son los
del espacio blanco. Si consideramos que de los cuatro movimientos posibles el costo
de los movimientos hacia arriba e izquierda van a costar 2 unidades frente a la unidad
de los movimientos abajo y derecha.
El problema propuesto es denido en el siguiente cdigo ejemplo.

Listado 28.15: Problema del 8 puzzle.


1 class Problema8Puzzle(Problema):
2
3 def __init__(self,estadoI,estadoFin,h=DESCOLOCADAS):
4 Problema.__init__(self,estadoI,"8Puzzle")
5 self.estadoFin=estadoFin
6 self.h=h
7
8 def estadoValido(self,estado):
9 valido=True
10 for ficha in range(1,9):
11 valido=valido and (str(ficha) in estado)
12 valido=valido and (len(estado)==9)
13 valido=B in estado
14 return valido
15
16 def sucesor(self,estado):
17 if self.estadoValido(estado):
18 PB=estado.find(B)
19 FB=PB/3
20 CB=PB %3
21 mv=[]
22 if FB<2:
23 NP=(FB+1)*3+CB
24 est=list(estado[:])
25 est[PB]=est[NP]
26 est[NP]=B
27 mv.append(({Acc:AB,val:1},"".join(est)))
28 if FB>0:
29 NP=(FB-1)*3+CB
30 est=list(estado[:])
31 est[PB]=est[NP]
32 est[NP]=B
33 mv.append(({Acc:AR,val:2},"".join(est)))
34 if CB<2:
35 NP=FB*3+CB+1
36 est=list(estado[:])
37 est[PB]=est[NP]
38 est[NP]=B
39 mv.append(({Acc:DE,val:1},"".join(est)))
40 if CB>0:
41 NP=FB*3+CB-1
42 est=list(estado[:])
43 est[PB]=est[NP]
44 est[NP]=B
45 mv.append(({Acc:IZ,val:2},"".join(est)))
46 return mv
28.3. Algoritmos de bsqueda [873]

47 else:
48 return None
49
50 def meta(self,estado):
51 return estado==self.estadoFin

Para un problema del 8puzzle donde la conguracin inicial consiste en 1342B5786


y la nal 12345678B el algoritmo de bsqueda con la estrategia de costo uniforme de-
vuelve la solucin siguiente:
1342B5786

1. ({Acc: IZ, val: 2},134B25786)


2. ({Acc: AB, val: 1},134725B86)
3. ({Acc: DE, val: 1},1347258B6)
4. ({Acc: DE, val: 1},13472586B)
5. ({Acc: AR, val: 2},13472B865)
6. ({Acc: AR, val: 2},13B724865)
7. ({Acc: IZ, val: 2},1B3724865)
8. ({Acc: AB, val: 1},1237B4865)
9. ({Acc: DE, val: 1},12374B865)
10. ({Acc: AB, val: 1},12374586B)
11. ({Acc: IZ, val: 2},1237458B6)
12. ({Acc: IZ, val: 2},123745B86)
13. ({Acc: AR, val: 2},123B45786)
14. ({Acc: DE, val: 1},1234B5786)
15. ({Acc: DE, val: 1},12345B786)
16. ({Acc: AB, val: 1},12345678B)

12345678B
El coste de esta solucin es de 23 que podremos asegurar que es el menor coste
posible, o en otras palabras, que no existe otra solucin con un coste menor.
AL considerar el coste que se sufre al generar un nodo, hemos asegurado que el
resultado que obtenemos es el mejor de todos; pero no se ha solucionado el problema
de la complejidad. La complejidad de la estrategia de costo uniforme se puede
equiparar a la de estrategia en anchura haciendo que cada paso sea un incremento
Ic del costo O(b(n/Ic) ).
Considerando que la mejor estrategia, siempre que no se pierda, es la estrategia
en profundidad una opcin muy interesante podra ser combinar la estrategia de costo
uniforme con algo parecida a la estrategia en profundidad. Para poder llevar acabo esta
estrategia deberamos ser capaces de decidirnos por la accin que mejor nos lleve a
C28

una solucin. En otras palabras, tendramos que ser capaces de poder evaluar el coste
que nos queda por realizar desde la situacin actual a una meta.
[874] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Este valor del costo que nos queda por realizar desde un estado para alcanzar un
estado meta se denomina valor de la heurstica.
Para aadir esta heurstica modicaremos la denicin de un problema incorpo-
rando una nueva funcin que evalue el valor de la heurstica. Dado un estado n la
funcin heuristica h(n) ser el valor estimado del costo que queda por realizar hasta
un estado meta.

Listado 28.16: Problema Heurstica


1 class Problema:
2 def __init__(self,estadoInit,nombre="NoDef"):
3 self.id=nombre
4 self.estInit=estadoInit
5
6 def estadoValido(self,estado):
7 pass
8
9 def sucesor(self,estado):
10 pass
11
12 def meta(self,estado):
13 pass
14
15 def estadoInit(self):
16 return self.estInit
17
18 def heuristica(self,estado):
19 pass

En el problema del 8puzzle podemos estimar el nmero de movimientos que


tendremos que hacer desde un estado hasta el estado meta con una cota mnima que
sera el nmero de casillas descolocadas. Este valor nos impone una cota mnima
para el coste que tendremos que hacer, ya que en una situacin idlica con un nico
movimiento colocsemos cada cha descolocada. Pero como tenemos que mover cada
pieza una posicin y slo son vlidos los movimientos horizontales y verticales, otra
estimacin un poco ms exacta sera la suma de las distancias manhattan de cada pieza
descolocada a su posicin destino.
Incorporado los nuevos mtodos a la clase del problema del 8 puzzle, el cdigo
queda como se muestra en el siguiente listado.
Inicial
Listado 28.17: Problema 8Puzzle Heurstica
1 class Problema8Puzzle(Problema): C(n)
2
3 def __init__(self,estadoI,estadoFin,h=DESCOLOCADAS):
4 Problema.__init__(self,estadoI,"8Puzzle")
5 self.estadoFin=estadoFin n
6 self.h=h
7
8 def estadoValido(self,estado):
9 valido=True H(n)
10 for ficha in range(1,9):
11 valido=valido and (str(ficha) in estado)
12 valido=valido and (len(estado)==9)
13 valido=B in estado Meta
14 return valido
15
16 def sucesor(self,estado):
17 ................
18
19 def meta(self,estado):
20 return estado==self.estadoFin Figura 28.59: Informacin de un
21 def posDistintas(self,estado1,estado2): problema: Costo y Heurstica
28.3. Algoritmos de bsqueda [875]

22 n=0
23 for i in range(len(estado1)):
24 if estado1[i]<>estado2[i]:
25 n=n+1
26 return n
27 def movDistintas(self,estado1,estado2):
28 n=0
29 for i in range(len(estado1)):
30 if estado1[i]<>estado2[i]:
31 py1=i/3
32 px1=i %3
33 pc=estado2.find(estado1[i])
34 py2=pc/3
35 px2=pc %3
36 n=abs(px2-px1)+(py2-py1)
37 return n
38
39 def setHeuristica(self,h=DESCOLOCADAS):
40 self.h=h
41
42 def heuristica(self,estado):
43 if self.h==DESCOLOCADAS:
44 return self.posDistintas(estado,self.estadoFin)
45 elif self.h==CUNIFORME:
46 return 0
47 else:
48 return self.movDistintas(estado,self.estadoFin)

Llegados a este punto, y para combinar la informacin del costo desarrollado con
la del costo por desarrollar o funcin heurstica, deniremos el valor del nodo como
la suma del costo ms el valor de la funcin heurstica.

f (n) = C(n) + H(n) (28.4)

A la estrategia que ordena el conjunto de nodos hojas en funcin del valor


f(n) 28.4 se denomina estrategia de bsqueda A*. Formalmente para poder nombra
esta estrategia como A* tendramos que haber denido la funcin heurstica como h
la que mejor que estima el costo por gastar; pero como normalmente nuestra funcin
h no es tan buena y minora a h tendramos que denominar a esta estrategia como
estrategia A.
Las caractersticas mas relevantes de esta estrategia A* son:

1. Es completa. Si existe solucin la encuentra.


2. Es la mejor. La solucin que encuentra es la de menor coste.
3. El nmero de nodos generados para encontrar la solucin es el menor que en las
estrategias anteriores.

Listado 28.18: Busca soluciones con informacion Heurstica


1 def GeneraNodo(p,n,ac,est,estr=A):
2 MUCHO=10000000
3 prof=n.inf[Prof]+1
4 coste=n.inf[Coste]+ac[val]
5 h=p.heuristica(est)
6
7 if estr==A: val=coste+h
C28

8 elif estr==PROF: val=MUCHO-prof


9 elif estr==ANCH: val=prof
10 elif estr==COST: val=coste
11 else: val=h
[876] CAPTULO 28. INTELIGENCIA ARTIFICIAL

12 return NodoArbol(n,ac,est,{Prof:prof,Coste:coste,h:h},val
)
13
14 def BuscaSolucionesA(prob,estr=A):
15 frontera=AlmacenOrdenado()
16 h=prob.heuristica(prob.estadoInit())
17 n=NodoArbol(None,None,prob.estadoInit(),{Prof:0,Coste:0,h
:h},0)
18 solucion=None
19 frontera.add((n.val,n.estado,n))
20 while (not frontera.esVacia()) and (solucion==None):
21 n=frontera.get()
22 if prob.meta(n.estado):
23 solucion=n
24 else:
25 for ac,est in prob.sucesor(n.estado):
26 if not frontera.esta(est):
27 h=GeneraNodo(prob,n,ac,est,estr)
28 frontera.add((h.val,h.estado,h))
29 return solucion

Las diferentes formas de actuar de las distintas estrategias se han plasmado en el


problema del 8 puzzle estudiado con los valores que se muestran en la siguiente tabla.

Estrategia Num de Nodos S. ptima


Profundidad >30000 No
Anchura 16018 No
Costo Uniforme 14873 Si
A* 2326 Si Figura 28.60: Escenario o mapa
2D.

28.4. Planicacin de caminos


En la actualidad, es muy difcil encontrar un videojuego que no contenga algn
tipo de agente o personaje autnomo. Coches que circulan por carreteras, pjaros
que vuelan, ratoncillos que corren para esconderse o malvados enemigos que nos
persiguen. Estos personajes pueden hacer multitud de cosas; pero si hay una nica
accin que como mnimo se les puede exigir es la de poder dirigirse de un lugar a otro
del mundo que habitan.
Este proceso de trasladarse de un lugar a otro se divide en un par de procesos Figura 28.61: Puntos visibles del
ms sencillos. El primero de ellos identica el camino o lugares por donde el actor mapa 2D.
a de pasar, y el segundo intenta proceder trasladando al personaje siguiendo el plan
trazado. Al primero se reconoce como el proceso de planicacin de recorridos o
caminos (pathnder) y ser el objeto de esta seccin.

28.4.1. Puntos visibles


Sea un mundo denido en un videojuego de dos dimensiones, como el de la
gura 28.60, donde habita un actor que se mueve entre los bloques. Para cada mapa, de
este tipo, se puede crear un grafo como el que se muestra en la gura 28.62 donde los
nodos son los puntos visibles para dicho mapa y los arcos son la lnea de visin directa
entre los puntos. El conjunto de puntos que nos permiten una visin completa del
escenario o mapa son los denominados puntos visibles 28.61 que sern identicados
Figura 28.62: Grafo de los puntos
por sus coordenadas (x,y) o por un identicador. visibles.
28.4. Planicacin de caminos [877]

El movimiento de un actor se desarrolla como el desplazamiento entre puntos


visibles. Este movimiento puede generalizarse como el desplazamiento por los nodos
de un grafo, y de esta forma denimos el problema de la construccin de un camino
desde un punto visible a otro punto visible como un problema de encontrar el camino
entre dos nodos de un grafo.

28.4.2. Problema de bsqueda en grafos


Un grafo es una estructura que consta de nodos y arcos. En el siguiente cdigo se
dene una clase para representar un grafo.

Listado 28.19: class Grafo


1 class Grafo:
2 def __init__(self,tipo=Dir,nombre=Noname):
3 self.g={}
4 self.tipo=tipo
5 self.id=nombre
6 self.NumNodos=0
7 self.NumArc=0
8
9 def addNodo(self,Nodo):
10 if not Nodo in self.g.keys():
11 self.g[Nodo]=[]
12
13 def addArco(self,NodoOrigen,NodoDestino,InfArco):
14 self.addNodo(NodoOrigen)
15 self.addNodo(NodoDestino)
16 self.g[NodoOrigen].append((InfArco,NodoDestino))
17 if not (self.tipo==Dir):
18 self.g[NodoDestino].append((InfArco,NodoOrigen))
19
20 def nodosSucesores(self,Nodo):
21 return self.g[Nodo]

Las operaciones bsicas denidas son: aadir un arco,un nodo y obtener los
sucesores de un nodo. Segn sea el acceso de un nodo a otro, encontraremos nodos
donde solo es posible en una direccin que denominaremos grafos dirigidos; en caso
contrario, tendremos grafos no dirigidos. Esta diferencia la introducimos en la clase
grafo mediante el valor tipo de la clase, que por defecto ser dirigido.
Denido la estructura de grafo, podemos construir un problema como se coment
en 28.3.1 para lo que denimos la clase ProblemaGrafo.

Listado 28.20: class ProblemaGrafo


1 class ProblemaGrafo(Problema):
2 def __init__(self,Grafo,NodoInicial,NodoFinal):
3 Problema.__init__(self,NodoInicial,"Grafo")
4 self.estadoFin=NodoFinal
5 self.graf=Grafo
6 def estadoValido(self,Nodo):
7 return Nodo in self.graf.g.keys()
8 def sucesor(self,Nodo):
9 return self.graf.nodosSucesores(Nodo)
10 def meta(self,Nodo):
11 return Nodo==self.estadoFin
12 def estadoInit(self):
13 return self.estInit
C28

Esto proporciona un mtodo eciente de construir el camino por el grafo, desde


un nodo origen a un nodo destino.
[878] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Obtenida la secuencia de puntos visibles por donde pasar el actor,el siguiente


proceso ser el desplazamiento fsico entre los puntos. Este proceso puede ser simple,
si no hay elementos que puedan interaccionar con las trayectorias elegidas, o por
contra, puede ser ms complejo si podemos ser interceptados o necesitamos modicar
en un momento dado la trayectoria planicada. Estos problemas quedan fuera del
alcance de esta seccin, as que slo nos centraremos en encontrar las trayectorias
correctas.

La construccin del camino a seguir, se realiza obteniendo la solucin al


problema de ir de un nodo (punto visible) de un grafo a otro nodo destino.
La solucin es la secuencia de punto visibles que ha de ir ocupando el agente
consecutivamente.

Una forma de mejorar los resultados obtenidos mediante la simple bsqueda de


soluciones es considerar un tipo especco de grafo, el grafo de punto visibles, y
explotar una serie de caractersticas propias de la localizacin de sus nodos.
La primera de estas caracterstica es que un punto visible P viene denido por unas
coordenadas. En un mundo de dos dimensiones P = (x, y) sern dichas coordenadas,
mientras que en un escenario en 3 dimensiones vendr denido por P = (x, y, z).
Esta caracteristica permite plantearse, no slo encontrar el camino que una dos puntos
visibles; si no que ese camino sea bueno. En un supuesto donde el desplazamiento
consume energa del agente (muy generalizado), un buen camino ser aquel que menos
recorrido tenga que realizar.
El cambio de un punto visible P hacia otro punto visible P provocar en un actor
un desgaste equivalente a trasladarse en linea recta desde P a P. Este gasto en trminos
de posiciones ser la distancia en lnea recta desde P a P, esto es:

d(P, P ) = d((x, y), (x , y )) = (x x )2 (y y )2 (28.5)

Esto establece que la accin de ir de un nodo a otro incurre en un gasto cuanticado


por la diferencia de posicin (ecuacin 28.5), por lo que se pude incorporar a las
estrategias de bsqueda informadas 28.3.3. La accin (arco) entre el nodo P y nodo
P vendr denida como (P->P,d(P,P)), incorporando el valor del costo del nodo P
como la suma de todas las distancias recorridas. En denitiva, el total de distancia
recorrida por el agente. De esta forma podremos utilizar una estrategia de costo
uniforme.
Otra de las cuestiones que se puede explotar es el conocimiento aproximado de la
distancia que nos queda por recorrer. Esta distancia sera la existen desde el nodo i al
nodo destino nal f, luego es factible utilizar la distancia que falta por recorrer como
valor heurstico para la bsqueda informada con una estrategia A*. Si el movimiento
estuviese limitado a los 4 desplazamientos bsicos: arriba, abajo, izquierda y derecha,
otro aproximacin ms el a la distancia que falta por recorrer es la distancia en
vertical ms la distancia en horizontal que separa el nodo del destino. Esta nueva
valoracin se denomina distancia manhattan y su expresin viene denida por la la
ecuacin 28.6. En la gura 28.63 se puede observar el signicado geomtrico de cada
una de las heursticas expresadas.

dm(Pi , Pf ) = |xi xf | + |yi yf | (28.6)

La incorporacin de la heurstica al problema del grafo se muentra en el siguiente Figura 28.63: Heursticas para la
cdigo. estrategia A*.
28.4. Planicacin de caminos [879]

Listado 28.21: class Grafo con informacin


1 import map
2
3 class GrafoMapa(Grafo):
4 def __init__(self,map,h=EUCLIDEA):
5 self.dato=map
6 self.id="Mapa"
7 self.h=h
8 def addNodo(self,Nodo):
9 pass
10 def addArco(self,NodoOrigen,NodoDestinod,InfArco):
11 pass
12 def nodosSucesores(self,Nodo):
13 f,c=Nodo
14 mv=[]
15 anchura,altura=self.dato.tablero
16 if (f<(altura-1)) and (not self.dato.mapa[f+1][c]==1):
17 mv.append(({Acc:AB,val:1},(f+1,c)))
18 if (f>0) and (not self.dato.mapa[f-1][c]==1):
19 mv.append(({Acc:AR,val:1},(f-1,c)))
20 if (c<(anchura-1)) and (not self.dato.mapa[f][c+1]==1):
21 mv.append(({Acc:DE,val:1},(f,c+1)))
22 if (c>0) and (not self.dato.mapa[f][c-1]==1):
23 mv.append(({Acc:IZ,val:1},(f,c-1)))
24 return mv
25
26 def heuristica(self,nodo1,nodo2):
27 y1,x1=nodo1
28 y2,x2=nodo2
29 s=0.0
30 if self.h==EUCLIDEA:
31 s= ((x1-x2)**2.0+(y1-y2)**2.0)**(1.0/2.0)
32 elif self.h==CUNIFORME:
33 s=0.0
34 else:
35 s= abs(x2-x1)+abs(y2-y1)
36 print "H( %s, %s)= %d" %(str(nodo1),str(nodo2),s)
37 return s

Hasta este punto hemos solucionado el problema de encontrar un camino, incluso


el mejor, entre un punto visible y otro de nuestro mapa. Siendo capaces de construir
o generar el grafo de los puntos visibles, ya tenemos solucionado el problema del
movimiento de los actores en el escenario o mapa.

28.4.3. Obtencin del grafo de puntos visibles


La primera opcin para la obtencin del grafo de los puntos visibles es de forma
manual. Dado un mapa se sealan en l los puntos y se unen para generar el grafo.
Esto obliga a construir un editor de grafos de puntos visibles y adjuntar a cada mapa
su grafo correspondiente.
Una alternativa a la construccin manual es construir un grafo de forma autom-
tica. El primer mtodo parte de un estructura particular de un mapa. Partamos de un
mapa construido con guras geomtricas denidas por unos vertices y unas arstas.
Consideremos las guras que conguran los obstculos o paredes de nuestro mapa,
y extendamos su geometra una distancia igual al tamao de nuestro agente (gura
28.64.
En estos momentos tendremos un nuevo conjunto de vertices que representarn
Figura 28.64: Construccin auto- los puntos visibles de nuestro mapa. Para terminar de construir el grafo, se aadirn
C28

mtica del grafo de puntos visibles.


las aristas desde un vertice a todos a quellos que tiene visin directa.
[880] CAPTULO 28. INTELIGENCIA ARTIFICIAL

De esta forma construimos un grafo deforma automtica para el desplazamiento de


los agentes en el mapa. Hemos de destacar que los grafo de puntos visibles generados
de esta forma originan un movimiento de los agentes siguiendo la geometra de los
objetos como obstculos o paredes.

28.4.4. Aumentando la resolucin


Con los GPV (grafos de puntos visibles), obtenidos se tiene un conjunto no muy
numeroso de putos visibles y con una distribucin no muy uniforme por todo el mapa.
Para aumentar el nivel de realismo en el movimiento de los agentes se tendra que
aumentar el nmero de puntos visibles, y que estos estn distribuidos por el mapa
de una forma uniforme. En un principio si consideramos una distancia mnima dm
para separar puntos visibles, tendramos una forma de ajustar el realismo o nuestra
resolucin mediante el ajuste del valor de dm.
Si adems consideramos que los movimientos que poden realizar los agentes estn
acotados, tendremos un nuevo mecanismo de generar un grafo de puntos visibles. Este
nuevo mecanismo es el siguiente:

1. Colocar un punto visible en una posicin del mapa.


Figura 28.65: Primeros pasos de la
2. Aadir este punto visible a una lista de PV no visitados. creacin de un grid.

3. Mientras tengamos punto visibles en la lista de no visitados


4. sacar un punto p
5. aadir a la lista de PV no vistados tantos nuevos puntos como movimientos
posibles y distancia dm que no hayamos visitado.
6. aadir el punto analizado a la lista de PV visitados

En el siguiente cdigo se ha creado un clase mapa, que permite denir un escenario


basado en tiles, donde podemos jar una casilla inicial, muros y una casilla nal.
Figura 28.66: Tiles utilizados.
Construido el mapa podemos denir un problema de bsqueda en grafo para el grid
creado y ver como se comporta los algoritmos para determinar el camino.
Un ejemplo con las primeras iteraciones se muestra en la gura 28.65.
Un caso particular de esta tcnica es cuando el propio mapa est diseado mediante
pequeas celdas o tiles.En estos caso el propio escenario es ya el grafo de puntos
visbles, y podemos asignar a cada una de las celdas accesibles un punto visible y los
movimientos a las celdas adyacentes como los arcos a nuevos puntos visbles.

Listado 28.22: class Grafo con informacin


1 import sys,pygame,csv
2
3 class Map:
4 def __init__(self,celda=(20,20),tablero=(50,30),m=None):
5 self.celda= 20,20
6 self.tablero= 50,30
7 self.size = width,height = tablero[0]*celda[0],tablero[1]*
celda[1]
8 self.s = pygame.display.set_mode(self.size)
9 loseta=self.CreaCelda((113,132,132))
10 muro=self.CreaCelda((255,181,66))
11 inicio=self.CreaCelda((255,0,0))
12 fin=self.CreaCelda((0,0,255))
13 self.elementos=[loseta,muro,inicio,fin]
28.4. Planicacin de caminos [881]

14 if m==None:
15 self.CreaMap(self.tablero[0],self.tablero[1])
16 else:
17 self.mapa=m
18
19 def CreaCelda(self,color):
20 s=pygame.Surface((self.celda[0],self.celda[1]),0,16)
21 s.fill(color)
22 pygame.draw.rect(s,(255,181,66),pygame.Rect(0,0,20,20),2)
23 return s
24
25 def AnadeCelda(self,color):
26 self.elementos.append(self.CreaCelda(color))
27
28 def CreaMap(self,w,h):
29 self.mapa=[]
30 for y in range(h):
31 self.mapa.append([])
32 for x in range(w):
33 self.mapa[y].append(0)
34
35 def PintaMapa(self):
36 for y in range(self.tablero[1]):
37 for x in range(self.tablero[0]):
38 rect=pygame.Rect(x*self.celda[0],y*self.celda[1],
self.celda[0],self.celda[1])
39 self.s.blit(self.elementos[self.mapa[y][x]],rect)
40
41 def ModificaMapa(self,celdaIndx,infor):
42 titulo=pygame.display.get_caption()
43 pygame.display.set_caption(infor)
44 NoFin=True
45 while NoFin:
46 for event in pygame.event.get():
47 if event.type == pygame.KEYDOWN and event.key ==
pygame.K_ESCAPE:
48 NoFin=False
49 pos=pygame.mouse.get_pos()
50 pos=pos[0]/self.celda[0],pos[1]/self.celda[1]
51 b1,b2,b3= pygame.mouse.get_pressed()
52 rect=pygame.Rect(pos[0]*self.celda[0],pos[1]*self.celda
[1],self.celda[0],self.celda[1])
53 if b1:
54 self.s.blit(self.elementos[celdaIndx],rect)
55 self.mapa[pos[1]][pos[0]]=celdaIndx
56 elif b3:
57 self.s.blit(self.elementos[0],rect)
58 self.mapa[pos[1]][pos[0]]=0
59 pygame.display.flip()
60 pygame.display.set_caption(titulo[0])
61
62 def SalvaMapa(self,fn):
63 f=csv.writer(open(fn,"wr"),delimiter=,)
64 for l in self.mapa:
65 f.writerow(l)
66
67 def CargaMapa(self,fn):
68 f=csv.reader(open(fn,rb),delimiter=,)
69 self.mapa=[]
70 for r in f:
71 self.mapa.append([int(x) for x in r])
72 def DefMuros(self):
73 self.ModificaMapa(1,Definir Muros)
74 def Entrada(self):
75 self.ModificaMapa(2,Definir Entrada)
76 def Salida(self):
77 self.ModificaMapa(3,Defir Salida)
C28
[882] CAPTULO 28. INTELIGENCIA ARTIFICIAL

En la gura 28.67 podemos ver un ejemplo de un mapa construido con la clase


mapa y el conjunto de tiles de la gura 28.66.
Finalmente, podemos observar el camino encontrado por la estrategia A* y una
heurstica de distancia eucldea en la gura 28.68

Figura 28.67: Mapa 2D

Figura 28.68: Camino con la heurstica de la distancia eucldea.


28.5. Diseo basado en agentes [883]

28.5. Diseo basado en agentes


Como ya se ha comentado en la Seccin 28.1, en los ltimos aos ha habido un
creciente uso de tecnologa relacionada con la IA en la construccin de videojuegos.
Este uso ha sido enfocado principalmente desde la perspectiva del desarrollo de
agentes inteligentes que hacen uso de algoritmos de IA para chasing (persecucin
y escape), para bsqueda de rutas, para planicacin, para decidir... Cada vez son ms
los creadores de videjuegos (p.e. Sid Meier (Civilization), Chris Crawford (Balance
of Power) y Peter Molyneux (Populous) por citar algunos) que emplean estos agentes
inteligentes con capacidad de decisin, para crear en sus juegos personajes virtuales
que se comporten de una forma parecida a como lo hace un humano. Se consigue as
que los jugadores piensen que se estn enfrentando a oponentes reales.
En la mayora de las ocasiones, en los videojuegos, los agentes no son desarro-
llados de manera independiente sino como entidades que constituyen un sistema ms
completo. De este modo, los videojuegos se estn conviertiendo en sistemas multi-
agente, a partir de ahora se har referencia a ellos como MAS (Multi-Agent Systems).
En estos sistemas existirn un conjunto de agentes que son empleados para simular
personalidades y caracteres diferentes para los personajes de los juegos y que pueden
coordinar sus comportamientos de grupo y colaborar para lograr un n (p.e. matar, o
Figura 28.69: Personajes Virtuales siendo menos drsticos, hacer prisionero al jugador).
dotados con comportamientos simi-
lares a los de un personaje real.
En esta seccin se mostrar como disear agentes que puedan ser empleados en
videojuegos, siendo esta ltima restriccin importante ya que no se va a pretender
crear agentes que emulen la inteligencia humana, sino que el objetivo ser crear
agentes que hagan que el usuario crea que el videojuego tiene inteligencia, aunque
no sea as. Se presentarn dos enfoques distintos de diseo de agentes uno basado en
comportamientos y otro basado en objetivos, prestando mayor atencin al primero
por ser el ms usual. Para ello primero se dar una denicin del concepto de agente,
y una posible clasicacin de los mismos. Despus se presentarn los tipos de agente
que se suelen construir en los videojuegos. Se nalizar la seccin con una discusin
sobre las implicaciones que tendra construir sistemas multiagentes completos y unas
breves reexiones sobre hacia donde puede tender el uso de agentes y sistemas
multiagentes en el campo de los videojuegos y los retos que hay abiertos.

28.5.1. Qu es un agente?
No existe una denicin plenamente aceptada por la comunidad cientca del
concepto agente inteligente, siendo la ms simple la de Russell y Norvig [79],
que fue presentada en la seccin 28.1.3 y que conviene recordar aqu: un agente
es una entidad que percibe y acta sobre un entorno (vea la gura 28.7). En
trminos matemticos esta denicin lleva a pensar en un agente como una funcin
(el comportamiento) que proyecta percepciones en acciones. Cabe notar, que esta
denicin es tan amplia, que permite que a numerosos sistemas software se les pueda
asignar la etiqueta de agente, por otra parte hay que destacar que existe un gran inters
tanto acadmico como industrial en ello (vea la gura 28.70).
Figura 28.70: Desde que el Profe-
sor Nick Jennings enunciara la fra- Para delimitar un poco ms el concepto de agente, comienzan a aparecer en la
se Los agentes constituyen el pr- literatura una serie de calicativos cuyo objetivo es denotar aquellas propiedades que
ximo avance ms signicativo en el debe cumplir un agente. De este modo Wooldridge y Jennings [104] denen a un
desarrollo de sistemas y pueden ser agente como un sistema capaz de actuar de forma autnoma y exible en un entorno,
considerados como la nueva revo-
lucin en el software, muchos son
donde exible es entendido como:
los que se han subido al carro in-
C28

dicando que sus aplicaciones son Reactivo, con capacidad de respuesta a cambios en el entorno donde est
agentes. Los agentes estn de mo- situado.
da.
[884] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Proactivo, con capacidad de decidir cmo comportarse para cumplir sus planes
u objetivos.
Social, con capacidad de comunicacin con otros agentes.

As, para que un software pudiera ser considerado un agente debera de tener las
caractersticas antes expuestas. No obstante, a lo largo de los aos, los investigadores
del campo han usado nuevas caractersticas para calicar a los agentes, a continuacin
se citan algunas de las recopiladas por Franklin y Graesser [36]:

Continuidad Temporal, con capacidad para ejecutarse ininterrumpidamente.

Racionalidad, con capacidad de elegir siempre la accin correcta en funcin


de lo percibido en el entorno.
Adaptabilidad, con capacidad de cambiar su comportamiento basndose en el
aprendizaje que realice.
Movilidad, con capacidad para trasladarse a travs de una red telemtica.

Otras caractersticas que se asumen como propiedades de los agentes son la


Veracidad, por la que se se supone que un agente no va a proporcionar informacin
falsa a propsito y la Benevolencia, que implica que un agente est dispuesto a ayudar
a otros agentes, si esto no entra en conicto con sus intereses.
Llegados a este punto, cabe entonces hacerse la siguiente pregunta, qu carac-
tersticas debe poseer un sistema para poderle asignar el calicativo de agente? Al
respecto, se debe indicar que no existe un concenso sobre cual es la importancia de
cada una de las propiedades que se han enunciado para denir un agente. Sin em-
bargo, si se puede armar que son la existencia de estas, en mayor o menor medida,
las que distinguen agentes de meros programas informticos (vea gura 28.71). Des-
de la perspectiva que nos ocupa, el diseo de videojuegos, cabe otra pregunta, qu
caractersticas deben poseer los agentes usados en los videojuegos?
En este sentido a la respuesta antes dada, hay que aadirle un matiz, y este
es el siguiente: Mientras en los agentes del campo de la IA acadmica se pueden
emplear mquinas especiales con gran poder de cmputo para ejecutarse (p.e. la Figura 28.71: El profesor Van Dy-
famosa deep blue), los agentes diseados para juegos tienen su poder de cmputo ke Parunak compara un agente con
limitado a mquinas ms normales y es mucho menor, y adems compartido con otros las navajas suizas, la navaja co-
procesos intensivos en CPU, como por ejemplo la simulacin fsica, la generacin de rresponde a la denicin bsica de
los grcos y el trco de red. Esto conduce al desarrollo de agentes ms limitados agente, y cada accesorio de la nava-
en caractersticas o en el despliegue de las mismas, pero que den sensacin de ja con cada una de las propiedades
inteligencia. del agente, que son usados solo si se
necesitan.
Una de las caractersticas que se han presentado como interesante, y que por otra
parte es resaltada como necesaria en los agentes cuando se le aade el calicativo
de inteligente, es la de racionalidad. Como ya se ha comentado, esta caracterstica
implica que el agente haga siempre lo correcto, pero hay que preguntarse qu es
lo correcto? Una primera aproximacin, y por otra parte usual, es la de denir lo
correcto como aquello que le permite al agente obtener un resultado mejor, por lo tanto
habr que medir el xito. Para ello, es preferible emplear medidas de rendimiento o
utilidad, que estn asociadas al entorno, ms que de desempeo asociadas a cmo
de bien lo hace el agente. Es conveniente resaltar en este punto que no existe una
medida universal para todo agente, cada agente tendr su propia medida y sta ser
dependiente del problema. Agente y Problema
Los agentes racionales representan
soluciones a problemas.
28.5. Diseo basado en agentes [885]

Un agente racional deber emprender en cada momento la accin que en principio


maximice su medida de desempeo o rendimiento, basndose en las evidencias
aportadas por la secuencia de percepciones de dicho momento y en el conocimiento
que el agente mantiene de momentos anteriores. De este modo, la racionalidad de un
agente en un instante determinado vendr dada por:

El valor de la medida de desempeo, rendimiento o utilidad del agente.


El conocimiento acumulado por el agente sobre el entorno en el que desempea
su funcin.
El conjunto de acciones que puede realizar en ese instante.
La secuencia de percepciones que ha captado haste ese instante.

En el caso de los videojuegos, un agente racional debera tener una medida


de rendimiento asociado al entorno que permitiera guiar su comportamiento. Por
ejemplo, en los juegos de accin en primera persona (FPS), los agentes podran ser
empleados para simular comportamientos de adversarios y la medida de rendimiento
ser inversamente propocional al nivel de stamina que tiene el jugador tras ejecutar
la accin. La eleccin de la siguiente accin se podra realizar en base a la capacidad
para reducir este valor, o considerando de las que ya ha ejecutado, aquella que mejor
resultado le dio.

28.5.2. Cmo se construye un agente?


Un agente es un programa junto con una arquitectura (vea la gura 28.72).
El programa implementar una funcin que transforma secuencias de percepciones
en acciones. La arquitectura dene los mecanismos que permiten interconectar los
componentes, tanto de software como de hardware, es decir establecen las relaciones
que uyen entre las entradas (sensores), las salidas (actuadores) y el razonamiento
interno del agente. La IA permite crear programas de agentes para el razonamiento
interno.
Figura 28.72: La relacin entre En la construccin de agentes, la complejidad del diseo de los mismos vendr
agentes, arquitectura y programas determinada, a parte de por su funcin (o comportamiento), complejidad de las
podra resumirse de la siguiente ma- percepciones, y la medida de desempeo, rendimiento o utilidad empleada para medir
nera: Agente = arquitectura + pro- como de correcto es su comportamiento, por las caractersticas del entorno en el que
grama. La construccin de un agen- tenga que llevarla a cabo. Los agentes son sistemas que toman decisiones empotrados
te implica desarrollar un programa
en entornos, su conocimiento es por tanto necesario para la construccin eciente de
y desplegarlo sobre una arquitectu-
ra.
agentes. Tradicionalmente, en el campo de la IA se han establecido los siguientes tipos
de entornos:

Totalmente observable / Parcialmente observable. Ser un entorno totalmen-


te observable aquel en el que se tiene acceso a todos los aspectos medibles
necesarios para la toma de decisin. Por contra un entorno parcialmente obser-
vable no permite captar todo el entorno. En aplicaciones reales, este ltimo tipo
de entorno es el ms frecuente. En trminos de rendimiento el primero es el ms
recomendable y conveniente.
Determinista / Estocstico. Un entorno es determinista cuando el siguiente
estado que se alcanza se puede determinar a partir del estado actual y la accin
llevada a cabo, no hay incertidumbre. En un entorno estocstico no siempre se
C28

puede determinar con precisin el estado que se alcanza desde el estado actual y
la accin del agente. Los entornos reales generalmente son estocsticos, siendo
lo preferible tratar con entornos deterministas.
[886] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Episdico / Secuencial. En los entornos episdicos un agente divide su funcin


en episodios atmicos que se suceden uno tras otro, no hay dependencia con las
acciones anteriores. Cada episodio es una percepcin ms su accin asociada.
En los entornos secuenciales las decisiones y acciones presentes pueden afectar
a las decisiones y acciones futuras.
Esttico / Dinmico. En el entorno esttico, las condiciones no puede cambiar
mientras el agente est deliberando, este tipo de entornos son los ms fciles
de tratar por no requerir estar pendiente de los cambios que suceden. En los
entornos dinmicos, estos pueden cambiar mientras el agente decide que hacer,
y puede ocurrir que este tome una decisin sin saber realmente como est el
entorno. Los ms complejos son los entornos dinmicos.
Discreto / Continuo. Los entornos discretos son aquellos en los que es posible
distinguir y establecer un conjunto nito de estados posibles. El caso contrario
es un entorno continuo.

El entorno, o mejor dicho la complejidad del mismo, tambin se ve afectada por


la cantidad de agentes que intervienen en l. De este modo se pueden encontrar desde
entornos con un nico agente o con un conjunto de agentes que no colaboran, a
entornos con multitud de agentes que colaboran y forman sistemas multiagente.
Cuanto ms simple es el entorno, ms llevadera ser la tarea de construccin
del agente o agentes. En el caso de los videojuegos, y puesto que son entornos
controlables, se tiende a trabajar simplicando al mximo el entorno, siendo lo
habitual entornos totalmente observables, deterministas y discretos.
La estructura de un programa agente simple, en pseudocdigo, es mostrada en
la gura 28.73. A continuacin se estudiarn varios tipos de agente en funcin del
programa y arquitectura que se emplean para su construccin:

Agente de Razonamiento Simblico. Emplean representaciones smbolicas


del entorno y de la conducta que se desea que tenga, toda esta representacin
es manipulada sintcticamente. Este tipo de agentes toman decisiones sobre
que es lo que tienen que hacer por medio de manipulaciones simblicas. El
ejemplo tpico es el del agente que utiliza la lgica para representar el entorno
y el razonamiento lgico explcito para decidir que hacer.
Este tipo de agentes requieren una traduccin precisa y adecuada del entorno
en una descripcin simblica, lo cual puede ser complejo. Adems su coste
en tiempo para obtener resultados es tambin alto, puesto que los algoritmos
de manipulacin simblica tienen una complejidad alta. Esto los hace poco
aplicables en el caso de los videojuegos.
Agente Reactivo Simple (o de Reejo Simple). Puede considerarse el progra-
ma de agente ms sencillo. Su funcionamiento se basa en seleccionar la accin
slo sobre las percepciones actuales del agente, ignorando las percepciones his-
tricas, para ello almacena asociaciones entrada/salida frecuentes en forma de
reglas condicin-accin: Si <Percepcin>entonces <accin>.
Este tipo de agentes cuentan con una inteligencia limitada y trabajan bien en
entornos totalmente observables, lo cual los hace poco tiles en problemas
reales medianamente complejos pero si pueden tener utilidad en el mundo de
los videojuegos: Si detectas un obstculo entonces cambia de direccin.
28.5. Diseo basado en agentes [887]

Agente Reactivo basado en modelo. En gran cantidad de ocasiones un agente


no podr tomar una decisin teniendo en cuenta una nica percepcin, porque
esta no proporciona toda la informacin necesaria, es por ello necesario emplear
estados que de alguna forma guarden informacin sobre las percepciones
histricas o que ya no son observables. Adems se necesita informacin sobre
cmo evoluciona el mundo, independiente del agente.
En este tipo de agentes la percepcin actual se interpreta a partir del estado an-
terior utilizando informacin sobre: cmo evoluciona el entorno independiente-
mente del agente y la inuencia en el entorno de las acciones del agente. Los
modelos sern empleados para representar cmo evoluciona el entorno.
Agente Reactivo basado en subsuncin. En este tipo de agentes la toma de
decisin (la accin) se realiza mediante un conjunto de mdulos de compor-
tamiento que realizan tareas. Los comportamientos se agrupan en capas que
estn organizadas en orden decreciente de priodidad. De este modo los com-
portamientos de las capas inferiores tendrn ms prioridad que las de las capas
superiores. No se pueden ejecutar mdulos de distintas capas, los de las capas
inferiores inhiben a los de las capas superiores. Es decir, cada percepcin puede
acarrear la ejecucin de varios mdulos (al satisfacer las condiciones de ejecu-
cin de los mismos), se deber entonces comprobar que comportamientos no
son inhibidos por otros y llevarlos a cabo.
Agente Deliberativo. Son una mejora sobre los Agentes de Razonamiento
Simblico, y surgen porque con los estados no es suciente para tomar una
decisin, ya que sta muchas veces depende de cual es la misin del agente. Por
tanto se requiere informacin sobre el objetivo o meta del agente. Estos agentes
se caracterizan por la utilizacin de modelos de representacin simblica del
conocimiento (tanto del entorno como de la meta) y suelen basarse en la teora
clsica de planicacin: Parten de un estado inicial y emplean un sistema de
planicacin que genera un conjunto de pasos (un plan) para lograr el objetivo
para el cual fueron diseados. Por tanto, la bsqueda y la planicacin son muy
importantes para la construccin de este tipo de agentes.
La arquitectura deliberativa ms estudiada y extendida es la arquitectura
BDI (Belief, Desire, Intention). Esta se caracteriza por el hecho de que los
agentes que la implementan estn dotados de los estados mentales de Creencias
(modelo del mundo), Deseos (metas) e Intenciones (plan de accin), y para
denir los planes se tienen en cuenta sus creencias y deseos. Su principal
problema a la hora de aplicarse es que requieren un elevado tiempo de respuesta,
esto lo hace especialmente poco tiles en el caso de los videojuegos. No
obstante, recientemente desde el campo acadmico se estn proponiendo formas
de usar la arquitectura BDI para modelar comportamientos de personajes en
videojuegos, por ejemplo algunas pruebas se han realizado en el videojuego
Quake 2.
En ocasiones, las metas por si solas tampoco son sucientes, ya que no basta
con alcanzar una meta sino hacerlo de la mejor forma posible. Para garantizar
la seleccin de la mejor meta es necesario una medida que permita comparar
decisiones o planes. Esta medida se denomina utilidad. Una accin puede tener
ms utilidad que otra, el agente debe ir buscando aquellas que les proporcionen
ms utilidad.
Agente Hbrido. Este tipo de agentes combinan componentes de tipo reactivo
y componentes de tipo deliberativo. Usualmente, la parte reaactiva se encarga
de reaccionar en funcin de lo que ocurra en el entorno sin dedicar demasiado
C28

tiempo a razonar y la parte deliberativa es la que se encarga de planicar a ms


largo plazo y a soportar los procesos de toma de decisin.
[888] CAPTULO 28. INTELIGENCIA ARTIFICIAL

function ESQUELETO-AGENTE (percepciones) returns accion


static: memoria /*La memoria del agente del entorno*/

memoria ACTUALIZAR-MEMORIA(memoria, percepciones);


accion ELEGIR-MEJOR-ACCION(memoria);
memoria ACTUALIZAR-MEMORIA(memoria, percepciones);
return accion ;

Figura 28.73: Visin abstracta del funcionamiento de un agente.

28.5.3. Los comportamientos como gua de diseo


Los agentes pueden ser programados en base a sus comportamientos. Un com-
portamiento especica las tareas o servicios que realiza un agente para lograr sus
objetivos. Cada comportamiento puede realizar una tarea simple, que en el caso de
los videojuegos pueden ser, Lanzar una pregunta o Resoplar aunque tambin se
pueden crear comportamientos ms complejos como por ejemplo Huir o Atacar.
Para realizar una programacin basada en comportamientos se deben seguir los
siguientes pasos (vea gura 28.74):

Estudiar el entorno.
Analizar que es lo qu debe ser capaz de hacer el agente. Estudiar cuales son
los comportamientos deseados y cuando ocurren en funcin del entorno.
Asociar a cada comportamiento una funcionalidad. Establecer que es lo que
debe ocurrir como accin asociada a cada comportamiento.
Relacionar los comportamientos. Establecer las conexiones que existen entre
Figura 28.74: Pasos en el diseo
los comportamientos, es decir como se conectan y cuando ocurrren. de agentes empleando los compor-
tamientos como gua de diseo.
El agente reactivo, en alguna de sus variantes, es el ms fcil de construir para
programar agentes basados en comportamientos, ya que realiza tareas sencillas y
de una forma rpida. Es por esto, por lo que resulta ms til en el campo de los
videojuegos. El esquema general del programa de agente reactivo es el que se muestra
en la gura 28.75.
Como se puede observar su funcionamiento se basa en una recepcin de percep-
ciones del entorno (proporcionados por los sensores) que al ser interpretada produce
un cambio en el estado interno del agente, lo cual lleva asociada la seleccin del pro-
cedimiento o rutina con la que se debe reaccionar en ese estado a esa percepcion, y
por ltimo se produce la reaccin propiamente dicha. sta consiste en la ejecucin
del procedimiento o rutina. En este esquema de funcionamiento no hay ningn meca-
nismo explcito de representacin del conocimiento ni hay procesos de manipulacin
sobre el mismo, es esto lo que simplica el diseo y construccin del agente, as como
su uso con tiempos de respuesta inmediatos.
28.5. Diseo basado en agentes [889]

function ESQUELETO-AGENTE-REACTIVO (percepciones) returns accion


static: reglas

estado INTERPRETAR(percepciones;
regla ELEGIR-REGLA(estado,reglas);
accion DISPARO(regla);
return accion;

Figura 28.75: Visin abstracta del funcionamiento de un agente reactivo.

Este tipo de agente se suele disear de forma parecida a los sistemas basados en
reglas, en los que existe una base de reglas y un motor de inferencia. Cada regla enlaza
una percepcin con una accin y el sistema de inferencia determina que regla ejecutar
en funcin de la percepcin recibida.
Los agentes reactivos ms usados en el campo de los videojuegos son los agentes
reactivos basados en modelos. Para el diseo de este tipo de agentes se emplean
autmatas de estados nitos (en la Seccin 28.1.4 ya se introdujo la idea). Se divide el
comportamiento general del objeto o personaje virtual del videojuego en un nmero
nito de estados, cada uno de ellos corresponde a una situacin del entorno, que tendr
asociado un comportamiento particular del personaje y se establecen las transiciones
que pueden ocurrir entre ellos y las condiciones o reglas que se deben cumplir
para pasar de un estado a otro de una manera determinista, lo cual implica que sea
predecible el comportamiento.
En la gura 28.76 se muestra cual es el esquema de funcionamiento del agente
reactivo basado en un atmata de estados nitos (FSM (Finite State Machine)). El
funcionamiento de este agente se basa en a partir del estado actual, almacenado en
la memoria, comprobar que regla de las posibles reglas que le permiten cambiar de
estado puede ser disparada en funcin de las percepciones del entorno recibidas. El
disparo de esa regla le permite cambiar de estado y ejecutar la accin asociada al
nuevo estado que se alcance. Una vez ejecutada esta accin hay que convertir el estado
alcanzado como estado actual. Conviene notar que solo ser posible disparar una nica
regla y que por lo tanto en cada instante el autmata se encontrar en un nico estado.
La eleccin de los autmatas como arquitectura de funcionamiento se debe a que:

Son simples y rpidos de codicar. Existen muchas formas de programar un


autmata todas ellas razonablemente simples de programar.
Son fciles de depurar. Al estar formados por estados es fcil de depurar ya
que se podra aadir codigo de depuracin en cada uno de ellos.
Tienen poca carga computacional. No poseen procesos computacionales
complejos, su funcionamiento es muy simple.
Son intuitivos. Se asemeja a la forma de pensar o actuar de los humanos.
Son exibles. Pueden ser fcilmente ajustados por el programador para propor-
C28

cionar el comportamiento requerido por el diseador del juego.


[890] CAPTULO 28. INTELIGENCIA ARTIFICIAL

function ESQUELETO-AGENTE-REACTIVO-FMS (percepcion) returns accion


static: reglas, estados
dinamic: memoria

regla ELEGIR-REGLA(percepcion,memoria,reglas);
estado REALIZAR-CAMBIO-ESTADO(memoria,regla);
accion EJECUTAR-ACCION(estado);
memoria ACTUALIZAR-ESTADO(estado);
return accion;

Figura 28.76: Visin abstracta del funcionamiento de un agente reactivo basado en un atmata de estados
nitos (FSM).

No es objetivo de esta seccin dar una denicin formal del autmata, para el
propsito de este curso basta con saber que es un modelo de comportamiento de un
sistema o un objeto complejo y que se compone de estados, eventos y acciones. Un
estado representa una situacin. Un evento es un suceso que provoca el cambio de un
estado a otro. Una accin es un tipo de comportamiento.

No hay que olvidar que en un instante el autmata solo podr estar en un


nico estado.

En realidad, lo que se usa en los videojuegos para modelizar comportamientos


no son autmatas de estados nitos, ya que la nalidad de estos autmatas es
la de reconocer lenguajes regulares (los lenguajes formales ms simples segn la
Clasicacin de Chomsky). Este tipo de autmatas no poseen salida asociada. Lo
nico que ofrecen es una decisin sobre si una cadena, formada por elementos
tomados de un alfabeto, dada como entrada, pertenece o no al lenguaje regular que
el autmata reconoce.
El funcionamiento de este tipo de autmatas est dirigido por una funcin de
transicin, que a partir de un estado y un elemento de la cadena que se est analizando,
desplaza a otro estado del autmata. Si tras leer todos los caracteres a partir del estado
inicial del autmata se detiene en un estado nal o de aceptacin, entonces la cadena
es del lenguaje en caso contrario no lo es (vea la gura 28.77).
En los videojuegos se usarn dos tipos especiales de autmatas nitos para
modelizar comportamientos, las mquinas de Moore y las mquinas de Mealy.
Estas mquinas o autmatas intentan aumentar la expresividad de los autmatas nitos
deterministas generando una salida en funcin de su estado actual y de la entrada. La
mquina de Moore tendr asociada las salidas al estado y las de Mealy las tendrn
asociadas a las transiciones (vea la gura 28.78).
28.5. Diseo basado en agentes [891]

a,b
a,b,c

a q1 c
q3

b,c
q2 a,b,c
q0

Figura 28.77: Autmata que reconoce el lenguaje a(a|b) |(b|c)(a|b|c) , p.e. la cadena abb pertenecera
al lenguaje que reconoce el autmata, sin embargo la cadena acc no pertenecera a dicho lenguaje.

Shadow (Blinky)

Si han pasado 10 seg

Si han pasado
2 seg
Persig iendo Huyendo
Inactivo
Perseguir(); Si Comecocos ha
Huir();
Comido una pastilla
o punto especial

Si Comecocos lo traga

Figura 28.78: Comportamiento del fantasma Shadow (Blinky) del juego del Comecocos (PacMan),
modelado por medio de una mquina de Moore.

Se emplearn las mquinas de Moore o Mealy para modelar los comporta-


mientos en los videojuegos. Estas mquinas son autmatas de estados nitos
determinista que poseen salida ms all de una decisin sobre si una cadena
C28

pertenece o no al lenguaje que reconoce el autmata.


[892] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Un ejemplo de uso de autmatas para modelar comportamientos en videojuegos


es el PacMan o como se denomin en Espaa, el Comecocos. En este videojuego
clsico creado por Toru Iwatani de la empresa Namco, se emplearon autmatas muy
simples para modelar el comportamiento de los fantasmas. Los fantasmas presentaban
dos comportamientos principales: Persiguiendo y Huyendo, y un tercero menos
importante Inactivo. En el primero el fantasma persegua al Comecocos para
comerlo y en el segundo tena que huir del Comecocos ya que este poda tragarlo.
El estado Inactivo ocurra al comienzo del juego y despus de haber sido tragado,
donde los fantasmas se regeneran en una caja situada en el centro del laberinto durante
un tiempo. Existan cuatro fantasmas (Shadow (Blinky), Speedy (Pinky), Bashful
(Inky) y Pokey (Clyde)) de colores rojo, rosa, cyan y naranja. Cada uno de ellos
tena sus propias caractersticas en la forma de perseguir y huir, por ejemplo, Blinky
era muy rpido y tena la habilidad de encontrar al Comecocos en el escenario, sin
embargo Inky era muy lento y muchas veces evitaba el encuentro con el Comecocos.
El autmata tendr por lo tanto tres estados uno para regenerarse, otro para huir
y el tercero para perseguir, el cambio del estado Perseguiendo a Huyendo se
producir cuando el Comecocos coma unos puntos especiales de tamao mayor (o
en ingls Power Pellets) y el contrario cuando pase un determinado tiempo en ese
estado de huida. En la gura 28.78 se muestra la mquina de Moore para modelar el
comportamiento del fantasma Shadow, las funcionalidades Perseguir y Huir sern
propias de este fantasma.
Otros ejemplos de uso de los autmatas en videojuegos, son por ejemplo en los
juegos FPS, tipo Quake, donde se emplean para modelar los comportamientos de
los personajes virtuales que aparecen, con estados como por ejemplo Huyendo,
Disparando o Peleando en los que el personaje huir de algo, disparar algn
arma o pelear de algn modo contra alguien. Incluso en este tipo de videojuegos
se pueden usar los autmatas para dotar de un comportamiento a las armas que se
emplean.
En juegos deportivos se emplean para simular el comportamiento de los jugadores
virtuales, con estados como Disparando, Driblando, Corriendo, Parando,
. . . Tambin se usan los autmatas para modelar el comportamiento del equipo,
Atacando, Defendiendo,. . .
En las prximas secciones se profundizar en la idea de uso de mquinas de
estado nito en la modelizacin de comportamientos y se mostrar como se pueden
realizar las implementaciones de este tipo de autmatas. Se estudiar como este tipo de
autmatas simples se hacen ms difciles de utilizar cuando se ponen en juego muchos
estados y se trabaja con requisitos de rehusabilidad. Se mostrar como solucin una
variante de estos para controlar los comportamientos: los autmatas de estados nitos
jerrquicos.

Implementacin de autmatas nitos deterministas

Antes de mostrar como se podra hacer la implementacin de agentes para su uso


en videojuegos, se va a presentar distintas formas de realizar la implementacin de un
autmata de estados nito. El objetivo de los ejemplos que se muestran a continuacin
es simplemente describir distintas formas de implementar autmatas, no se busca
obtener las implementaciones de la mejor calidad posible.
La primera forma que se va a presentar es mediante el uso de funciones (se
construye una funcin por cada estado del autmata) y por medio de estructuras
de seleccin mltiple (como el switch en C) se establecen las transiciones que
ocurren entre los estados. A continuacin se muestra el cdigo en lenguaje C de la
implementacin del autmata de la gura 28.77 realizado de esta forma.
28.5. Diseo basado en agentes [893]

La implementacin de un autmata se puede hacer, de una manera ms eciente


que la expuesta antes, empleando una matriz. La matriz tendr las las etiquetadas
con los estados del autmata y las columnas con los smbolos del alfabeto de entrada.
Cada interseccin [i, j] informa del estado al que pasar el autmata si estando en el
estado i se lee el smbolo j.

Listado 28.23: Autmata de la gura 28.77 (sentencias switch).


1 #include <stdio.h>
2
3 int estado_q0(void) {
4 char c;
5 c=getchar();
6 switch (c) {
7 case a: return estado_q1();
8 break;
9 case b:
10 case c: return estado_q2();
11 break;
12 default: return 0;
13 }
14 }
15
16 int estado_q1(void) {
17 int c;
18 c=getchar();
19 while ((c==a) || (c==b)) c=getchar();
20 switch (c) {
21 case \n: return 1;
22 break;
23 case c: return estado_q3();
24 break;
25 default: return 0;
26 }
27 }
28
29 int estado_q2(void) {
30 int c;
31 c=getchar();
32 while ((c==a) || (c==b) || (c==c)) c=getchar();
33 switch (c) {
34 case \n: return 1;
35 break;
36 default: return 0;
37 }
38 }
39
40 int estado_q3(void) {
41 int c;
42 c=getchar();
43 while ((c==a) || (c==b) || (c==c)) c=getchar();
44 switch (c) {
45 case \n: return 0;
46 break;
47 default: return 0;
48 }
49 }
50
51 int main (int argc,char *argv[]) {
52 if(estado_q0())
53 printf("Es una cadena del Lenguaje\n");
54 else
55 printf("\nNo es una cadena del Lenguaje \n");
56 return 1;
57 }
C28

La forma ms general de la matriz se podra denir de la siguiente forma:


[894] CAPTULO 28. INTELIGENCIA ARTIFICIAL

estado_siguiente = matriz[estado_actual][entrada]
Esto no es ms que una implementacin de la tabla de transiciones del autmata.
La matriz asociada al autmata de la gura 28.77, es la que se muestra en la gura
28.79. El cdigo en lenguaje C, que corresponde a la implementacin del autmata de
la gura 28.77 de esta forma, es el que se muestra a continuacin.
a b c
Listado 28.24: Autmata de la gura 28.77 (tabla transicin).
q0 q1 q2 q2
1 #include <stdio.h>
2 q1 q1 q1 q3
3 /* Se define la Tabla de Transicion del automata */
4 q2 q2 q2 q2
5 #define N 4 //numero de filas de la tabla de transicion
6 #define M 3 //numero de columnas de la tabla de transicion q3 q3 q3 q3
7
8 int TablaTrans[N][M] = { 1,2,2, Figura 28.79: Tabla de Transicin
9 1,1,3, para el autmata de la gura 28.77.
10 2,2,2,
11 3,3,3};
12
13 int AFD() {
14 int estado_actual, estado_siguiente;
15 int c;
16
17 estado_actual=0;
18 c=getchar();
19
20 while (c!=\n) {
21 switch (c) {
22 case a: estado_siguiente=TablaTrans[estado_actual][0];
23 break;
24 case b: estado_siguiente=TablaTrans[estado_actual][1];
25 break;
26 case c: estado_siguiente=TablaTrans[estado_actual][2];
27 break;
28 default: return 0;
29 }
30 if (c!=\n) estado_actual=estado_siguiente;
31 c=getchar();
32 }
33 if ((estado_actual==1) || (estado_actual==2)) return 1;
34 else return 0;
35 }
36
37 int main (int argc,char *argv[]) {
38 if(AFD())
39 printf("Es una cadena del Lenguaje\n");
40 else
41 printf("\nNo es una cadena del Lenguaje \n");
42 return 1;
43 }

Otra posibilidad es emplear una sentencia switch para seleccionar el estado y


sentencias if-else para determinar las transiciones a partir de cada uno de los estados.
En el siguiente cdigo en C++ se muestra como sera la implementacin del autmata
de la gura 28.77.

Listado 28.25: Autmata de la gura 28.77 (sentencias condicionales).


1 #include <iostream>
2 #include <string>
3
4 using namespace std;
5
6 enum StateType {q0, q1, q2, q3, error};
28.5. Diseo basado en agentes [895]

7
8 int main() {
9 string palabra;
10 int n, i, mensaje=0;
11 StateType CurrentState;
12
13 cout << "Introduzca la cadena a analizar: "; cin >> palabra;
14 n = palabra.length(); i = 0;
15 CurrentState = q0;
16 while (i<=n-1) {
17 switch(CurrentState) {
18
19 case q0:
20 if (palabra[i]==a) CurrentState=q1;
21 else if (palabra[i]==b || palabra[i]==c) CurrentState=q2;
22 else CurrentState=error;
23 break;
24
25 case q1:
26 if (palabra[i]==a || palabra[i]==b) CurrentState=q1;
27 else if (palabra[i]==c) CurrentState=q3;
28 else CurrentState=error;
29 break;
30
31 case q2:
32 if ((palabra[i]==a) || (palabra[i]==b) || (palabra[i]==c
))
33 CurrentState=q2;
34 else CurrentState=error;
35 break;
36
37 case q3:
38 if ((palabra[i]==a) || (palabra[i]==b) || (palabra[i]==c
))
39 CurrentState=q3;
40 else CurrentState=error;
41 break;
42
43 default:
44 if (!mensaje) {
45 cout << "Error alfabeto no valido" << endl;
46 cout << palabra[i-1] << endl;
47 mensaje=1;
48 }
49 }
50 i++;
51 }
52
53 if ((CurrentState==q1) || (CurrentState==q2))
54 cout << "La cadena " + palabra + " pertenece al lenguaje." <<
endl;
55 else cout << "La cadena " + palabra + " NO pertenece al lenguaje.
" << endl;
56
57 return 0;
58 }

Y para nalizar se mostrar como se puede realizar la implentacin de un autmata


nito determinista empleando la programacin orientada a objetos. En el siguiente
cdigo en C++ se muestra como se podra realizar en dicho lenguaje.

Listado 28.26: Autmata de la gura 28.77 (objetos).


1 #include <iostream>
C28

2 #include <string>
3
4 using namespace std;
5
[896] CAPTULO 28. INTELIGENCIA ARTIFICIAL

6 enum StateType {q0, q1, q2, q3, error};


7
8 string palabra;
9 int n, i, mensaje = 0;
10
11 class automata {
12 StateType CurrentState;
13
14 public:
15 void Inicializar(void);
16 void ChangeState(StateType State);
17 void UpdateState(void);
18 StateType Informar(void);
19 };
20
21 void automata::Inicializar(void) {
22 CurrentState=q0;
23 }
24
25 StateType automata::Informar(void) {
26 return CurrentState;
27 }
28
29 void automata::ChangeState(StateType State) {
30 CurrentState=State;
31 }
32
33 void automata::UpdateState(void) {
34 switch(CurrentState) {
35 case q0:
36 if (palabra[i]==a) ChangeState(q1);
37 else if (palabra[i]==b || palabra[i]==c) ChangeState(q2);
38 else ChangeState(error);
39 break;
40
41 case q1:
42 if (palabra[i]==a || palabra[i]==b) ChangeState(q1);
43 else if (palabra[i]==c) ChangeState(q3);
44 else ChangeState(error);
45 break;
46
47 case q2:
48 if ((palabra[i]==a) || (palabra[i]==b) || (palabra[i]==c)
)
49 ChangeState(q2);
50 else ChangeState(error);
51 break;
52
53 case q3:
54 if ((palabra[i]==a) || (palabra[i]==b) || (palabra[i]==c)
)
55 ChangeState(q3);
56 else ChangeState(error);
57 break;
58
59 default:
60 if (!mensaje) {
61 cout << "Error alfabeto no valido" << endl;
62 cout << palabra[i-1] << endl;
63 mensaje=1;}
64 }
65 }
66
67 int main() {
68 automata ejemplo;
69 StateType State;
70
71 cout<<"Introduzca la cadena a analizar: ";
72 cin>>palabra;
73 n = palabra.length();
28.5. Diseo basado en agentes [897]

74 i = 0;
75 ejemplo.Inicializar();
76
77 while (i<=n-1) {
78 ejemplo.UpdateState();
79 i++;
80 }
81
82 State=ejemplo.Informar();
83 if ((State==q1) || (State==q2))
84 cout << "La cadena " + palabra + " pertenece al lenguaje." <<
endl;
85 else cout << "La cadena " + palabra + " NO pertenece al lenguaje.
" << endl;
86 return 0;
87 }

Se ha creado una clase llamada automata, que se usar para crear un objeto
autmata. Esta clase contiene una variable privada, CurrentState, que almacenar
el estado en el que se encuentra el autmata en cada momento. A esta variable so-
lo se podr acceder desde las funciones miembro de la clase, que son: Inicializar(),
ChangeState(), UpdateState() y Informar(). La funcin Inicializar(() es una fun-
cin empleada para poner al autmata en el estado inicial. La funcin ChangeState()
es empleada para cambiar el estado en el que se encuentra el autmata.
La funcin Informar() devuelve el estado en el que est el autmata en un instante
cualquiera. Por ltimo, la funcin UpdateState() es la que implementa la tabla de
transiciones del autmata. Para conseguir el funcionamiento deseado lo que se har
ser crear un objeto de la clase automata, posteriormente ser inicializado, luego se
consultar la tabla de transiciones (ejecuciones de la funcin UpdateState()) mientras
la cadena que se est analizando tenga elementos, momento en el que se consultar el
estado en el que se queda el autmata tras leer toda la cadena para chequear si es un
estado de aceptacin o no.
En todas las implementaciones, la cadena de entrada ser aceptada si, una vez
leda, el estado que se alcanza es uno perteneciente al conjunto de estados nales. En
caso contrario, la cadena no ser un elemento del lenguaje que reconoce el autmata.

28.5.4. Implementacin de agentes basados en autmatas


En el campo del desarrollo de videojuegos, como ya ha sido comentado, se suele
trabajar con unas mquinas especiales, las mquinas de Moore o Mealy, que extienden
los autmatas de estados nitos (FSM) para dotarlos de mayor expresividad. En la
gura 28.80 se muestra un autmata del tipo de los que se usarn en el diseo de
videojuegos.
Los ci que aparecen en el autmata mostrado son estados, cada uno de los cuales
tiene asociado una funcionalidad, o en el caso de los videojuegos un comportamiento,
diferente. El autmata que se muestra en la gura 28.80 tiene 4 estados y su
funcionamiento contar por lo tanto con otros tantos comportamientos {c0 , c1 , c2 , c3 }.
Las rij son reglas que son disparadas ante estmulos que ocurren en el entorno y que le
permiten cambiar de estado y por lo tanto modicar su comportamiento. Por ejemplo,
si estando en el estado c0 se satisfacen unas condiciones en el entorno que permite el
disparo de la regla r01 , esto provocar un cambio de estado, del estado c0 al estado
c1 y por lo tanto un cambio de comportamiento. Tambin se puede observar como
desde el estado c3 podran dispararse dos reglas diferentes r31 y r32 , en estos casos
C28

los estmulos que disparan cada regla deben ser diferentes, se recuerda la necesidad
de estar en un nico estado en cada instante del funcionamiento del autmata (son
deterministas).
[898] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Los juegos que son desarrollados usando lenguajes de alto nivel, como por ejemplo
C o C++, tpicamente almacenan todos los datos relacionados a cada agente en una
estructura o clase. Esta estructura o clase puede contener variables para almacenar
informacin como la posicin, vida, fuerza, habilidades especiales, y armas, entre
muchas otras. Por supuesto, junto a todos estos elementos, en la estructura o clase
tambin se almacenar el estado actual del autmata, y ser ste el que determine el
comportamiento del agente.
En el siguiente cdigo se muestra cmo se podra almacenar los datos que denen
al agente dentro de una clase.

Listado 28.27: Clase contenedora de los datos de un agente.


1 class AIEntity
2 {
3 public:
4 int type;
5 int state;
6 int row;
7 int column;
8 int health;
9 int strength;
10 int intelligence;
11 int magic;
12 };

c r32
r01
r11 r31
c0 c3

r21
c2

Figura 28.80: Forma de los Autmatas con los que se va a trabajar

Para implementar este tipo de mquinas de estado nito dotadas con salida, o
ms concretamente agentes como los comentados en la seccin anterior para su uso
en videojuegos, existen varias alternativas. stas estn basadas en las que ya se
han presentado en la seccin anterior pero con mejoras. La manera ms simple es
por medio de mltiples sentencias If-Else o la mejora inmediata al uso de estas
sentencias, por medio de una sentencia switch. La sentencia switch(estado_actual)
es una sentencia de seleccin. Esta sentencia permite seleccionar las acciones a
realizar de acuerdo al valor que tome la variable estado_actual, habr un case
para cada estado posible. La forma general de usar esta sentencia para implementar
una mquina de estados es la siguiente:

Listado 28.28: Implementacin del autmata por medio de sentencias switch.


28.5. Diseo basado en agentes [899]

1 switch (estado_actual)
2 {
3 case estado 1:
4 Funcionalidad del comportamiento del estado 1;
5 Transiciones desde este estado;
6 break;
7 case estado 2:
8 Funcionalidad del comportamiento del estado 2;
9 Transiciones desde este estado;
10 break;
11 case estado N:
12 Funcionalidad del comportamiento del estado N;
13 Transiciones desde este estado;
14 break;
15 default:
16 Funcionalidad por defecto;
17 }

A modo de ejemplo, se muestra como sera la implementacin en C++ del


autmata que simula el comportamiento del fantasma Shadow del videojuego del
ComeCocos (vea la gura 28.78), partiendo de la implementacin ms avanzada que
se present en la seccin anterior, es decir con programacin orientada a objetos y
por medio de una sentencia switch y sentencias if-else. No se entra en detalles
sobre como se implementaran las funcionalidades asociadas a cada estado, aunque
se recuerda que esas seran especcas de cada fantasma. Tampoco se muestra como
sera la implementacin de la clase Ghost.

Listado 28.29: Implementacin del autmata que simula el comportamiento del fantasma en el
juego del ComeCocos.
1 enum StateType{Inactivo, Huyendo, Persiguiento};
2
3 void Ghost::UpdateState(StateType CurrentState, GhostType GType)
4 {
5
6 switch(CurrentState)
7 {
8
9 case Huyendo:
10 Huir(GType);
11 if (PacManTraga())
12 {
13 ChangeState(Inactivo);
14 }
15 else if (Temporizador(10)) ChangeState(Persiguiendo);
16 break;
17
18 case Persiguiendo:
19 Perseguir(GType);
20 if (PacManComePastilla()) ChangeState(Huyendo);
21 break;
22
23 case Inactivo:
24 Regenerar();
25 if (Temporizador(2)) ChangeState(Persiguiendo);
26 break;
27 }
28 }
Figura 28.81: Los juegos de fran-
cotiradores son juegos con gran Otro ejemplo ms sobre esta forma de implementar los autmatas, se muestra en
aceptacin (p.e. Sniper Elite V2), el siguiente cdigo, que corresponde a la implementacin de la mquina de estados
probablemente por estar conectados
C28

con una niez en la que juegos co-


nitos que podra simular el comportamiento de un soldado que se enfrenta a un
mo el escondite o el pilla-pilla eran francotirador (el jugador) en un juego FPS, tipo Sniper Elite V2 (vea la gura 28.81).
toda una aventura.
[900] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Como se puede observar en el cdigo existen cinco estados: Huyendo, Patrullando,


Persiguiendo, Disparando e Inactivo. El estado inicial podra ser Patrullando. En este
estado el soldado estar observando el entorno buscando al francotirador (el jugador).
Si lo encuentra lo perseguir (pasando al estado Persiguiendo). Si por contra recibe
un ataque huir (pasando al estado Huyendo). En el estado Persiguiendo intentar
poner a tiro al francotirador, si lo consigue le disparar, pasando al estado Disparando.
Pero si en la persecucin, es el francotirador el que le amenaza por tener una mejor
posicin, provocar que el soldado pase al estado Huyendo para evitar el ataque. En el
estado Disparando, el soldado dispara si cree que puede abatir al francotirador, si no,
lo persigue hasta volver a conseguirlo. En el estado Huyendo un soldado huir hasta
que logre ponerse a salvo, momento en el que pasa al estado Patrullando. Si no logra
ponerse a salvo es porque haya sido cazado por el francotirador por lo que pasar a
un estado de inactividad (estado Inactivo). En este estado el soldado se regenerar y
pasado un tiempo se posicionar sobre el escenario, donde volver a patrullar en el
estado Patrullando.

Listado 28.30: Implementacin del autmata que simula el comportamiento de un soldado que
se enfrenta a un francotirador en un juego de tipo FPS (p.e. Sniper Elite V2)
1 enum StateType{Patrullando, Persiguiendo, Disparando, Huyendo,
Inactivo};
2
3 void Soldier::UpdateState(StateType CurrentState)
4 {
5 switch(CurrentState)
6 {
7 case Patrullando:
8 Patrullar();
9 if (ObjetivoLocalizado()) ChangeState(Persiguiendo);
10 else (AtaqueRecibido()) ChangeState(Huyendo);
11 break;
12
13 case Persiguiendo:
14 Perseguir();
15 if (ObjetivoVulnerable()) ChangeState(Disparando);
16 else ChangeState(Huyendo);
17 break;
18
19 case Disparando:
20 if (ObjetivoAbatible()) Disparar();
21 else ChangeState(Persiguiendo);
22 break;
23
24 case Huyendo:
25 Huir();
26 if (PuestoaSalvo()) ChangeState(Patrullando);
27 else if (Abatido()) ChangeState(Inactivo);
28 break;
29
30 case Inactivo:
31 Regenerar();
32 if (TranscurridoTiempo()) ChangeState(Patrullando);
33 break;
34 }
35 }

Aunque a primera vista, este enfoque puede parecer razonable por lo menos en
autmatas no complejos, cuando se aplica a juegos, o mejor an, a comportamientos
de objetos ms complejos, la solucin presentada se convierte en un largo y tortuoso
camino. Cuantos ms estados y condiciones se tengan que manejar, ms riesgo hay de
convertir este tipo de estructura en cdigo spaghetti (vea la gura 28.82), por lo que
el ujo del programa ser difcil de entender y la tarea de depuracin una pesadilla.
Adems, este tipo de implementacin es difcil de extender ms all del propsito de
28.5. Diseo basado en agentes [901]

Estado Condicin Estado_Destino


Inactivo Regenerado Persiguiendo
Persiguiendo PastillaComida Huyendo
Huyendo TranscurridoTiempo Persiguiendo
Huyendo Comido Inactivo

Cuadro 28.6: Tabla de Transicin para el autmata implementado para simular el comportamiento del
fantasma en el juego del Comecocos (vea Figura 28.78.

su diseo original. Esta exiblidad en el diseo es una caracterstica deseable ya que


las extensiones son a menudo muy frecuentes en este campo. Con frecuencia habr
que ajustar el comportamiento del agente (por ende el autmata) para hacer frente
a las circunstancias no planicadas y conseguir un comportamiento como el que se
esperaba.
Otro aspecto que hace poco interesante este tipo de implementacin es la
asociacin de eventos con los estados, es decir puede que interese hacer que ocurra
algo cuando se entre o salga de un estado, Por ejemplo, en el juego del Comecocos,
cuando el Comecocos come alguna de las pastillas especiales, se puede querer (de
hecho es as) que los fantasmas entren en el estado de huda y cambien su color
a azul. La inclusin de este tipo de eventos asociados a la entrada y salida en esta
implementacin por medio de sentencias switch/if-else implican que ocurran en el
cambio de estado ms que en el estado propiamente dicho, y complicaran an ms el
Figura 28.82: Cdigo spaghetti. cdigo.
Trmino peyorativo para los pro-
gramas de computacin que tienen
una estructura de control de ujo Implementacin basada en tabla de transicin
compleja e incomprensible.
El uso de la tabla de transicin con alguna modicacin podra tambin ser til
en la implementacin de los agentes. La forma de implementar un autmata nito
determinista empleando su tabla de transicin ya ha sido presentada en la seccin
anterior. Esta tabla almacenar los cambios de estado que ocurren cuando estando
en un determinado estado se satisface alguna condicin. La Tabla 28.6 muestra un
ejemplo de como sera esta tabla para modelar el comportamiento del fantasma en el
juego del Comecocos.
A continuacin se muestra como sera la implementacin del autmata que modela
el comportamiento del fantasma en el juego del Comecocos por medio de la tabla de
transiciones (vea la Tabla 28.6 y gura 28.78). Se han aadido las funcionalidades
del fantasma y un mecanismo para la recepcin de estmulos de una forma muy
simplicada, con el objetivo de permitir la comprobacin de su funcionamiento.
La tabla ser consultada por el agente cada vez que reciba estmulos, stos sern
chequeados para ver si le permite realizar alguna transicin con el objeto de cambiar
de estado. Cada estado podra ser modelado como un objeto independiente del agente,
ofreciendo de esta forma una arquitectura limpia y exible.

Listado 28.31: Implementacin del autmata que simula el comportamiento del fantasma en el
juego del Comecocos
1 #include <iostream>
2 #include <string>
3
4 using namespace std;
C28

5
6 #define N 4 //numero de filas de la tabla de transicion
7 #define M 4 //numero de columnas de la tabla de transicion
[902] CAPTULO 28. INTELIGENCIA ARTIFICIAL

8
9 enum StateType {inactivo, persiguiendo, huyendo, error};
10 enum Rule {regenerado, pastillacomida, transcurridotiempo, comido};
11
12 class ghost {
13
14 StateType CurrentState;
15
16 /* Se define la Tabla de Transicion del automata */
17 StateType TablaTrans[N][M];
18
19 public:
20 void Inicializar(void);
21 void ChangeState(StateType State);
22 void UpdateState(Rule Transicion);
23 StateType Informar(void);
24 void Huir(void);
25 void Perseguir(void);
26 void Regenerar(void);
27 void Error(void);
28 };
29
30 void ghost::Inicializar(void) {
31 CurrentState=inactivo;
32 TablaTrans[0][0] = persiguiendo; TablaTrans[0][1] = error;
33 TablaTrans[0][2] = error; TablaTrans[0][3] = error;
34 TablaTrans[1][0] = error; TablaTrans[1][1] = huyendo;
35 TablaTrans[1][2] = error; TablaTrans[1][3] = error;
36 TablaTrans[2][0] = error; TablaTrans[2][1] = error;
37 TablaTrans[2][2] = persiguiendo; TablaTrans[2][3] = inactivo;
38 TablaTrans[3][0] = error; TablaTrans[3][1] = error;
39 TablaTrans[3][2] = error; TablaTrans[3][3] = error;
40 }
41
42 StateType ghost::Informar(void) {
43 return CurrentState;
44 }
45
46 void ghost::ChangeState(StateType State) {
47 CurrentState=State;
48 }
49
50 void ghost::UpdateState(Rule Transicion) {
51 StateType NextState;
52
53 NextState=TablaTrans[CurrentState][Transicion];
54 ChangeState(NextState);
55
56 switch(CurrentState) {
57 case huyendo:
58 Huir();
59 break;
60 case persiguiendo:
61 Perseguir();
62 break;
63 case inactivo:
64 Regenerar();
65 break;
66 default: Error();
67 }
68 }
69
70 /* Funciones de prueba */
71 void ghost::Huir(void) {
72 cout << "El fantasma huye!" << endl;
73 }
74
75 void ghost::Perseguir(void) {
76 cout << "El fantasma te persigue!" << endl;
77 }
78
28.5. Diseo basado en agentes [903]

79 void ghost::Regenerar(void) {
80 cout << "El fantasma se regenera!" << endl;
81 }
82
83 void ghost::Error(void) {
84 cout << "El fantasma esta en un estado de error!" << endl;
85 }
86
87 int main() {
88 ghost shadow;
89 StateType State;
90 string palabra;
91 shadow.Inicializar();
92 cout<<"Introduzca la regla: ";
93 cin>>palabra;
94
95 while (palabra.compare("salir")!=0) {
96 if (palabra.compare("regenerado")==0)
97 shadow.UpdateState(regenerado);
98 else if (palabra.compare("pastillacomida")==0)
99 shadow.UpdateState(pastillacomida);
100 else if (palabra.compare("transcurridotiempo")==0)
101 shadow.UpdateState(transcurridotiempo);
102 else if (palabra.compare("comido")==0)
103 shadow.UpdateState(comido);
104 else cout << "Accion no valida" << endl;
105 cout<<"Introduzca la regla: ";
106 cin>>palabra;
107 }
108
109 State=shadow.Informar();
110
111 if (State==error)
112 cout << "El fantasma no funciona bien." << endl;
113 else cout << "El fantasma funciona perfecto." << endl;
114
115 return 0;
116 }

Implementacin basada en patrones de diseo

Hasta ahora, la mayora de las implementaciones de autmatas que se han presen-


tado hacen uso de la sentencia switch, que cuando son usadas para modelar compor-
tamientos no muy complejos parece ms que adecuado. Sin embargo esta sentencia,
que permite una programacin tipo goto, se complica cuando los comportamientos
son complejos. En estos casos se requerirn muchas ramas, una por estado, y cada una
de ellas tendr largas sentencias condicionales para establecer las transisiones. Cada
vez que deba ser cambiada la sentencia switch, porque sea necesario aadir o eliminar
algn estado, por lo general acarrear un montn de cambios en el resto de la imple-
mentacin. A continuacin se muestra una posible solucin, basada en objetos, para la
implementacin de autmatas, y por lo tanto de agentes, que evita la sentencia switch.
El n que se prentende lograr con la sentencia switch es controlar el comporta-
miento en funcin del estado. En el mundo orientado a objetos un cambio de com-
Patrn de diseo portamiento implica un tipo diferente de objeto. Por lo tanto, en este caso, lo que se
necesita es un objeto que parezca cambiar su tipo en funcin de su estado interno. Lo
Un patrn de diseo es una solucion
simple y elegante a un problema es-
que se va a hacer es intentar describir el comportamiento de un objeto como una colec-
pecco y comn en el diseo orien- cin de estados, de manera que sea el propio objeto el que maneje su comportamiento
tado a objetos, que est basada en mediante transiciones de un estado a otro. Para ello se va a hacer uso del patrn de
la experiencia y su funcionamiento diseo State, cuyo diagrama UML es mostrado en la gura 28.83. El patrn de diseo
C28

ha sido demostrado. Su objetivo es


permitir la construccin de software state es una solucin al problema de cmo hacer que un comportamiento dependa del
orientado a objetos exible y fcil estado.
de mantener.
[904] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Las clases y/o objetos participantes en este patrn son:

Context Class, esta clase dene la interfaz, y es la responsable de mantener el


estado actual. Esta ser la clase que use la aplicacin.
State Class, esta clase dene la interfaz para encapsular el comportamiento
asociado con un estado particular de la clase Context.
ConcreteState Class, cada una de las subclases que implementa un comporta-
miento asociado con un estado del contexto.

Figura 28.83: Diagrama UML del Patrn de diseo State.

La idea de este patrn es tener una clase, Contexto (Context class), que tendr
un comportamiento dependiendo del estado actual en el que se encuentre, es decir un
comportamiento diferente segn su estado. Tambin tiene una clase Estado abstracta
(State Class) que dene la interfaz pblica de los estados. En ella, se pondrn
todas las funciones del Contexto cuyo comportamiento puede variar. Luego hay una
implementacin de los estados concretos, que heredarn de la clase Estado abstracto.
La clase Contexto, poseer un puntero sobre el estado actual, que ser almacenado en
una variable miembro de la clase. Cuando se desea que el Contexto cambie de estado,
solo hay que modicar el estado actual.
El uso de este patrn implicar:

Denir una clase contexto para presentar una interfaz simple al exterior.
Denir una clase base abstracta estado.
Representar los diferentes estados del autmata como clases derivadas de la
clase base estado.
Denir la conducta especca de cada estado en la clase apropiada de las
denidas en el paso anterior (derivadas de la clase abstracta estado).
Mantener un puntero al estado actual en la clase contexto.
Para cambiar el estado en el que se encuentra el autmata, hay que cambiar el
puntero hacia el estado actual.

La implementacin del autmata que simula el comportamiento del fantasma en el


juego del Comecocos empleando el patrn de diseo State se muestra en el siguiente
listado de cdigo.
28.5. Diseo basado en agentes [905]

El patrn de diseo State no especica donde son denidas las transiciones entre
estados. Esto se puede hacer de dos formas diferentes: en la clase Context, o en cada
estado individual derivado de la clase State. En el cdigo mostrado anteriormente
se ha realizado de esta segunda forma. La ventaja de hacerlo as es la facilidad que
ofrece para aadir nuevas clases derivadas de la clase State. Su inconveniente es
que cada estado derivado de la clase State tiene que conocer como se conecta con
otras clases, lo cual introduce dependencias entre las subclases.
A modo de resumen, conviene indicar que no hay una nica manera de decribir
estados. Dependiendo de la situacin podra ser suciente con crear una clase abstracta
llamada State que posea unas funciones Enter(), Exit() y Update(). La creacin de
los estados particulares del autmata que se quiera implementar implicara crear una
subclase de la clase State por cada uno de ellos.

Listado 28.32: Implementacin del autmata que simula el comportamiento del fantasma en el
juego del Comecocos.
1 #include <iostream>
2 #include <string>
3
4 using namespace std;
5
6 class FSM;
7
8 // Clase base State.
9 // Implementa el comportamiento por defecto de todos sus mtodos.
10 class FSMstate {
11 public:
12 virtual void regenerado( FSM* ) {
13 cout << " No definido" << endl; }
14 virtual void pastillacomida( FSM* ) {
15 cout << " No definido" << endl; }
16 virtual void transcurridotiempo( FSM* ) {
17 cout << " No definido" << endl; }
18 virtual void comido( FSM* ) {
19 cout << " No definido" << endl; }
20 protected:
21 void changeState(FSM*, FSMstate*);
22 };
23
24 // Automa context class. Reproduce el intefaz de la clase State y
25 // delega todo su comportamiento a las clases derivadas.
26 class FSM {
27 public:
28 FSM();
29 void regenerado() { _state->regenerado(this); }
30 void pastillacomida() { _state->pastillacomida(this); }
31 void transcurridotiempo() { _state->transcurridotiempo(this); }
32 void comido() { _state->comido( this ); }
33
34 private:
35 friend class FSMstate;
36 void changeState( FSMstate* s ) { _state = s; }
37 FSMstate* _state;
38 };
39
40 void FSMstate::changeState( FSM* fsm, FSMstate* s ) {
41 fsm->changeState( s ); }
42
43 // Clase derivad de State.
44 class Inactivo : public FSMstate {
45 public:
46 static FSMstate* instance() {
if ( ! _instance ) _instance = new Inactivo;
C28

47
48 cout << "El fantasma se regenera!" << endl;
49 return _instance; };
50 virtual void regenerado( FSM* );
[906] CAPTULO 28. INTELIGENCIA ARTIFICIAL

51 private:
52 static FSMstate* _instance; };
53
54 FSMstate* Inactivo::_instance = 0;
55
56 class Persiguiendo : public FSMstate {
57 public:
58 static FSMstate* instance() {
59 if ( ! _instance ) _instance = new Persiguiendo;
60 cout << "El fantasma te persigue!" << endl;
61 return _instance; };
62 virtual void pastillacomida( FSM* );
63 private:
64 static FSMstate* _instance; };
65
66 FSMstate* Persiguiendo::_instance = 0;
67
68 class Huyendo : public FSMstate {
69 public:
70 static FSMstate* instance() {
71 if ( ! _instance ) _instance = new Huyendo;
72 cout << "El fantasma huye!" << endl;
73 return _instance; };
74 virtual void transcurridotiempo( FSM* );
75 virtual void comido( FSM* );
76
77 private:
78 static FSMstate* _instance; };
79 FSMstate* Huyendo::_instance = 0;
80
81 void Inactivo::regenerado( FSM* fsm ) {
82 cout << "Cambio de Estado a Persiguiendo." << endl;
83 changeState( fsm, Persiguiendo::instance()); };
84
85 void Persiguiendo::pastillacomida( FSM* fsm ) {
86 cout << "Cambio de Estado a Huyendo." << endl;
87 changeState( fsm, Huyendo::instance()); };
88
89 void Huyendo::transcurridotiempo( FSM* fsm ) {
90 cout << "Cambio de Estado a Persiguiendo." << endl;
91 changeState( fsm, Persiguiendo::instance()); };
92
93 void Huyendo::comido( FSM* fsm ) {
94 cout << "Cambio de Estado a Inactivo." << endl;
95 changeState( fsm, Inactivo::instance()); };
96
97 // El estado de comienzo es Inactivo
98 FSM::FSM() {
99 cout << "Inicializa:" << endl;
100 changeState( Inactivo::instance() );
101 }
102
103 int main() {
104 FSM ghost;
105 string input;
106
107 cout<<"Introduzca regla: ";
108 cin>> input;
109
110 while (input.compare("salir")!=0) {
111 if (input.compare("regenerado")==0)
112 ghost.regenerado();
113 else if (input.compare("pastillacomida")==0)
114 ghost.pastillacomida();
115 else if (input.compare("transcurridotiempo")==0)
116 ghost.transcurridotiempo();
117 else if (input.compare("comido")==0)
118 ghost.comido();
119 else cout << "Regla no existe, intente otra" << endl;
120
121 cout<<"Introduzca regla: ";
28.5. Diseo basado en agentes [907]

122 cin>> input;


123 }
124 }

Pros y Contras de cada tipo de implementacin

A continuacin se determinan los pros y los contras principales de cada uno de


estos dos mtodos usados para implementar un atmata:

Usando sentencias Switch-If/Else. Como pros hay que destacar dos, la primera
es que es muy fcil aadir y chequear condiciones que permitan cambiar de
estado, y la segunda es que permite la construccin rpida de prototipos. La
principal contra es que cuando se tiene un nmero elevado de estados, el
cdigo se puede hacer con relativa rapidez, muy enredado y dcil de seguir
(cdigo spaghetti). Otra contra importante, es que es difcil programar
efectos asociados a la entrada salida de un estado (y se recuerda que esto es
muy habitual en el desarrollo de videojuegos).
Usando una implementacin orientada a objetos. Este mtodo posee como
Pros:
Altamente extensible: Incluir un nuevo estado simplemente implica crear
una nueva clase que hereda de la clase abstracta State.
Fcil de mantener: Cada estado reside en su propia clase, se podr ver
fcilmente las condiciones asociadas a ese estado que le permiten cambiar
de estado sin tener que preocuparse acerca de los otros estados.
Intuitivo: Este tipo de implementacin es ms fcil de entender cuando se
usa para modelar comportamientos complejos.
Como contra principal, hay que destacar el tiempo de aprendizaje de uso de este
mtodo que es superior al anterior.

Avances en el uso de autmatas

Como ya se ha mostrado a lo largo de esta seccin, los autmatas nitos son de


gran utilidad a la hora de controlar el comportamiento de los agentes inteligentes,
sin embargo, conforme crece el nmero de estados y se trabaja con requisitos de
reusabilidad este tipo de autmatas se hacen ms difciles de utilizar. Una solucin
a este problema son los autmatas nitos jerrquicos HFSM (Hierarchical Finite State
Machine). Estos no son ms que autmatas nitos en los que cada estado puede
contener otra mquina de estado subordinada. Los estados contenedores se denominan
superestados. El uso de superestados permite la agrupacin de comportamientos,
facilitando de esta forma la reusabilidad y la tarea de ampliar detalles en los estados.
En esta jerarqua de mquinas de estado, las mquinas de niveles superiores
proporcionan el comportamiento general y el marco para ensamblar comportamientos
ms especcos, modelados por medio de mquinas de estado de niveles inferiores que
son anidadas en estados del autmata de nivel superior. El uso de HFSM no reduce el
nmero de estados, ms bien al contrario, los aumenta al introducir los superestados.
Donde si hay una reduccin, y adems considerable, es en el nmero de transiciones.
Esta reduccin es lo que hace este tipo de autmatas ms simples, facilitando de este
C28

modo su representacin y anlisis.


[908] CAPTULO 28. INTELIGENCIA ARTIFICIAL

c0 c1

c02 r32
r01 c11
r11 r31
c01 c04
c0

c12
c03 r21

Figura 28.84: Forma de los Autmatas nitos jerrquicos.

A nivel de funcionamiento las HFSMs no aportan nada al modelo FSM. Existe


un conjunto de estados y transiciones que llevan de un estado a otro. Alcanzar un
superestado implica emplear el autmata contenido en l. Este autmata tendr un
punto de entrada y uno de salida, el alcanzar un superestado supone la ejecucin de su
autmata asociado desde su punto de entrada, por contra alcanzar el punto de salida
implicar subir un nivel en la jerarqua.
Una transicin que quede activa de un superestado a otro interrumpe el proceso
que est actualmente en marcha en grados jerrquicos menores. Respecto al resto de
elementos, el funcionamiento es exactamente el mismo a las mquinas de estados
nitos denidas anteriormente.
El uso de HFSM para el modelado de comportamientos a la hora de desarrollar
agentes permite:

Aumentar el nivel de abstraccin: La jerarqua establece distintos niveles


de detalle para cada una de las mquinas de estado. La consideracin del
superestado como una caja negra, permite la ocultacin de los detalles presentes
en los niveles ms bajos de la jerarqua.
Facilitar la tarea de desarrollo: La jerarqua permite seguir un diseo top-
down. Se divide el problema en componentes, empezando por los niveles ms
altos, deniendo superestados abstractos, para posteriormente trabajar dentro de
cada uno de esos estados abstractos de la misma manera.
Aumentar la modularidad: Cada mquina de estado de la jerarqua puede ser
vista y considerada como un mdulo, esto facilita su reuso en otros puntos del
desarrollo.

La gura 28.84 muestra un autmata nito jerrquico.


28.5. Diseo basado en agentes [909]

28.5.5. Usando los objetivos como gua de diseo


La manera ms usual de implementar agentes, para su uso en videojuegos es, como
ya se ha comentado, utilizando aproximaciones reactivas. En estas aproximaciones los
agentes reaccionan ante las situaciones del juego mediante una accin o una secuencia
de acciones predenidas. Esto obliga a que los diseadores de los comportamientos
tengan que pensar a priori en todas las posibilidades que pueden darse y cmo
deben reaccionar en ellas los agentes. Esto es un problema ya que provoca un
comportamiento repetitivo y por lo tanto predecible. Es por ello por lo que en la
actualidad se est tendiendo a usar otro tipo de agentes.
En la seccin 28.5.2 se presentaron otras posibilidades para construir agentes, una
de ellas eran los agentes deliberativos. Estos agentes poseen su propia representacin
interna del entorno y tienen capacidad para planicar una secuencia de acciones y
ejecutarlas para alcanzar el n para el que han sido diseados.
La planicacin es un rea clave en la Inteligencia Articial. Tradicionalmente se
ha tratado como un problema de bsqueda de un plan que permitiera cambiar el estado
actual del entorno al estado deseado. Por ejemplo, el planicador STRIPS (Stanford
Research Institute Problem Solver), dado un estado inicial, trataba de encontrar un
plan para que se cumpliera un conjunto de objetivos.
Un plan puede ser considerado como una secuencia de operadores, cada uno de
los cuales constar de:

Una lista de precondiciones. Estas deben ser ciertas para poder aplicar el
operador.
Una lista de proposiciones a aadir. La aplicacin del operador implica aadir
estas proposiciones al estado actual.
Una lista de proposiciones a eliminar. La aplicacin del operador implica
eliminar estas proposiones del estado actual.

Normalmente, estos sistemas trabajan en entornos estticos, es decir el estado


del entorno no cambia entre la planicacin y la ejecucin del plan, lo que supone
una limitacin en el campo del desarrollo de videojuegos. Un cambio en el estado
en el que se hace el plan implica realizar una replanicacin, es decir hacer un
nuevo plan, partiendo del estado actual. Por otra parte, y como ya se indic en la
seccin 28.5.2 el principal problema que presentaban los agentes deliberativos es que
necesitaban un tiempo elevado de respuesta, lo cual los haca poco tiles en el campo
del desarrollo de videojuegos. Es por ello, por lo que se han tenido que desarrollar
versiones simplicadas de planicadores que obtengan respuetas en tiempo real.

Planicacin de acciones orientada a objetivos

La modelizacin de comportamientos basados en aproximaciones reactivas pue-


den provocar que los personajes del juego muestren comportamientos repetitivos y
por ello predecibles. Las aproximaciones deliberativas otorgan mayor libertad a estos
personajes, ya que les premiten adaptar sus acciones para que se ajusten a la situacin
en la que se encuentran.
C28
[910] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Huir
Estado Inicial
Correr
Coger Descansar
Arma
Cargar
Arma
Ponerse a Apuntar
salvo

Curarse Disparar
Estado Objetivo
Jugador Abatido

Figura 28.85: Formulacin de un Plan.

La planicacin de acciones orientada a objetivos, o GOAP (Goal-Oriented Action


Planning), permite en tiempo real planicar el comportamiento de personajes en
los juegos. Es una tcnica de planicacin que permite a los personajes decidir, no
slo qu hacer, sino tambin cmo hacerlo en tiempo real. Encontrarn soluciones
alternativas a situaciones que ocurran durante el juego y gestionarn las situaciones
no previstas durante el diseo (conviene notar que esto no se permite con las
aproximaciones reactivas).
En el diseo basado en objetivos, los agentes son desarrollados sobre arquitecturas
deliberativas. Un objetivo es una condicin que un agente desea satisfacer. Los agentes
pueden ser diseados con uno o varios objetivos, pero en un instante concreto de
tiempo, slo uno podr estar activo, y este ser el que controle su comportamiento.
Las acciones son las que van a permitir al agente satisfacer sus objetivos, y sern
organizadas por medio de planes. Cada accin que pueda realizar el agente tendr
asociada unas condiciones que determinan cuando ser posible su ejecucin (es la
precondicin) y los efectos que tendr en el estado del entorno su ejecucin. Gracias
a esto ser posible el secuenciamiento de las acciones y su organizacin en planes.
El funcionamiento de este tipo de agentes es el siguiente: El sistema de generacin
de planes o planicador del agente buscar la secuencia de acciones (una accin es la
aplicacin un operador con unas precondiciones y unos efectos) que permita ir desde
el estado inicial hasta el estado objetivo. Por lo general, el proceso de planicacin
implica buscar operadores cuyos efectos hagan cumplir los objetivos y haciendo de
las precondiciones de estos operadores nuevos subobjetivos (encadenamiento hacia
atrs).
28.5. Diseo basado en agentes [911]

El plan se ejecutar hasta que se complete, se invalide o hasta que haya un cambio
de objetivo. Si cambia el objetivo o no se puede continuar con el plan actual por alguna
razn, se aborta el plan actual y se genera uno nuevo. Existe tambin la posibilidad de
que cuando exista ms de un plan vlido, el planicador obtenga el de menor coste,
considerando que cada accin acarrea un coste. En cierto modo, tras todo este proceso
lo que hay es una bsqueda de un camino o del camino de menor coste.
En la gura 28.85 se muestra un plan para matar al jugador, en el se pasa del estado
inicial al estado objetivo mediante las acciones: Coger Arma, Cargar Arma, Apuntar
y Disparar.
Con el uso de GOAP:

Se consigue dotar a los personajes de comportamientos adaptativos. La capaci-


dad de generacin de planes permite ajustar sus acciones a su entorno actual, y
encontrar diferentes soluciones a un problema.
Se consigue comportamientos ms simples de disear. Es el planicador quien
se encarga de analizar las posibles alternativas existentes y encontrar la mejor
opcin, no hay que prever para cada situacin posible la accin a realizar.
Se consigue comportamientos variables. Los planes no estn denidos de
antemano, se elaboran en tiempo de ejecucin dependiendo del estado actual
del entorno.

Ejemplos de juegos que emplean GOAP son:

Mushroom Men: The Spore Wars (Wii) - Red Fly Studio, 2008
Los Cazafantasmas (Wii) - Red Fly Studio, 2008
Silent Hill: Homecoming (X360/PS3) - Double Helix Games / Konami de 2008
Fallout 3 (X360/PS3/PC) - Bethesda Softworks, 2008
Figura 28.86: Los desarrolladores
del juego S.T.A.L.K.E.R.: Shadow Empire: Total War (PC) - Creative Assembly / Sega, 2009
of Chernobyl han usado agentes
reactivos basados en FSM, despus FEAR 2: Project Origin (X360/PS3/PC) - Monolith Productions Bros / Warner,
han empleado agentes basados en 2009
HFSM, para acabar usando agentes
deliberativos basados en planica- Demigod (PC) - Gas Powered Games / Stardock, 2009
dores GOAP jerrquicos.
Just Cause 2 (PC/X360/PS3) - Avalanche Studios / Eidos Interactive, 2010
Transformers: War for Cybertron (PC/X360/PS3) - High Moon Studios /
Activision, 2010
Trapped Dead (PC) - Juegos Headup, 2011
Deus Ex: Human Revolution (PC/X360/PS3) - Eidos Interactive, 2011

La tendencia actual en el desarrollo de videojuegos es usar planicadores


jerrquicos. Un planicador jerrquico primero formula un plan abstracto expresado
en trminos de tareas de alto nivel, por lo que no puede realizarse directamente.
Despus rena dicho plan con el objetivo de producir un plan completo en tminos
de operadores bsicos, o acciones que el agente pueda realizar directamente.
C28
[912] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Abatir al Jugador

Armarse
Apuntar Disparar

Coger Cargar
Arma Arma

Figura 28.87: Planicador jerrquico.

En la gura 28.87 se muestra un ejemplo de uso de planicador jerrquico, en el se


indica que para pasar de un estado inicial a un estado en el que el jugador haya muerto,
se puede trazar un plan compuesto por una tarea de alto nivel Abatir al Jugador. Esta
tarea se descompone en Armarse, Apuntar y Disparar. Apuntar y Disparar son tareas
atmicas que el agente puede llevar a cabo, pero Armarse debe descomponerse en
acciones atmicas realizables por el agente: Coger Arma y Cargar Arma.
Un ejemplo de uso de planicadores jerrquicos est representado por el juego
S.T.A.L.K.E.R.: Shadow of Chernobyl (GSC Game World / THQ, 2007) (vea
gura 28.86), que usa jerarquas de planicadores GOAP de una manera muy
particular.
Uno de los formalismos que aplican la planicacin jerrquica son las redes
jerrquicas de tareas (Hierarchical Task Networks o HTNs). Esta se dene como
una coleccin de tareas que se desean que sean realizadas, junto con una serie
de restricciones sobre el orden en que pueden llevarse a cabo, la forma en que se
instancian sus variables y las condiciones que deben ser ciertas antes o despus de
que cada tarea se lleve a cabo. Su uso en el desarrollo de videojuegos es cada vez es
ms extendido.

28.5.6. Reexiones sobre el futuro


El algoritmo de bsqueda A* y la Mquina Finita de Estados (FSM) son proba-
blemente las dos tcnicas ms empleadas de la IA en el desarrollo de videojuegos,
para establecer caminos o rutas y para modelar el comportamiento de un personaje,
respectivamente.
No obstante, la IA cada vez tiene ms importancia en el desarrollo de videojuegos Figura 28.88: Cuando Resistance 3
(vea la gura 28.88). Puesto que cada vez se podr disponer de computacin adicional, fue presentado por Cameron Chris-
esto da pie a dedicar un procesador, o parte de l, a la IA y aplicar tcnicas tian, de Insomniac Games, lo que
ms avanzadas (Lgica Difusa, Computacin Evolutiva, Redes Neuronales, Modelos seal como la mayor mejora de la
Probabilsticos,. . . ). secuela era su IA mejorada.
28.6. Caso de estudio. Juego deportivo [913]

El reto en el desarrollo de los videojuegos, es hacer que estos sean ms naturales,


ms interactivos y ms sociables. El uso de la IA en los videojuegos est en una fase
de progreso signicativo.
Por otra parte hay que indicar que los videojuegos son un gran reto para la IA y
representan un campo de investigacin en s mismos, tratando de proponer mtodos
y tcnicas que aporten un tipo de inteligencia que aunque no sea perfecta, si permita
mejorar la experiencia de juego.
Hay que tener en cuenta que las situaciones manejadas por la IA son altamente
dependientes del tipo juego y de sus requisitos de diseo, por lo que es posible que
una solucin utilizada en un videojuego no pueda ser aplicable en otro. Esto conduce
al desarrollo de tcnicas especcas. Por lo tanto, ste es un campo que ofrece un gran
potencial en trminos de investigacin y desarrollo de nuevos algoritmos.
El objetivo de los videojuegos es desarrollar mundos virtuales, con el mximo
parecido posible al mundo real, tanto a nivel visual como funcional y de interaccin.
En este sentido hay que destacar que en el mundo real es habitual el concepto de
organizacin, es decir dos o ms personas se juntan para cooperar entre s y alcanzar
objetivos comunes, que no pueden lograrse, o bien cuesta ms lograrlos, mediante
iniciativa individual.
En el campo del desarrollo de videojuegos este concepto deber ser tratado,
permitiendo a los personajes organizarse y cooperar para lograr un objetivo comn,
por ejemplo abatir al jugador o conseguir ganar una batalla o un partido. Por una
parte, la organizacin implicar la existencia de una jerarqua y una distribucin
y especializacin en tareas, y por otra la cooperacin, implica la existencia de un
mecanismo de comunicacin comn. Adems, en el mundo real tambin es necesario
facilitar la movilidad de profesionales, las organizaciones mejoran sus modos de
trabajo gracias a la aportacin de nuevos miembros que poseen mejor cualicacin.
En el campo de los videojuegos, la movilidad implicara permitir que personajes
virtuales con un desempeo demostrado pudieran desplazarse de unas instacias de
juego a otras, a travs la red. Y esto ltimo permite introducir otro aspecto de suma
importancia, la capacidad de adaptabilidad o aprendizaje, un personaje mejorar
su desempeo si tiene capacidad para aprender. Todo esto conduce a los sistemas
multiagentes que tendrn mucho que aportar en los prximos aos en el campo del
desarrollo de videojuegos.
Los sistemas multiagentes proporcionan el marco donde un conjunto de agentes
con tareas y habilidades claramente denidas colaboren y cooperen para la consecu-
sin de sus objetivos. Existen multitud de metodologas y enfoques para el diseo de
sistemas multiagentes as como plataformas para su desarrollo. Estas proporcionan
soporte a los diseadores y desarrolladores para construir sistemas ables y robustos,
pero seguro que ser necesaria su adaptacin para el mundo de los videojuegos.

28.6. Caso de estudio. Juego deportivo


El objetivo de la presente seccin consiste en plantear el diseo bsico para la
Inteligencia Articial de las distintas entidades que participan en un juego deportivo
de equipos. Aunque la propuesta inicial que se discute es general y se podra reutilizar
para una gran parte de los juegos deportivos, en esta seccin se ha optado por estudiar
el caso concreto del ftbol [16].
En el ao 2050...
El diseo y la implementacin de la IA en un juego deportivo en equipo,
C28

...se espera que un equipo de ftbol particularmente en el ftbol, no es una tarea trivial debido a la complejidad que supone
compuesto de robots fsicos autno- crear agentes autnomos capaces de jugar de una forma similar a la de un contrincante
mos sea capaz de derrotar a un equi-
po de ftbol real. humano.
[914] CAPTULO 28. INTELIGENCIA ARTIFICIAL

En este contexto, resulta interesante destacar la Robocup2 , una competicin que


alberga torneos de ftbol entre equipos fsicos de robots, por una parte, y entre equipos
simulados mediante software, por otra, desde 1996.
Este ltimo tipo de modalidad gira principalmente en torno al desarrollo de
componentes de IA con el objetivo de dotar a los jugadores virtuales del mayor
realismo posible y, eventualmente, derrotar al equipo virtual contrario.
La liga de simulacin de ftbol (Soccer Simulation League) es una de las
competeciones ms antiguas de la Robocup y est centrada en la IA y las estrategias
en equipo. Los jugadores virtuales o agentes se mueven de manera independiente en
un terreno de juego virtual simulado en un computador. Dicha liga est compuesta
a su vez de dos subligas, una simulada en el espacio 2D y otra en el espacio 3D.
El software necesario para llevar a cabo simulaciones e incluso recrear la IA de los
equipos virtuales ganadores de las distintas ediciones de estas ligas se puede descargar
libremente de Internet3 .
Estos mdulos de IA se han ido modicando hasta convertirse en sosticados
componentes software, los cuales rigen el comportamiento de los jugadores virtuales
no slo a nivel individual, sino tambin a nivel de equipo. Estos componentes se
basan en algunas de las tcnicas estudiadas en el presente captulo, como por ejemplo
la lgica difusa, y hacen uso de estructuras de datos no lineales, como los grfos
estudiados en el mdulo 3, Tcnicas Avanzadas de Desarrollo, para llevar a cabo una
coordinacin multi-agente.
Para facilitar el diseo y desarrollo de comportamientos inteligentes en el caso
particular del ftbol, en este captulo se discuten las bases de un framework que
permite la integracin y validacin de comportamientos en un entorno 2D. De este
modo, el lector podr dedicar principalmente su esfuerzo a la creacin y testing de
dichos comportamientos con el objetivo de adquirir las habilidades necesarias para
desarrollar la IA en un juego deportivo.
Evidentemente, este framework representa una versin muy simplicada de las
reglas reales del ftbol con el objetivo de no incrementar la complejidad global del
mismo y facilitar el desarrollo de comportamientos inteligentes.
Antes de pasar a discutir la arquitectura propuesta en la seccin 28.6.2, relativa al
framework que permitir la implementacin de comportamientos para los jugadores
virtuales de un equipo de ftbol, en la siguiente seccin se realiza una breve
introduccin a Pygame, un conjunto de mdulos de Python creados con el principal
objetivo de programar juegos 2D. En este contexto, Pygame se ha utilizado para llevar
a cabo la representacin grca de dicho framework.
Figura 28.89: Ejemplo de robot
compitiendo en la liga de Robocup
28.6.1. Introduccin a Pygame de 2010 en Singapur (fuente Wiki-
pedia).
Pygame4 es un conjunto de mdulos para el lenguaje de programacin Python
concebidos para el desarrollo de juegos. Bsicamente, Pygame aade funcionalidad
sobre la biblioteca multimedia SDL (estudiada en el mdulo 1, Arquitectura del Mo-
tor, para el caso particular de la integracin de sonido), abstrayendo la representacin
grca, la integracin de sonido y la interaccin con dispositivos fsicos, entre otros
aspectos, al desarrollador de videojuegos.
2 http://www.robocup.org/
3 http://sourceforge.net/projects/sserver/
4 http://pygame.org
28.6. Caso de estudio. Juego deportivo [915]

Pygame es altamente portable y, por lo tanto, se puede ejecutar sobre una gran
cantidad de plataformas y sistemas operativos. Adems, mantiene una licencia LGPL5 ,
permitiendo la creacin de open source, free software, shareware e incluso juegos
comerciales.
A continuacin se enumeran las principales caractersticas de Pygame:

Gran nivel funcional, debido a la continuidad del proyecto.


Independencia respecto a la biblioteca grca de bajo nivel, no siendo necesario
utilizar exclusivamente OpenGL.
Soporte multi-ncleo.
Uso de cdigo C optimizado y de ensamblador para la implementacin de la
funcionalidad del ncleo de Pygame.

Figura 28.90: Pygame mantiene la Alta portabilidad.


losofa de Python, simplicidad y
exibilidad a travs de un conjunto Facilidad de aprendizaje.
de mdulos de alto nivel para el
desarrollo de juegos 2D.
Amplia comunidad de desarrollo.
Autonoma para controlar el bucle principal de juego.
No es necesaria una interfaz grca para poder utilizar Pygame.
Sencillez en el cdigo, debido a la simplicidad de Pygame.

Al igual que ocurre en SDL, uno de los componentes fundamentales en Pygame


est representado por el concepto de supercie (surface)6 , debido a que es el objeto
contemplado por Pygame para la representacin de cualquier tipo de imagen. En la
tabla 28.7 se resume la lista de mdulos que conforman Pygame junto a una breve
descripcin.
La instalacin de Pygame en Debian GNU/Linux es trivial mediante los siguientes
comandos:

$ sudo apt-get update


$ sudo apt-get install python-pygame

El siguiente listado de cdigo muestra la posible implementacin de una clase


que encapsula la informacin bsica de un campo de ftbol a efectos de renderizado
(ver gura 28.91). Como se puede apreciar en el mtodo constructor (__init()__) de la
clase SoccerField, se crea una variable de clase, denominada surface, que representa
la supercie de renderizado sobre la que se dibujarn las distintas primitivas que
conforman el terreno de juego. Dicha supercie tiene una resolucin determinada.

Listado 28.33: Clase SoccerField


1 # Incluir mdulos de pygame...
2 TOP_LEFT = (50, 50)
3 WIDTH_HEIGHT = (1050, 720)
4
5 class SoccerField:
6
7 def __init__ (self, width, height):
C28

5 http://www.pygame.org/LGPL
6 http://www.pygame.org/docs/ref/surface.html
[916] CAPTULO 28. INTELIGENCIA ARTIFICIAL

9 self.width, self.height = width, height


10
11 pygame.display.set_caption(CEDV - 2011/2012)
12 self.surface = pygame.display.set_mode(
13 (self.width, self.height), 0, 32)
14 # Terreno real de juego.
15 self.playing_area = Rect(TOP_LEFT, WIDTH_HEIGHT)
16
17 def render (self):
18
19 # Renderiza el terreno de juego.
20 self.surface.fill(THECOLORS[green]);
21 pygame.draw.rect(self.surface, THECOLORS[white],
22 self.playing_area, 7)
23 pygame.draw.circle(self.surface, THECOLORS[white],
24 self.playing_area.center, 10, 5)
25 pygame.draw.circle(self.surface, THECOLORS[white],
26 self.playing_area.center, 75, 5)
27 pygame.draw.line(self.surface, THECOLORS[white],
28 self.playing_area.midtop,
29 self.playing_area.midbottom, 5)

Por otra parte, el mtodo render() contiene las primitivas de Pygame necesarias
para dibujar los aspectos bsicos del terreno de juego. Note cmo se hace uso de las
primitivas geomtricas rect, circle y line, respectivamente, para dibujar las lneas de
fuera y el centro del campo. Adems de especicar el tipo de primitiva, es necesario
indicar el color y, en su caso, el grosor de la lnea.
Tpicamente, el mtodo render() se ejecutar tan rpidamente como lo permitan
las prestaciones hardware de la mquina en la que se ejecute el ejemplo, a no ser que
se establezca un control explcito del nmero de imgenes renderizadas por segundo. Figura 28.91: Resultado del rende-
rizado de un terreno de juego sim-
El siguiente listado de cdigo muestra cmo inicializar Pygame (lnea 14) e plicado mediante Pygame.
instanciar el terreno de juego (lnea 17). El resto de cdigo, es decir, el bucle innito
permite procesar los eventos recogidos por Pygame en cada instante de renderizado. Event-oriented
Note cmo en cada iteracin se controla si el usuario ha cerrado la ventana grca,
capturado mediante el evento QUIT, y se lleva a cabo el renderizado del terreno de La combinacin de un esquema ba-
sado en eventos y una arquitectura
juego (lnea 27) junto con la actualizacin de Pygame (lnea 29). con varias capas lgicas facilita la
delegacin y el tratamiento de even-
La gestin de la tasa de frames generados por segundo es simple mediante la clase tos.
Clock. De hecho, slo es necesario especicar la tasa de frames deseada mediante la
funcin clock.tick(FPS) para obtener una tasa constante.

Listado 28.34: Ejecutando el ejemplo


1 # Incluir mdulos de pygame...
2 WIDTH, HEIGHT = 1200, 800
3 FPS = 30
4
5 if __name__ == "__main__":
6 pygame.init()
7
8 clock = pygame.time.Clock()
9 soccerField = SoccerField(WIDTH, HEIGHT)
10
11 while True:
12 tick_time = clock.tick(FPS)
13
14 for event in pygame.event.get():
15 if event.type == QUIT:
16 pygame.quit()
17 sys.exit()
18
19 soccerField.render()
20 pygame.display.update()
28.6. Caso de estudio. Juego deportivo [917]

Mdulo Breve descripcin


camera Soporte para el uso de cmaras
cdrom Manejo de unidades de CD/DVD
color Representacin de colores
cursors Soporte y gestin de cursores
display Control sobre dispositivos y ventanas
draw Renderizado de formas sobre una supercie
event Interaccin con eventos
examples Mdulo con ejemplos bsicos
font Carga y renderizado de fuentes
gfxdraw Renderizado de formas sobre una supercie
image Carga y almacenamiento de imgenes
joystick Interaccin con joysticks
key Interaccin con el teclado
locals Constantes usadas por Pygame
mask Soporte de mscaras sobre imgenes
midi Interaccin con dispositivos midi
mixer Carga y reproduccin de sonido
mouse Interaccin con el ratn
movie Carga de vdeo mpeg
music Control de streaming de audio
overlay Soporte para overlays grcos
pixelarray Acceso directo a pxeles en supercies
pygame Paquete de alto nivel de Pygame
rect Objeto para gestionar rectngulos
scrap Soporte para clipboards
sndarray Acceso a samples de sonido
sprite Mdulo con objetos bsicos
surface Objeto para representar imgenes
surfarray Acceso a pxeles de supercies mediante arrays
tests Paquete para pruebas unitarias
time Monitorizacin de tiempo
transform Transformacin de supercies

Cuadro 28.7: Resumen de los mdulos que conforman Pygame.

28.6.2. Arquitectura propuesta

Simplicacin Como se ha comentado anteriormente, en este caso de estudio se ha llevado


a cabo una simplicacin de las reglas del ftbol con el objetivo de facilitar la
Recuerde que la simplicidad de una implementacin y permitir focalizar el esfuerzo en la parte relativa a la IA. Por
solucin facilita la mantenibilidad ejemplo, se ha obviado la situacin de fuera de juego pero s que se contempla el
del cdigo y el desarrollo de nuevos
componentes basados en los previa- saque de banda cuando el baln sale del terreno de juego. La gura 28.92 muestra de
mente desarrollados. manera grca el estado inicial del juego, con un equipo a la izquierda del centro del
campo y otro a la derecha.
A continuacin, en esta subseccin se discutir el diseo de la versin inicial del
C28

framework propuesto [16]. Ms adelante se prestar especial atencin a cmo incluir


nuevos tipos de comportamiento asociados a los propios jugadores.
[918] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Figura 28.92: Terreno de juego con la posicin inicial de los jugadores.

La gura 28.93 muestra, desde un punto de vista de alto nivel, el diagrama de cla-
ses de la arquitectura propuesta. Como se puede apreciar, la clase SoccerField aparece
en la parte superior de la gura y est compuesta de dos equipos (SoccerTeam), dos
porteras (SoccerGoal), un baln (SoccerBall) y un determinado nmero de regiones
(SoccerRegion).
Por otra parte, el equipo de ftbol est compuesto por un determinado nmero de
jugadores, cuatro en concreto, modelados mediante la clase SoccerPlayer, que a su
vez mantiene una relacin de herencia con la clase MovingEntity. A continuacin se
realizar un estudio ms detallado de cada una de estas clases.

El terreno de juego

Una de las clases principales de la arquitectura propuesta es la clase SoccerField,


la cual mantiene como estado interno referencias a los elementos ms importantes que
forman parten de dicha arquitectura. Estos elementos son los siguientes:

surface, de tipo pygame.surface, que representa la supercie de Pygame


utilizada para renderizar cada uno de los elementos que forman parte del juego.
playing_area, de tipo pygame.rect, que representa el terreno real de juego
considerando las lneas de fuera de banda y de crner.
walls, de tipo lista, que contiene cuatro instancias de la clase Wall2d, las cuales
representan las dos bandas y las dos lneas de crner. Estas estructuras se
utilizan para detectar cuando el baln sale del terreno de juego.
regions, de tipo lista, que contiene estructuras del tipo SoccerRegion que
facilitan la divisin del terreno de juego en distintas zonas para gestionar
aspectos como la posicin inicial de los jugadores o la confeccin de estrategias
de equipo.
28.6. Caso de estudio. Juego deportivo [919]

SoccerField

1..n 2 2 1

SoccerRegion SoccerGoal SoccerTeam SoccerBall

SoccerPlayer MovingEntity

Figura 28.93: Diagrama de clases de la arquitectura del simulador de ftbol.

ball, de tipo SoccerBall, que representa al baln usado para llevar a cabo las
simulaciones.
teams, de tipo diccionario, que permite indexar a dos estructuras de tipo
SoccerTeam a partir del identicador del equipo (red o blue).
goals, de tipo diccionario, que permite indexar a dos estructuras de tipo
SoccerGoal a partir del identicador del equipo (red o blue).

El siguiente listado de cdigo muestra la parte relativa a la inicializacin de


la clase SoccerField. Note cmo en el mtodo constructor (__init()__) se lleva a
cabo la instanciacin de los elementos principales que conforman la simulacin.
Como se estudiar ms adelante, algunas de estos elementos mantienen relaciones de
agregacin con otros elementos, como por ejemplo ocurre entre la clase SoccerTeam
y la clase SoccerPlayer (un equipo est compuesto por varios jugadores).
Inicializacin Adems de coordinar la creacin de las principales instancias del juego, la clase
Recuerde inicializar completamen-
SoccerField tambin es responsable de la coordinacin de la parte especca de
te el estado de una instancia en el renderizado o dibujado a travs del mtodo render(). Este mtodo se expone en el
proceso de creacin. En el caso de siguiente listado de cdigo.
Python, el constructor est repre-
sentado por el mtodo __init()__. Entre las lneas 9-17 se puede apreciar cmo se hace uso de ciertas primitivas
bsicas, ofrecidas por Pygame, para renderizar el terreno de juego, las lneas de
fuera de banda y el centro del campo. Posteriormente, el renderizado del resto de
estructuras de datos que forman parte de la clase se delega en sus respectivos mtodos
render(), planteando as un esquema escalable y bien estructurado. La actualizacin
del renderizado se hace efectiva mediante la funcin update() de Pygame (lnea 26).
C28

Listado 28.35: Clase SoccerField. Mtodo constructor


1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
[920] CAPTULO 28. INTELIGENCIA ARTIFICIAL

3
4 class SoccerField:
5
6 def __init__ (self, width, height):
7
8 self.width, self.height = width, height
9
10 self.surface = pygame.display.set_mode(
11 (self.width, self.height), 0, 32)
12 # Terreno real de juego.
13 self.playing_area = Rect(TOP_LEFT, WIDTH_HEIGHT)
14
15 # Walls.
16 self.walls = []
17 # Clculo de coordenadas clave del terreno de juego.
18 self.walls.append(Wall2d(top_left, top_right))
19 self.walls.append(Wall2d(top_right, bottom_right))
20 self.walls.append(Wall2d(top_left, bottom_left))
21 self.walls.append(Wall2d(bottom_left, bottom_right))
22
23 # Zonas importantes en el campo.
24 self.regions = {}
25 # Clculo de las regiones...
26
27 # Bola.
28 self.ball = SoccerBall(Vec2d(self.playing_area.center),
29 10, 2, Vec2d(0, 0), self)
30
31 # Equipos
32 self.teams = {}
33 self.teams[red] = SoccerTeam(red, RED, 4, self)
34 self.teams[blue] = SoccerTeam(blue, BLUE, 4, self)
35
36 # Porteras.
37 self.goals = {}
38 self.goals[red] = SoccerGoal(
39 red, RED, self.playing_area.midleft, self)
40 self.goals[blue] = SoccerGoal(
41 blue, BLUE, self.playing_area.midright, self)

En la gura 28.94 se muestra la divisin lgica del terreno de juego en una serie
de regiones con el objetivo de facilitar la implementacin del juego simulado, los
comportamientos inteligentes de los jugadores virtuales y el diseo de las estrategias
en equipo.
Internamente, cada regin est representada por una instancia de la clase Soc-
cerRegion, cuya estructura de datos ms relevante es un rectngulo que dene las
dimensiones de la regin y su posicin.
Aunque cada jugador conoce en cada momento su posicin en el espacio 2D, como Ms info...
se discutir ms adelante, resulta muy til dividir el terreno de juego en regiones para El uso de informacin o estructuras
implementar la estrategia interna de cada jugador virtual y la estrategia a nivel de de datos adicionales permite gene-
equipo. Por ejemplo, al iniciar una simulacin, y como se muestra en la gura 28.94, ralmente optimizar una solucin y
cada jugador se posiciona en el centro de una de las regiones que pertenecen a su plantear un diseo que sea ms sen-
cillo.
campo. Dicha posicin se puede reutilizar cuando algn equipo anota un gol y los
jugadores han de volver a su posicin inicial para efectuar el saque de centro.

Listado 28.36: Clase SoccerField. Mtodo render


1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 class SoccerField:
5
6 def render (self):
7
28.6. Caso de estudio. Juego deportivo [921]

8 # Renderiza el terreno de juego.


9 self.surface.fill(GREEN)
10 pygame.draw.rect(self.surface, WHITE, self.playing_area, 3)
11 pygame.draw.circle(self.surface, WHITE,
12 self.playing_area.center, 10, 2)
13 pygame.draw.circle(self.surface, WHITE,
14 self.playing_area.center, 75, 2)
15 pygame.draw.line(self.surface, WHITE,
16 self.playing_area.midtop,
17 self.playing_area.midbottom, 2)
18
19 for g in self.goals.values(): # Porteras.
20 g.render()
21 for t in self.teams.values(): # Equipos.
22 t.render()
23 self.ball.render() # Baln.
24
25 # Actualizacin pygame.
26 pygame.display.update()

La regin natural de un jugador tambin puede variar en funcin de la estrategia


elegida. Por ejemplo, un jugador que haga uso de un comportamiento defensivo puede
tener como meta principal ocupar regiones cercanas a su portera, con el objetivo de
evitar un posible gol por parte del equipo contrario.

El baln de juego

Una de las entidades ms relevantes para recrear la simulacin de un juego


deportivo es la bola o baln, ya que representa la herramienta con la que los jugadores
virtuales interactan con el objetivo de ganar el juego. Esta entidad se ha modelado
mediante la clase SoccerBall, la cual hereda a su vez de la clase MovingEntity, como
se puede apreciar en el siguiente listado de cdigo. Recuerde que el caso de estudio
discutido en esta seccin se enmarca dentro de un mundo virtual bidimensional.
Desde un punto de vista interno, el baln ha de almacenar la posicin actual, la
posicin del instante de tiempo anterior y la informacin relevante del terreno de
juego, es decir, la informacin relativa a las lneas de fuera de banda y de crner.
El resto de informacin la hereda directamente de la clase MovingEntity, como por
ejemplo la velocidad del baln, la velocidad mxima permitida o la direccin.
Una instancia de la clase SoccerBall se puede entender como una entidad pasiva,
ya que sern los propios jugadores o el modelo bsico de simulacin fsica los que
actualicen su velocidad y, en consecuencia, su posicin. Por ejemplo, cuando un
jugador golpee el baln, en realidad lo que har ser modicar su velocidad. Esta
informacin se utiliza a la hora de renderizar el baln en cada instante de tiempo.

Listado 28.37: Clase SoccerBall. Mtodo constructor


1 class SoccerBall (MovingEntity):
2
3 def __init__ (self, pos, size, mass, velocity, soccer_field):
4
5 MovingEntity.__init__(
6 self, pos, size, velocity, velocity,
7 Vec2d(soccer_field.playing_area.center), mass)
8
9 self.oldPos = pos
10 self.soccer_field = soccer_field
11 self.walls = soccer_field.walls
C28

12
13 class MovingEntity:
14
15 def __init__ (self, pos, radius, velocity, max_speed,
[922] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Figura 28.94: Divisin del terreno de juego en regiones para facilitar la integracin de la IA.

16 heading, mass):
17
18 self.pos = pos
19 self.radius = radius
20 self.velocity = velocity
21 self.max_speed = max_speed
22 self.heading = heading
23 # Vector perpendicular al de direccin (heading).
24 self.side = self.heading.perpendicular
25 self.mass = mass

Dentro de la clase SoccerBall existen mtodos que permiten, por ejemplo, detectar
cundo el baln sale fuera del terreno de juego. Para ello, es necesario comprobar,
para cada uno de los laterales, la distancia existente entre el baln y la lnea de fuera.
Si dicha distancia es menor que un umbral, entonces el baln se posicionar en la
posicin ms cercana de la propia lnea para efectuar el saque de banda.

Listado 28.38: Clase SoccerBall. Mtodo testCollisionWithWalls


1 def testCollisionWithWalls (self):
2
3 # Iterar sobre todos los laterales del campo
4 # para calcular si el baln interseca.
28.6. Caso de estudio. Juego deportivo [923]

5 for w in self.walls:
6 if distToWall(w.a, w.b, self.pos) < TOUCHLINE:
7 self.reset(self.pos)

Por otra parte, el baln sufrir el efecto del golpeo por parte de los jugadores
virtuales mediante el mtodo kick(). Bsicamente, su funcionalidad consistir en
modicar la velocidad del baln en base a la fuerza del golpeo y la direccin del
mismo.

Listado 28.39: Clase SoccerBall. Mtodo kick


1 # Golpea el baln en la direccin dada.
2 def kick (self, direction, force):
3
4 # Normaliza la direccin.
5 direction = direction.normalized()
6 # Calculo de la aceleracin.
7 acceleration = (direction * force) / self.mass
8 # Actualiza la velocidad.
9 self.velocity = acceleration

pi Tambin resulta muy interesante incluir la funcionalidad necesaria para que los
jugadores puedan prever la futura posicin del baln tras un intervalo de tiempo. Para
llevar a cabo el clculo de esta futura posicin se ha de obtener la distancia recorrida
por el baln en un intervalo de tiempo t, utilizando la ecuacin 28.7,

1
distancia cubierta

x = ut + at2 (28.7)
2
donde x representa la distancia recorrida, u es la velocidad del baln cuando se
golpea y a es la deceleracin debida a la friccin del baln con el terreno de juego.
Tras haber obtenido la distancia a recorrer por el baln, ya es posible usar esta
informacin para acercar el jugador al baln. Sin embargo, es necesario hacer uso de
la direccin del propio baln mediante su vector de velocidad. Si ste se normaliza y
se multiplica por la distancia recorrida, entonces se obtiene un vector que determina
tanto la distancia como la direccin. Este vector se puede aadir a la posicin del baln
para predecir su futura posicin.
Este tipo de informacin es esencial para implementar estrategias de equipo y
planicar el movimiento de los jugadores virtuales. Dicha funcionalidad, encapsulada
en la clase SoccerBall, se muestra en el siguiente listado de cdigo.

p Listado 28.40: Clase SoccerBall. Mtodo futurePosition


1 # Devuelve la posicin del baln en el futuro.
Figura 28.95: Esquema grco de 2 def futurePosition (self, time):
la prediccin del baln tras un ins- 3
tante de tiempo. 4 # u = velocidad de inicio.
5 # Clculo del vector ut.
6 ut = self.velocity * time
7
8 # Clculo de 1/2*a*t*t, que es un escalar.
9 half_a_t_squared = 0.5 * FRICTION * time * time
10
11 # Conversin del escalar a vector,
12 # considerando la velocidad (direccin) de la bola.
13 scalarToVector = half_a_t_squared * self.velocity.normalized()
C28

14
15 # La posicin predicha es la actual
16 # ms la suma de los dos trminos anteriores.
17 return self.pos + ut + scalarToVector
[924] CAPTULO 28. INTELIGENCIA ARTIFICIAL

El jugador virtual

La entidad base para representar a cualquier jugador de ftbol virtual est


representada por la clase SoccerPlayer, la cual a su vez es una especializacin de
la clase MovingEntity (al igual que el baln de juego). Esta entidad base se podr
extender para especializarla en funcin de distintos tipos de jugadores, tpicamente
jugadores de campo y portero.
El siguiente listado de cdigo muestra una posible implementacin bsica de esta
clase. Note cmo en el mtodo update() se actualiza la velocidad interna del jugador
en funcin de los comportamientos que tenga activos, tal y como se discute en la
siguiente seccin.

Listado 28.41: Clase SoccerPlayer


1 class SoccerPlayer (MovingEntity):
2
3 def __init__ (self, team, colour, number, pos, soccer_field):
4 # Inicializacin de MovingEntity.
5 self.team = team
6 self.colour = colour
7 self.number = number
8 self.initialPos = pos
9 self.soccer_field = soccer_field
10 aux = (soccer_field.playing_area.center - pos).normalized()
11 self.direction = aux
12
13 # Comportamientos asociados.
14 self.steeringBehaviours = SteeringBehaviours(
15 self, soccer_field.ball)
16
17 # Posicin inicial (e.g. kickoff).
18 def reset (self):
19 self.pos = self.initialPos
20
21 # Actualizacin de la posicin del jugador.
22 def update (self):
23 self.velocity = self.steeringBehaviours.calculate()
24 self.pos += self.velocity

Precisamente, estos comportamientos son los que determinan las habilidades


del jugador y de su implementacin depender en gran parte el resultado de una
simulacin.

28.6.3. Integracin de comportamientos bsicos


La gura 28.98 muestra el diagrama de clases con las distintas entidades involu-
cradas en la integracin de IA dentro del simulador futbolstico. La parte ms relevante
de este diagrama est representado por la clase SteeringBehaviours, la cual aglutina
los distintos comportamientos asociados, desde un punto de vista general, al jugador
de ftbol. Note tambin cmo la clase base asociada al jugador de ftbol, Soccer-
Player, ha sido especializada en otras dos clases, FieldPlayer y Goalkeeper, con el
objetivo de personalizar el comportamiento de los jugadores de campo y el portero,
respectivamente. Figura 28.96: Seguimiento de un
camino por una entidad basada en
un modelo de comportamientos ba-
dados en direcciones.
28.6. Caso de estudio. Juego deportivo [925]

El modelo de comportamientos usado en esta seccin se basa en el modelo7


presentado originalmente por C.W. Reynolds en el Game Developers Conference de
1999 [74].
Bsicamente, este modelo se basa en la combinacin de mltiples comporta-
mientos para navegar en un espacio fsico de una manera improvisada, es decir, guian-
do el movimiento de la entidad en cuestin en base a las condiciones del entorno en
cada instante y en la posicin de la propia entidad. Estos comportamientos se plantean
de manera independiente a los medios de locomocin de la entidad o el carcter mvil.

El modelo de Steering Behaviours trata de facilitar el diseo de situaciones


como la siguiente: ir de A a B mientras se evitan obstculos y se viaja junto
a otras entidades.

Existen distintas herramientas y bibliotecas que facilitan la integracin de este tipo


de comportamientos en problemas reales, como es el caso de la biblioteca OpenSteer8 ,
una implementacin en C++ que facilita el uso, la combinacin y la construccin de
este tipo de comportamientos.
Este tipo de bibliotecas suelen integrar algn mecanismo de visualizacin para
facilitar la depuracin a la hora de implementar comportamientos. En el caso de
OpenSteer se incluye una aplicacin denominada OpenSteerDemo que hace uso
de OpenGL para llevar a cabo dicha visualizacin, como se puede apreciar en la
gura 28.97.

Se recomienda al lector la descarga y estudio de OpenSteer como ejemplo


particular de biblioteca para el estudio e implementacin de diversos com-
portamientos sobre entidades virtuales mviles.

Retomando el diagrama de clases de la gura 28.98, resulta importante profundizar


en la implementacin de la clase SteeringBehaviours, la cual representa la base para
la integracin de nuevos comportamientos. Bsicamente, la idea planteada en esta
Figura 28.97: Captura de pantalla clase se basa en los siguientes aspectos:
de un ejemplo en ejecucin de la
biblioteca OpenSteer.
Cada comportamiento tiene como objetivo calcular la fuerza que se ha de
aplicar al objeto que lo implementa, en el espacio 2D, para materializar dicho
comportamiento.
Los comportamientos son independientes entre s, siendo necesario incluir
algn tipo de esquema para combinarlos. Por ejemplo, es posible incluir un
esquema basado en prioridades o pesos para obtener la fuerza global a aplicar a
un objeto.
La implementacin planteada es escalable, siendo necesario implementar un
nuevo mtodo en la clase SteeringBehaviours para incluir un nuevo comporta-
miento.
7 http://www.red3d.com/cwr/steer/
C28

8 http://opensteer.sourceforge.net/
[926] CAPTULO 28. INTELIGENCIA ARTIFICIAL

1
MovingEntity SoccerPlayer SteeringBehaviours

GoalKeeper FieldPlayer
1 4

SoccerTeam

Figura 28.98: Diagrama de clases del modelo basado en comportamientos para la simulacin de ftbol.

El siguiente listado de cdigo muestra el mtodo constructor de la clase Steering-


Behaviours. Como se puede apreciar, dicha clase incluye como estado un diccionario,
denominado activated, con los distintos tipos de comportamientos habilitados en la
misma que, inicialmente, no estn activados.

Listado 28.42: Clase SteeringBehaviours. Mtodo constructor


1 class SteeringBehaviours:
2
3 def __init__ (self, player, ball):
4
5 self.player = player
6 self.ball = ball
7
8 self.target = ball
9
10 # Comportamientos activados.
11 self.activated = {}
12 self.activated[seek] = False
13 self.activated[pursuit] = False
14 self.activated[arrive] = False
15 # Ms comportamientos aqu...

Tpicamente ser necesario activar varios comportamientos de manera simultnea.


Por ejemplo, en el caso del simulador de ftbol podra ser deseable activar el
comportamiento de perseguir el baln pero, al mismo tiempo, recuperar una posicin
defensiva. En otras palabras, en un determinado instante de tiempo un jugador virtual
podra tener activados los comportamientos de pursuit (perseguir) y arrive (llegar).
Desde el punto de vista de la implementacin propuesta, la instancia del jugador
virtual tendra que activar ambos comportamientos mediante el diccionario denomi-
nado activated. Posteriormente, sera necesario calcular la fuerza resultante asociada
a ambos comportamientos en cada instante de tiempo para ir actualizando la fuerza a
aplicar y, en consecuencia, la posicin del objeto virtual.
28.6. Caso de estudio. Juego deportivo [927]

En el siguiente listado de cdigo se muestra la implementacin de los mtodos cal-


culate(), sumForces() y truncate, utilizados para devolver la fuerza global resultante,
calcularla en base a los comportamientos activados y truncarla si as fuera necesario,
respectivamente.

Listado 28.43: Clase SteeringBehaviours. Clculo de fuerza


1 # Devuelve la fuerza global resultante
2 # en base a los comportamientos activados.
3 def calculate (self):
4
5 steeringForce = self.sumForces()
6 return steeringForce
7
8 # Habra que incluir un esquema basado en
9 # prioridades o pesos para combinar de
10 # manera adecuada los comportamientos.
11 def sumForces (self):
12
13 force = Vec2d(0, 0)
14
15 if self.activated[seek]:
16 force += self.seek(self.target)
17 if self.activated[pursuit]:
18 force += self.pursuit(self.target)
19 if self.activated[arrive]:
20 force += self.arrive(self.target)
21 # Ms comportamientos aqu...
22
23 return force
24
25 # Trunca a una fuerza mxima.
26 def truncate (self, max_force):
27
28 if self.steeringForce > max_force:
29 self.steeringForce = max_force

Como se ha comentado anteriormente, la inclusin de un nuevo comportamiento


consiste en implementar un nuevo mtodo dentro de la clase SteeringBehaviours y
registrar la entrada asociada al mismo en el diccionario activated, con el objetivo de
que un jugador virtual pueda activarlo o desactivarlo en funcin del estado de juego
actual. El siguiente listado muestra la implementacin de los comportamientos seek
(buscar) y pursuit (perseguir).
Ambos comportamientos son esenciales para llevar a cabo la construccin de
otros comportamientos ms complejos. Note cmo la implementacin se basa en
geometra 2D bsica para, por ejemplo, calcular la velocidad vectorial resultante como
consecuencia de orientar a una entidad en una direccin determinada.

Listado 28.44: Clase SteeringBehaviours. Seek y pursuit


1 # Dado un objetivo, este comportamiento devuelve la fuerza
2 # que orienta al jugador hacia el objetivo y lo mueve.
3 def seek (self, target):
4
5 desiredVelocity = (target - self.player.pos).normalized()
6 desiredVelocity *= self.player.max_speed
7
8 return (desiredVelocity - self.player.velocity)
9
10 # Crea una fuerza que mueve al jugador hacia la bola.
C28

11 def pursuit (self, target):


12
13 toBall = self.ball.pos - self.player.pos
14 self.direction = toBall.normalized()
[928] CAPTULO 28. INTELIGENCIA ARTIFICIAL

15 lookAheadTime = 0.0
16
17 if self.ball.velocity.get_length() != 0.0:
18 sc_velocity = self.ball.velocity.get_length()
19 lookAheadTime = toBall.get_length() / sc_velocity
20
21 # Dnde estar la bola en el futuro?
22 target = self.ball.futurePosition(lookAheadTime)
23
24 # Delega en el comportamiento arrive.
25 return self.arrive(target)

28.6.4. Diseo de la Inteligencia Articial


El esquema bsico planteado hasta ahora representa la base para la implemen-
tacin del mdulo de IA asociado a los distintos jugadores del equipo virtual. En
otras palabras, el cdigo fuente base discutido hasta ahora se puede entender como un
framework sobre el que construir comportamientos inteligentes a nivel de jugador
individual y a nivel de estrategia de equipo.
Por ejemplo, la gura 28.99 muestra un ejemplo clsico en simuladores futbolsti-
cos donde un jugador que no mantiene el baln en un determinado instante de tiempo,
es decir, un jugador de apoyo (supporting player) ha de emplazarse en una buena po-
sicin para recibir el baln. Evidentemente, es necesario garantizar que no todos los
jugadores de un equipo que no mantienen el baln se muevan a dicha posicin, sino
que han de moverse a distintas posiciones para garantizar varias alternativas de pase.
En el ejemplo de la gura 28.99, el jugador 2 representa una mejor alternativa 2
de pase frente al jugador 3. Para evaluar ambas alternativas es necesario establecer 1
un mecanismo basado en puntuaciones para computar cul de ellas es la mejor (o la
menos mala). Para ello, se pueden tener en cuenta los aspectos como los siguientes:

Facilidad para llevar a cabo el pase sin que el equipo contrario lo intercepte.
3
Para ello, se puede considerar la posicin de los jugadores del equipo rival y el
tiempo que tardar el baln en llegar a la posicin deseada. Figura 28.99: Esquema de apoyo
Probabilidad de marcar un gol desde la posicin del jugador al que se le quiere de jugadores. El jugador 2 repre-
senta una buena alternativa mien-
pasar.
tras que el jugador 3 representa una
Histrico de pases con el objetivo de evitar enviar el baln a una posicin que mala.
ya fue evaluada recientemente.

La gura 28.100 muestra de manera grca el clculo de la puntuacin de los


diversos puntos de apoyo al jugador que tenga el baln con el objetivo de llevar a cabo
el siguiente pase. El radio de cada uno de los puntos grises representa la puntuacin
de dichos puntos y se pueden utilizar para que el jugador de apoyo se mueva hacia
alguno de ellos.
El estado de juego determina en gran parte el estado interno de los jugadores, ya
que no se debera actuar del mismo modo en funcin de si el equipo ataca o deende.
Desde el punto de vista defensivo, una posible alternativa consiste en retrasar la
posicin de los jugadores de un equipo con el objetivo de dicultar los pases a los
jugadores del equipo contrario en posiciones cercanas a la portera.
Para ello, se puede establecer un sistema defensivo basado en posicionar a los
jugadores en ciertas zonas o regiones clave del terreno de juego, utilizando para ello la Figura 28.100: Esquema grco
informacin espacial de la gura 28.94. En este contexto, se pueden denir una serie relativo al clculo de los mejores
de posiciones iniciales o home positions a las que los jugadores se replegarn cuando puntos de apoyo para recibir un pa-
haya una prdida de baln. se.
28.7. Sistemas expertos basados en reglas [929]

Esta estrategia tambin puede facilitar el contraataque cuando se recupera el baln.


Evidentemente, esta estrategia defensiva no es la nica y es posible establecer un
esquema defensivo que sea ms agresivo. Por ejemplo, se podra plantear un esquema
basado en la presin por parte de varios jugadores al jugador del equipo rival que
mantiene el baln. Sin embargo, este esquema es ms arriesgado ya que si el jugador
rival es capar de efectuar un pase, sobrepasando a varios jugadores del equipo que
deende, la probabilidad de anotar un tanto se ve incrementada sustancialmente.
5 3 1 La gura 28.101 muestra un ejemplo concreto de posicionamiento de los jugadores
del equipo que deende en base a regiones clave del terreno de juego. Este mismo
esquema se puede utilizar cuando el jugador contrario realice un saque de centro.
6 4 2

28.6.5. Consideraciones nales


En esta seccin se ha planteado una implementacin inicial de un simulador de
Figura 28.101: Esquema defensivo
basado en la colocacin de los ju- ftbol con el objetivo de proporcionar un enfoque general relativo a este tipo de
gadores del equipo que deende en problemtica. A partir de aqu, es necesario prestar especial atencin a los siguientes
determinadas regiones clave. aspectos para incrementar la funcionalidad del prototipo discutido:

La utilizacin de la informacin bsica de los jugadores, especialmente la


posicin, la velocidad y la direccin actuales, junto con el estado del baln,
permite integrar una gran cantidad de funcionalidad asociada a los jugadores,
desde la interceptacin de pases hasta esquemas de combinacin avanzados (e.g.
uno-dos o paredes).
El comportamiento interno a nivel de jugador individual y de equipo debera
modelarse utilizando algn tipo de mquina de estados, como las discutidas
en la seccin 28.5.4. As, sera posible conocer si, por ejemplo, un jugador
ataca o deende. Tpicamente, la recuperacin o prdida del baln disparar
una transicin que condicionar el cambio del estado actual.
ti t i j
El modelo basado en Steering Behaviours representa la base para aadir nuevos
comportamientos. En los captulos 3 y 4 de [16] se discuten estos aspectos ms
Figura 28.102: Esquema de com- en profundidad prestando especial atencin al simulador futbolstico.
binacin avanzada entre jugadores
para sorter al jugador rival mediante La funcionalidad de los jugadores virtuales se puede extender aadiendo
paredes. complejidad e integrando tcnicas ms avanzadas, como por ejemplo el regate
o dribbling.
Es posible integrar tcnicas como la Lgica Difusa, estudiada en la seccin 28.2,
para manejar la incertidumbre y, por lo tanto, establecer un marco ms realista
para el tratamiento de la problemtica de esta seccin.

28.7. Sistemas expertos basados en reglas

28.7.1. Introduccin
Los sistemas expertos son una rama de la Inteligencia Articial simblica que
simula comportamientos humanos razonando con comportamientos implementados
por el diseador del software. Los sistemas expertos se usan desde hace ms de 30
aos, siendo unos de los primeros casos de xito de aplicacin prctica de tcnicas de
C28

Inteligencia articial. Existen diversos tipos: basados en casos, rboles de decisin,


basados en reglas, etc. Este ltimo tipo ser el que se aplicar al desarrollo de
inteligencia articial para un juego a lo largo del presente apartado.
[930] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Los sistemas expertos basados en reglas (rule-based expert systems en ingls)


estn compuestos por los siguientes tres elementos que se observan en la gura 28.103:

Figura 28.103: Arquitectura bsica de un sistema experto basado en reglas.

Base de conocimiento incluye los hechos, informacin almacenada en el sistema, y


las reglas de inferencia, que permiten generar nuevo conocimiento a partir de
los hechos.
Motor de inferencia es el algoritmo encargado de analizar la satisfacibilidad de las
reglas con los hechos presentes en el sistema.
Agenda es el conjunto de reglas del sistema activables en un momento dado. Debe
incorporar una estrategia de resolucin de conictos, que es un algoritmo para
decidir de qu regla activar en caso de que haya varias posibles. Algunos
algoritmos tpicos son la cola (ejecuta la primera regla activable que se deni
en el sistema), aleatorio, activacin de la regla menos usada, etc

El funcionamiento del sistema es sencillo: el motor de inferencia comienza


evaluando las reglas activables inicialmente en el sistema (aquellas cuyas condiciones
de activacin se cumplen con los hecho iniciales), pasando a introducirlas en la
agenda. De entre las reglas en la agenda, selecciona una de acuerdo a la estrategia
de resolucin de conictos denida, y la ejecuta. Dicha ejecucin (tambin llamado
disparo, del ingls trigger) suele provocar cambios en los hechos del sistema. Por lo
tanto, es necesario recalcular la agenda, pues habr reglas que dejen de ser activables
(pues alguna de sus condiciones ya no se cumpla) y otras que pasen a serlo (porque
ya se cumplan todas sus condiciones). Tras este paso de nuevo se usa la estrategia de
resolucin de conictos para elegir la regla a disparar, realizando una nueva iteracin.
El sistema continuar iterando de esta manera hasta que no tiene reglas que activar en
la agenda.
Este proceso podra parecer ineciente computacionalmente, sobre todo en trmi-
nos de tiempo necesario para recorrer la lista de reglas evaluando condiciones. Sin
embargo, como el conjunto de reglas (y sus condiciones) no cambian a lo largo de
la vida del sistema, los entornos de ejecucin suelen introducir diversas optimizacio-
nes, siendo la ms destacada la creacin de una red de reglas que comparten nodos
(condiciones).
28.7. Sistemas expertos basados en reglas [931]

Dado que el nmero de hechos que suele cambiar tras cada activacin de una regla
suele ser bajo, una adecuada organizacin de condicionantes permite, a nivel prctico,
dar unos tiempos de ejecucin adecuados. Si se desea ampliar informacin sobre este
asunto, el principal libro de referencia es [39].

28.7.2. Caso de estudio: Stratego


Los conceptos presentados anteriormente se aplicarn a una versin simplicada
del juego de tablero Stratego [103]. En el juego participan dos jugadores que tienen
a su cargo dos ejrcitos idnticos de 16 chas, cada una con un valor. El objetivo es
capturar la cha ms dbil del rival, la nica con valor 1. Las partidas se juegan en un
cuadrante de 8x8 casillas, comenzando un ejrcito en las dos primeras las del tablero
y el otro en las dos ltimas. Inicialmente los dos jugadores conocen las posiciones
ocupadas por las chas de los dos ejrcitos, pero slo conocen los valores concretos
de sus chas, que las coloca en el orden que considere ms adecuado. Por lo tanto
existe un conocimiento parcial del entorno como se observa en la gura 28.104, que
muestra un estado inicial de una partida.
Los jugadores mueven una cha propia a una casilla adyacente por turnos. Cada
casilla slo la puede ocupar una cha, excepto las casillas obstculo, que no las puede
ocupar cha alguna. En caso de que dos chas de distinto ejrcito coincidan en la
misma casilla la de menor valor queda capturada (eliminndose del tablero) y la
de mayor valor se queda en la casilla siendo descubierta su puntuacin al jugador
contrario. En caso de que colisionen dos chas de igual valor, se eliminan ambas.
En la gura 28.105 se muestra un estado intermedio de la partida con chas en
posiciones distintas a las iniciales, algunas con su valor descubierto y otras capturadas
no aparecen.
Para implementar un sistema experto que pueda jugar a este juego se usar
Figura 28.104: Posible estado ini- CLIPS9 , un entorno para el desarrollo de sistemas expertos basados en reglas que
cial de una partida. inicialmente fue desarrollado por la NASA y actualmente mantenido por uno de sus
creadores, Gary Riley. CLIPS est disponible bajo licencia libre en su web [75], y se
puede usar interactivamente desde consola o integrndolo con diversos lenguajes de
programacin (C/C++, Java, Python, etc).
En el listado 28.45 se puede observar diversos hechos de ejemplo. El primero
(lnea 3) nos indica que se est jugando el turno nmero 30 de la partida (duracin por
defecto de la partida es 200 turnos). El segundo hecho tiene dos campos (pos-x y pos-
y), e informa que en la casilla (4, 5) del tablero hay un obstculo. El tercer hecho nos
indica que en la esquina inferior izquierda del tablero hay a una cha con identicador
111 y su valor es 3 puntos. Este valor lo desconoce el otro jugador porque el campo
descubierta est a 0. El siguiente hecho (lnea 10) nos informa que justo arriba de la
cha anterior se tiene otra con el identicador 929 y tambin de 3 puntos de valor, pero
en esta ocasin su valor s es conocido por el rival. Por ltimo, en las dos lineas de
abajo del todo se nos indica que dos chas del jugador contrario (equipo B) rodeando
la cha 929, una en la casilla (1, 3) cuyo valor no se conoce y otra en la casilla (2, 2)
Figura 28.105: Posible estado in- que vale 3 puntos.
termedio de una partida.

Listado 28.45: Ejemplos de hechos del sistema


1 ; Esta linea es un comentario
2
3 (tiempo 30)
4
5 (obstaculo (pos-x 4) (pos-y 5))
C28

9 No confundir con Common LISP (CLISP), otro lenguaje cuya sintaxis tambin se basa en parntesis

anidados.
[932] CAPTULO 28. INTELIGENCIA ARTIFICIAL

6
7 (ficha (equipo "A") (num 111) (pos-x 1) (pos-y 1)
8 (puntos 3) (descubierta 0))
9
10 (ficha (equipo "A") (num 929) (pos-x 1) (pos-y 2)
11 (puntos 3) (descubierta 1))
12
13 (ficha (equipo "B") (pos-x 1) (pos-y 3)
14 (descubierta 0)
15
16 (ficha (equipo "B") (pos-x 2) (pos-y 2) (puntos 3)
17 (descubierta 1))

En el listado 28.46 se puede observar una regla de ejemplo. La regla comienza


con la palabra reservada defrule seguida de la cadena EQUIPO-A::, que indica que la
regla pertenece al mdulo de inteligencia articial del ejrcito. A continuacin aparece
el nombre de la regla, que suele describir su intencin (atacar en este caso). En la
segunda lnea se indica la prioridad de la regla con la palabra reservada salience.
CLIPS permite reglas de prioridades desde 1 a 100, de modo que su estrategia de
resolucin de conictos slo ejecutar una regla de una prioridad determinada si no
existen reglas de prioridad superior en la agenda. En el caso de este juego se usarn
prioridades de 1 a 80 para las reglas de inteligencia articial, dejando prioridades
superiores para el control del game loop.
Las siguientes lneas hasta el smbolo => son las condiciones que deben cumplir
los hechos del sistema para poder activar la regla. Cada campo de las condiciones
puede tener bien una constante (en este caso la letra A en la lnea 3, pero tambin se
podra jar la posicin X o Y por ejemplo) o una variable. Las variables son cadenas
de alfanumricas sin espacios que comienzan por el smbolo ?. En este caso concreto
la regla busca una cha propia cualquiera, sobre la que no se imponen restricciones a
identicador, posiciones X e Y ni valor en puntos.
La lnea despus del smbolo => indica la accin a efectuar si la regla se activa.
Hay que recordar que de todas las reglas activables en la agenda slo se activa
una en cada paso, pues su activacin probablemente cambie hechos del sistema y
como consecuencia la composicin de la agenda). En nuestro caso se usa la palabra
reservada assert para indicar que la cha que se seleccion anteriormente (porque se
usa su mismo identicador, ?n1) se mueva hacia arriba10 en el turno actual (tiempo
?t).

Listado 28.46: Versin inicial de la regla atacar


1 (defrule EQUIPO-A::atacar
2 (declare (salience 30))
3 (ficha (equipo "A") (num ?n1) (pos-x ?x1) (pos-y ?y1)
4 (puntos ?p1))
5 (tiempo ?t)
6 =>
7 (assert (mueve (num ?n1) (mov 3) (tiempo ?t)))
8 )

A primera vista, esta regla puede parecer adecuada, al n y al cabo slo mueve una
cha hacia delante, que es donde estn los contrarios al comenzar la partida. Y como
su prioridad es bastante baja, slo se ejecutar si no hay otra regla de mayor prioridad
(que se supone har una accin mejor). Sin embargo es muy mejorable. Por ejemplo,
10 Los movimientos estn denidos como 1 avanza en X, 2 retrocede en X, 3 avanza en Y y 4 retrocede

en Y.
28.7. Sistemas expertos basados en reglas [933]

pudiera ser que la cha se dirigiera hacia una cha contraria de mayor puntuacin,
como podra ser la cha de 2 puntos del equipo prpura que est en la primera la
del tablero en la gura 28.106. Si se aplica la regla anterior a dicha cha se estaran
perdiendo varios turnos para suicidarla.
As que se podra cambiar la regla por la que aparece en el listado 28.47. En este
caso se han aadido dos condiciones nuevas. La primera (lnea 5) busca la existencia
de una cha contraria (equipo B) en la misma columna (pues es usa la misma
variable ?x1 para los dos campos pos-x en su lnea 5). Y la segunda condicin (lnea
7) comprueba que la cha contraria est arriba de la propia y que tenga menos valor
que esta. Ntese el uso de notacin preja, primero aparece el operador y despus los
operandos.
Puede observarse que la primera vez que aparece una variable se instancia al
valor de un hecho del sistema, y a partir de ah el resto de apariciones en esa y otras
condiciones ya se considera ligada a una constante. En caso de que se cumplan todas
Figura 28.106: Estado de partida las condiciones, la regla se considera activable. Si alguna de las condiciones no puede
para ataque. satisfacerse, se intenta satisfacer de nuevo instanciando las variables con otros hechos
del sistema.

Listado 28.47: Primera mejora de la regla atacar


1 (defrule EQUIPO-A::atacar1
2 (declare (salience 30))
3 (ficha (equipo "A") (num ?n1) (pos-x ?x1) (pos-y ?y1)
4 (puntos ?p1))
5 (ficha (equipo "B") (num ?n2) (pos-x ?x1) (pos-y ?y2)
6 (puntos ?p2) (descubierta 1))
7 (test (and (> ?y1 ?y2) (> ?p1 ?p2)))
8 (tiempo ?t)
9 =>
10 (assert (mueve (num ?n1) (mov 3) (tiempo ?t)))
11 )

Parece evidente que la regla ha mejorado respecto a la aproximacin anterior. Pero


al comprobar simplemente que la contraria est por arriba existe la posibilidad de
suicidio (si entre ellas hay chas contrarias con ms puntos entre ellas), y adems se
ha restringido mucho la posibilidad de aplicar la regla (ya que si la cha contraria
cambia de columna se dejara de avanzar hacia ella).
Aunque debido al conocimiento parcial del entorno que tienen los jugadores no
existen estrategias netamente mejores unas que otras, lo cierto es que las reglas que
son ms concretas suelen ser muy efectivas, pero se aplican pocas veces en la partida.
Mientras que las reglas ms generalistas suelen aplicarse ms veces, pero su resultado
es menos benecioso. Por ello se suele buscar un equilibro entre reglas concretas con
prioridades altas y otras ms generalistas con prioridades ms bajas.
As pues se propone una variante (ojo, que no mejora necesariamente) sobre esta
regla para hacerla ms aplicable. Simplemente se elimina el condicionante de que
estn las dos chas en la misma columna, resultando la regla del listado 28.48.

Listado 28.48: Segunda mejora de la regla atacar


1 (defrule EQUIPO-A::atacar2
2 (declare (salience 20))
3 (ficha (equipo "A") (num ?n1) (pos-y ?y1)
4 (puntos ?p1))
5 (ficha (equipo "B") (num ?n2) (pos-y ?y2)
C28

6 (puntos ?p2) (descubierta 1))


7 (test (and (> ?p1 ?p2) (> ?y1 ?y2)))
8 (tiempo ?t)
9 =>
[934] CAPTULO 28. INTELIGENCIA ARTIFICIAL

10 (assert (mueve (num ?n1) (mov 3) (tiempo ?t)))


11 )

Para evitar el suicidio hay que el elemento condicional not, que comprueba
la no existencia de hechos. Si se desea indicar la no existencia de hechos con
determinados valores constantes en sus campos simplemente se antepone not a la
condicin. Por ejemplo (not (cha (equipo A) (num 111))). Pero si se quieren incluir
comprobaciones ms complejas hay que incluir la conectiva Y, &. A continuacin
se muestra un ejemplo en que se aplicar la una regla de huida del listado 28.49,
quedando como ejercicio la aplicacin a las reglas anteriores de ataque.

Listado 28.49: Versin inicial de la regla huir


1 (defrule EQUIPO-A::huir
2 (declare (salience 20))
3 (ficha (equipo "A") (num ?n1) (pos-x ?x) (pos-y ?y1)
4 (puntos 1))
5 (ficha (equipo "B") (pos-x ?x) (pos-y ?y2)
6 (puntos 5))
7 (test (> ?y1 ?y2))
8
9 (tiempo ?t)
10 =>
11 (assert (mueve (num ?n1) (mov 4) (tiempo ?t)))
12
13 )

Esta regla, sin embargo, adolece los problemas anteriormente comentados: posibi-
lidad de suicidio y aplicacin incluso con chas contrarias muy lejanas (vase estado
de la gura 28.107). Se propone como mejora la regla del listado 28.50.

Listado 28.50: Versin nal de la regla huir


1 (defrule EQUIPO-A::huir5
2 (declare (salience 20))
3 (ficha (equipo "A") (num ?n1) (pos-x ?x) (pos-y ?y1)
4 (puntos 1))
5 (ficha (equipo "B") (pos-x ?x) (pos-y ?y2)
6 (puntos 5))
7 (test (or (= ?y1 (+ ?y2 1)) (= ?y1 (- ?y2 1))))
8 (not (ficha (equipo "B") (pos-x ?x1) (pos-y (+ 1 ?y1))))
9 (not (ficha (equipo "B") (pos-x ?x1) (pos-y (- 1 ?y1))))
10 (tiempo ?t)
11 => Figura 28.107: Estado de partida
12 (assert (mueve (num ?n1) (mov 1) (tiempo ?t)))
13 (assert (mueve (num ?n1) (mov 2) (tiempo ?t))) para huida.
14 )

En esta regla se observa otro aspecto muy importante: el no determinismo en


la accin. En este caso se proponen dos posible escapatorias, pues se considera
que las dos posibilidades son igual de interesantes. De este modo, adems, se
enriquece la experiencia del usuario, al que le resultar ms complicado adivinar el
comportamiento implementado en el juego11
El no determinismo tambin se puede (y por general se debe) implementar a
nivel de reglas: se pueden tener varias reglas con la misma prioridad que compartan
ciertas condiciones, o que las condiciones de una regla sean un subconjunto de
las condiciones de otra e incluso condiciones exactamente iguales. Sin embargo,
11 Ntese que es un ejemplo un poco forzado para mostrar el no determinismo. Realmente sera mejor

tener dos reglas, una con una huida para cada lado, que al tener menos condiciones sera ms aplicables.
28.7. Sistemas expertos basados en reglas [935]

hay veces que los programadores se plantean poner reglas que tengan condiciones
excluyentes u ordenar todas las reglas por prioridades. Aunque esta estrategia no es
necesariamente mala, se pierde el potencial de los sistemas expertos basados en reglas,
pues realmente se estara implementando un rbol de decisin determinista.
Otro aspecto interesante es que tambin se pueden crear otros hechos para
facilitar la planicacin de estrategias. Por ejemplo, si una estrategia cambia su
comportamiento cuando pierde las chas de 5 y 6 puntos se aadira la primera regla
del listado 28.51 y el hecho fase 2 se usara como condicin de las reglas exclusivas
de dicho comportamiento.

Listado 28.51: Regla de cambio de fase


1 (defrule EQUIPO-A::cambioDeFase
2 (declare (salience 90))
3 (not (ficha (equipo "A") (puntos 5)))
4 (not (ficha (equipo "A") (puntos 6)))
5 =>
6 (assert (fase 2))
7 )
8
9 (defrule EQUIPO-A::despistar
10 (declare (salience 50))
11 (fase 2)
12 ...
13 )

Hasta aqu llega este captulo de introduccin. Como se habr observado en


los ejemplos una de las ventajas de los sistemas expertos basados en reglas es la
facilidad de modicacin. El comportamiento de una estrategia se puede cambiar
fcilmente aadiendo, modicando o eliminando condiciones de reglas. Igualmente,
la re-priorizacin de reglas es una forma sencilla de renar estrategias. CLIPS tiene
muchas otras funciones y extensiones que se pueden consultar en la documentacin
en lnea del proyecto [75].
El juego que se ha presentado en este captulo est implementado en Gades Siege,
disponible bajo licencia libre en su web [99]. El entorno incluye diversas opciones para
facilitar su uso, como jugar partidas entre dos estrategias, jugar un humano contra una
estrategia, visualizar partidas jugadas o jugar campeonatos. En el web se encuentran
instrucciones de instalacin y uso, as como un amplio abanico de estrategias de
ejemplo resultado de su uso docente en la asignatura Diseo de Videojuegos de la
Universidad de Cdiz [70].

C28
[936] CAPTULO 28. INTELIGENCIA ARTIFICIAL

Figura 28.108: Captura de Gades Siege.


Captulo 29
Networking
Flix Jess Villanueva
David Villa Alises

I
nternet, tal y como la conocemos hoy en da, permite el desarrollo de videojuegos
en red escalando el concepto de multi-jugador a varias decenas o centenas de
jugadores de forma simultnea. Las posibilidades que ofrece la red al mercado de
los videojuegos ha posibilitado:

La aparicin de nuevos tipos de videojuegos directamente orientados al juego


en red y que suponen, en la actualidad, una gran parte del mercado.
Evolucionar juegos ya distribuidos mediante actualizaciones, nuevos escena-
rios, personajes, etc.
Permitir nuevos modelos de negocio como por ejemplo, distribuyendo el juego
de forma gratuita y cobrando por conexiones a los servidores para jugar en red.

29.1. Conceptos bsicos de redes


Internet es posible gracias al conjunto de protocolos denominado pila TCP/IP (Pi-
la de protocolos de Internet). Un protocolo es un conjunto de reglas sintcticas, se-
mnticas y de temporizacin que hacen posible la comunicacin entre dos procesos
cualesquiera.
En todo proceso de comunicaciones estn involucrados un conjunto de protocolos
cuyo cometido es muy especco y que tradicionalmente se han dividido en capas.
Estas capas nos sirven para, conceptualmente, resolver el problema complejo de las
comunicaciones aplicando el principio de divide y vencers.

937
[938] CAPTULO 29. NETWORKING

Figura 29.1: Funcionalidades, Capas y Protocolos en Internet

Figura 29.2: Encapsulacin de protocolos

Como podemos ver en la gura 29.1 cada funcionalidad necesaria se asocia a una
capa y es provista por uno o varios protocolos. Es decir, cada capa tiene la misin
especca de resolver un problema y, generalmente, existen varios protocolos que
resuelven ese problema de una forma u otra. En funcin de la forma en la cual resuelve
el problema el protocolo, dicha solucin tiene unas caractersticas u otras. En la capa
de aplicacin la funcionalidad es determinada por la aplicacin.
La agregacin de toda esta informacin dividida en capas se realiza mediante
un proceso de encapsulacin en el cual los protocolos de las capas superiores se
encapsulan dentro de las capas inferiores y al nal se manda la trama de informacin
completa. En el host destino de dicha trama de informacin se realiza el proceso
inverso. En la imagen 29.2 podemos ver un ejemplo de protocolo desarrollado para
un videojuego (capa de aplicacin) que utiliza UDP (User Datagram Protocol) (capa
de transporte), IPv4 (Capa de red) y nalmente Ethernet (capa de Acceso a Red).
En las siguientes secciones vamos a dar directrices para el diseo e implementa-
cin del protocolo relacionado con el videojuego as como aspectos a considerar para
seleccionar los protocolos de las capas inferiores.
29.2. Consideraciones de diseo [939]

29.2. Consideraciones de diseo


Desde el punto de vista de la red, los juegos multijugador se desarrollan en tiempo
real, esto es, varios jugadores intervienen de forma simultnea, y por tanto son mas
exigentes. Su desarrollo y diseo tiene que lidiar con varios problemas:

Sincronizacin, generalmente, identicar las acciones y qu est pasando en


juegos de tiempo real requiere de una gran eciencia para proporcionar al
usuario una buena experiencia. Se debe disear el sistema de transmisin y
sincronizacin as como los turnos de red para que el juego sea capaz de
evolucionar sin problemas.
Identicacin de las actualizaciones de informacin y dispersin de dicha
informacin necesaria a los jugadores especcos para que, en cada momento,
todos las partes involucradas tengan la informacin necesaria del resto de las
partes.
Determinismo que asegure que todas las partes del videojuego son consistentes.

La forma en la que se estructura y desarrolla un videojuego en red, puede


determinar su jugabilidad desde el principio del proceso de diseo.
Se debe identicar, desde las primeras fases del desarrollo, qu informacin se va
a distribuir para que todas las partes involucradas tengan la informacin necesaria para
que el juego evolucione.
Al mas bajo nivel, debemos disear un protocolo de comunicaciones que, median-
te el uso de sockets (como veremos en las siguientes secciones), nos permita transmitir
toda la informacin necesaria en el momento oportuno. Como cualquier protocolo, s-
te debe denir:

Sintaxis Qu informacin y cmo se estructura la informacin a transmitir. Esta


especicacin va a denir la estructura de los mensajes, su longitud, campos
que vamos a tener, etc. El resultado de esta fase de diseo debe ser una serie de
estructuras a transmitir y recibir a travs de un socket TCP o UDP.
Semntica Qu signica la informacin transmitida y cmo interpretarla una vez re-
cibida. Directamente relacionada con la sintaxis, la interpretacin de la informa-
cin de este proceso se realiza mediante el parseo de los mensajes transmitidos
e interpretando la informacin recibida. De igual forma, se construyen los men-
sajes a transmitir en funcin de la semntica que queramos transmitir.
Temporizacin El modelo de temporizacin expresa la secuencia de mensajes que
se deben recibir y transmitir en funcin de los mensajes recibidos y enviados
con anterioridad y de la informacin que necesitemos o queramos transmitir.
La temporizacin y la semntica generalmente se traducen en una mquina
de estados cuyo salto entre estados lo determina los mensajes recibidos y
transmitidos y la informacin contenida en ellas.

El protocolo de un videojuego debe especicar la sintaxis, semntica y


temporizacin
C29
[940] CAPTULO 29. NETWORKING

Un aspecto determinante en el diseo es qu informacin es necesario transmitir.


Esta pregunta es especca del videojuego a desarrollar y por lo tanto tiene tantas
respuestas como videojuegos en red existen. De forma genrica, debemos identicar:

Aquella informacin relacionada con el aspecto visual de un objeto y su estado


como puede ser: posicin, orientacin, movimiento, accin que est realizando,
etc.
Informacin relativa a la lgica del juego siempre y cuando sea necesario su
transmisin en red.
Aquellos eventos que un jugador puede realizar y que afectan al resto y al propio
juego (es decir, al estado del mismo).

La eciencia en la transmisin y procesamiento de la informacin de red,


as como minimizar la informacin transmitida, deben regir el diseo y la
implementacin de la parte de networking

Aunque es posible plantear arquitecturas de comunicaciones P2P (Peer To Peer)


o con mltiples servidores (como veremos posteriormente), en la mayor parte de los
juegos en red, cliente y servidor tienen tareas bien diferenciadas. El cliente enva su
posicin y representa al enemigo en la posicin que recibe. Se encarga tambin del
tratamiento de la informacin hacia y desde los perifricos del computador (teclado,
ratn, jostick, micrfono, etc.) y la enva al servidor. El servidor por su parte mantiene
el mundo en el que estn representados todos los jugadores, calcula la interacciones
entre ellos y enva a los clientes informacin de forma constante para actualizar su
estado.
Podemos ver un ejemplo de esta implementacin de networking en los motores de
juego de VALVE [22] que simulan eventos discretos para su mundo virtual entorno a
33 veces por segundo. El motor del juego del servidor enva actualizaciones de estado
unas 20 veces por segundo a los clientes que guardan los 100 ltimos milisegundos
recibidos. De forma simultnea cada cliente enva el estado del ratn y teclado entorno
a 20 veces por segundo (no se genera un evento por cada cambio de estado sino que
se discretiza a intervalos peridicos).
Obviamente estos datos pueden modicarse de forma esttica en funcin del tipo
de juego que estemos desarrollando (por ejemplo, en los juegos de estrategia suelen
ser mas amplios) o de forma dinmica en funcin de los clientes conectados. Es
necesario resaltar que en el caso de clientes conectados con diversas caractersticas
en conexiones de red, la velocidad se debe ajustar al mas lento (dentro de una calidad
aceptable). Estos ajustes dinmicos se suelen hacer mediante pruebas de ping que
ayudan a ver el retardo completo existente en la red. Habitualmente jugadores con
un retardo excesivo son rechazados puesto que degradaran el rendimiento global del
juego.
En cualquier caso, cada tipo de juego tiene unos requerimientos aceptables en
cuanto a latencia y el estudio del comportamiento de los jugadores nos pueden dar
indicaciones en cuanto a frecuencia de envo de comandos, latencia aceptable, etc.
A pesar de la evolucin en ancho de banda de las redes actuales, es necesario
establecer mecanismos para mejorar la eciencia de las comunicaciones y dotar a
clientes y servidores de mecanismos para proporcionar un rendimiento aceptable
an en presencia de problemas y prdidas en la red. Este tipo de mecanismos son,
principalmente [22]:
29.2. Consideraciones de diseo [941]

Compresin de datos La compresin de datos entendida como las tcnicas destina-


das a reducir el ancho de banda necesario para el juego multijugador. Entre las
tcnicas utilizadas podemos citar:
Actualizaciones incrementales, es decir, enviar a los clientes slo y
exclusivamente la informacin que ha cambiado con respecto a la ltima
actualizacin enviada. En esta tcnica, de forma general, cada cierto
tiempo se enva un estado completo del mundo para corregir posibles
prdidas de paquetes.
Seleccionar el destino de la informacin transmitida en funcin de qu
clientes se ven afectados por el cambio de estado de los objetos en lugar
de transmitir toda la informacin a todos.
agregacin: envo de datos comunes a varios jugadores con un solo
paquete gracias, por ejemplo, el uso de comunicaciones multicast.
Interpolacin La interpolacin permite obtener nuevos estados del mundo virtual del
juego en el cliente a partir de los datos recibidos del servidor. En funcin de la
complejidad de estos nuevos estados siempre son una aproximacin al estado
que tendramos si obtuviramos eventos de estado del servidor a una altsima
frecuencia. Esta tcnica nos permite actualizar el estado del juego en el cliente
mas a menudo que las actualizaciones enviadas por el SERVIDOR y, al mismo
tiempo, poder inferir informacin perdida en la red.
Prediccin La prediccin nos ayuda a obtener nuevos estados futuros del mundo
virtual del juego en el cliente a partir de los datos recibidos del servidor. La
diferencia respecto a la interpolacin es que la sta genera nuevos estados entre
dos estados recibidos por el servidor de cara a la renderizacin mientras que
la prediccin se adelanta a los eventos recibidos desde el servidor. Para ello, el
cliente puede predecir su movimiento en funcin de la entrada que recibe del
usuario y sin que el servidor se lo comunique.
Compensacin del retardo A pesar de las tcnicas anteriores siempre las acciones
cuentan con un retardo que se debe compensar, en la medida de lo posible,
guardando los eventos recibidos por los clientes y sincronizando el estado del
mundo de forma acorde a como estaba el mundo cuando el evento fue originado,
es decir, eliminando el intervalo de comunicacin.

Una vez implementadas estas tcnicas se debe estimar y medir los parmetros de
networking para ver la efectividad de las tcnicas, estudiar el comportamiento de la
arquitectura, detectar cuellos de botella, etc... Por lo tanto, las estimaciones de retardo
y en general, todos los parmetros de red deben ser incluidos en los tests.
Con estas tcnicas en mente, la responsabilidad del cliente, a grandes rasgos, queda
en un bucle innito mientras se encuentre conectado. En dicho bucle, para cada turno
o intervalo de sincronizacin, enva comandos de usuario al servidor, comprueba si
ha recibido mensajes del servidor y actualiza su visin del estado del juego si es as,
establece los clculos necesarios: prediccin, interpolacin, etc. y por ltimo muestra
los efectos al jugador: grcos, mapas, audio, etc.
El servidor por su parte, actualiza el estado del mundo con cada comando recibido
desde los clientes y enva las actualizaciones a cada uno de los clientes con los cambios
que se perciben en el estado del juego.
En ambos casos se necesita un algoritmo de sincronizacin que permita a clientes
y servidores tener consciencia del paso del tiempo. Existen varios algoritmos y
protocolos empleados para esta sincronizacin (a menudo heredados del diseo y
desarrollo de sistemas distribuidos).
C29
[942] CAPTULO 29. NETWORKING

Un algoritmo bsico espera a obtener comandos de todos los clientes involucrados


en el juego, realiza los cmputos necesarios y a continuacin salta al siguiente slot de
sincronizacin donde, de nuevo, espera todos los comandos de los clientes.
Otro de los algoritmos bsicos consta de una espera ja que procesa slo los
comandos recibidos hasta ese momento, a continuacin, pasa al siguiente slot.
En cualquier caso, siempre existe un retardo en las comunicaciones por lo que los
comandos de los usuarios llegan con retardo al servidor. De cara a compensar este
retardo y que no inuya en el juego se suelen realizar compensaciones de retardo
que, junto con estimaciones locales de las consecuencias de los comandos de usuario,
permiten mitigar completamente este retardo de la experiencia del jugador.

29.3. Eciencia y limitaciones de la red


Disear e implementar un videojuego implica realizar un uso eciente de los
recursos del computador, que atae principalmente a la capacidad de cmputo, la
memoria, la representacin grca, sonido e interfaces de interaccin fsica con el
usuario.
Si adems el juego requiere que sus distintos componentes se ejecuten en varios
computadores conectados en red, el diseo y la propia arquitectura de la aplicacin
puede cambiar drsticamente. Existen tres modelos de comunicacin bsicos que se
utilizan en los juegos en red.

29.3.1. Peer to peer


Varias instancias idnticas del programa colaboran intercambindose informacin.
El programa que ejecuta el usuario tiene toda la lgica y el modelo del juego y puede
funcionar de forma completamente autnoma. Era una tcnica muy utilizada en los
juegos de los 90, especialmente en los RTS y los juegos sobre LAN (Local Area
Network)1 , como por ejemplo Starcraft, de Blizzard R .
Cada instancia del juego comparte informacin con todas las dems. El modelo
del juego suele corresponder con una secuencia de turnos (denominado peer-to-peer
lockstep) al comienzo de los cuales se produce un intercambio de mensajes que dene
el estado del juego. En principio, esto garantiza un estado global determinista, pero en
la prctica supone problemas graves:

Dado que supone un aumento exponencial del nmero de mensajes (todos


hablan con todos) es un modelo que escala mal para cantidades de pocas decenas
de usuarios, si no se est utilizando una LAN.
Adems, como se requiere el visto bueno de todos los pares, la duracin de cada
turno estar determinada por el par con mayor latencia.
Como existen n instancias concurrentes del juego que evolucionan de forma
independiente, es relativamente fcil que haya discrepancias en la evolucin
del juego en distintas mquinas resultando en situaciones de juego diferentes a
partir de un estado inicial supuestamente idntico.
Aadir una nueva instancia a un juego en marcha suele ser problemtico puesto
que puede resultar complicado identicar y transmitir un estado completamente
determinista al nuevo jugador.
1 Los juegos de esta poca utilizaban normalmente el protocolo IPX para las comunicaciones en LAN
29.3. Eciencia y limitaciones de la red [943]

Adems, el modelo peer-to-peer es vulnerable a varias tcnicas de cheating:

Latencia falsa (fake latency o articial lag). Consiste en crear problemas


de conectividad o carga en la red hacia la instancia que utiliza el jugador
tramposo (cheater) para alterar el funcionamiento de todas las otras instancias.
Como todos los jugadores han de esperar los comandos de estado de los
dems jugadores, el resultado es una grave prdida de uidez dejando el juego
aparentemente bloqueado al nal de cada turno. El tramposo puede aprovechar
esta situacin para dicultar a otros que su personaje sea localizado o alcanzado
por disparos en un shooter. Se puede implementar sin necesidad de acceder al
cdigo fuente del juego, en ocasiones con un mtodo tan rudimentario como un
pulsador fsico en el cable de red.
Look-ahead. Con esta tcnica el tramposo retrasa sus mensajes de estado a los
otros pares hasta que conoce la informacin que le interesa: posicin y estado
de los oponentes. Despus crea un mensaje de estado con una marca de tiempo
en el pasado, que corresponde al momento en el que debi enviarla realmente.
Las otras instancias del juego interpretan que el mensaje lleg tarde debido a la
latencia y puede ser difcil de detectar que se trata de un mensaje sinttico.

29.3.2. Cliente-servidor
Con la llegada de los primeros FPS y la adopcin masiva de internet (mediante
mdem) a mediados de los 90, los efectos de la latencia de los que adolece
especialmente el modelo peer-to-peer obligaron a los desarrolladores a buscar otras
alternativas. Uno de los primeros juegos en probar algo diferente fue Quake, de id
Software R
En el modelo cliente-servidor muchos jugadores se conectan a un nico servidor
que controla el juego y mantiene la imagen global de lo que ocurre. Este modelo
elimina muchos de los problemas y restricciones del modelo peer-to-peer: ya no se
requiere mantener una realidad coherente compartida por todos los jugadores, puesto
que nicamente el servidor determina el estado del juego. Los clientes muestran
dicho estado con pequeas modicaciones que representan la evolucin inmediata
del escenario en funcin de la entrada del usuario. La topologa lgica pas de ser una
malla completa a una estrella de modo que mejor la escalabilidad y redujo la latencia;
ya no era necesario esperar al cliente ms lento. Tambin simplica la incorporacin
de nuevos jugadores a una partida en curso.
Este modelo desacopla en muchos casos el desarrollo del juego y las interacciones
entre los jugadores simplicando las tareas de la parte cliente.

29.3.3. Pool de servidores


Cuando la cantidad de jugadores es muy grande (cientos o miles) se utilizan
varios servidores coordinados de modo que se pueda balancear la carga. Sin embargo,
su sincronizacin puede ser compleja. Se podra ver como un modelo hbrido que
incorpora caractersticas de los dos anteriores. Sin embargo, no adolece de los
problemas que el modelo peer-to-peer (o al menos no a la misma escala) puesto que
los servidores que forman el pool se ejecutan en computadores de alto rendimiento,
con conexiones de alto ancho de banda y bajo el control de la compaa y su nmero
es mucho menor que uno por usuario.
C29
[944] CAPTULO 29. NETWORKING

29.4. Restricciones especicas de los juegos en red


Cuando la red entra en juego debemos tener en cuenta tres limitaciones adicionales
adems de las habituales:

29.4.1. Capacidad de cmputo


Aunque la utilizacin de la CPU ya es por s misma un factor determinante en la
mayora de los videojuegos modernos, en el caso de un juego en red debemos tener en
cuenta adems la sobrecarga de cmputo que implica el procesamiento y generacin
de los mensajes, aparte de las relativamente costosas tcnicas de compensacin de la
latencia que veremos a continuacin.

29.4.2. Ancho de banda


Es la tasa de transferencia efectiva (en bits por segundo) entre los distintos compu-
tadores que alojan partes del juego. Si existen ujos asimtricos deben considerarse
por separado. Es tambin habitual que entre dos componentes existan ujos de control
y datos diferenciados. Resulta determinante cuando se deben soportar grandes canti-
dades de jugadores y es el aspecto que ms limita la escalabilidad de la arquitectura
cliente-servidor.

29.4.3. Latencia
Es el tiempo que tarda un mensaje desde que sale del nodo origen hasta que llega
al nodo destino. Hay muchos factores que afectan a la latencia: las limitaciones fsicas
del medio, los procedimientos de serializacin de los mensajes, el sistema operativo,
el tiempo que los paquetes pasan en las colas de los encaminadores, los dispositivos
de conmutacin, etc.
Muy relacionado con la latencia est el tiempo de respuesta, que es el tiempo total
desde que se enva un mensaje hasta que se recibe la respuesta. En ese caso se incluye
tambin el tiempo de procesamiento del mensaje en el servidor, adems de la latencia
de ida y vuelta. Un juego con restricciones importantes debera evitar depender de
informacin que se obtiene como respuesta a una peticin directa. Es frecuente utilizar
mensajes con semntica oneway, es decir, que no implican esperar una respuesta en
ese punto.
An hay otra consideracin muy importante relacionada con la latencia: el jitter.
El jitter es la variacin de la latencia a lo largo del tiempo. Cuando se utilizan
redes basadas en conmutacin de paquetes con protocolos best-effort como IP
las condiciones puntuales de carga de la red en cada momento (principalmente
los encaminadores) afectan decisivamente al retardo de modo que puede cambiar
signicativamente entre un mensaje y el siguiente.
La latencia y el jitter permisible dependen mucho del tipo de juego y estn muy
inuenciados por factores psicomotrices, de percepcin visual del espacio, etc. Los
juegos ms exigentes son los que requieren control continuo, como la mayora de
los shotters, simuladores de vuelo, carreras de coches, etc., que idealmente deberan
tener latencias menores a 100 ms. El punto de vista subjetivo aumenta la sensacin de
realismo y provoca que el jugador sea mucho ms sensible, afectando a la jugabilidad.
En otros tipos de juegos como los de estrategia o que dependen de decisiones
colaborativas, el retardo admisible puede ser de hasta 500 ms o incluso mayor.
29.5. Distribucin de informacin [945]

La evolucin de los modelos de comunicaciones en los videojuegos en red y


la mayora de las tcnicas que veremos a continuacin tienen un nico objetivo:
compensar los efectos que la latencia tienen en la experiencia del usuario.

29.5. Distribucin de informacin


Existen muchas tcnicas para reducir el impacto de las limitaciones del ancho
de banda y la latencia en las comunicaciones. Por ejemplo, para reducir el ancho de
banda necesario se pueden comprimir los mensajes, aunque eso aumenta la latencia y
el consumo de CPU tanto en el emisor como en el receptor.
Tambin se puede mejorar la eciencia agrupando mensajes (message batching).
La mayor parte de los mensajes que se intercambian los componentes de un juego
son bastante pequeos (apenas unos cientos de bytes). Utilizar mensajes mayores
(cercanos a la MTU (Maximum Transfer Unit)) normalmente es ms eciente, aunque
de nuevo eso puede afectar negativamente a la latencia. El primer mensaje de cada
bloque tendr que esperar a que la cantidad de informacin sea suciente antes de salir
del computador. Obviamente eso puede ser prohibitivo dependiendo de la informacin
de que se trate. La posicin del jugador debera noticarse lo antes posible, mientras
que su puntuacin actual puede esperar varios segundos.
Como en tantas otras situaciones, las tcnicas que permiten mejorar un aspecto a
menudo son perjudiciales para otros. Por ese motivo no se pueden dar pautas generales
que sean aplicables en todos los casos. En cada juego, y para cada ujo de informacin
habrn de analizarse las opciones disponibles.
Una de las tcnicas ms tiles consiste en modelar el estado de los objetos en
particular los jugadores y el mundo virtual de modo que los cambios puedan ser
noticados de forma asncrona, es decir, limitar al mnimo las situaciones en las que
la progresin del juego dependa de una respuesta inmediata a una peticin concreta
o de un evento asncrono (como el protocolo lockstep). Como se ha visto en otros
captulos, la mayora de los juegos se pueden modelar como un sistema dirigido por
eventos y no es diferente en el caso de la interaccin con componentes remotos.
Una peticin asncrona es aquella que no bloquea al programa, habitualmente
al cliente. Eso se puede conseguir realizando la peticin en un hilo de ejecucin
adicional y especicando un callback, permitiendo as progresar al juego. Cuando
llega la respuesta, se trata como un evento asncrono ms.
Otra posibilidad interesante siguiendo la misma idea es utilizar comunicacio-
nes oneway, es decir, enviar mensajes que anuncian cambios (hacia o desde el servi-
dor) pero que no implican una respuesta de vuelta a la fuente del mensaje. Los men-
sajes oneway (que podemos llamar eventos) permiten disear estrategias mucho ms
escalables. Una comunicacin basada en intercambio de mensajes peticinrespuesta
solo se puede implementar con un sistema de entrega unicast (un nico destino por
mensaje) mientras que una comunicacin basada en eventos puede aprovechar los me-
canismos de difusin multicast de TCP/IP y otros protocolos.
Es bien sabido que Internet tiene un soporte de multicast bastante limitado
debido a que hay relativamente pocas operadoras que ofrezcan ese servicio de forma
generalizada. Adems, IP multicast solo es aplicable si se utiliza UDP como protocolo
de transporte (aunque es los ms frecuente en los juegos en red). Incluso con estas
restricciones, es posible utilizar mecanismos de propagacin de eventos que permiten
que las comunicaciones del juego resulten mucho ms ecientes y escalables. En
juegos con miles de jugadores y decenas de servidores, la comunicacin basada en
eventos simplica la sincronizacin de estos y los algoritmos de consistencia.
C29
[946] CAPTULO 29. NETWORKING

29.6. Modelo de informacin


En un juego en red hay mucha informacin esttica (como el mapa del nivel
en un shotter), pero tambin muchos datos que se actualizan constantemente. La
responsabilidad de almacenar, actualizar y transmitir toda esa informacin lleva a
implementar diferentes modelos, que obviamente estn muy relacionados con los
modelos de comunicaciones que vimos en la seccin anterior:

Centralizado Es el modelo ms sencillo. Un nico componente (el servidor) recoge


la informacin procedente de los jugadores, actualiza el estado global del
juego, comprueba que es completo y correcto aplicando las reglas del juego
y otras restricciones y distribuye el resultado a los jugadores. Esto incluye
evaluar la interaccin entre los jugadores, detectar errores o trucos ilegales
(cheats) o actualizar las puntuaciones. Habr informacin que interese a todos
los jugadores por igual y deba ser transmitida a todos ellos, pero habr ciertos
datos que pueden implicar nicamente a algunos de los jugadores. Por ejemplo,
la orientacin y ngulo exacto del arma de cada jugador solo es importante para
aquellos que pueden verlo o ser alcanzados si dispara. Este tipo de anlisis puede
ahorrar muchos mensajes innecesarios. En resumen, en el modelo centralizado
nicamente el servidor conoce el mundo completo y cada jugador solo tiene la
informacin que le atae.
Replicado En este caso, varios (incluso todos) los participantes en el juego tienen
una copia del mundo virtual. Eso implica la necesidad de sincronizar esa
informacin entre los participantes. Por eso, resulta conveniente cuando la
mayor parte del estado del juego puede ser determinada de forma aislada, es
decir, con poca interaccin con los participantes.
Distribuido Cada participante tiene una parte del estado global del juego, normal-
mente la que ms le afecta, de modo que se reducen los mensajes necesarios
para determinar la situacin de cada jugador. El modelo distribuido es adecuado
cuando el estado depende mucho de variables difciles de prever. Lgicamente,
es menos adecuado para evitar inconsistencias.

29.7. Uso de recursos de red


El uso que cualquier aplicacin hace de los recursos de red se puede cuanticar en
funcin de ciertas variables objetivas [90]:

El nmero de mensajes transmitidos.


La cantidad media de destinatarios por mensaje.
El ancho de banda medio requerido para enviar un mensaje a cada destinatario.
La urgencia de que el mensaje llegue a cada destinatario.
La cantidad de procesamiento necesario para procesar cada mensaje entrante.

Estos valores pueden ser medidos en una situacin de funcionamiento ptimo


o bien pueden ser calculados a partir de un modelo terico del juego. Tambin es
posible determinar cmo afectarn a estas variables los posibles cambios respecto a
una situacin de partida. Por ejemplo, en qu medida crece la cantidad o el tamao de
los mensajes en funcin del nmero de jugadores, el tamao del mapa, la latencia del
jugador ms lento, etc.
29.8. Consistencia e inmediatez [947]

29.8. Consistencia e inmediatez


La consistencia se puede cuanticar en funcin de las diferencias entre el estado
del juego que percibe cada jugador respecto al estado de referencia que mantiene el
servidor. Estas diferencias se deben a la dicultad de compartir la misma informacin
con todos los nodos en el mismo instante. Existen algoritmos distribuidos que
permiten obtener un estado global consistente, por ejemplo utilizando reglas de orden
causal. Sin embargo, la obtencin de ese estado global lleva demasiado tiempo,
aumentando sensiblemente la latencia. Por eso muchas veces es preferible sacricar
algo de consistencia en favor de la inmediatez.
La inmediatez (del ingls responsiveness) es el tiempo que necesita un participante
para procesar un evento que afecta al estado global. Si cada nodo procesa cada evento
tan pronto como le llega, el jugador tendr realimentacin inmediata de sus acciones y
las de otros, pero si lo hace sin vericar que el evento ha llegado tambin a los dems
participantes se producirn inconsistencias. Es decir, consistencia e inmediatez son
propiedades esenciales, pero contrapuestas.
Cuanto menor sea el ancho de banda, mayor la latencia y el nmero de participan-
tes, ms difcil ser mantener un equilibrio razonable entre consistencia e inmediatez.
Desde el punto de vista del jugador es determinante que el juego reaccione
inmediatamente, al menos a las acciones que realiza l. En el modelo cliente-servidor
puro las acciones del jugador (ej. pulsaciones de teclas) se envan al servidor y la
representacin de sus acciones en su interfaz grca no ocurre hasta que el servidor
las valida y determina la posicin y estado del jugador. Esto supone entre 100 y 200
ms en el mejor de los casos si intervienen conexiones WAN. Algunos juegos cliente-
servidor utilizan un pequeo truco que se aprovecha de la percepcin humana. Si
el sistema sensorial humano percibe un estmulo visual y otro sonoro que se supone
que son simultneos (ej. una puerta que se cierra y el sonido que produce) el cerebro
lo interpreta as a pesar de que no se produzcan realmente en el mismo instante. La
tolerancia mxima que parece aplicar el cerebro ronda los 100-150 ms. Este efecto se
denomina vinculacin multisensorial.
vinculacin multisensorial Una mejora obvia es conseguir que el cliente calcule los cambios en el estado
El jugador dispara su arma, el clien- que estima corresponden a la interaccin con el jugador previendo lo que ms tarde
te reproduce el sonido del disparo anunciar el servidor. Obviamente, el servidor dispone de la informacin de todos los
inmediatamente y enva un mensa- participantes mientras que el participante solo dispone de la interaccin con el jugador
je al servidor. El servidor enva un y la historia pasada.
mensaje indicando la rotura de un
cristal como efecto del disparo. Si Veamos un ejemplo. El jugador presiona el botn de avance, la aplicacin calcula
todo ello ha ocurrido en un lapso
de alrededor de 100 ms, el cerebro la nueva posicin del personaje y la representa. Al mismo tiempo un mensaje que
del jugador interpretar que todo ha incluye esa accin se ha enviado al servidor. El servidor realiza el mismo clculo y
ocurrido simultneamente. enva a todos los clientes involucrados la nueva posicin del personaje. En condiciones
normales, habr relativamente poca variacin entre la posicin calculada por el cliente
y por el servidor y no ser necesario que el cliente tome ninguna accin adicional.
Pero puede presentarse otra situacin. En el preciso momento en el que el jugador
avanzaba el personaje recibe un disparo. El servidor descarta el mensaje con la accin
Eco local de avance y notica la muerte de ese personaje. Todos los participantes han visto morir
Un ejemplo cotidiano de la relacin al personaje mientras que el jugador ha sido testigo de la inconsistencia, primero lo
entre consistencia e inmediatez lo vio avanzar normalmente y luego de pronto aparecer muerto unos metros atrs.
tenemos en las aplicaciones de shell
o escritorio remoto. En estas apli- Es una mejora obvia pero compleja de implementar. Para que el cliente pueda
caciones suele existir la opcin de determinar el estado previsible de personaje necesita aplicar todas las restricciones
utilizar eco local o eco remoto, es fsicas del escenario. En el caso de un juego 3D eso implica colisiones, gravedad,
decir, si la aparicin de las pulsa-
ciones o posicin del ratn depende inercia, ..., cosas que no eran necesarias con un modelo cliente-servidor puro.
directamente de nuestras acciones o
de la realimentacin que d el ser-
C29

vidor.
[948] CAPTULO 29. NETWORKING

Un modelo altamente consistente no puede hacer este tipo de predicciones,


pero tendr poca inmediatez (es decir alta latencia) porque las acciones deben ser
corroboradas por el servidor antes de ser consideradas vlidas. Si en lugar de un
modelo centralizado utilizamos un modelo distribuido la situacin es ms grave
porque la accin deber ser corroborada por todos los implicados.

29.9. Predicciones y extrapolacin


Cuando la inmediatez es un requisito ms importante que la consistencia, la
solucin pasa por predecir. Para conseguir mayor inmediatez se requiere mnima
latencia, y dado que se trata de una restriccin fsica la nica alternativa es predecir. A
partir de los valores previos de las propiedades de un objeto, se extrapola el siguiente
valor probable. De ese modo, el usuario percibir un tiempo de respuesta cercano a
cero, similar al de un juego monojugador. En la seccin anterior vimos un esbozo para
conseguir predecir la consecuencias de las acciones del usuario (esto se denomina
prediccin del jugador), pero tambin podemos predecir las acciones de las otras
partes activas del juego, que se denomina en general prediccin del oponente o del
entorno [10].

29.9.1. Prediccin del jugador


Veamos en detalle el algoritmo que aplica el cliente para predecir cmo las
acciones del usuario afectan al estado del juego:

1. Procesar eventos procedentes de los controles (teclado, ratn, joystick, etc.).


2. Serializar las acciones del usuario y enviarlas al servidor.
3. Determinar qu elementos del mundo virtual sern inuidos con la accin del
usuario y calcular las modicaciones en su estado.
4. Renderizar la nueva situacin del juego.
5. Esperar las actualizaciones de estado del servidor.
6. Si hay diferencias, modicar el estado del juego para compensar los errores
cometidos.

Aunque el usuario tiene realimentacin inmediata de sus acciones, el tiempo


transcurrido entre el paso 4 y el 6 (el tiempo de respuesta) sigue siendo determinante.
Si las predicciones no son rpidamente corroboradas o corregidas con la informacin
procedente del servidor, el error cometido por la prediccin se ir acumulando
aumentando las diferencia entre el estado real del juego (mantenido por el servidor) y
el estimado por el cliente.
Ntese que el cliente puede adems enviar la prediccin al servidor, de modo
que ste pueda validarla. As el servidor solo enviar datos si detecta que el error ha
superado determinado umbral. Mientras el cliente sea capaz de predecir correctamente
la trayectoria, el servidor simplemente tiene que dar el visto bueno, y no tiene porqu
hacerlo constantemente. Esto reduce el trco desde el servidor hacia los clientes,
pero le supone una carga de trabajo adicional.
29.9. Predicciones y extrapolacin [949]

El problema surge cuando el servidor enva correcciones respecto a la prediccin.


La correccin tendr una marca de tiempo en el pasado, de modo que el cliente debera
desechar todos las modicaciones que ha hecho sobre el estado el juego desde la marca
hasta el presente. Despus debe volver a aplicar todos los eventos que se han producido
desde ese instante para estimar el nuevo estado.
Eso signica que el estado previsto por el cliente que no han sido corroborada por
el servidor debe considerarse tentativo, y los eventos que se han producido desde el
ltimo instante vericado deben almacenarse temporalmente.

29.9.2. Prediccin del oponente


Esta tcnica (tambin conocida como dead reckoning [10, 9.3]) se utiliza para
calcular el estado de entidades bajo el control de otros jugadores o del motor del
juego.
Para predecir la posicin futura de un objeto se asume que las caractersticas
de su movimiento (direccin y velocidad) se mantendrn durante el siguiente ciclo.
Como en la prediccin del jugador, el cliente puede publicar sus predicciones. El
servidor (o los otros participantes si es una arquitectura distribuida) verica el error
cometido y decide si debe enviar la informacin real para realizar las correcciones.
Esta informacin puede incluir la posicin, direccin y sentido, velocidad e incluso
aceleracin. De ese modo, el cliente no necesita estimar o medir esos parmetros,
puede utilizar los que proporciona la fuente, sea otro jugador o el propio motor del
juego, ahorrando clculos innecesarios. La gura 29.3 ilustra este proceso.

Figura 29.3: Cuando la prediccin calcula una posicin fuera del umbral permitido (fuera de la lnea roja),
el servidor enva una actualizacin (marcas azules) que incluye la posicin, direccin y velocidad real
actual. El cliente recalcula su prediccin con la nueva informacin

Conocido el vector de direccin y la velocidad es sencillo predecir la operacin


con fsica elemental (ver formula 29.1), siendo t0 el instante en que se calcul la
posicin anterior y t1 el instante que se desea predecir.

e(t1 ) = e(t0 ) + v(t1 t0 ) (29.1)

Si adems se desea considerar la aceleracin se utiliza la formula para un


movimiento uniformemente acelerado y rectilneo:

a(t1 t1 )2
C29

e(t1 ) = e(t0 ) + v(t1 t0 ) + (29.2)


2
[950] CAPTULO 29. NETWORKING

Considerar la aceleracin puede llevar a cometer errores de prediccin mucho


mayores si no se tiene realimentacin rpida, por lo que se desaconseja con latencias
altas. El cliente puede medir continuamente el RTT (Round Trip Time) al servidor y
en funcin de su valor decidir si es conveniente utilizar la informacin de aceleracin.
Dependiendo del tipo de juego, puede ser necesario predecir la posicin de partes
mviles, rotaciones o giros que pueden aumentar la complejidad, pero no hacerlo
podra llevar a situaciones anmalas, como por ejemplo un personaje que debe apuntar
continuamente a un objetivo jado.
Cuando la diferencia entre el estado predicho y el real (calculado por el servidor)
es signicativa no es admisible modicar sin ms el estado del juego (snap). Si se
trata, por ejemplo, de la posicin de un personaje implicara saltos (se teletransporta),
que afectan muy negativamente a la experiencia de juego. En ese caso se utiliza un
algoritmo de convergencia que determina los ajustes necesarios para llegar a un estado
coherente con el estado real desde la posicin incorrecta en el pasado en un tiempo
acotado. Si se trata de posiciones se habla de trayectoria de convergencia lineal. La
gura 29.4 ilustra la diferencia entre aplicar o no un algoritmo de convergencia de
trayectoria para el ejemplo anterior.

Figura 29.4: Cuando se reajusta la posicin se producen saltos (lneas discontinuas azules) mientras que
aplicando un algoritmo de convergencia (lneas verdes) el objeto sigue una trayectoria continua a pesar de
no seguir elmente la trayectoria real y tener que hacer ajustes de velocidad

Aunque es una tcnica muy efectiva para evitar que el usuario sea consciente de
los errores cometidos por el algoritmo de prediccin, pueden presentarse situaciones
ms complicadas. Si en un juego de carreras, la ruta predicha implica que un coche
sigue en lnea recta, pero en la real toma un desvo, la ruta de convergencia llevara
el coche a atravesar una zona sin asfaltar, que puede transgredir las propias reglas del
juego. Este escenario es muy comn en los receptores de GPS (Global Positioning
System) que utilizan una tcnica de prediccin muy similar (ver gura 29.5).

Figura 29.5: Ejemplo de trayectoria de convergencia incorrecta debido a un obstculo


29.10. Sockets TCP/IP [951]

29.10. Sockets TCP/IP


La programacin con sockets es la programacin en red de mas bajo nivel que
un desarrollador de videojuegos generalmente realizar. Por encima de los sockets
bsicos que veremos a lo largo de esta seccin se pueden usar libreras y middlewares,
algunos de los cuales los veremos mas adelante.
Estas libreras y middlewares nos aportan un nivel mas alto de abstraccin
aumentando nuestra productividad y la calidad del cdigo, a costa, en algunas
ocasiones, de una prdida de la eciencia.
Conceptualmente, un socket es un punto de comunicacin con un programa
que nos permite comunicarnos con l utilizando, en funcin del tipo de socket que
creemos, una serie de protocolos y, a ms alto nivel, mediante un protocolo que
nosotros mismo diseamos. En funcin del rol que asuma el programa, cabe distinguir
dos tipos de programas:

Cliente: Que solicita a un servidor un servicio.


Servidor: Que atiende peticiones de los clientes.

En la literatura tradicional, hablamos de mecanismos de IPC (InterProcess Com-


munication) (Inter-Process Communication) a cualquier mecanismo de comunicacin
entre procesos. Cuando los procesos a comunicar se encuentran dentro del mismo
computador, hablamos de programacin concurrente. Cuando hay una red de ordena-
dores entre los dos procesos a comunicar, hablamos de programacin en red.
En ambos casos se pueden utilizar sockets, no obstante, nosotros vamos a tratar
con un tipo especco de sockets denominados Internet Sockets y que nos permiten
comunicar un proceso en nuestra mquina con cualquier otra mquina conectada a
Internet.

29.10.1. Creacin de Sockets


Aunque, por norma general, los sockets desarrollados en los sistemas operativos no
son exactamente iguales, es cierto que casi todos los sistemas operativos han seguido
los denominados Berkeley Sockets como gua de diseo y por lo tanto, como API para
la programacin de aplicaciones en red. En esta seccin vamos a seguir los pasos de
programacin de un entorno GNU/Linux que es extensible a toda la familia UNIX.
Con matices mas amplios sirve para entender el interfaz de programacin socket en
general.
Veamos paso a paso la creacin de sockets, si vemos la primitiva para crear un
socket:
sockfd = socket(int socket_family, int socket_type, int protocol);
Admite tres argumentos de entrada, el socket_family dene la familia del socket
que vamos a crear, aunque en esta seccin nos vamos a centrar en la familia AF_INET
relacionada con los sockets para comunicaciones entre procesos a travs de Internet
(usando IPv4), es necesario resaltar que se pueden usar sockets para cualquier tipo de
comunicacin inter-proceso. El parmetro que devuelve es un descriptor del socket
que utilizaremos para referirnos a l.
C29
[952] CAPTULO 29. NETWORKING

Siguiendo la losofa de diseo de los sistemas operativos Unix y Linux, los


descriptores de sockets son tambin descriptores de archivos

En concreto, tenemos las familias de sockets AF_UNIX y AF_LOCAL para la


comunicacin local donde la direccin de comunicacin es el nombre de un archivo,
AF_INET6 para IPv6, AF_NETLINK para comunicar el kernel con el espacio de
usuario o procesos entre ellos (en este caso similar a la familia AF_UNIX), etc.
El segundo argumento nos especica qu tipo de socket vamos a crear dentro de la
familia seleccionada en el argumento anterior. Para AF_INET socket tenemos como
principales opciones:

SOCK_STREAM : indica un tipo de socket que soporta una comunicacin entre


procesos conable, orientada a la conexin y que trata la comunicacin como
un ujo de bytes.
SOCK_DGRAM : indica un tipo de socket que soporta una comunicacin entre
procesos no conable, no orientada a la conexin y que trata la comunicacin
como un intercambio de mensajes.

Dentro de esta familia tambin se incluyen otro tipo de sockets como SOCK_RAW
que permite al programador acceder a las funciones de la capa de red directamente y
poder, por ejemplo, construirnos nuestro propio protocolo de transporte. El otro tipo
de socket que nos podemos encontrar es SOCK_SEQPACKET que proporciona una
comunicacin able, orientada a conexin y a mensajes. Mientras que las tres familias
anteriores estn soportadas por la mayora de los sistemas operativos modernos, la
familia SOCK_SEQPACKET no tiene tanta aceptacin y es mas difcil encontrar una
implementacin para segn qu sistemas operativos.
Si nos jamos, los tipos de sockets nos describen qu tipo de comunicacin
proveen. El protocolo utilizado para proporcionar dicho servicio debe ser especicado
en el tercer parmetro. Generalmente, se asocian SOCK_DGRAM al protocolo UDP,
SOCK_STREAM al protocolo TCP, SOC_RAW directamente sobre IP. Estos son los
protocolos que se usan por defecto, no obstante, podemos especicar otro tipos de
protocolos siempre y cuando estn soportados por nuestro sistema operativo.
Como ya hemos comentado, cada tipologa de socket nos proporciona una
comunicacin entre procesos con unas determinadas caractersticas y usa unos
protocolos capaces de proporcionar, como mnimo, esas caractersticas. Normalmente,
esas caractersticas requieren de un procesamiento que ser mas o menos eciente en
cuanto a comunicaciones, procesamiento, etc.
Es imprescindible que el desarrollador de la parte de networking conozca el coste
de esas caractersticas de cara a disear un sistema de comunicaciones eciente y que
cumpla con las necesidades del videojuego. La decisin de qu tipo de comunicacin
quiere determina:

El tipo de estructura del cliente y servidor.


La eciencia en las comunicaciones
Otras caractersticas asociadas al tipo de servicio como pueden ser si es
orientada a la conexin o no, comunicacin mediante bytes o mensajes,
capacidad de gestionar QoS o no, etc.
29.10. Sockets TCP/IP [953]

Esta decisin, a groso modo est relacionada con si queremos una comunicacin
conable o no, es decir, en el caso de Internet, decidir entre usar un SOCK_STREAM
o SOCK_DGRAM respectivamente y por ende, en la mayora de los casos, entre TCP
o UDP.
El uso de SOCK_STREAM implica una conexin conable que lleva asociado
unos mecanismos de reenvo, ordenacin, etc. que consume tiempo de procesamien-
to a cambio de garantizar la entrega en el mismo orden de envo. En el caso de
SOCK_DGRAM no se proporciona conabilidad con lo que el tiempo de procesa-
miento se reduce.
Qu tipo de comunicacin empleo?, bien, en funcin del tipo de datos que este-
mos transmitiendo deberemos usar un mecanismo u otro. Como norma general todos
aquellos datos en tiempo real que no tiene sentido reenviar irn sobre SOCK_DGRAM
mientras que aquellos tipos de datos mas sensibles y que son imprescindibles para el
correcto funcionamiento deben ir sobre SOCK_STREAM.
Algunos ejemplos de uso de ambos protocolos nos pueden claricar qu usos se le
dan a uno y otro protocolo. En las ltimamente populares conexiones multimedia en
tiempo real, por ejemplo una comunicacin VoIP, se suele usar para la transmisin del
audio propiamente dicho el protocolo RTP (Real Time Protocol) que proporciona una
conexin no conable. Efectivamente, no tiene sentido reenviar una porcin de audio
que se pierde ya que no se puede reproducir fuera de orden y almacenar todo el audio
hasta que el reenvo se produce generalmente no es aceptable en las comunicaciones.
No obstante, en esas mismas comunicaciones el control de la sesin se delega en
el protocolo (SIP (Session Initiation Protocol)) encargado del establecimiento de la
llamada. Este tipo de datos requieren de conabilidad y por ello SIP se implementa
sobre TCP.
Podemos trasladar este tipo de decisiones al diseo de los mdulos de networking
de un determinado videojuego. Una vez decidida la informacin necesaria que se debe
transmitir entre los diversos participantes, debemos clasicar y decidir, para cada ujo
de informacin si va a ser mediante una conexin conable no conable.
Es difcil dar normas genricas por que, como adelantamos en la introduccin,
cada videojuego es diferente, no obstante, generalmente se utilizar comunicaciones
no conables para:

Aquella informacin que por su naturaleza, lleven el tiempo implcito en su


validez como informacin. Generalmente toda la informacin de tiempo real
como movimientos de granularidad na , audio y vdeo, etc.
Informacin que pueda ser inferida a partir de datos que recibimos antes
o despus. Por ejemplo, si el movimiento de un personaje se compone de
varios movimientos intermedios y se puede inferir uno de esos movimiento
intermedios a partir del resto (o incluso ignorar), esa informacin es candidata
a ser transmitida mediante una conexin o conable.

El resto de informacin que no cae en estas dos secciones puede ser susceptible de
implementarse utilizando comunicaciones conables.
Este primer paso de decisin de qu tipo de comunicacin se necesita es
importante ya que determina la estructura del cliente y el servidor.
C29
[954] CAPTULO 29. NETWORKING

29.10.2. Comunicaciones conables


Las comunicaciones conables garantizan la llegada de la informacin transmi-
tida, sin prdidas y la entrega al destino en el orden en el cual fueron enviados. La
conguracin por defecto de los sockets utiliza TCP y la estructura asociada a los
clientes tiene la siguientes primitivas:

struct hostent *gethostbyname(const char *name): Esta estructura nos sirve para
identicar al servidor donde nos vamos a conectar y puede ser un nombre (e.j.
ejemplo.nombre.es), direccin IPv4 (e.j. 161.67.27.1) o IPv6. En el caso de
error devuelve null.
int connect(int sockfd, const struct sockaddr
*serv_addr, socklen_t addrlen): Esta primitiva nos permite realizar la conexin
con un servidor especicado por serv_addr usando el socket sockfd. Devuelve
0 si se ha tenido xito.
ssize_t
send(int s, const void *msg, size_t len, int ags): esta primitiva es utilizada para
mandar mensajes a travs de un socket indicando, el mensaje (msg), la longitud
del mismo (len) y con unas opciones denidas en ags. Devuelve el nmero de
caracteres enviados.
ssize_t
recv(int s, void *buf, size_t lon, int ags): esta primitiva es utilizada para recibir
mensajes a travs de un socket ((s)), el mensaje es guardado en buf, se leen los
bytes especicados por lon y con unas opciones denidas en ags. Devuelve el
nmero de caracteres recibidos.
close(int sockfd): cierra un socket liberando los recursos asociados.

Con estas primitivas podemos escribir un cliente que se conecta a cualquier ser-
vidor e interacciona con l. Para la implementacin del servidor tenemos las mismas
primitivas de creacin, envo, recepcin y liberacin. No obstante, necesitamos algu-
nas primitivas mas:

int bind(int sockfd, struct sockaddr *my_addr,


socklen_t addrlen): Una vez creado un socket que nos va a servir de servidor
necesitamos asociarle una direccin local (my_addr y su longitud addrlen).
int listen(int s, int backlog): Con esta primitiva se indica que el socket s va a
aceptar conexiones entrantes hasta un lmite de conexiones pendientes denido
en backlog.
int accept(int s, struct sockaddr *addr,
socklen_t *addrlen): Esta funcin acepta una conexin pendiente, le asocia
un nuevo socket que ser devuelto por la funcin y que se emplear para la
transmisin y recepcin de datos propiamente dicho con el cliente que se acaba
de aceptar. Llamaremos a este nuevo socket, socket de servicio.

Veamos una implementacin de ejemplo que nos sirva para inscribir a un personaje
en una partida gestionada por un servidor. En este primer ejemplo un usuario se
desea registrar en un servidor de cara a enrolarse en una partida multijugador. La
informacin de usuario ser expresada por una estructura en C que podemos ver en el
listado 29.1 conteniendo el nick del jugador, su tipo de cuenta y su clave. Este tipo
de informacin es necesario transmitirlo por una conexin conable ya que queremos
que el proceso se realice de forma conable.
29.10. Sockets TCP/IP [955]

Listado 29.1: Estructura de registro de jugadores


1 struct player{
2 char nick[10];
3 int account_type;
4 char passwd[4];
5 };

En las estructuras denidas para su transmisin en red no se deben usar


punteros. Es necesario resaltar que los punteros slo tienen sentido en el
espacio de memoria local y al transmitirlo por la red, apuntan a datos distintos
en la mquina destino. Salvo mecanismo que tenga en cuenta este aspecto, el
uso de punteros no tiene sentido en este tipo de estructuras

La implementacin relativa al proceso de comunicacin por parte del servidor se


muestra, a groso modo, en el listado 29.2. Por motivos didcticos y de simplicacin
no se ha introducido cdigo de gestin de errores. Cada una de las invocaciones
relativas a sockets, retornan cdigos de error que el desarrollador debe tratar de forma
adecuada.
En el servidor podemos ver la denicin de las variables necesarias para las
comunicaciones, incluidas las direcciones del propio servidor y del cliente que se
le ha conectado. Denimos, con la constante PORT, el puerto donde estaremos a la
escucha. Inicializaremos la direccin a la cual vamos a vincular el socket servidor de
forma apropiada (lineas 17 a 20). Desde un punto de vista prctico en sockets de red,
las direcciones locales y remotas se representan mediante la estructura sockaddr_in,
donde existen tres campos cruciales, sin_family que indica el tipo de familia al cual
pertenece la direccin, el sin_port que es el puerto y la estructura sin_addr dentro de
la cual el campo s_addr nos indica la direccin IP remota a la cual vamos asociar al
socket. Si indicamos, como en el ejemplo INADDR_ANY indicamos que aceptamos
conexiones de cualquier direccin.
Para evitar problemas con el orden de los bytes (utilizando bit_endian o little_indian)
se pasan a un formato de red utilizando las funciones htons y htonl que convierten del
formato host, a un formato de red. Existen de igual manera ntohl y ntohs para realizar
el proceso inverso. La l y la s al nal de las funciones indican si es el tipo sort o long.

Listado 29.2: Servidor de registro de jugadores


1 int main(int argc, char *argv[])
2 {
3
4 struct sockaddr_in serverAddr;
5 int serverSocket;
6 struct sockaddr_in clientAddr;
7 int serviceSocket;
8 int read_bytes=0;
9 unsigned int serverPort;
10 unsigned int len_client=0;
11 struct player *new_player;
12
13
14 serverPort = PORT;
15 serverSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
16
17 memset(&serverAddr, 0, sizeof(serverAddr));
18 serverAddr.sin_family = AF_INET;
19 serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
C29

20 serverAddr.sin_port = htons(serverPort);
[956] CAPTULO 29. NETWORKING

21
22 bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(
serverAddr));
23 listen(serverSocket, MAXCONNECTS);
24 while(1)
25 {
26 printf("Listening clients..\n");
27 len_client = sizeof(clientAddr);
28 serviceSocket = accept(serverSocket, (struct sockaddr *) &
clientAddr, &(len_client));
29 printf("Cliente %s conectado\n", inet_ntoa(clientAddr.
sin_addr));
30 new_player = (struct player *)malloc(sizeof(struct player));
31 read_bytes= recv(serviceSocket, new_player,sizeof(struct
player) ,0);
32 printf("Nuevo jugador registrado %s con cuenta %d y password
%s\n", new_player->nick, new_player->account_type,
new_player->passwd);
33 close(serviceSocket);
34 }
35
36 }

Una vez creado y vinculado un socket a una direccin, estamos listos para entrar
en un bucle de servicio donde, se aceptan conexiones, se produce el intercambio de
mensajes con el cliente conectado y con posterioridad se cierra el socket de servicio y
se vuelven a aceptar conexiones. Si mientras se atiende a un cliente se intenta conectar
otro, queda a la espera hasta un mximo especicado con la funcin listen. Si se supera
ese mximo se rechazan las conexiones. En este caso la interaccin se limita a leer los
datos de subscripcin del cliente.
El cliente, tal y como vemos en el listado 29.3, sigue unos pasos similares en
cuanto a la creacin del socket e inicializacin de las direcciones. En este caso se debe
introducir la direccin IP y puerto del servidor al cual nos queremos conectar. En este
caso localhost indica que van a estar en la misma mquina (linea 19). A continuacin
nos conectamos y enviamos la informacin de subscripcin.
En este ejemplo, el diseo del protocolo implementado no podra ser mas
simple, la sintaxis de los paquetes estn denidos por la estructura, la semntica es
relativamente simple ya que el nico paquete que se enva indican los datos de registro
mientras que la temporizacin tambin es muy sencilla al ser un nico paquete.

Listado 29.3: Cliente que registra un usuario en el servidor


1 int main(int argc, char *argv[])
2 {
3
4 struct sockaddr_in serverAddr;
5 struct hostent *server_name;
6 int serviceSocket;
7 unsigned int serverPort;
8 struct player myData;
9 int send_bytes=0;
10 int len_server=0;
11
12 strncpy(myData.nick,"Carpanta",sizeof("Carpanta"));
13 myData.account_type = PREMIUM;
14 strncpy(myData.passwd,"123",sizeof("123"));
15
16 serverPort = PORT;
17 serviceSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
18
19 server_name = gethostbyname("localhost");
20 memset(&serverAddr, 0, sizeof(serverAddr));
21 memcpy(&serverAddr.sin_addr, server_name->h_addr_list[0],
server_name->h_length);
29.10. Sockets TCP/IP [957]

22 serverAddr.sin_family = AF_INET;
23 serverAddr.sin_port = htons(serverPort);
24
25 len_server = sizeof(serverAddr);
26 printf("Connecting to server..");
27 connect(serviceSocket,(struct sockaddr *) &serverAddr,(
socklen_t)len_server);
28 printf("Done!\n Sending credentials.. %s\n", myData.nick);
29 send_bytes = send(serviceSocket, &myData, sizeof(myData),0);
30 printf("Done! %d\n",send_bytes);
31 close(serviceSocket);
32 return 0;
33 }

29.10.3. Comunicaciones no conables

Informacin de estado En el caso de las comunicaciones no conables, y tal y como vimos al principio
de esta seccin, se utilizan para enviar informacin cuya actualizacin debe ser muy
La informacin de estado que un
servidor le enva a un cliente es slo eciente y en la cual, la prdida de un paquete o parte de la informacin, o no tiene
aquella informacin relativa a su sentido un proceso de reenvo o bien se puede inferir. Para mostrar este ejemplo
visin parcial del estado del juego. podemos ver, para un juego FPS, como un cliente va a enviar informacin relativa a su
Es decir, la mnima imprescindible posicin y orientacin, y el servidor le enva la posicin y orientacin de su enemigo.
para que el cliente pueda tener la
informacin de qu le puede afectar Asumiremos que la informacin de la posicin del enemigo siempre es relevante
en cada momento de cara a la representacin del juego. En el listado 29.4 podemos ver la estructura
position que nos sirve para situar un jugador en un juego tipo FPS. Este es un ejemplo
de estructura que podemos asociar a comunicaciones no conables ya que:

Cada cambio en la posicin y orientacin del jugador debe generar una serie
de mensajes al servidor que, a su vez, debe comunicarlo a todos los jugadores
involucrados.
Al ser un tipo de informacin que se modica muy a menudo y vital para el
juego, se debe noticar con mucha eciencia.
En principio la prdida ocasional de algn mensaje no debera plantear mayores
problemas si se disea con una granularidad adecuada. La informacin perdida
puede inferirse de eventos anteriores y posteriores o incluso ignorarse.

Listado 29.4: Estructura que representa la posicin y orientacin de un jugador


1 struct position{
2 int userID;
3 float x; // x coordenate
4 float y; // y coordenate
5 float z; // z coordenate
6 float rotation[3][3]; // 3x3 rotation matrix
7 };

El servidor (listado 29.5) en este caso prepara una estructura que representa la
posicin de un jugador (lineas 10-14), que podra ser un jugador manejado por la
IA del juego, a continuacin se crea el socket de servicio y se prepara para aceptar
paquetes de cualquier cliente en el puerto 3000. Finalmente se vincula el socket a
la direccin mediante la operacin bind y se pone a la espera de recibir mensajes
mediante la operacin recvfrom. Una vez recibido un mensaje, en la direccin
clientAddr tenemos la direccin del cliente, que podemos utilizar para enviarle
mensajes (en este caso mediante la operacin sendto).
C29
[958] CAPTULO 29. NETWORKING

Listado 29.5: Servidor de estado del juego


1 int main(int argc, char *argv[])
2 {
3 int serviceSocket;
4 unsigned int length;
5 struct hostent *server_name;
6 struct sockaddr_in localAddr, clientAddr;
7 struct position otherplayer;
8 struct position player;
9
10 otherplayer.userID=9;
11 otherplayer.x=10;
12 otherplayer.y=10;
13 otherplayer.z=0;
14 otherplayer.rotation[0][0]=2;
15
16 serviceSocket = socket( AF_INET, SOCK_DGRAM, 0 );
17 localAddr.sin_family = AF_INET;
18 localAddr.sin_port = htons(3000);
19 localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
20
21 length = sizeof(clientAddr);
22 bind( serviceSocket, (struct sockaddr *)&localAddr, sizeof(
localAddr) );
23 recvfrom(serviceSocket, &player, sizeof(struct position), 0, (
struct sockaddr *)&clientAddr, &length );
24 printf("Player ID: %d is in position %f, %f, %f with orientation %f"
,player.userID,player.x, player.y, player.z, player.rotation
[0][0]);
25 sendto(serviceSocket, &otherplayer, sizeof(struct position), 0, (
struct sockaddr *)&clientAddr, length );
26 return 0;
27 }

El cliente en este caso guarda muchas similitudes con el servidor (listado 29.6).
En primer lugar se prepara la estructura a enviar, se crea el socket, y se prepara la
direccin de destino, en este caso, la del servidor que se encuentra en el mismo host
(lineas 21-25), una vez realizada la vinculacin se enva la posicin y se queda a la
espera de paquetes que provienen del servidor mediante la funcin recvfrom.

Listado 29.6: Cliente de un jugador


1 int main(int argc, char *argv[])
2 {
3 int serviceSocket;
4 unsigned int length;
5 struct hostent *server_name;
6 struct sockaddr_in localAddr, serverAddr;
7 struct position mypos;
8 struct position enemy;
9
10 mypos.userID=10;
11 mypos.x=23;
12 mypos.y=21;
13 mypos.z=0;
14 mypos.rotation[0][0]=1;
15
16 serviceSocket = socket( AF_INET, SOCK_DGRAM, 0 );
17 localAddr.sin_family = AF_INET;
18 localAddr.sin_port = 0;
19 localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
20
21 server_name = gethostbyname("localhost");
22 memset(&serverAddr, 0, sizeof(serverAddr));
23 memcpy(&serverAddr.sin_addr, server_name->h_addr_list[0],
server_name->h_length);
24 serverAddr.sin_family = AF_INET;
29.11. Sockets TCP/IP avanzados [959]

25 serverAddr.sin_port = htons(3000);
26
27 memset( &( serverAddr.sin_zero), \0, 8 );
28 length = sizeof( struct sockaddr_in );
29 bind( serviceSocket, (struct sockaddr *)&localAddr, length );
30 sendto(serviceSocket, &mypos, sizeof(struct position), 0, (struct
sockaddr *)&serverAddr, length );
31 recvfrom(serviceSocket, &enemy, sizeof(struct position), 0, (
struct sockaddr *)&serverAddr, &length );
32 printf("My enemy ID: %d is in position %f, %f, %f with orientation %
f\n",enemy.userID,enemy.x, enemy.y, enemy.z, enemy.rotation
[0][0]);
33 return 0;
34 }

A lo largo de esta seccin hemos visto las funciones bsicas de envo y recepcin
mediante sockets TCP/IP. Aunque ya tenemos la base para construir cualquier tipo de
servidor y cliente, en la parte de servidores hace falta introducir algunas caractersticas
adicionales de cara al desarrollo de servidores ecientes y con la capacidad de atender
a varios clientes de forma simultnea.

29.11. Sockets TCP/IP avanzados


Cuando hablamos de programacin multijugador es necesario la gestin de varios
clientes de forma simultnea. Existen dos alternativas a la atencin de peticiones
simultneas de diversos clientes:

crear un nuevo proceso por cada peticin. El socket servidor recibe una peticin
de conexin y crea un socket de servicio. Este socket de servicio se pasa a
un proceso creado de forma expresa para atender la peticin. Este mecanismo
desde el punto de vista del diseo es muy intuitivo con la consiguiente ventaja
en mantenibilidad. De igual manera, para sistemas que slo tienen un slo
procesador, este sistema adolece de problemas de escalabilidad ya que el
nmero de cambios de contexto, para nmeros considerables de clientes, hace
perder eciencia a la solucin.
Utilizar un mecanismo que nos permita almacenar las peticiones y noticar
cuando un cliente nos manda una peticin al servidor.

En el caso de acumular peticiones y atenderlas de forma secuencial necesitamos


utilizar un mecanismo que nos permita saber qu sockets necesitan ser atendidos. Para
ello tenemos la funcin select. La funcin select tiene la siguiente denicin:
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval
*timeout);
Donde el primer argumento n, es el mayor descriptor de los monitorizados mas
uno, timeout es un lmite de tiempo de espera antes de que la funcin retorne, con 0 la
funcin retorna de manera inmediata mientras que si se especica NULL la funcin
select es bloqueante. readfds, writefds y exceptfds son tres conjuntos de descriptores
de archivos (sockets) donde se esperar para ver si hay datos de lectura (readfds), para
escribir (writefds) o donde existen excepciones (exceptfds).
C29
[960] CAPTULO 29. NETWORKING

La funcin select nos permite realizar entrada/salida asncrona

La idea del select es que cuando retorna en cada uno de esos conjuntos se
encuentran los identicadores de los sockets de los cuales puedes leer, escribir o hay
alguna excepcin. Con las operaciones FD_ZERO, FD_SET, FD_CLR y FD_ISSET
se limpian, aaden y eliminan descriptores y se pregunta por un descriptor concreto
en cualquiera de los conjuntos respectivamente.
Supongamos que queremos atender distintos tipos de conexiones en varios sockets,
en principio no hay un orden de llegada de los paquetes y por lo tanto, no podemos
bloquearnos en uno slo de los sockets. La funcin select nos puede ayudar. Podemos
evolucionar nuestro servidor UDP desarrollado anteriormente para incluir la funcin
select (listado 29.7).

Listado 29.7: Utilizacin del select en monitorizacin de sockets


1
2 fd_set readSet;
3 FD_ZERO(&readSet);
4 FD_SET(serviceSocket,&readSet);
5
6 while(select(serviceSocket+1, &readSet,0,0,NULL)){
7 if (FD_ISSET(serviceSocket,&readSet)){
8 recvfrom(serviceSocket, &player, sizeof(struct position), 0,
(struct sockaddr *)&clientAddr, &length );
9 printf("Player ID: %d is in position %f, %f, %f with orientation
%f",player.userID,player.x, player.y, player.z, player.
rotation[0][0]);
10 sendto(serviceSocket, &otherplayer, sizeof(struct position),
0, (struct sockaddr *)&clientAddr, length );
11 }
12 }
13 return 0;
14 }

Debemos tener en cuenta los siguientes puntos a la hora de utilizar la funcin


select

Debemos aadir a los conjuntos monitorizados por el select todos aquellos


descriptores de archivos que creemos y deseemos usar, bien sea para lectura
o para escritura.
En el caso de TCP, si aadimos el socket al cual se nos conectan los clientes,
debemos aadir el socket de servicio creado al aceptar esa conexin a los
conjuntos monitorizados si queremos hacer un seguimiento de este socket.
El select nos retorna un nmero de descriptores en los cuales hay datos nuevos.
Despus del select debemos comprobar con FD_ISSET qu descriptor concreto
se ha originado.
Los descriptores no tienen por que ser solamente de sockets, pueden ser tambin
descriptores de archivos.
29.11. Sockets TCP/IP avanzados [961]

Un aspecto a tener en cuenta con la funcin select es la semntica asociada a la


estructura timeval. Algunos sistemas operativas actualizan el valor almacenado en esa
estructura para reejar la cantidad de tiempo que quedaba antes del timeout asociado.
El problema es que otros sistemas operativos no actualizan este valor por lo que esta
semntica puede variar.

29.11.1. Operaciones bloqueantes


De las funciones que estamos viendo, muchas de ellas son bloqueantes, es decir, se
invocan y duermen el hilo que la invoca hasta que algo relevante sucede. Por ejemplo,
el accept es una funcin bloqueante, recv, recvfrom, etc.
Mas que las funciones, la propiedad de que las operaciones sean o no bloqueantes
est asociada a la creacin del socket. Por defecto en la creacin de un socket, se
establece la propiedad de que las operaciones relacionadas con ese descriptor de socket
sean bloqueantes. Para cambiar esta propiedad, debemos hacer uso de la funcin int
fcntl(int fd, int cmd, long arg); que coge como argumento un descriptor de archivo
(fd), un comando y una serie de opciones. La funcin fcntl se dedica a los descriptores
de archivo en general, para establecer un socket no bloqueante hay que invocarla
con el identicador del socket, FS_SETFL para el comando y O_NONBLOCK como
argumento.
Las operaciones realizadas sobre el socket no sern bloqueantes por lo que habr
que gestionar apropiadamente esta condicin y consultar el estado de los sockets de
forma continua. Esta conguracin es muy til, por ejemplo, para hacer peridico
el acceso de las comunicaciones hacindolas mas predecibles y mientras seguir
realizando otras tareas.
Hay que tener en cuenta que, en entornos Linux, las operaciones bloqueantes
duermen el hilo que las invoca mientras no pase algo asociado a la funcionalidad de
la funcin bloqueante (por ejemplo, accept, recv, etc.). Si optamos por operaciones no
bloqueantes y por ello nos vemos obligados a realizar continuas consultas, deberemos
gestionar nosotros mismos nuestros periodos dormidos si no queremos consumir
recursos de CPU de forma innecesaria.
De igual manera, el valor devuelto por las operaciones realizadas sobre socket
no bloqueantes generalmente ser un error si no hay nada interesante asociado (por
ejemplo, no hay datos que leer o conexin que aceptar). El valor al cual se asocia
errno es EWOULDBLOCK (no obstante, en funcin de la implementacin y sistema
operativo utilizado debe consultarse este valor).

29.11.2. Gestin de buffers


Los buffers son estructuras que se utilizan para almacenar informacin de forma
temporal para su procesado. Son estructuras necesarias para muchos cometidos como
puede ser asociar procesos que tienen distintas velocidades en el procesamiento de la
informacin, almacenamiento temporal, etc.
Cuando realizamos operaciones con sockets, los datos pasan realmente por una
sucesin de buffers en el kernel que permiten el tratamiento de ujos continuos de
bytes mediante operaciones parciales. Por lo tanto cuando recibimos informacin de
un socket puede que no se nos proporcione toda la informacin recibida al igual que
cuando enviamos una informacin puede que no se mande de forma completa en la
misma operacin. Esta gestin de cuando procesar la informacin tiene que ver con
decisiones del kernel.
C29
[962] CAPTULO 29. NETWORKING

En el caso de send, tenemos la operacin sendall que, con la misma semntica que
el send, nos permite enviar todo un buffer.
Existen otros casos en el cual la gestin de buffers puede ralentizar las prestaciones
ya que se necesitan copiar de forma continua. Por ejemplo si leemos de un archivo, la
operacin read sobre un descriptor de archivo almacena la informacin en un buffer
interno al kernel que es copiado al buffer en espacio de usuario. Si a continuacin
queremos enviar esa informacin por un sockets, invocaramos la funcin send y
esa funcin copiara ese buffer, de nuevo a un buffer interno para su procesado.
Este continuo trasiego de informacin ralentiza la operacin si lo que queremos es
transmitir por completo el archivo. Lo ideal sera que las dos operaciones entre los
buffers internos al buffer de aplicacin se convirtiera en una nica operacin entre
buffers internos.

Un ejemplo de funcin que implementa la losofa zero-copy y lidia con


el problema descrito de copia entre buffers es la operacin transferTo(..)
del paquete java.nio.channels.FileChannel. De igual forma algunos sistemas
operativos *ix tienen una llamada al sistema sendle(..) que tiene la misma
semntica.

La tcnica que minimiza las copias innecesarias entre buffers se denomina zero-
copy y, a menudo, puede obtener una considerable mejora de las prestaciones. Es
difcil estudiar esta tcnica en profundidad porque depende del sistema operativo y de
la aplicacin concreta.

29.11.3. Serializacin de datos


La amplia variedad de sistemas y conguraciones posibles hacen que la repre-
sentacin de datos y portabilidad de cualquier programa sea un aspecto importante a
tener en cuenta en el desarrollo de sistemas multiplataforma. La programacin con
sockets no escapa a este problema y, al ser una programacin de bajo nivel, tenemos
que ocuparnos de aspectos como la representacin de datos binarios en distintas pla-
taformas y como transmitir este tipo de datos. A este nivel la principal distincin es el
ordenamiento de los bytes (little-endian y big-endian).
En los listados anteriores, esta caracterstica la omitamos a excepcin de la
utilizacin de las funciones htons() y ntohs() que utilizamos para pasar a una
codicacin comn denominada Network Byte Order. Estas funciones nos permiten
lidiar con un formato comn en muchas plataformas y las podemos utilizar para la
gestin de enteros y otantes:

1 #include <arpa/inet.h>
2 uint32_t htonl(uint32_t hostlong);
3 uint16_t htons(uint16_t hostshort);
4 uint32_t ntohl(uint32_t netlong);
5 uint16_t ntohs(uint16_t netshort);

Si queremos trabajar con ordenaciones concretas deberamos consultar el


archivo endian.h
29.11. Sockets TCP/IP avanzados [963]

A mas alto nivel se puede usar XDR (eXternal Data Representation), un estndar
para la descripcin y representacin de datos utilizado por ONC (Open Network
Computing)-RPC (Remote Procedure Call) pero el cual se puede usar de forma directa
mediante las libreras adecuadas. Como veremos mas adelante, una de las principales
labores que nos ahorra el uso de middlewares es precisamente, la gestin de la
representacin de datos.

29.11.4. Multicast y Broadcast


Los modelos de comunicacin vistos hasta ahora estn asociados a una comuni-
cacin cliente-servidor uno a uno. Este tipo de comunicaciones entre pares iguales se
denomina unicast. Existen otros paradigmas de comunicacin como son las comuni-
caciones uno a muchos (Multicast) y uno a todos (Broadcast). En el caso de comuni-
caciones Multicast, el servidor comunica a un grupo de clientes identicados por un
identicador (en IP existen un grupo de direcciones especiales para identicar estos
grupos) de forma comn algn dato de su inters. Este tipo de comunicacin es mucho
mas efectiva ya que substituye, para un grupo de n clientes, a n conexiones unicast.
En el caso de las comunicaciones Broadcast la comunicacin se establece dentro de
una subred entre el servidor y todos los computadores de ese dominio. Por motivos
obvios las comunicaciones broadcast nunca atraviesan un router.
Para implementar la comunicacin multicast se debe subscribir el cliente y el
servidor a una direccin de grupo comn. El rango de direcciones IPv4 para grupos
multicast va desde 224.0.0.0 hasta la 239.255.255.255. Es necesario consultar la
pgina web de la IANA (Internet Assigned Numbers Authority) ya que muchas de
esas direcciones estn asignadas a grupos preestablecidos.
El envo de un mensaje a un grupo multicast no se diferencia, en el cdigo fuente,
salvo por la direccin a la cual se enva que pertenece al rango anteriormente descrito.
En la parte del receptor hay ligeras variaciones con dos objetivos principales:

Abrir un socket que pertenece a un grupo multicast.


Indicarle al sistema operativo que se una al grupo para recibir sus mensajes.

Para abrir un socket que pertenece a un grupo multicast:

1 setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&argument,sizeof(argument))

en esta llamada a setsockopt (que nos ayuda a establecer las opciones de los socket)
donde bsicamente se especica que para una direccin multicast, vamos a reutilizar
la direccin compartiendo varios socket el mismo puerto. En este caso argument es un
u_int con un valor de 1. Faltara la segunda parte:

1 struct ip_mreq multreq;


2 multreq.imr_multiaddr.s_addr=inet_addr("224.0.1.115");
3 multreq.imr_interface.s_addr=htonl(INADDR_ANY);
4 if (setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&multreq,sizeof(
multreq)) < 0) {
5 perror("setsockopt");
6 exit(1);
7 }

donde de nuevo se indica mediante setsockopt, que el kernel se aada al grupo


especicado en la estructura multreq. Con la funcin getsockopt podramos consultar
C29

las opciones del socket.


[964] CAPTULO 29. NETWORKING

La emisin en broadcast sera de forma similar:

1 setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (void *) &argument,


2 sizeof(argument))

De nuevo, argument es un u_int con un valor 1 e indicamos que vamos a utilizar


una direccin broadcast. Las direcciones broadcast tienen la parte de host puesto a uno
y dependen de la red/subred.
No obstante, desde el punto de vista de la programacin de videojuegos, los
mensajes broadcast no tienen tanta utilidad como los mensajes multicast ya que, como
dijimos anteriormente, los paquetes broadcast se quedan en el dominio de difusin por
lo que no llegaran a jugadores fuera de la red/subred donde son emitidos.

29.11.5. Opciones de los sockets


Aunque a lo largo de estas secciones hemos visto algunas de las opciones,
representamos a continuacin un breve resumen de las opciones mas comunes que
podran ser de inters en la implementacin con sockets:

SO_DONTROUTE: Evitar el uso de la tabla de rutas


SO_ERROR: Identica el error relacionado con el socket.
SO_KEEPALIVE :Comprobacin peridica de conexin.
SO_LINGER: Indica qu hacer con los datos pendientes una vez cerrada la
conexin.
SO_RCVLOWAT: Tamao en bytes que un socket debe recibir para que select
reaccione. El valor por defecto es 1.
SO_REUSEADDR: Reutilizacin de la direccin de mquina.
SO_SNDLOWAT: lo mismo que SO_RCVLOWAT pero en envo.
SO_RCVBUF: Indica el tamao en bytes del buffer de recepcin
SO_SNDBUF: Especica el tamao en bytes del buffer de envo en conexiones
conables.
SO_SNDTIMEO: timeout de envo.
SO_RCVTIMEO: timeout de recepcin.
SO_BROADCAST: Permitir mensajes broadcast.

Aunque la implementacin con sockets es muy eciente, el programador debe


realizar un diseo e implementacin de bajo nivel. Existen alternativas que nos
permiten elevar el nivel de abstraccin de las comunicaciones. Estas alternativas sern
objeto de estudio en las siguientes secciones.
29.12. Middlewares de comunicaciones [965]

29.12. Middlewares de comunicaciones


Un middleware de comunicaciones es un sosticado sistema de IPC (Inter Process
Communication) orientado a mensajes. A diferencia de los otros IPC como los sockets,
los middlewares suelen ofrecer soporte para interfaces concretas entre las entidades
que se comunican, es decir, permiten denir estructura y semntica para los mensajes.
Existen muchsimos middlewares: RPC (Remote Procedure Call), CORBA, Java
RMI (Remote Method Invocation), .Net Remoting, etc. En todos ellos, el programador
puede especicar un API. En el caso de RPC se indican un conjunto de funciones que
podrn ser invocadas remotamente por un cliente. Los dems, y la mayora de los
actuales, son RMI (Remote Method Invocation), es decir, son middlewares orientados
a objetos. La gura 29.6 muestra el esquema de invocacin remota a travs de un
ncleo de comunicaciones tpico de este tipo de middlewares.

Figura 29.6: Invocacin a mtodo remoto

Objeto distribuido En un diseo orientado a objetos, el ingeniero puede decidir qu entidades del
dominio sern accesibles remotamente. Puede particionar su diseo, eligiendo qu
Es un objeto cuyos mtodos (algu- objetos irn en cada nodo, cmo se comunicarn con los dems, cules sern los ujos
nos al menos) pueden ser invocados
remotamente. de informacin y sus tipos. En denitiva est diseando una aplicacin distribuida.
Obviamente todo eso tiene un coste, debe tener muy claro que una invocacin remota
puede suponer hasta 100 veces ms tiempo que una invocacin local convencional.
Un videojuego en red, incluso sencillo, se puede disear como una aplicacin
distribuida. En este captulo veremos cmo comunicar por red las distintas partes del
juego de un modo mucho ms exible, cmodo y potente que con sockets.
Obviamente, el middleware se basa en las mismas primitivas del sistema operativo
y el subsistema de red. El middleware no puede hacer nada que no se pueda hacer
con sockets. Pero hay una gran diferencia; con el middleware lo haremos con mucho
menos esfuerzo gracias a las abstracciones y servicios que proporciona, hasta el punto
que habra muchsimas funcionalidades que seran prohibitivas en tiempo y esfuerzo
sin el middleware. El middleware encapsula tcnicas de programacin de sockets y
gestin de concurrencia que pueden ser realmente complejas de aprender, implemen-
tar y depurar; y que con l podemos aprovechar fcilmente. El middleware se encarga
de identicar y numerar los mensajes, comprobar duplicados, retransmisiones, com-
probaciones de conectividad, asignacin de puertos, gestin del ciclo de vida de las
conexiones, despachado asncrono y un largo etctera.
C29
[966] CAPTULO 29. NETWORKING

29.12.1. ZeroC Ice


I CE (Internet Communication Engine) es un middleware de comunicaciones
orientado a objetos desarrollado por la empresa ZeroC Inc2 . Ya hemos visto algunas
utilidades de I CE en los captulos de concurrencia y patrones. Aqu lo utilizaremos con
su nalidad principal, y la que le da el nombre.
I CE soporta mltiples lenguajes (Java, C#, C++, ObjectiveC, Python, Ruby,
PHP, etc.) y multiplataforma (Windows, GNU/Linux, Solaris, Mac OS X, Android,
iOS, etc.) lo que proporciona una gran exibilidad para construir sistemas muy
heterogneos o integrar sistemas existentes.
Adems ofrece servicios comunes muy valiosos para la propagacin de eventos,
persistencia, tolerancia a fallos, seguridad,

29.12.2. Especicacin de interfaces


Cuando nos planteamos una interaccin con un objeto remoto, lo primero es denir
el contrato, es decir, el protocolo concreto que cliente y objeto (servidor) van a
utilizar para comunicarse.
Antes de las RPC cada nueva aplicacin implicaba denir un nuevo protocolo
(independientemente si es pblico o no) y programar las rutinas de serializacin y
des-serializacin de los parmetros de los mensajes para convertirlos en secuencias
de bytes, que es lo que realmente podemos enviar a travs de los sockets. Esta tarea
puede ser compleja porque se tiene que concretar la ordenacin de bytes, el padding
para datos estructurados, las diferencias entre arquitecturas, etc.
Los middleware permiten especicar la interfaz mediante un lenguaje de progra-
macin de estilo declarativo. A partir de dicha especicacin, un compilador genera
cdigo que encapsula toda la lgica necesaria para (des)serializar los mensajes espe-
ccos produciendo una representacin externa cannica de los datos. A menudo el
compilador tambin genera esqueletos para el cdigo dependiente del problema. El
ingeniero nicamente tiene que rellenar la implementacin de los funciones o mto-
dos.
El lenguaje de especicacin de interfaces de Ice se llama S LICE (Specication S LICE
Language for Ice) y proporciona compiladores de interfaces (translators) para todos El lenguaje S LICE (Specication
los lenguajes soportados dado que el cdigo generado tendr que compilar/enlazar con Language for Ice) al igual que otros
el cdigo que aporte el programador de la aplicacin. En cierto sentido, el compilador muchos lenguajes para denicin
de interfaces (como IDL (Interface
de interfaces es un generador de protocolos a medida para nuestra aplicacin. Denition Language) o XDR) son
puramente declarativos, es decir, no
permiten especicar lgica ni fun-
29.12.3. Terminologa cionalidad, nicamente interfaces.

Conviene denir algunos conceptos bsicos que se utilizan habitualmente en el


desarrollo de sistemas distribuidos:

Servidor
Es la entidad pasiva, normalmente un programa que inicializa y activa los
recursos necesarios para poner objetos a disposicin de los clientes.
Objeto
Los objetos son entidades abstractas que se identican con un identidad, que
debera ser nica al menos en la aplicacin. Los objetos implementan al menos
una interfaz remota denida en una especicacin Slice.
2 http://www.zeroc.com
29.12. Middlewares de comunicaciones [967]

Sirviente
Es el cdigo concreto que respalda la funcionalidad del objeto, es quin
realmente hace el trabajo.
Cliente
Es la entidad activa, la que solicita servicios de un objeto remoto mediante
invocaciones a sus mtodos.
Proxy
Es un representante de un objeto remoto. El cliente en realidad interacciona (in-
voca) a un proxy, y ste se encarga con la ayuda del ncleo de comunicaciones,
de llevar el mensaje hasta el objeto remoto y despus devolverle la respuesta al
cliente.

Ntese que servidor y cliente son roles en la comunicacin, no tipos de


programas. Es bastante frecuente que un mismo programa sirva objetos a la
vez que invoca servicios de otros.

La gura 29.7 muestra los componentes principales del middleware y su relacin


en una aplicacin tpica que involucra a un cliente y a un servidor. Los componentes
de color azul son proporcionados en forma de libreras o servicios. Los componentes
marcados en naranja son generados por el compilador de interfaces.

aplicacin cliente aplicacin servidora

proxy esqueleto adaptador


Ice API Ice API de objeto
red
ncleo de Ice (cliente) ncleo de Ice (servidor)

Figura 29.7: Componentes bsicos del middleware

El diagrama de secuencia de la gura 29.8 describe una interaccin completa


correspondiente a una invocacin remota sncrona, es decir, el cliente queda bloqueado
hasta que la respuesta llega de vuelta. En el diagrama, el cliente efecta una invocacin
local convencional sobre un mtodo sobre el proxy. El proxy funciona como una
referencia remota al objeto distribuido alojado en el servidor y por ello implementa
la misma interfaz. El proxy serializa la invocacin y construye un mensaje que ser
enviado al host servidor mediante un socket. Al tratarse de una llamada sncrona, el
proxy queda a la espera de la respuesta lo que bloquea por tanto a la aplicacin cliente.
Ya en el nodo servidor, el esqueleto recibe el mensaje, lo des-serializa, identica
el objeto destino y sintetiza una llamada equivalente a la que realiz al cliente. A
continuacin realiza una invocacin local convencional a un mtodo del objeto destino
y recoge el valor de retorno. Lo serializa en un mensaje de respuesta y lo enva de
vuelta al nodo cliente. El proxy recoge ese mensaje y devuelve el valor de retorno al
cliente, completando la ilusin de una invocacin convencional.
C29
[968] CAPTULO 29. NETWORKING

Figura 29.8: Diagrama de secuencia de una invocacin a un objeto remoto

29.12.4. Hola mundo distribuido


En esta aplicacin, el servidor proporciona un objeto que dispone del mtodo
remoto puts() que imprimir en la salida estndar la cadena que el cliente pase
como parmetro.
Tal como hemos visto, lo primero que necesitamos es escribir la especicacin
de la interfaz remota de nuestros objetos. El siguiente listado corresponde al chero
Hello.ice y contiene la interfaz Hello en lenguaje S LICE.

Listado 29.8: Especicacin S LICE para un Hola mundo distribuido: Hello.ice


1 module Example {
2 interface Hello {
3 void puts(string message);
4 };
5 };

Lo ms importante de este chero es la declaracin del mtodo puts(). El


compilador de interfaces generar un esqueleto que incluir una versin del la interfaz
Hello en el lenguaje de programacin que el programador decida. Cualquier clase
que herede de es interfaz Hello deber denir un mtodo puts() con la misma
signatura, que podr ser invocado remotamente. De hecho, en la misma aplicacin
distribuida puede haber varias implementaciones del mismo interfaz incluso en
diferentes lenguajes.
El compilador tambin genera el proxy para el cliente y, del mismo modo, clientes
escritos en distintos lenguajes o sobre distintas arquitecturas podrn usar los objetos
remotos que cumplan la misma interfaz.
Para generar proxy y esqueleto en C++ (para este ejemplo) usamos el translator
slice2cpp.

$ slice2cpp Hello.ice

Esto genera dos cheros llamados Hello.cpp y Hello.h que deben ser
compilador para obtener las aplicaciones cliente y servidor.
29.12. Middlewares de comunicaciones [969]

Sirviente

El compilador de interfaces ha generado una clase Example::Hello. La implemen-


tacin del sirviente debe heredar de esa interfaz proporcionando una implementacin
(por sobrecarga) de los mtodos especicados en la interfaz S LICE.
El propio compilador de interfaces puede generar una clase hueca que sirva al
programador como punto de partida para implementar el sirviente:

$ slice2cpp --impl Hello.ice

De este modo genera adems los cheros HelloI.cpp y HelloI.h. La letra


I hace referencia a Implementacin del interfaz. El chero de cabecera generado
(HelloI.h) tiene el siguiente aspecto:

Listado 29.9: Sirviente de la aplicacin Hello: Hello.h


1 #ifndef __HelloI_h__
2 #define __HelloI_h__
3
4 #include <Hello.h>
5
6 namespace Example {
7
8 class HelloI : virtual public Hello {
9 public:
10 virtual void puts(const ::std::string&,
11 const Ice::Current&);
12 };
13 }
14
15 #endif

Y el chero de implementacin HelloI.cpp:

Listado 29.10: Sirviente de la aplicacin Hello: Hello.cpp


1 #include <iostream>
2 #include "HelloI.h"
3
4 void
5 Example::HelloI::puts(const ::std::string& message,
6 const Ice::Current& current) {
7 std::cout << message << std::endl;
8 }

La nica modicacin respecto al chero generado es la lnea 9.

Servidor

Nuestro servidor consiste principalmente en la implementacin de una clase que


herede de Ice::Application. De ese modo ahorramos parte del trabajo de inicializacin
del communicator3 . En esta clase debemos denir el mtodo run(). Vea el
listado 29.11.
3 El communicator representa el broker de objetos del ncleo de comunicaciones.
C29
[970] CAPTULO 29. NETWORKING

Listado 29.11: Servidor de la aplicacin Hello: Server.cpp


1 #include <Ice/Ice.h>
2 #include "HelloI.h"
3
4 using namespace std;
5 using namespace Ice;
6
7 class Server: public Application {
8 int run(int argc, char* argv[]) {
9 Example::HelloPtr servant = new Example::HelloI();
10
11 ObjectAdapterPtr adapter = \
12 communicator()->createObjectAdapter("HelloAdapter");
13 ObjectPrx proxy = adapter->add(
14 servant, communicator()->stringToIdentity("hello1"));
15
16 cout << communicator()->proxyToString(proxy) << endl;
17
18 adapter->activate();
19 shutdownOnInterrupt();
20 communicator()->waitForShutdown();
21
22 return 0;
23 }
24 };
25
26 int main(int argc, char* argv[]) {
27 Server app;
28 return app.main(argc, argv);
29 }

En la lnea 9 se crea el sirviente (una instancia de la clase HelloI). Las lneas 11


12 crea un adaptador de objetos, que es el componente encargado de multiplexar entre
los objetos alojados en el servidor. El adaptador requiere un endpoint un punto de
conexin a la red materializado por un protocolo (TCP o UDP), un host y un puerto.
En este caso esa informacin se extrae de un chero de conguracin a partir de el
nombre del adaptador (HelloAdapter en este caso).
En las lineas 1314 registramos el sirviente en el adaptador mediante el mtodo
add() indicando para ello la identidad que tendr el objeto (hello1). En este
ejemplo la identidad es un identicador bastante simple, pero lo recomendable es
utilizar una secuencia globalmente nica. El mtodo add() devuelve una referencia
al objeto distribuido recin creado, que llamamos proxy. La lnea 17 imprime en
consola su representacin textual. A partir de esa representacin el cliente podr
obtener a su vez un proxy para el objeto remoto.
La lnea 18 es la activacin del adaptador, que se ejecuta en otro hilo. A partir
de ese momento el servidor puede escuchar y procesar peticiones para sus objetos.
El mtodo waitForShutown() invocado en la lnea 20 bloquea el hilo principal
hasta que el comunicador sea terminado. El mtodo shutdownOnInterrupt(),
que se invoca en la lnea 19, le indica a la aplicacin que termine el comunicador
al recibir la seal SIGQUIT (Control-C). Por ltimo, las lneas 2629 contienen la
funcin main() en la que se crea una instancia de la clase Server y se invoca su
mtodo main().
29.12. Middlewares de comunicaciones [971]

Cliente

La aplicacin cliente nicamente debe conseguir una referencia al objeto remoto


e invocar el mtodo puts(). El cliente tambin se puede implementar como una
especializacin de la clase Ice::Application. El cdigo completo del cliente aparece a
continuacin:

Listado 29.12: Cliente de la aplicacin Hello: Cliente.cpp


1 #include <Ice/Ice.h>
2 #include "Hello.h"
3
4 using namespace Ice;
5 using namespace Example;
6
7 class Client: public Ice::Application {
8 int run(int argc, char* argv[]) {
9 ObjectPrx proxy = communicator()->stringToProxy(argv[1]);
10 HelloPrx hello = HelloPrx::checkedCast(proxy);
11
12 hello->puts("Hello, World!");
13
14 return 0;
15 }
16 };
17
18 int main(int argc, char* argv[]) {
19 Client app;
20 return app.main(argc, argv);
21 }

El programa acepta por lnea de comandos la representacin textual del proxy del
objeto remoto. A partir de ella se obtiene un objeto proxy (lnea 8). Sin embargo esa
referencia es para un proxy genrico. Para poder invocar los mtodos de la interfaz
Hello se requiere una referencia de tipo HelloPrx, es decir, un proxy a un objeto
remoto Hello.
Para lograrlo debemos realizar un moldeado del puntero (downcasting4 ) mediante
el mtodo esttico HelloPrx::checkedCast() (lnea 9). Gracias al soporte de
introspeccin de los objetos remotos, Ice puede comprobar si efectivamente el objeto
remoto es del tipo al que tratamos de convertirlo. Esta comprobacin no se realizara
si empleramos la modalidad uncheckedCast().
Una vez conseguido el proxy del tipo correcto (objeto hello) podemos invocar
el mtodo remoto puts() (lnea 12) pasando por parmetro la cadena "Hello,
World!".

Compilacin

La gura 29.9 muestra el esquema de compilacin del cliente y servidor a partir del
chero de especicacin de la interfaz. El cheros marcados en amarillo corresponden
a aquellos que el programador debe escribir o completar, mientras que los cheros
generados aparecen en naranja.
Para automatizar el proceso de compilacin utilizamos un chero Makefile, que
se muestra en el listado 29.13
4 Consiste en moldear un puntero o referencia de una clase a una de sus subclases asumiendo que

realmente es una instancia de ese tipo.


C29
[972] CAPTULO 29. NETWORKING

Servidor

HelloI.h
HelloI.cpp

Server.cpp

Hello.ice
slice2cpp Hello.h
translator Hello.cpp

Client.cpp

Cliente

Figura 29.9: Esquema de compilacin de cliente y servidor

Listado 29.13: Aplicacin Hello: Makefile


1 CC=g++
2 CXXFLAGS=-I.
3 LDLIBS=-lIce -lIceUtil
4
5 APP=Hello
6 STUBS=$(addprefix $(APP), .h .cpp)
7
8 all: Server Client
9
10 Server: Server.o $(APP)I.o $(APP).o
11 Client: Client.o $(APP).o
12
13 Server.cpp Client.cpp: $(STUBS)
14
15 %.cpp %.h: %.ice
16 slice2cpp $<
17
18 clean:
19 $(RM) Server Client $(STUBS) *.o *~
20 $(RM) *.bz2 IcePatch2.sum

Ejecutando el servidor

Si ejecutamos el servidor obtendremos un error:

$ ./Server
!! 03/10/12 19:52:05.733 ./Server: error: ObjectAdapterI.cpp:915: Ice
::InitializationException:
initialization exception:
object adapter HelloAdapter requires configuration
29.12. Middlewares de comunicaciones [973]

Como vimos en la seccin 29.12.4, el servidor necesita informacin especca que


le indique los endpoint en los que debe escuchar el adaptador HelloAdapter. Para
ello se debe proporcionar escribir un chero adicional (hello.cfg) cuyo contenido
aparece en el siguiente listado:

Listado 29.14: Servidor de la aplicacin Hello: hello.cfg


1 HelloAdapter.Endpoints=tcp -p 9090

Este tipo de cheros contiene deniciones de propiedades, que son parejas


clave=valor. La mayora de los servicios de Ice puede congurarse por medio de
propiedades, lo que le otorga gran exibilidad sin necesidad de recompilar el cdigo.
Esta propiedad en concreto indica que el adaptador debe utilizar un socket TCP en el
puerto 9090.
Para que el servidor cargue las propiedades del chero de conguracin ejecuta:

$ ./Server --Ice.Config=hello.cfg
hello1 -t:tcp -h 192.168.0.12 -p 9090:tcp -h 10.1.1.10 -p 9090

En esta ocasin el programa arranca sin problemas y queda ocupando la shell


como corresponde a cualquier servidor. Lo que aparece en consola es, como ya vimos,
la representacin textual del proxy para el objeto distribuido. La lnea contiene varios
datos:

La identidad del objeto: hello1.


El tipo de proxy (-t), que corresponde a twoway. Existen otros tipos que
implican semnticas de llamada diferentes: -o para oneway, -d para datagram,
etc.
Una lista de endpoints separados con el carcter :, que corresponden con
sockets. Para cada uno de ellos aparece el protocolo, la direccin IP indicada
con el parmetro -h y el puerto indicado con el parmetro -p.

Concretamente, el adaptador de nuestro servidor escucha en el puerto TCP de dos


interfaces de red puesto que el chero de conguracin no lo limitaba a una interfaz
concreta.

Ejecutando el cliente

Para ejecutar el cliente debemos indicar el proxy del objeto remoto en lnea de
comandos, precisamente el dato que imprime el servidor. Ntese que se debe escribir
entre comillas para que sea interpretado como un nico parmetro del programa:

$ ./Client "hello1 -t:tcp -h 192.168.0.12 -p 9090"

El programa se ejecuta y retorna inmediatamente y veremos como la cadena Hello,


World! aparece en la consola del servidor.

29.12.5. twoway, oneway y datagram


I CE puede realizar diferentes tipos de invocaciones:
C29
[974] CAPTULO 29. NETWORKING

twoway: Son las que I CE trata de realizar por defecto. La invocacin twoway
implica el uso de un protocolo de transporte conable (TCP) y es obligatorio si
el mtodo invocado tiene un valor de retorno.
oneway: Las invocaciones oneway slo pueden utilizarse cuando los mtodos
no retornan ningn valor (void) y no tienen parmetros de salida. Tambin
requieren de un protocolo conable, pero en determinadas circunstancias
pueden fallar la entrega.
datagram: Igual que las oneway nicamente pueden utilizase para invocar
mtodos sin valor de retorno y sin parmetros de salida. Utilizan un protocolo de
transporte no conable (UDP). A su vez eso implica que el tamao del mensaje
est limitado al mximo de un paquete IP (64KiB) y sufran de todas la otras
limitaciones de UDP: no hay control de orden, duplicados ni garanta de entrega.

La principal diferencia entre las invocaciones twoway con respecto a los otros tipos
es que el cliente recupera el control del ujo de ejecucin tan pronto como el mensaje
de invocacin es enviado, sin necesidad de esperar ningn tipo de conrmacin o
respuesta desde el servidor (ver gura 29.10).

Figura 29.10: Diagrama de secuencia de una invocacin datagram

A partir de un proxy vlido es posible obtener proxies adicionales que pueden


utilizarse para efectuar los distintos tipos de llamadas, siempre que el adaptador
disponga de los endpoints adecuados. Para ello, el proxy dispone de los mtodos
ice_twoway(), ice_oneway() e ice_datagram().
Existen otras dos modalidades de proxy: batched oneway, que se obtiene con el
mtodo ice_batchOneway(), y batched datagram que se maneja a travs del m-
todo ice_batchDatagram(). Estos proxies pueden almacenar temporalmente los
mensajes de invocacin dirigidos al mismo destino, agruparlos en un solo segmento
y enviarlos como una sola entidad. Este procedimiento aumenta sensiblemente la e-
ciencia y el servidor los procesa del mismo modo que si se tratara de invocaciones
convencionales.

29.12.6. Invocacin asncrona


Una llamada sncrona o bloqueante (lo habitual en un middleware RMI) implica
una espera explcita no determinista, algo que obviamente degrada gravemente el
desempeo de una aplicacin que debe tener un algo grado de inmediatez. La solucin
tradicional cuando este tipo de interaccin con el servidor es ineludible es realizar
estas invocaciones en un hilo de ejecucin diferente, de modo que el hilo principal,
que se encarga de actualizar la interfaz grca y de leer la entrada del usuario, puede
seguir progresando normalmente.
29.12. Middlewares de comunicaciones [975]

Este mecanismo lo incorpora I CE de forma transparente, de modo que el programa-


dor no se tiene que preocupar del manejo de los hilos ni el tratamiento de las respues-
tas. El programador puede realizar llamadas remotas a los mismos mtodos denidos
en la interfaz utilizando un procedimiento diferente que involucra un objeto que per-
mite obtener el estado de la invocacin y, eventualmente, obtener el resultado de la
operacin. A este mecanismo es a lo que denominamos invocacin asncrona.
De hecho I CE proporciona varias modalidades para realizar invocaciones asncro-
nas. La que vemos aqu es probablemente la ms limpia y se basa en la creacin de
objetos callback. Para realizar la invocacin remota, el cliente utiliza un mtodo del
proxy con el prejo begin que acepta un parmetro adicional: el callback object.
Esta invocacin retorna inmediatamente y el cliente puede seguir ejecutando otras
operaciones.
Cuando la invocacin remota se ha completado, el ncleo de ejecucin de la
parte del cliente invoca el mtodo indicado en el objeto callback pasado inicialmente,
proporcionando los resultados de la invocacin. En caso de error, invoca otro mtodo
en el que proporciona informacin sobre la excepcin asociada.
En el listado 29.15 se muestra el cdigo completo para el cliente que crea y
proporciona un callback segn la tcnica indicada.

Listado 29.15: Cliente AMI que recibe la respuesta mediante un callback


(cpp-ami/Client-callback.cpp)
1 #include <Ice/Ice.h>
2 #include <factorial.h>
3
4 using namespace std;
5 using namespace Ice;
6 using namespace Example;
7
8 class FactorialCB : public IceUtil::Shared {
9 public:
10 void response(const Ice::Long retval) {
11 cout << "Callback: Value is " << retval << endl;
12 }
13
14 void failure(const Exception& ex) {
15 cout << "Exception is: " << ex << endl;
16 }
17 };
18
19 class Client: public Ice::Application {
20 public:
21 int run(int argc, char* argv[]) {
22
23 ObjectPrx proxy = communicator()->stringToProxy(argv[1]);
24 MathPrx math = MathPrx::checkedCast(proxy);
25
26 Callback_Math_factorialPtr factorial_cb =
27 newCallback_Math_factorial(new FactorialCB,
28 &FactorialCB::response,
29 &FactorialCB::failure);
30
31 math->begin_factorial(atoi(argv[2]), factorial_cb);
32 return 0;
33 }
34
35 };
36
37 int main(int argc, char* argv[]) {
38 if (argc != 3) {
39 cerr << "usage: " << argv[0] << "<server> <value>" << endl;
40 return 1;
C29

41 }
[976] CAPTULO 29. NETWORKING

42
43 Client app;
44 return app.main(argc, argv);
45 }


Las 8-17 corresponden con la denicin de la clase callback FactorialCB. El
mtodo response() ser invocado por el ncleo de ejecucin cuando el mtodo
remoto haya terminado y el resultado est de vuelta. Tambin se dene un mtodo

failure() que ser invocado con una excepcin en el caso de producirse.
En la 26 se crea la instancia del callback, mediante una funcin especca para
este mtodo: newCallback_Math_factorial() pasndole una instancia de
nuestra clase FactorialCB y sendos punteros a los mtodos denidos en ella.
Esa funcin y todos los tipos nombrados siguiendo el patrn Callback_{interfa-
ce}_{mtodo} son generados automticamente por el compilador de interfaces en el

espacio de nombre Example.
Por ltimo en la 31 aparece la invocacin al mtodo remoto pasando la instancia
del callback (factorial_cb) como segundo parmetro.

29.12.7. Propagacin de eventos


Uno de los servicios ms sencillos y tiles de I CE es I CE S TORM. Se trata de un
servicio de propagacin de eventos, pero a diferencia de otros servicios similares en
otros middlewares, I CE S TORM propaga invocaciones en lugar de datos. Es a todos los
efectos una implementacin distribuida del patrn de diseo publicacin/subscripcin
u observador.
Para utilizar el servicio de eventos se requiere un proxy a un TopicManager. Este
objeto permite listar, crear y destruir canales (topics). Los canales tambin son objetos,
que residen en el mismo servidor en el que se encuentre el TopicManager.
Para recibir eventos procedentes de un canal necesitamos subscribir un objeto.
Para enviar invocaciones a un canal necesitamos el proxy al publicador del canal.
El publicador es un objeto especial que no tiene interfaz concreta. Podemos invocar
cualquier mtodo sobre l. El canal enviar esa invocacin a todos sus subscriptores.
Sin embargo, si el subscriptor no soporta la invocacin elevar una excepcin y
el canal lo des-suscribir inmediatamente. Por esa razn debemos crear canales
diferentes para cada interfaz de la que queramos propagar eventos.
Los canales solo pueden propagar invocaciones oneway o datagram, es decir,
que no tengan valores de retorno, dado que sera complejo tratar las respuestas de
todos los subscriptores. El canal es un intermediario que desacopla completamente a
publicadores y suscriptores. Todos conocen el canal, pero no se conocen entre s.
Veamos su funcionamiento sobre un ejemplo. Crearemos un canal de eventos para
la interfaz Exam::Hello del ejemplo anterior. Empezamos por codicar el subscriptor;
es bsicamente un servidor: crea un sirviente y un adaptador, y lo aade el primero al
segundo (lneas 3942) del siguiente listado. El cdigo completo para el subscriptor
aparece el en siguiente listado:

Listado 29.16: Subscriptor


1 #include <Ice/Application.h>
2 #include <IceStorm/IceStorm.h>
3 #include <IceUtil/UUID.h>
4 #include "Hello.h"
5
6 using namespace std;
7 using namespace Ice;
29.12. Middlewares de comunicaciones [977]

8 using namespace IceStorm;


9 using namespace Example;
10
11
12 class HelloI : public Hello {
13 void puts(const string& s, const Current& current) {
14 cout << "Event received: " << s << endl;
15 }
16 };
17
18 class Subscriber : public Application {
19 TopicManagerPrx get_topic_manager() {
20 string key = "IceStormAdmin.TopicManager.Default";
21 ObjectPrx base = communicator()->propertyToProxy(key);
22 if (!base) {
23 cerr << "property " << key << " not set." << endl;
24 return 0;
25 }
26
27 cout << "Using IceStorm in: " << key << " " << endl;
28 return TopicManagerPrx::checkedCast(base);
29 }
30
31 public:
32 virtual int run(int argc, char* argv[]) {
33 TopicManagerPrx topic_mgr = get_topic_manager();
34 if (!topic_mgr) {
35 cerr << appName() << ": invalid proxy" << endl;
36 return EXIT_FAILURE;
37 }
38
39 ObjectPtr servant = new HelloI;
40 ObjectAdapterPtr adapter = \
41 communicator()->createObjectAdapter("Hello.Subscriber");
42 ObjectPrx base = adapter->addWithUUID(servant);
43
44 TopicPrx topic;
45 try {
46 topic = topic_mgr->retrieve("HelloTopic");
47 topic->subscribeAndGetPublisher(QoS(), base);
48 }
49 catch(const NoSuchTopic& e) {
50 cerr << appName() << ": " << e
51 << " name: " << e.name << endl;
52 return EXIT_FAILURE;
53 }
54
55 cout << "Waiting events... " << base << endl;
56
57 adapter->activate();
58 shutdownOnInterrupt();
59 communicator()->waitForShutdown();
60
61 topic->unsubscribe(base);
62
63 return EXIT_SUCCESS;
64 }
65 };
66
67 int main(int argc, char* argv[]) {
68 Subscriber app;
69 return app.main(argc, argv);
70 }

Podemos destacar la obtencin del proxy al TopicManager (lnea 33), que se


delega a un mtodo privado (get_topic_manager()).
C29
[978] CAPTULO 29. NETWORKING

Despus se obtiene una referencia al canal "HelloTopic" (lnea 46), que


debera existir. Si no existe se produce una excepcin y el programa termina. Si el
canal existe, se subscribe el objeto (linea 47). Por ltimo se activa el adaptador y el
servidor queda a la espera de recibir eventos como en ejemplo anterior.
Si el subscriptor es un objeto, el publicador es un cliente. En este caso le hemos
dado al publicador la responsabilidad de crear el canal si no existe (lneas 1013 del
siguiente listado). Solo se muestra el mtodo run() puesto que el resto del cdigo es
prcticamente idntico.
Lo ms interesante es la obtencin del publicador del canal a partir de la referencia
al canal (lnea 19) y el downcast para poder invocar sobre l mtodo de la interfaz
Example::Hello. Ntese que este molde usa la modalidad uncheckedCast() dado
que el publicador no implementa realmente ninguna interfaz.
Por ltimo utilizamos el proxy hello para enviar diez eventos con la cadena
"Hello World!".

Listado 29.17: Publicador


1 virtual int run(int argc, char*[]) {
2 TopicManagerPrx topic_mgr = get_topic_manager();
3 if (!topic_mgr) {
4 cerr << appName() << ": invalid proxy" << endl;
5 return EXIT_FAILURE;
6 }
7
8 TopicPrx topic;
9 try {
10 topic = topic_mgr->retrieve("HelloTopic");
11 } catch (const NoSuchTopic& e) {
12 cerr << appName()
13 << ": no sucho topic found, creating" << endl;
14 topic = topic_mgr->create("HelloTopic");
15 }
16
17 assert(topic);
18
19 ObjectPrx prx = topic->getPublisher();
20 HelloPrx hello = HelloPrx::uncheckedCast(prx);
21
22 cout << "publishing 10 Hello World events" << endl;
23 for (int i = 0; i < 10; ++i)
24 hello->puts("Hello World!");
25
26 return EXIT_SUCCESS;

Arranque del servicio

IceStorm est implementado como un servicio I CE. Los servicios son librera
dinmicas que deben ser lanzadas con el servidor de aplicaciones cuyo nombre es
IceBox. Vemos la conguracin de IceBox en el siguiente listado:

Listado 29.18: Conguracin de IceBox para lanzar IceStorm


1 IceBox.Service.IceStorm=IceStormService,34:createIceStorm --Ice.
Config=icestorm.config
2 IceBox.ServiceManager.Endpoints=tcp -p 7000
29.12. Middlewares de comunicaciones [979]

Slo se denen dos propiedades: La carga del servicio IceStorm y los endpoints
del gestor remoto de IceBox. La conguracin de IceStorm a la que se hace referencia
(icestorm.config) es la siguiente:

Listado 29.19: Conguracin de IceStorm


1 IceStorm.TopicManager.Endpoints=tcp -p 10000
2 IceStormAdmin.TopicManager.Default=IceStorm/TopicManager:tcp -p
10000
3 IceStorm.Publish.Endpoints=tcp -p 2000
4 Freeze.DbEnv.IceStorm.DbHome=db
5
6 Hello.Subscriber.Endpoints=tcp

Las lneas 13 especican los endpoints para los adaptadores del TopicManager,
el objeto de administracin y los publicadores de los canales. La lnea 4 es el nombre
del directorio donde se almacenar la conguracin del servicio (que es persistente
automticamente). La lnea 6 es el endpoint del adaptador del publicador, que se ha
incluido en este chero por simplicidad aunque no es parte de la conguracin de
IceStorm.
Una vez congurado podemos lanzar el servicio y probar el ejemplo. Lo primero
es arrancar el icebox (en background):

$ icebox --Ice.Config=icebox.config &

A continuacin podemos usar la herramienta de administracin en lnea de


comando para crear el canal, asumiendo que no existe:

$ icestormadmin --Ice.Config=icestorm.config -e "create HelloTopic"

Es momento de arrancar el suscriptor:

$ ./subscriber --Ice.Config=icestorm.config

Y por ltimo en una consola diferente ejecutamos el publicador:

$ ./publisher --Ice.Config=icestorm.config

Hecho lo cual veremos los eventos llegar al suscriptor:

$ ./subscriber --Ice.Config=icestorm.config
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!
Event received: Hello World!

Este servicio puede resultar muy til para gestionar los eventos de actualizacin
en juegos distribuidos. Resulta sencillo crear canales (incluso por objeto o jugador)
para informar a los interesados de sus posicin o evolucin. El acoplamiento mnimo
que podemos conseguir entre los participantes resulta muy conveniente porque
permite que arranquen en cualquier orden y sin importar el nmero de ellos, aunque
C29

obviamente una gran cantidad de participantes puede degradar el desempeo de la red.


[980] CAPTULO 29. NETWORKING

Tambin resulta muy interesante que los eventos estn modelados como invoca-
ciones. De ese modo es posible eliminar temporalmente el canal de eventos e invocar
directamente a un solo subscriptor durante las fases iniciales de modelado del proto-
colo y desarrollo de prototipos.
Sonido y Multimedia
Captulo 30
Guillermo Simmross Wattenberg
Francisco Jurado Monroy

E
n este captulo se discuten dos aspectos que son esenciales a la hora de afrontar
el desarrollo de un videojuego desde el punto de vista de la inmersin del
jugador. Uno de ellos est relacionado con la edicin de audio, mientras que
el otro est asociado a la integracin y gestin de escenas de vdeo dentro del propio
juego.
El apartado sonoro es un elemento fundamental para dotar de realismo a un
juego, y en l se aglutinan elementos esenciales como son los efectos sonoros y
elementos que acompaan a la evolucin del juego, como por ejemplo la banda
sonora. En este contexto, la primera parte de esta captulo describe la importancia
del audio en el proceso de desarrollo de videojuegos, haciendo especial hincapi en
las herramientas utilizadas, el proceso creativo, las tcnicas de creacin y aspectos
bsicos de programacin de audio.
Respecto a la integracin de vdeo, en la segunda parte del captulo se muestra la
problemtica existente y la importancia de integrar escenas de vdeo dentro del propio
juego. Dicha integracin contribuye a la evolucin del juego y fomenta la inmersin
del jugador en el mismo.

981
[982] CAPTULO 30. SONIDO Y MULTIMEDIA

30.1. Edicin de Audio

30.1.1. Introduccin
Videojuegos, msica y sonido

Tanto la msica como los efectos sonoros son parte fundamental de un videojuego,
pero en ocasiones el oyente no es consciente de la capacidad que ambos tienen
para cambiar por completo la experiencia ldica. Una buena combinacin de estos
elementos aumenta enormemente la percepcin que el usuario tiene de estar inmerso
en la accin que sucede en el juego. La capacidad de la msica para cambiar el
estado de nimo del oyente es clave en el papel que ejerce en un videojuego: permite
transportar al jugador al mundo en el que se desarrolla la accin, inuirle en su
percepcin del contenido visual y transformar sus emociones.
Dependiendo del momento del juego, de la situacin y de las imgenes que Proceso de inmersin
muestre nuestro juego, la msica debe ser acorde con las sensaciones que se desee La msica es una herramienta nica
generar en el oyente: en un juego de estrategia militar, se optar por utilizar para hacer que el jugador se impli-
instrumentos musicales que nos evoquen al ejrcito: cornetas, tambores. . . si el que, viva y disfrute del videojuego.
juego es de estrategia militar futurista, sera buena idea combinar los instrumentos
mencionados con otros instrumentos sintticos que nos sugieran estar en un mundo
del futuro.
Pero la msica no es el nico recurso disponible que permite llevar al jugador
a otras realidades, los efectos de sonido tambin son una pieza fundamental en la
construccin de realidades virtuales.
La realidad cotidiana de nuestras vidas est repleta de sonidos. Todos ellos son
consecuencia de un cambio de presin en el aire producido por la vibracin de una
fuente. En un videojuego se intenta que el jugador reciba un estimulo similar al de la
realidad cuando realiza una accin. Cualquier sonido real se puede transportar a un
videojuego, pero y los sonidos que no existen? cmo suena un dragn? y lo ms
interesante cmo se crean los sonidos que emitira un dragn? seran diferentes esos
sonidos dependiendo del aspecto del dragn? El trabajo del creador de audio en un
videojuego es hacer que el jugador crea lo que ve, y un buen diseo del sonido en un
videojuego ayudar de forma denitiva a conseguir este objetivo.
El artista, por tanto, debe utilizar una buena combinacin de las dos herramientas
de las que dispone msica y efectos sonoros para conseguir ambientar, divertir,
emocionar y hacer vivir al jugador la historia y la accin que propone el videojuego.

Interactividad y no linealidad

Elaborar el sonido de un videojuego tiene puntos en comn a elaborar el de


una pelcula, pero dos caractersticas fundamentales de los videojuegos hacen que
el planteamiento de una labor y otra sean totalmente diferentes: la interactividad y la
no linealidad.

La interactividad en un videojuego es la capacidad que este tiene de reaccio-


nar a acciones realizadas por el jugador.
30.1. Edicin de Audio [983]

Las acciones que se llevan a cabo al jugar a un videojuego pueden ser pulsaciones
de teclas, movimientos del ratn o del joystick, movimientos de un volante o de mando
inalmbrico y hoy en da hasta movimientos corporales del propio jugador. Todas estas
acciones pueden producir una reaccin en un videojuego.

La no linealidad en un videojuego es la capacidad que este tiene de reaccionar


a largo plazo a acciones realizadas por el jugador.

La no linealidad en un videojuego es consecuencia de su interactividad: subir los


impuestos en un simulador de ciudades puede aumentar el descontento de la poblacin
a largo plazo.
En el cine, una banda sonora es siempre de principio a n una obra esttica e
invariante, independientemente del momento en el que el msico la componga (antes o
despus de rodar la pelcula): en el montaje nal es donde se decidir en qu momento
sonar cada pieza.
En un videojuego el artista debe componer pensando en los diferentes ambientes
que puede haber en el videojuego, en si las situaciones sern relajadas o de accin
trepidante, y muchas veces con una exigencia extra sobre la composicin para el
cine: la accin est por determinar. En la mayora de los casos no se podr saber
de antemano cunto tiempo tiene que durar la pieza musical, lo cual obliga al autor
a hacer que pueda repetirse indenidamente sin cansar al jugador, o a componer
diferentes piezas que puedan encajar entre s sin que el jugador note un corte brusco.
Lo mismo que pasa con la msica suceder con los efectos sonoros de forma ms
pronunciada. En el cine, los efectos sonoros siempre se montan sobre lo ya grabado
por la cmara. Esto permite conocer de antemano desde qu ngulo ve el espectador al
elemento que produce el sonido en cada momento. Disear el sonido que producira
un avin al despegar en una escena de una pelcula se reduce a un solo sonido que
responda exactamente a la imagen (ya sea un sonido estereofnico o envolvente).
Por el contrario, disear el sonido que se debe producir en un videojuego conlleva
trabajar con el programador del motor de sonido para conocer las exigencias tcnicas
de ste y conocer qu sonidos sern necesarios y qu tcnicas habr que aplicar.
Muy probablemente ser necesaria la utilizacin de diferentes tcnicas de tratamiento
de audio y la creacin de varios sonidos que tengan continuidad entre s y puedan
repetirse indenidamente sin que lo parezca.
En contraposicin a lo dicho hasta ahora, en muchos juegos actuales existen
escenas no interactivas, en las que la forma de trabajar ser muy similar a la utilizada
en la creacin de audio para pelculas.

El creador de audio para videojuegos debe conocer el funcionamiento interno


del videojuego en lo que se reere a la forma que este tiene de tratar y generar
el sonido.

Por todo lo anterior, la composicin musical y el diseo de sonido para videojue-


gos requiere de ciertas aptitudes complementarias sobre la creacin de audio general.
C30
[984] CAPTULO 30. SONIDO Y MULTIMEDIA

La msica en la industria del videojuego

En el proceso de creacin de msica y efectos sonoros para videojuegos no solo


inuyen los factores relacionados directa y estrictamente con materias musicales y
de sonido. Este proceso es solo una parte del proyecto que supone la creacin de un
videojuego, cuyo tamao, presupuesto, objetivo y fecha de entrega pueden variar en
funcin de otras muchas caractersticas.
Dependiendo del tamao de la compaa y del proyecto, una misma persona puede
ejecutar varios de los roles tpicos de este equipo: diseador de sonido, encargado de
dilogos, director de contratacin, compositores y programadores de audio.

El volumen de negocio del mercado espaol de videojuegos se increment


un 5 % en 2010, hasta situarse en 1.000 millones de euros, gracias al impulso
de los juegos online y los videojuegos para mviles. Las previsiones para el
ejercicio 2011-2012 indican que se alcanzarn los 1.225 millones de euros al
nal del periodo.

El compositor se encarga de la creacin de la msica del videojuego y en muchas


ocasiones de orquestar esta msica. Tambin suele encargarse de contratar y dirigir
las grabaciones en directo que se necesiten para el juego. El diseador de sonido
trabaja conjuntamente con los desarrolladores de herramientas sonoras del videojuego,
creando, integrando y supervisando las tomas de audio y las libreras de sonido que se
utilizarn, que pueden ser libres, compradas a una empresa externa o elaboradas por
la propia compaa para su futura utilizacin en otros proyectos.
El encargado de los dilogos se ocupa de contratar, grabar y dirigir a los actores
que pondrn su voz a los personajes del juego. Los programadores de audio desarrollan
las herramientas de audio e integran los sonidos en el juego, tomando muchas veces
el rol de diseadores de sonido. Las herramientas que programan pueden ser externas
al videojuego -para facilitar la integracin de las grabaciones de audio- o internas,
formando parte del motor de sonido del videojuego, que a su vez se integrar con los
dems motores: el de inteligencia articial, el de grcos y el de fsica.

Por lo general, no se comienza a trabajar en el sonido de un videojuego hasta


que estn bien sentadas las bases del diseo y se ha comenzado el desarrollo.

Durante el proceso de desarrollo de un videojuego, es muy probable que evolu-


cione en cierta manera la visin del videojuego que tengan el jefe de proyecto o el
director artstico y se vean obligados a replantear la ambientacin y aspecto de es-
te, y por tanto las exigencias para la banda sonora o los efectos de sonido. Es una
situacin muy comn que piezas musicales que ya se haban aceptado como integran-
tes del videojuego tengan que ser recompuestas, adaptadas, recortadas, reorquestadas,
reinstrumentalizadas o en el peor de los casos descartadas para satisfacer las nuevas
caractersticas del juego o de un nuevo giro en su planteamiento.
30.1. Edicin de Audio [985]

Por todo lo anterior, el msico y diseador de sonido debe cumplir una serie
de aptitudes muy especiales para ser capaz de desarrollar tareas muy diversas, tanto
tcnicas como artsticas, para las que se necesitan conocimientos variados. Adems
de estas aptitudes, debe ser creativo, innovador, perseverante, trabajar rpido y ser
meticuloso.

30.1.2. Herramientas para la creacin de audio


Hoy da el ordenador es la parte central del conjunto de elementos necesarios
para construir un estudio de creacin musical. En l se realizan todas las tareas de
secuenciacin, edicin del sonido, grabacin multipista, masterizacin y exportacin
a diferentes formatos.
Dependiendo del software que se preera, determinaremos las capacidades mni-
mas necesarias de nuestro ordenador. Eso s, si deseamos que las tareas se realicen
con uidez, cuanto ms potente sea la mquina, mayor productividad tendremos. Un
procesador ms rpido, ms memoria RAM y un disco duro de gran capacidad nos
facilitarn la vida enormemente, ya que los cheros de audio suelen ocupar mucho
espacio y las tareas de procesamiento necesitan de altas velocidades de proceso si se
desea que lleven poco tiempo (o incluso puedan aplicarse y modicarse en tiempo
real).

Figura 30.1: Para comenzar a crear un estudio casero slo necesitaremos un ordenador con una buena
tarjeta de sonido. Poco a poco podremos complementarlo con instrumentos reales, diferente hardware y
nuevo software.
Secuenciad. multipistas
Muchos secuenciadores son tam-
bin multipistas en los que se pue-
den crear pistas de audio adems de
pistas MIDI. Esto permite mezclar
instrumentos reales con instrumen-
tos generados en el ordenador.
C30
[986] CAPTULO 30. SONIDO Y MULTIMEDIA

Software

La herramienta principal con la que contamos hoy da para la creacin de audio es


el software musical. Existen multitud de programas de audio en el mercado con muy
diferentes utilidades:

Secuenciadores: son programas capaces de grabar y reproducir una serie de


eventos MIDI, asignndoles diferentes muestras de sonido. Gracias a este tipo
de software podremos memorizar los eventos MIDI procedentes de diferentes
instrumentos. Ejemplos de este tipo de software son Linux Multimedia Studio
(LMMS), Rosegarden, Steinberg Cubase, Cakewalk Sonar, Avid Pro Tools,
Apple Logic o Propellerheads Reason.
Multipistas: permite reproducir y controlar diferentes instrumentos o sonidos
de forma independiente o conjunta, facilitando las labores de mezcla. Ejemplos
de este tipo de software son Ardour, Digidesign Pro Tools, Steinberg Cubase,
Cakewalk Sonar, Adobe Audition y Sony Vegas.
Editores: permiten grabar, manipular y procesar archivos de sonido provenien-
tes de cheros en diferentes formatos y guardarlos en otros formatos. Las dos
caractersticas ms importantes de estos programas -la edicin y la conversin
a diferentes formatos- son esenciales en el mundo de la creacin de audio para
videojuegos. Ejemplos de este tipo de software son Audacity, Adobe Audition
o Sony Sound Forge.
Software de masterizacin: permiten retocar y ajustar un grupo de cheros
de sonido para que todos ellos estn igualados en volumen y funcionen como
un nico conjunto de piezas de audio. Aunque este tipo de software est ms
orientado a la produccin de discos musicales, puede ser tambin til para que,
una vez creado el material sonoro que se incluir en el videojuego, adaptemos
sus niveles y retoquemos por ltima vez la ecualizacin y compresin en caso
de ser necesario. Entre este tipo de programas podemos encontrar Sony CD
Architect, Steinberg Wavelab o T-RackS.
Plug-ins: son pequeas extensiones de la funcionalidad de un programa
antrin. Por lo general, en el mbito de la msica por ordenador, los plug-
ins suelen ser mdulos de efectos para el procesado de sonido o instrumentos
virtuales. Hoy da existen todo tipo de plug-ins para aadir reverberacin, delay,
procesado dinmico, modulacin, efectos de guitarra, ltrado, etc y para simular
sintetizadores e instrumentos reales.
Software de loops: este tipo de software permite crear msica superponiendo SW libre y audio
diferentes bloques que pueden repetirse una y otra vez. Estos programas resultan Existen multitud de alternativas
tiles para crear bucles que sern incluidos en otras piezas musicales. Ejemplos de software libre para la crea-
de este tipo de software son Sony Acid, Propellerhead Recycle, Ableton Live o cin de audio: LMMS (secuencia-
FL Studio. cin y software de loops), Ardour,
energyXT, Qtractor (secuenciacin,
multipistas y masterizacin), Auda-
city (edicin), mda plugin collec-
Hardware tion (plug-ins) y otros muchos.

El apoyo necesario para facilitar la composicin y la conguracin del software


de audio puede conseguirse con diferentes tipos de hardware:

Instrumentos y controladores MIDI: el instrumento por excelencia para


dinamizar la comunicacin con el ordenador es el teclado. La casi totalidad
de los teclados actuales son capaces de comunicarse con otros dispositivos
mediante el protocolo MIDI. Gracias a l, la introduccin de notas y la
30.1. Edicin de Audio [987]

composicin en general ser notablemente ms fcil y uida. Adems de otros


muchos tipos de instrumentos MIDI como bateras o guitarras, tambin pueden
encontrarse controladores de diferentes formas y utilidades: pads, mesas de
mezclas, trackballs, supercies...
Samplers: son dispositivos electrnicos que pueden almacenar y modicar
muestras de sonidos. El usuario puede cargar o grabar sonidos y crear tablas para
asignarlos a notas musicales y poder as utilizar un teclado, un secuenciador u
otro dispositivo para lanzar los sonidos almacenados.
Otros instrumentos: adems del teclado como instrumento central para com-
poner msica por ordenador, la utilizacin de instrumentos clsicos, especial-
mente los que son difciles de emular digitalmente, como guitarras, vientos y
voz, dar a nuestra msica una nueva dimensin, aportando riqueza, humanidad
y realismo a las piezas musicales que compongamos, adems de abrirnos nuevas
posibilidades y estimular nuestra creatividad.

Figura 30.2: Existen multitud de controladores MIDI en el mercado. En la imagen, Native Instruments
Maschine (que es adems un sampler y un secuenciador).

30.1.3. El proceso creativo


Actualmente las empresas desarrolladoras de videojuegos han realizado un esfuer-
zo considerable para adaptar la msica a las diferentes situaciones de los videojuegos,
creando tensin si algn suceso importante va a suceder en el desarrollo del juego o
tranquilizando el estilo musical si la accin que se desarrolla se corresponde con un
instante relajado de la historia.
Este enfoque no siempre es posible, y otros juegos simplemente se sirven de la
msica para crear una ambientacin que se adapte al estilo del juego o para mantener
su ritmo. El compositor de msica para videojuegos debe ser capaz de dominar
muchos de los procesos involucrados en la creacin musical: compositor, arreglista,
ingeniero, productor, msico, mezclador. . . Cada uno de estos procesos se llevan a
cabo en diferentes fases del proceso creativo.
C30
[988] CAPTULO 30. SONIDO Y MULTIMEDIA

Preproduccin

El proceso creativo comienza con la fase de preproduccin, donde se comienza a


elaborar el documento de diseo del juego. Este documento detalla todos los aspectos
relevantes: historia, dilogos, mapas, sonido, grcos, animaciones y programacin.
En este momento se ja el tipo de msica y su papel en el videojuego y se elabora
una lista que dena la msica que tendr cada parte del juego, si habr un tema
principal, si la msica deber cambiar dependiendo de ciertas variables del videojuego
o cmo ciertos elementos musicales deben transmitir o corresponderse con detalles o
situaciones concretas.

Produccin

Una vez que el editor del juego aprueba el documento de diseo, el equipo de
desarrollo comienza su trabajo. La persona o personas encargadas de crear el audio de
un videojuego se incorpora al proyecto en las ltimas fases de desarrollo (al nalizar
la fase de produccin normalmente). Hasta este momento, lo ms probable es que el
equipo de programadores habr utilizado placeholders para los sonidos y su msica
favorita para acompaar al videojuego.
El creador de audio tiene que enfrentarse a la dura labor de crear sonidos y msica
lo sucientemente buenos como para conseguir que todo el equipo de desarrollo d
por buenos el nuevo material (aunque la decisin nal no sea del equipo de desarrollo,
pueden ejercer una gran inuencia sobre el veredicto nal).
En esta fase debe comprobarse tanto la validez tcnica de los sonidos y msica
(si se adaptan a las especicaciones) como la calidad del material entregado. Es
muy comn que se cambien, repitan o se aadan sonidos y/o composiciones en este
momento, por lo que el creador de audio debe estar preparado para estas contingencias
(previndolo en el presupuesto o creando a priori ms material del entregado).
Al nalizar el proceso de produccin, se determina cmo y en qu momentos
se reproducir qu archivo de sonido y con qu procesado en caso de utilizar
procesamiento en tiempo real. Por lo general, y pese a lo que pudiera parecer en
primera instancia, este proceso puede llevar hasta la mitad del tiempo dedicado a todo
el conjunto de la produccin.

Las consolas de ltima generacin incluyen procesadores digitales de seal


(DSPs) que permiten modicar en tiempo real efectos de sonido, o sus CPUs
son lo sucientemente potentes como para hacer estos clculos por s mismas.
A partir de un sonido de partida (un golpe metlico, por ejemplo), se puede
generar una versin del mismo sonido con eco o reverberacin (si el golpe
se produjese en una caverna), ocluida (si el golpe se produjese detrs de una
pared), enfatizada (si el golpe se produce ms cerca del jugador), etc.

Actualmente ha aumentado la tendencia a la utilizacin de distintas APIs (paquetes


de programacin a modo de libreras de software), como son FMOD o ISACT, ya que
disminuyen el tiempo necesario para llevar a cabo esta fase del proceso.
El posicionamiento del sonido en un entorno tridimensional tambin ha cobrado
gran importancia en los ltimos tiempos. La mayora de las videoconsolas y ordena-
dores personales actuales soportan sonido surround, lo que permite crear un entorno
ms realista e inmersivo en los videojuegos. Este tipo de tecnologas dan una nueva
30.1. Edicin de Audio [989]

dimensin a los videojuegos y otra variable con la que jugar durante el proceso de di-
seo: el sonido tridimensional puede convertirse en otro elemento protagonista para el
jugador, ya que puede permitirle posicionar cualquier fuente de sonido en un mundo
virtual (un enemigo, un objeto, etc.).

Post-produccin

Durante el proceso de post-produccin se lleva a cabo la masterizacin y mezcla


de los archivos de sonido una vez incluidos en el videojuego. Los sonidos y msica
que se reproducen durante una partida compiten simultneamente por ser odos por el
jugador. Es necesario tener en cuenta qu sonidos son ms o menos importantes en
cada momento del juego, y asignar un volumen adecuado a cada uno de ellos.
Al realizar la mezcla de los diferentes sonidos creados, debe tenerse en cuenta
el espectro de frecuencias de cada uno de los sonidos, para no acumular demasiados
sonidos en un mismo rango frecuencial. El proceso de mezclado tambin puede ser
dinmico y ser llevado a cabo automticamente por el motor de sonido. Los efectos
de sonidos pueden ser ecualizados o procesados en funcin de diferentes variables del
videojuego, pero tambin puede modicarse dinmicamente una partitura aadiendo
o eliminando instrumentos segn lo que suceda en el juego.
Una forma prctica de preparar el mezclado nal y evitar posibles problemas
es decidir desde un principio durante el proceso de pre-produccin incluso qu
frecuencias se asignarn a cada tipo de efecto de sonido, a la msica y a los dilogos.
Durante la produccin se tendr en cuenta esto para ya asignar bandas de frecuencias
durante el retoque de los sonidos, facilitando el posterior proceso de post-produccin.
El proceso de mezclado no consiste en aumentar el volumen de determinados
sonidos, sino de atenuar los menos importantes en cada momento. El problema es
decidir cul es la prioridad de la gran cantidad de sonidos que existen en un juego,
incluida msica y dilogos. Una vez ms, la naturaleza de los videojuegos hace
enormemente difcil la obtencin de una solucin ptima. La interactividad da un
grado de libertad al jugador que permite que coincidan varios sonidos o partes de la
msica importantes con dilogos clave para el desarrollo del juego. Esto obliga al
encargado de mezclar o de priorizar los sonidos a tomar la decisin menos mala o ms
ptima para los casos ms probables.
El proceso creativo de sonido en los videojuegos no solo se reere a la creacin
de audio, sino que una gran parte del tiempo se invierte en la implementacin y
acoplamiento de los sonidos y msica creada. Esta implementacin es especialmente
importante, puesto que determina cmo y cundo se reproducirn los sonidos durante
el videojuego. Adems, la complicacin que supone generar una mezcla de sonidos
adecuada hace an ms determinante esta parte del proceso creativo.
Por tanto, no solo debemos tener en cuenta la parte creativa y musical cuando
pensemos en el audio de un videojuego, los aspectos tcnicos tambin son muy
importantes y harn que una buena pieza musical, unos efectos sonoros realistas y
una buena grabacin de dilogos destaquen adecuadamente en el videojuego.
C30
[990] CAPTULO 30. SONIDO Y MULTIMEDIA

30.1.4. Tcnicas de creacin


Interactividad e inmersin

El sonido y la msica de un videojuego pueden tener diferentes funciones. En


ciertos juegos, el sonido y tiene un papel fundamental, afectando directamente a la
narrativa o al desarrollo del juego. En otros, la msica es un acompaamiento a la
accin y, aunque puede ser ms o menos adaptativa, no constituye un elemento del
juego en s misma.
La msica tambin puede utilizarse para realzar la estructura del videojuego,
utilizando temas principales en momentos diferentes de la accin o ciertos leitmotifs.
Los efectos sonoros pueden tener, adems de su funcin normal de responder
directamente a ciertas acciones del juego, una funcin simblica. En algunos juegos,
los sonidos son utilizados para dar pistas o dirigir al jugador hacia un lugar o para
advertirle de un peligro inminente. Tambin pueden utilizarse para centrar la atencin
del jugador en un elemento concreto de la accin.
Una regla de oro a tener en cuenta a la hora de crear efectos sonoros es que el
sonido ms real no es necesariamente ms verosmil. Intuitivamente nuestro cerebro
puede asociar sonidos a seres fantsticos (de los que no tiene por qu conocer su
sonido) o asociar sonidos irreales a situaciones cotidianas (el golpe de un puetazo en
una pelcula de accin). Lo anterior es aplicable tambin a la msica: qu msica es
ms apropiada para un videojuego ambientado en un mundo virtual?
Nuestro cerebro es capaz de identicar elementos presentes en el videojuego y
asociarlos a pocas histricas, pelculas o experiencias que hacen ms verosmiles
ciertos tipos de msica que otros en cada tipo de juego segn su ambientacin. El
objetivo es, por tanto, crear audio creble por el jugador, ya que dar un valor aadido
a la capacidad de inmersin del videojuego.
Figura 30.3: Un sonido que res-
ponde exactamente a la realidad no
Tipos y estilos de msica es siempre el ms acertado en un vi-
deojuego. Muchas veces se exage-
En un videojuego, el tipo de msica se diferencia por el momento o parte del ran las cualidades de un sonido pa-
juego en la que suena. Puede ir desde la necesaria para una escena cinemtica, un ra producir el efecto deseado en el
jugador.
tema principal para el ttulo, la msica ambiental que suena durante el juego, escenas
cinemticas, eventos especiales, etc. El estilo de msica depende de las necesidades
del videojuego, siendo muy comn encontrarse en la necesidad de componer muy
diferentes estilos para un mismo proyecto.
Stingers (fanfarrias)
Composicin Son piezas musicales de pocos se-
gundos que dan respuesta a accio-
Aunque no hay una receta mgica que asegure un buen resultado cuando se trabaja nes concretas del jugador para lla-
en la msica de un videojuego, pueden seguirse una serie de buenas prcticas que mar su atencin. Para mantener la
ambientacin musical del juego, es-
harn el trabajo ms fcil. tas piezas se componen con las mis-
mas caractersticas e instrumenta-
Crear una paleta de sonidos, escuchar msica de otros autores en el estilo que cin que la msica de fondo, pero
pide el videojuego, guardar ideas sueltas que puedan ser tiles en un momento son mucho ms llamativas y sono-
de poca creatividad, buscar inspiracin en libreras de sonidos, experimentar con ras.
combinaciones de sonidos o conguracin que no hayamos probado antes o, cuando
sea posible, jugar a una versin de prueba del videojuego en el que trabajemos, pueden
ser formas de agilizar el trabajo y estimular la creatividad para encontrar la inspiracin
necesaria.
30.1. Edicin de Audio [991]

Seleccionar una serie de sonidos (la paleta de sonidos) adecuados a la


ambientacin y dinmica del videojuego en el que estemos trabajando,
adems de facilitarnos el trabajo de composicin y evitarnos perder tiempo
buscando un sonido concreto, nos llevar a crear una banda sonora coherente,
conexa y con una personalidad constante durante el juego.

Creacin de efectos sonoros

Hasta que el diseo y programacin no estn lo sucientemente avanzados, es


probable que el equipo desarrollador no sepa concretar cuntos sonidos y con qu
caractersticas concretas son necesarios en el videojuego. Cuanto ms grande sea el
proyecto, ms difcil ser concretar la lista de sonidos necesarios, por lo que el proceso
de creacin de efectos sonoros debe aplazarse lo mximo posible en la planicacin
del proyecto.
Antes de comenzar a trabajar, debemos tener claro el gnero del videojuego, la
Figura 30.4: El estilo orquestal es calidad sonora y el tamao mximo de los archivos de sonido, qu motor de sonido
uno de los estilos de msica ms se va a utilizar y si procesar los sonidos en tiempo real, si debemos crear sonidos
utilizados hoy da en las grandes ambientales y cul debe ser su duracin, qu prioridad tienen unos sonidos sobre otros,
producciones, muchas veces graba-
do con orquestas reales cuando se
si el juego tendr dilogos y en qu idiomas, cul ser el equipo de reproduccin tpico
dispone de suciente presupuesto. que tendr el jugador del videojuego, si los efectos sonoros sonarn sobre msica de
fondo, si hay libreras de sonido a nuestra disposicin y cules son, qu nombres deben
Coordinando el trabajo
tener los cheros a entregar o qu reglas de nombrado siguen y si podemos trabajar
con una versin de prueba de videojuego.
Una tarea a realizar antes y duran-
te la creacin de efectos sonoros es Una buena forma de empezar a trabajar es crear una paleta de sonidos. Esta
mantener un contacto constante con tarea consiste en buscar unos cien o doscientos sonidos que tengamos previamente
los desarrolladores y tratar de re- en nuestro ordenador, procedentes de libreras de sonidos o que hayamos grabado
copilar la mayor informacin posi- previamente para otros videojuegos y que sean del estilo del videojuego en el que
ble que nos pueda ayudar en nuestro
trabajo. vayamos a trabajar.
Debemos tratar de que estos sonidos peguen entre s y que tengan una calidad
similar. En caso de no disponer de libreras de sonidos o de trabajo anterior, debemos
obtener estos sonidos por otros medios: grabndolos nosotros mismos, adquiriendo
alguna de las mltiples libreras de efectos sonoros que existen, o generndolos a
partir de instrumentos virtuales o samplers.
Esta paleta de sonidos no tiene por qu contener necesariamente los sonidos que
elegiremos para el videojuego, sino que sern la base a partir de la cual trabajaremos.
Superponer (un software multipistas facilitar esta tarea), mezclar, procesar, combinar,
cambiar de tono, texturizar, reorganizar, cortar, dar la vuelta, invertir, comprimir o
expandir son tcnicas que podemos utilizar de diferentes formas sobre todos estos
sonidos base y que nos permitirn muy probablemente ser capaces de crear los sonidos
que se nos haya pedido. En caso de encontrar nuevos tipos de sonido que no podamos
conseguir con las anteriores tcnicas a partir de nuestra paleta de sonidos, tendremos
que crearlos desde cero.
No existen frmulas mgicas para crear efectos de sonido: combinando las
tcnicas mencionadas con sonidos de la vida real grabados de diferentes formas
o generados con sintetizadores o samplers se pueden conseguir cualquier sonido
imaginable. Combinar sonidos que a priori pueden parecer no tener nada que ver y
experimentar con cualquier idea son tambin prcticas claves en la creacin de efectos
sonoros.
C30
[992] CAPTULO 30. SONIDO Y MULTIMEDIA

La utilizacin de libreras de sonidos, aunque sea muy tentador para los


desarrolladores, casi nunca es la mejor opcin. Si nos jamos, en muchas
pelculas, anuncios, trailers y videojuegos, podemos encontrar los mismos
sonidos repetidos una y otra vez. Crear sonidos originales para un videojuego
da la exclusividad y adaptacin necesaria que el jugador busca y agradece.

30.1.5. Programacin de audio


Un videojuego es un programa, normalmente grande y complejo. Parte de este
programa debe estar dedicada a ejecutar las rdenes necesarias para que el ordenador,
videoconsola, telfono mvil o plataforma en la que se ejecute el videojuego
reproduzca en el momento necesario los sonidos y msica creados por el compositor.
El sonido, por lo general, no es la parte a la que se presta mayor atencin en un
videojuego. Durante aos, al audio se le ha dado aproximadamente un 10 % del total de
memoria, espacio, procesado, personal, presupuesto y publicidad en los videojuegos.
Los primeros videojuegos se ejecutaban en mquinas en las que la mayor parte de
recursos se dedicaban a la presentacin de imgenes en la pantalla. Esto ha sido una
tnica comn en la evolucin de las plataformas en las que se ejecutan los videojuegos,
pero segn han avanzado tecnolgicamente estas plataformas, y segn se han ido
mejorando su capacidad de vdeo, ms protagonismo ha ido tomando el sonido.
En lo que se reere a la programacin del sonido, la evolucin ha sido lenta
y tortuosa hasta llegar a las tcnicas y posibilidades actuales. En un principio, las
herramientas de desarrollo o bien no existan o eran particulares de la plataforma
para la que se desarrollaba. Los programas que reproducan msica y sonidos se
desarrollaban en lenguaje ensamblador y compiladores especcos de la plataforma, y
los msicos estaban limitados enormemente tanto por la propia plataforma como por
su habilidad como programadores. Todo ello, a causa de la ntima relacin que exista
entre el contenido y el programa.
Con el tiempo, y paralelamente a la evolucin tecnolgica de las plataformas, las
herramientas para crear contenido fueron madurando. Esto permiti la separacin
progresiva de contenido y software propiamente dicho, y comenzaron a aparecer
motores y editores de contenido por separado.
Hoy en da, y con la complejidad que han adquirido los videojuegos, es posible
comprar motores software de todo tipo para crear videojuegos, permitiendo a grupos
de desarrolladores centrarse en programar las partes del videojuego que lo diferencia-
ran de otros delegando en estos motores para el resto de partes, y a los creadores de
contenido dedicarse nicamente a su labor: crear msica y sonidos sin tener que ser
expertos en programacin.
Uno de los mayores problemas con el que se encuentran los equipos desarrollo de
videojuegos a la hora de crear un ttulo multiplataforma es que no existe consistencia
en las caractersticas de audio entre las mltiples plataformas de videojuegos, y esto
provoca tener que desarrollar motores de sonido especcos para cada una de ellas por
separado. En cierta medida, la utilizacin de APIs de terceros puede solucionar este
problema.
30.1. Edicin de Audio [993]

Figura 30.5: Algunos motores y APIs de audio vienen acompaadas de herramientas para la creacin y
organizacin del material sonoro de un videojuego. En la imagen, FMOD Designer.

Motores y APIs

En un videojuego, el sonido debe adaptarse a las acciones y decisiones que tome


el jugador. Idealmente, el motor de audio de un videojuego debera ser capaz de
reproducir, pausar y parar un sonido en cualquier instante, cambiar su volumen y
panorama (lugar estereofnico que ocupa), reproducirlo indenidamente, saltar a otro
punto del sonido o msica durante su reproduccin o pasar de una pieza musical a otra
mediante una transicin.
Dado que un videojuego puede desarrollarse para varias plataformas, debe minimi-
zarse la creacin de contenido e implementaciones especcas para cada plataforma,
proporcionando ciertos comportamientos consistentes en todas ellas.

Los motores y APIs de audio permiten a los desarrolladores no perder tiempo


en programar cdigo especco para diferentes plataformas y a los creadores
de contenido ser capaces de familiarizarse con el proceso de integracin del
sonido en los videojuegos. Todo ello, con el objetivo nal de aumentar la
productividad, la eciencia y la calidad del producto nal.
C30
[994] CAPTULO 30. SONIDO Y MULTIMEDIA

Los programadores encargados del audio pueden elegir utilizar una de las diferen-
tes herramientas software que existen en el mercado para facilitar su tarea o progra-
mar desde cero un motor de sonido. Esta decisin debe tomarse en las primeras fases
del ciclo de creacin del videojuego, para que en el momento en el que el desarrolla-
dor o grupo de desarrolladores especializados en audio que llevarn a cabo esta labor
sepan a qu tipo de reto deben enfrentarse.
Desde el punto de vista del creador de contenido, esta informacin tambin
resulta relevante, ya que en multitud de ocasiones sern estas APIs las encargadas
de reproducir este contenido, y porque algunas de ellas vienen acompaadas de
herramientas que debe conocer y ser capaz de manejar.
Existe un amplio nmero de subsistemas de audio en el mercado escritos explci-
tamente para satisfacer las necesidades de una aplicacin multimedia o un videojuego.
Estos sistemas se incorporan en libreras que pueden asociarse al videojuego tanto es-
ttica como dinmicamente en tiempo de ejecucin, facilitando la incorporacin de un
sistema de audio completo al programador sin conocimientos especializados en este
mbito.
La facilidad de integracin de estas libreras vara segn su desarrollador y la
plataforma a la que van dirigidas, por lo que siempre existir una cierta cantidad de
trabajo a realizar en forma de cdigo que recubra estas libreras para unir el videojuego
con la funcionalidad que ofrecen.
Algunas de las libreras ms utilizadas hoy da son OpenAL, FMOD, Miles
Sound System, ISACT, Wwise, XACT, Beatnik Audio Engine o Unreal 3 Sound
System.

30.2. Video digital en los videojuegos


A lo largo de los captulos y mdulos previos, han quedado pantentes algunos de
los importantes avances realizados en el marco de los grcos 3D en los ltimos aos.
Por su parte, los avances en el mbito del vdeo digital no han sido menos. As,
estamos acostumbrados a reproducir videos en nuestros dispositivos porttiles de mp4,
ver video a travs de ujos (del ingls streamming) bajo demanda por internet, tener
televisin en alta denicin y reprodutores de DVDs y discos Blue-Ray en el saln de
nuestros hogares, etc. Es decir, el video sea o no de alta denicin (del ingls High
Denition, HD), es algo comn en nuestros das.
El mundo de los videojuegos puede aprovecharse del efecto sinrgico conseguido
mediante la integracin de las tecnologas de video digital junto a las de grcos 3D.
Con ello, a lo largo del resto del captulo, se ver como pueden enriquecerse escenarios
3D mediante la integracin de video digital.

30.3. Grcos 3D y video digital


Figura 30.6: Imagenes de la serie
Los grcos 3D y el video digital son tecnologas complementarias, y un buen de fotografas de Muybridge. A lo
ejemplo de ello son los Videojuegos. Con grcos en 3D, se pueden crear mundos largo de la pista, se dispusieron 16
cmaras de alta velocidad. Al pa-
y personajes imaginarios que nunca existieron, e incluso interactuar con ellos. Sin sar ante cada una ellas, el caballo
embargo, en ocasiones es difcil que estos grcos sean lo sucientemente realistas. rompa el hilo que la accionaba pro-
Es en este punto donde el video digital entra en escena. Aunque el video no vocando el disparo. La sucesin de
sea interactivo, es capaz de captar el mundo real con gran cantidad de detalles e cada fotograma permite generar el
incluso con caractersticas tridimensionales, como ha quedado patente en numerosas movimiento.
producciones cinematogrcas recientes.
30.4. Estndares en video digital [995]

Ambas tecnologas tienen mucho en comn. En los grcos 3D los objetos


son matemticamente transformados a coordenadas 2D para su visualizacin en la
pantalla. Las texturas de las supercies de los objetos estn almacenados en imagenes
(mapas de bist) que son adheridas a los objetos.
Cuando se desea mostrar un video por pantalla, un rea determinada de la pantalla
se designa para mostrar el vdeo, ya sea en un rectngulo dentro de una ventana o
a pantalla completa. Este video est formado por fotogramas (gura 30.6), y cada
fotograma es un mapa de bits que se estira para adaptarse al rectngulo de destino.
Como puede verse, la principal diferencia entre grcos 3D y video digital, radica
en que el mapa de bits es actualizado varias veces por segundo en el caso del video,
mientras que la mayora de los modelos 3D usan texturas estticas.
En las prximas secciones se mostrarn algunos conceptos esenciales necesarios
para entender los formatos de video digital para posteriormente pasar a ver cmo
puede realizarse su integracin dentro de escenarios creados mediante Ogre 3D.

30.4. Estndares en video digital


En este captulo nos centraremos en la integracin del video digital junto a grcos
3D. El video digital es aquel que emplea una representacin digital en lugar de
analgica de la seal de video.
Al hablar de video digital, es conveniente destacar dos factores clave que
intervienen directamente sobre la calidad del vdeo: la captura y el almacenamiento.
La captura de vdeo est fuera del alcance de este captulo. En lo que respecta al
almacenamiento, el video digital puede grabarse directamente sobre soportes digitales
como DVDs, discos duros o memorias ash o bien sobre soportes analgicos como
cintas magnticas y posteriormente distribuirse en soportes digitales.
Para la edicin de video digital, al principio se requera digitalizar una fuente
de vdeo analgica a un formato digital que pudiera manipularse por ordenador.
Posteriormente, el empleo de los formatos digitales para cinta conocidos como DV
y miniDV, posibilit el hecho de grabar directamente en formato digital simplicando
as el proceso de edicin.
El primer formato de video digital para el ordenador orientado al mercado no
profesional en hogares fue introducido por Apple Computer en 1991. Nos referimos
al formato Quicktime. A partr de este punto, el vdeo digital no slo fue mejorando
rpidamente en calidad, sino en facilidad de procesamiento y manipulacin gracias a
la introduccin de estndares como MPEG-1, MPEG-2 y MPEG-4. Tal fue el xito de
estos estndares que fueron rpidamente adoptados por industrias como la televisin
(tanto digital como satlite), soportes como DVD, la distribucin de video on-line y
streaming bajo demanda, etc.
Figura 30.7: Quicktime fue el pri-
mer formato de video digital para Los principales estndares de codicacin de audio y video y su uso fundamental
ordenadores de mercano no profe- son los que se enumeran a continuacin:
sional en hogares

MPEG (Moving Picture Experts Group)-1: Es el nombre de un grupo de


estndares desarrollado por el grupo de expertos en imgenes en movimiento
(del ingls Moving Pictures Experts Group MPEG) de ISO/IEC. Para el audio,
el MPEG deni el MPEG-1 Audio Layer 3, ms conocido como MP3. Es el
estandar usado en el soporte de disco Video CD y en los reproductores MP3.

Figura 30.8: DVD Video emplea


MPEG-2
C30
[996] CAPTULO 30. SONIDO Y MULTIMEDIA

MPEG-2: Es la designacin para el grupo de estndares publicados como


ISO 13818. MPEG-2 es usado para codicar audio y vdeo en seales de
Televisin Digital Terrestre (TDT (Televisin Digital Terrestre) o DVB (Digital
Video Broadcasting) de sus siglas en ingls Digital Video Broadcasting), por
Satlite o Cable. Es tambin el formato de codicacin empleado para los
soportes de discos SVCD y DVD comerciales de pelculas y el estndar actual
de las transmisiones en televisin de alta denicin (HDTV (High Denition
Television)).
MPEG-4: An en desarrollo, conforma el estndar ISO/IEC 14496. Se trata del
heredero de muchas de las caractersticas de MPEG-1, MPEG-2 y estndares
como soporte de VRML (Virtual Reality Modeling Language) para renderizado
3D, soporte para la gestin digital de derechos externa y variados tipos de
interactividad. Es el implementado por algunos codecs populares como DivX,
Xvid, Nero Digital y Quicktime en sus versiones 6 y 7 y en vdeo de Figura 30.9: Blu-Ray emplea
alta denicin como Blu-ray. Los principales usos de este estndar son los MPEG-4 Part 10
streaming en medios audiovisuales como los canales de video on-line bajo
demanda, la grabacin y distribucin de video en memorias ash, la transmisin
bidireccional como videoconferencia y emisin de televisin.
Ogg: Surgido como alternativa libre al MPEG-4, se encuentra estandarizado
pero an en desarrollo por la Fundacin Xiph.org. As, permite trabajar con
audio y vdeo sin tener que emplear formatos propietarios o de pago. Ogg
emplea Theora para implementar la capa de vdeo y Vorbis, Speex, FLAC
u OggPCM para la capa de audio. Al igual que MPEG-4, es empleado
principalmente en streaming de medios audiovisuales y video bajo demanda.

Cuando se desarrollan aplicaciones que deben procesar vdeo digital ya sea para
su creacin, edicin o reproduccin, debe tenerse en cuenta cul es el estndar que Figura 30.10: Ogg emplea Vorbis
se est siguiendo, dado que de ello depende el formato de archivo contenedor que se para audio y Theora para video
manipular.
Cuando se habla de formato contenedor multimedia o simplemente formato
contenedor, se est haciendo referencia a un formato de archivo que puede contener
varios tipos diferentes de datos que han sido codicados y comprimidos mediante
lo que se conocen como cdecs. Los cheros que estn codicados siguiendo
un formato contenedor son conocidos como contenedores. stos almacenan todos
aquellos elementos necesarios para la reproduccin, como el audio, el vdeo, los
subttulos, los captulos, etc., as como los elementos de sincronizacin entre ellos.
Algunos de los formatos contenedores ms conocidos son AVI, MOV, MP4, Ogg o
Matroska.
Dada la complejidad que implicara elaborar una aplicacin que interprete los
cheros contenedores as como los datos multimedia contenidos en ellos y codicados
mediante codecs, para poder manipular el vdeo de modo que pueda ser integrado
en cualquier aplicacin en nuestro caso junto a grcos 3D, suelen emplearse
libreras o APIs (Application Programming Interface) que nos faciliten la tarea. En
las siguientes secciones nos adentraremos en algunas de ellas.

30.5. Plugins de vdeo para Ogre


La librera de Ogre no proporciona manipulacin de vdeo. Sin embargo, existen
dos plugins que permiten el uso de vdeo en texturas de Ogre. El primero de ellos es
Theora, el cual funciona bajo sistemas Win32 y Linux y permite reproducir cheros
Ogg. El segundo es Directshow, que slo funciona bajo sistemas Win32 y permite
reproducir cualquier vdeo de Windows.
30.5. Plugins de vdeo para Ogre [997]

30.5.1. Instalacin de TheoraVideoPlugin


En primer lugar se debern instalar las libreras Ogg, Vorbis y Theora. Pueden
descargarse de http://xiph.org/downloads/ o podemos emplear los reposi-
torios de paquetes del sistema y usar comandos como:

apt-get install libogg-dev libvorbis-dev libtheora-dev

Adems es preciso instalar la librera C++ Portable Types (ptypes) descargable de


http://www.melikyan.com/ptypes/. Una vez descargado el tarball, puede
descomprimirse y compilarse con la siguiente secuencia de comandos:

tar zxvf ptypes-2.1.1.tar.gz


cd ptypes-2.1.1
make
sudo make install

Finalmente ya se puede instalar el plugin de Theora disponible en http:


//ogrevideo.svn.sf.net/. Una vez descargado el cdigo, bien empleando
subversin o bien descargando directamente el tarball o chero .tar.gz, en el directorio
trunk ejecutamos la siguiente secuencia de comandos:

./autogen.sh
export CXXFLAGS=-I/usr/include/OGRE/
./configure
make
sudo make install
ln -s /usr/local/lib/Plugin_TheoraVideoSystem.so \
/usr/lib/OGRE/Plugin_TheoraVideoSystem.so

30.5.2. Incluyendo vdeo en texturas


Para emplear vdeo en texturas, bastar con incluir en el bloque texture_unit
un bloque texture_source ogg_video, para indicar que se desea introducir el vdeo
codicado mediante Theora en un contenedor ogg. Este bloque permite denir:

lename: nombre del chero. Este parmetro es imprescindible.


play_mode: establece el estado play/pause de comienzo. Por defecto es play.
precache: cuntos frames debe renderizar de antemano. Por defecto es 16.
output: modo de salida (rgb, yuv, grey).

En el ejemplo de la gura 30.11 se dene un material SimpleVideo donde en su


textura se ha denido un texture_source ogg_video con un chero contenedor ogg
indicado en el parmetro lename.
C30
[998] CAPTULO 30. SONIDO Y MULTIMEDIA

material VideoMaterial {
technique {
pass {
texture_unit {
texture_source ogg_video {
filename clip.ogg
precache 16
play_mode play
}
}
}
}
}
Figura 30.11: Material que incluye textura con vdeo.

30.6. Reproduccin de vdeo con GStreamer


El uso de plugins de Ogre puede llevarnos a limitaciones a la hora de reproducir
vdeo junto a grcos 3D. Existe una alternativa a ellos: el empleo de entornos de
desarrollo de vdeo digital especializados.
En esta seccin se mostrar uno de ellos, concretamente GStreamer, y se ver
cmo puede integrarse dentro de nuestros grcos 3D con Ogre.

30.6.1. Instalacin del framework de desarrollo y libreras necesa-


rias
Antes de comenzar a trabajar con GStreamer debern instalarse el framework y las
libreras en el sistema. Para ello, en sistemas basados en Debian bastar con ejecutar
indicarle a apt-get:

# apt-get install gstreamer0.10-alsa gstreamer0.10-plugins-base \


gstreamer0.10-plugins-good libgstreamer0.10-dev \
libstreamer-plugins-base0.10-dev

Si no se dispone de un sistema de gestin de paquetes, podrn descargarse de la


direccin http://gstreamer.freedesktop.org/.
A la hora de compilar las aplicaciones, debern aadirse a los ags de compilacin
aquellos devueltos por:

# pkg-config --cflags gstreamer-0.10

para la compilacin y

# pkg-config --libs gstreamer-0.10

para el enlazado.
30.6. Reproduccin de vdeo con GStreamer [999]

30.6.2. Introduccin a GStreamer


GStreamer es un entorno de desarrollo (framework) para la creacin de editores
y reproductores de vdeo, aplicaciones de emisin de streamming de vdeo, etc. Est
basado en el pipeline de vdeo del OGI (Oregon Graduate Institute) y en DirectShow,
por lo que muchos de los conceptos que se vern en esta seccin podrn extrapolarse
a ste ltimo.
GStreamer puede conectarse a otros frameworks multimedia con el n de permitir
reutilizar codecs, adems de emplear los mecanismos de entrada-salida de cada plata-
forma. As, usa OpenMAX-IL mediante gst-openmax para funcionar en Linux/Unix,
DirectShow para plataformas Windows y QuickTime para Mac OS X.
La principal caracterstica de GStreamer es que est constituido por varios codecs
y funcionalidades que pueden ser cargados a modo de plugins. Del mismo modo,
puede extenderse por medio de nuevos plugins. Esto le permite poder trabajar con
multitud de formatos contenedores, protocolos de streaming bajo demanda, codecs de
vdeo y audio, etc.
Los plugins de GStreamer pueden clasicarse en:

gestores de protocolos
fuentes de audio y vdeo
manejadores de formatos con sus correspondientes parsers, formateadores,
muxers, demuxers, metadatos, subttulos, etc.
codecs, tanto de codicacin como de decodiacin
ltros, como conversores, mezcladores, efectos, etc.
sinks para volcar la salida de audio y vdeo resultante de todo el proceso

En la gura 30.12 se muestra un esquema de la arquitectura de GStreamer.


En la parte superior de la gura puede verse cmo tanto las herramientas de
GStreamer como otras aplicaciones multimedia como reproductores de video y audio,
herramientas de VoIP y video/audio conferencia, servidores de streaming de audio
y/o video, etc., emplean el framework de GStreamer. Dicho framework tiene a su
disposicin un conjunto de plugins (gestores de protocolos, fuentes de audi y video,
manejadores de formatos, etc.) tal y como se muestra en la parte inferior de la gura.
Por su parte, el framework de GStreamer consta de una arquitectura de en pipeline
o ujos de informacin. Los plugins pueden entrelazarse e interactuar por medio
de estos pipelines, de manera que es posible escribir complejas aplicaciones para la
gestin de audio y vdeo.
La herramienta en lnea de comandos gst-launch resulta muy til para realizar
algunas pruebas y prototpos rpidos de construccin de pipelines. Veamos algunos
ejemplos.
Playbin2 es un plugin de GStreamer que proporciona un reproductor tanto de audio
como de vdeo. As, un comando sencillo para la reproduccin de un chero en la
correspondiente ventana de playbin2 es:

# gst-launch playbin2 uri=file:///path/to/file.ogg

El siguiente comando produce la misma salida, pero especica toda la pipe, en


lugar de dejarlo en manos del reproductor playbin2. Fjese cmo ha creado un demux
de ogg al que se le ha asignado un nombre. A continuacin su salida se pasa tanto a
una cola para el decodicador de Theora para el vdeo, como a un decodicador de
Vorbis para el audio:
C30
[1000] CAPTULO 30. SONIDO Y MULTIMEDIA

gst-launch
Reproductor Audio/video Servidor de Editor de
gst-inspect
multimedia conferencia streamming vdeo
gst-editor

Framework de GStreammer
Clases base
Librera de utilidades
Pipeline Bus de mensajes
Plugins de sistema
Bindings de lenguajes
...

Gestores de Filtros:
Fuentes: Formatos: Codecs: Sinks:
protocolos: conversores,
Alsa, v4l2, avi, mp4, mp3, mpeg4, Alsa, xvideo,
http, fichero, mezcladores,
tcp/udp, etc. ogg, etc. vorbis, etc. tcp/udp, etc.
rstp, etc. efectos

Plugins de GStreammer
Plugins de terderos

Figura 30.12: Arquitectura de GStreamer.

# gst-launch filesrc location=/path/to/file.ogg \


! oggdemux name="demux" \
demux. ! queue ! theoradec ! xvimagesink \
demux. ! vorbisdec ! audioconvert ! pulsesink

Para trabajar con la cmara web se emplear v4l2 para capturar frames de la
cmara. Un ejemplo es el siguiente comando donde se captura un frame desde
v4l2src, se procesa por ffmpegcolorspace para que no aparezca con colores extraos,
transforma a formato png con pngenc y se pasa a un chero llamado picture.png con
lesink

# gst-launch-0.10 v4l2src ! ffmpegcolorspace ! pngenc \


! filesink location=picture.png

Si lo que queremos es mostrar todos los frames que recoga la cmara web
directamente en una ventana, podemos hacerlo mediante el siguiente pilepile:

# gst-launch-0.10 v4l2src ! ffmpegcolorspace ! ximagesink

30.6.3. Algunos conceptos bsicos


Antes de adentrarnos en la programacin de aplicaciones con GStreamer es preciso
describir algunos conceptos bsicos.

Elementos. Son los objetos ms importantes en GStreamer. Las aplicaciones


que empleen GStreamer especicarn cadenas de elementos unidos unos a otros
para construir el pipeline. Los elementos harn uir la informacin a travs de
la cadena. Cada elemento tiene una funcin concreta y bien denida, que puede
ser leer datos de un chero, decodicar los datos o poner los datos en la tarjeta
de vdeo.
30.6. Reproduccin de vdeo con GStreamer [1001]

Pads. Son las conexiones de entradas y salidas de los elementos. Cada pad
gestiona datos de tipo especco, es decir, restringen el tipo de datos que
uye a travs de ellos. Son el elemento que permiten crear las pipelines entre
elementos.
Bins y Pipelines. Un bin es un contenedor de elementos. Una pipeline es un tipo
especial de bin que permite la ejecucin de todos los elementos que contiene.
Un bin es un elemento, y como tal puede ser conectado a otros elementos para
construir una pipeline.
Comunicacin. Como mecanismos de comunicacin GStreamer proporciona:
Buffers. Objetos para pasar datos entre elementos de la pipeline. Los
buffers siempre van desde los fuentes hasta los sinks
Eventos. Objetos enviados entre elementos o desde la aplicacin a los
elementos.
Mensajes. Objetos enviados por los elementos al bus de la pipeline.
Las aplicaciones pueden recoger estos mensajes que suelen almacenar
informacin como errores, marcas, cambios de estado, etc.
Consultas. Permiten a las aplicaciones realizar consultas como la dura-
cin o la posicin de la reproduccin actual.

30.6.4. GStreamer en Ogre


Una vez introducidos los principios de funcionamiento de GStreamer, as como los
objetos que maneja, se pasar a mostrar cmo puede integrarse en Ogre. Para ello se
ir desgranando un ejemplo en el que se usa playbin2 de GStreamer para reproduccin
de vdeo en una textura Ogre, que ser mostrada mediante el correspondiente overlay.
Comenzaremos escribiendo el mtodo createScene.

Listado 30.1: Mtodo createScene para cargar vdeo en un overlay


1 void GStreamerPlayer::createScene(){
2 // Establecer cmara, luces y dems objetos necesarios
3 ...
4
5 mVideoTexture = Ogre::TextureManager::getSingleton().createManual
(
6 "VideoTexture",
7 ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
8 TEX_TYPE_2D,
9 1, 1,
10 0, PF_B8G8R8A8,
11 TU_DYNAMIC_WRITE_ONLY);
12
13 mVideoMaterial = MaterialManager::getSingleton().create(
14 "VideoMaterial",
15 ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
16 Ogre::Technique* technique = mVideoMaterial->createTechnique();
17 technique->createPass();
18 mVideoMaterial->getTechnique(0)->getPass(0)->
19 createTextureUnitState(mVideoTexture->getName());
20
21 mVideoOverlay = OverlayManager::getSingleton().create("overlay");
22 OverlayContainer* videoPanel =
23 static_cast<OverlayContainer*>(OverlayManager::getSingleton().
24 createOverlayElement("Panel", "videoPanel"));
25
26 mVideoOverlay->add2D(videoPanel);
27 mVideoOverlay->show();
28 videoPanel->setMaterialName(mVideoMaterial->getName());
C30
[1002] CAPTULO 30. SONIDO Y MULTIMEDIA

29
30 gst_init(0, 0);
31
32 if (!g_thread_supported()){
33 g_thread_init(0);
34 }
35
36 mPlayer = gst_element_factory_make("playbin2", "play");
37
38 GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(mPlayer));
39 gst_bus_add_watch(bus, onBusMessage, getUserData(mPlayer));
40 gst_object_unref(bus);
41
42 mAppSink = gst_element_factory_make("appsink", "app_sink");
43
44 g_object_set(G_OBJECT(mAppSink), "emit-signals", true, NULL);
45 g_object_set(G_OBJECT(mAppSink), "max-buffers", 1, NULL);
46 g_object_set(G_OBJECT(mAppSink), "drop", true, NULL);
47
48 g_signal_connect(G_OBJECT(mAppSink), "new-buffer",
49 G_CALLBACK(onNewBuffer), this);
50
51 GstCaps* caps = gst_caps_new_simple("video/x-raw-rgb", 0);
52 GstElement* rgbFilter = gst_element_factory_make("capsfilter",
53 "rgb_filter");
54 g_object_set(G_OBJECT(rgbFilter), "caps", caps, NULL);
55 gst_caps_unref(caps);
56
57 GstElement* appBin = gst_bin_new("app_bin");
58
59 gst_bin_add(GST_BIN(appBin), rgbFilter);
60 GstPad* rgbSinkPad = gst_element_get_static_pad(rgbFilter,
61 "sink");
62 GstPad* ghostPad = gst_ghost_pad_new("app_bin_sink", rgbSinkPad);
63 gst_object_unref(rgbSinkPad);
64 gst_element_add_pad(appBin, ghostPad);
65
66 gst_bin_add_many(GST_BIN(appBin), mAppSink, NULL);
67 gst_element_link_many(rgbFilter, mAppSink, NULL);
68
69 g_object_set(G_OBJECT(mPlayer), "video-sink", appBin, NULL);
70 g_object_set(G_OBJECT(mPlayer), "uri", uri, NULL);
71
72 gst_element_set_state(GST_ELEMENT(mPlayer), GST_STATE_PLAYING);
73 }

1. Inicializacin de cmara, luces, etc. Como siempre, al comienzo del mtodo


se inicializar la cmara, las luces y en general todo aquello que sea necesario
para construir la escena.
2. Creacin de textura, material y overlay. En el mtodo createScene se debern
en el que se
crear la textura, el material al que aplicar el overlay, y el overlay
reproducir el vdeo. Todo esto puede verse en las lneas 5-11 , 13-19 y 21-28
respectivamente.
3. Inicializacin de GStreamer y soporte multihilo. A continuacin, dado que
toda aplicacin que emplee las libreras de GStreamer debern comenzar por
inicializarlo, se har uso de la funcin gst_init. La llamada a esta funcin
inicializar la librera y procesar los parmetros especcos de GStreamer. As,

tras la creacin a la funcin
de la textura, el material y el overlay seinvocar
gst_init 30 , y se activar el soporte multihilo invocando 32-34 .
4. Captura de mensajes. Para poder recoger los mensajes producidos por play-
bin2 desde el bus del pipeline, se aadir el mtodo que se invocar cada vez que
se reciba un mensaje mediante la funcin
gst_bus_add_watch aplicada sobre el
bus, tal y como se muestra en las lneas 38-40 .
30.6. Reproduccin de vdeo con GStreamer [1003]

5. Creacin y conguracin de objeto appsink. El siguiente paso es el de crear


un objeto appsink que har que el vdeo sea pasado a la textura de Ogre
42 . A continuacin se har que appsink emita seales de manera que nuestra
44 , hacer que el
aplicacin conozca cundo se ha producido un nuevo frame
tamao del buffer sea de uno 45 , y nalmente volcarlo 46 . Al nal habr que
indicar cul es la funcin que se encargar de procesar
los frames que genere
appsink mediante la funcin g_signal_connect 48 .
6. Creacin de ltro video/x-raw-rgb. A continuacin se deber crear un ltro
que produzca datos RGB tal y como se muestra en las lneas 51-55 .
7. Creacin deunbin que conecte el ltro con appsink. Seguidamente se deber
crear un bin 57 al que se le aadir
el ltro y un pad que conecte la salida del
ltro con la entrada de appsink 59-64 . A continuacin se aadira el appsink al
bin.


8. Reempazo de la ventana de salida El paso nal es el de reemplazar la ventana
de salida por defecto conocida por video-sink, por nuestra aplicacin 69 e
indicar la uri del vdeo a ejecutar 70
9. Comienzo de la visualizacin del vdeo Bastar poner el estado de nuestro
objeto playbin2 a play.

algunos
Como ha podido verse, a lo largo del cdigo anterior se han establecido
callbacks. El primero de ellos ha sido onBusMessage en la lnea 39 del listado
anterior. En el listado siguiente es una posible implementacin para dicho mtodo,
el cual se encarga de mostrar el mensaje y realizar la accin que considere oportuna
segn el mismo.

Listado 30.2: Mtodo onBusMessage para captura de mensajes


1 gboolean GStreamerPlayer::onBusMessage(
2 GstBus* bus, GstMessage* message, gpointer userData){
3
4 GstElement* player = getPlayer(userData);
5
6 switch (GST_MESSAGE_TYPE(message)){
7 case GST_MESSAGE_EOS:
8 std::cout << "End of stream" << std::endl;
9 gst_element_set_state(GST_ELEMENT(player), GST_STATE_NULL);
10 break;
11
12 case GST_MESSAGE_ERROR:
13 std::cout << "Error" << std::endl;
14 gst_element_set_state(GST_ELEMENT(player), GST_STATE_NULL);
15 break;
16
17 default:
18 break;
19 }
20 return true;
21 }


El otro callback es onNewBuffer en la lnea 49 del mismo listado. Este mtodo
ser invocado por GStreamer cada vez que haya un nuevo frame disponible. Una
posible implementacin puede verse en el listado 30.3 continuacin, donde se pone
un atributo booleano llamado mNewBufferExists a verdadero cada vez que llega un
nuevo frame.
C30
[1004] CAPTULO 30. SONIDO Y MULTIMEDIA

Listado 30.3: Mtodo onNewBuffer


1 GstFlowReturn GStreamerPlayer::onNewBuffer(GstAppSink *sink,
gpointer userData){
2
3 GStreamerPlayer* mediaPlayer = reinterpret_cast<GStreamerPlayer
*>(userData);
4 assert(mediaPlayer);
5 mediaPlayer->mNewBufferExists = true;
6 return GST_FLOW_OK;
7 }

Para hacer que Ogre actualice la textura dinmicamente, se debera construir un


Listener que herede de ExampleFrameListener e implemente los mtodos frameS-
tarted y frameRenderingQueued. El mtodo frameRenderingQueued es aquel desde
donde se actualizar la textura dinmicamente. Una posible implementacin puede ser
la siguiente.

Listado 30.4: Mtodo frameRenderingQueued y frameStarted del Listener


1 bool GStreamerPlayer::Listener::frameRenderingQueued(
2 const FrameEvent& evt){
3 mMediaPlayer->updateVideo();
4 return ExampleFrameListener::frameRenderingQueued(evt);
5 }
6
7 bool GStreamerPlayer::Listener::frameStarted(
8 const FrameEvent& evt){
9 return mMediaPlayer->mRunning &&
10 ExampleFrameListener::frameStarted(evt);
11 }

El mtodo frameRenderingQueued encargado de actualizar la textura, invoca al


mtodo updateVideo del objeto mMediaPlayer. Una implementacin es la siguiete.

Listado 30.5: Mtodo updateVideo


1 void GStreamerPlayer::updateVideo(){
2 if (mNewBufferExists){
3 GstBuffer* buffer;
4 g_signal_emit_by_name(mAppSink, "pull-buffer", &buffer);
5
6 GstCaps* caps = gst_buffer_get_caps(buffer);
7
8 int width = 0;
9 int height = 0;
10 int ratioNum;
11 int ratioDen;
12 float pixelRatio = 1.0;
13
14 for (size_t i = 0; i < gst_caps_get_size(caps); ++i){
15 GstStructure* structure = gst_caps_get_structure(caps, i);
16 gst_structure_get_int(structure, "width", &width);
17 gst_structure_get_int(structure, "height", &height);
18
19 if (gst_structure_get_fraction(structure,
20 "pixel-aspect-ratio",
21 &ratioNum, &ratioDen)){
22 pixelRatio = ratioNum / static_cast<float>(ratioDen);
23 }
24 }
25
26 if (width && height &&
27 (width != mVideoWidth || height != mVideoHeight)){
28 mVideoWidth = width;
29 mVideoHeight = height;
30.7. Comentarios nales sobre vdeo digital [1005]

30
31 TextureManager* mgr=Ogre::TextureManager::getSingletonPtr();
32
33 if (!mVideoTexture.isNull()){
34 mgr->remove(mVideoTexture->getName());
35 }
36
37 mVideoTexture = Ogre::TextureManager::getSingleton().
38 createManual(
39 "VideoTexture",
40 ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
41 TEX_TYPE_2D,
42 mVideoWidth, mVideoHeight,
43 0, PF_B8G8R8A8,
44 TU_DYNAMIC_WRITE_ONLY);
45
46 mVideoMaterial->getTechnique(0)->getPass(0)->
47 removeAllTextureUnitStates();
48 mVideoMaterial->getTechnique(0)->getPass(0)->
49 createTextureUnitState(mVideoTexture->getName());
50
51 float widthRatio =
52 mVideoWidth / static_cast<float>(mWindow->getWidth()) *
pixelRatio;
53 float heightRatio =
54 mVideoHeight / static_cast<float>(mWindow->getHeight());
55 float scale =
56 widthRatio > heightRatio ? widthRatio : heightRatio;
57
58 mVideoOverlay->setScale(widthRatio/scale, heightRatio/scale);
59 }
60
61 HardwarePixelBufferSharedPtr pixelBuffer =
62 mVideoTexture->getBuffer();
63 void* textureData = pixelBuffer->lock(
64 HardwareBuffer::HBL_DISCARD);
65 memcpy(textureData, GST_BUFFER_DATA(buffer),
66 GST_BUFFER_SIZE(buffer));
67 pixelBuffer->unlock();
68
69 gst_buffer_unref(buffer);
70 mNewBufferExists = false;
71 }
72 }

En el anterior listado puede verse cmo lo primero que se hace en la lnea


2 es comprobar si hay nuevo buffer, establecido a verdadero en el listado de

onNewBuffer.
En caso armativo, recoge el buffer desde la seal emitida por appsink

3-4 . Seguidamente, recoger informacin relativa a las dimensiones del vdeo 8-24

y (en nuestro caso) si el vdeo ha cambiado de tamao crea una nueva texturaborrando

la previamente existente 26-49 y calcular el factor para escalar la imagen 51-58 . A
continuacin bloquear el buffer de la textura, copiar los datos del nuevo frame y
desbloquear de nuevo 61-67 . Finalmente, liberar el de la memoria reservada
para
los datos del buffer 69 y pondr el atributo mNewBufferExists a falso 70 .

30.7. Comentarios nales sobre vdeo digital


En estas secciones del captulo se ha mostrado cmo es posible sacar provecho del
efecto sinrgico entre el mundo de los grcos 3D y el vdeo digital.
C30
[1006] CAPTULO 30. SONIDO Y MULTIMEDIA

Con ello, tras una introduccin a algunos conceptos fundamentales, se han


analizado diferentes estndares a n de ofrecer una panormica que ayude a tener
una mejor perspectiva a la hora de seleccionar el mejor formato, atendiendo a diversas
necesidades y requisitos.
Desde el punto de vista prctico, se ha abordado la integracin de vdeo digital
en Ogre mediante el uso de plugins ms concretamente el de Theora que permitan
incrustar vdeo en texturas Ogre. Adems, se ha mostrado cmo sacar partido del
empleo de frameworks de desarrollo de aplicaciones de vdeo digital, analizando con
cierta profundidad GStreamer.
Captulo

Interfaces de Usuario
31
Avanzadas
Francisco Jurado Monroy
Carlos Gonzlez Morcillo

L
as Interfaces Naturales de Usuario (del ingls Natural User Ingerface) son
aquellas que emplean los movimientos gestuales para permitir una interaccin
con el sistema sin necesidad de emplear dispositivos de entrada como el ratn,
el teclado o el joystick. As, el cuerpo, las manos o la voz del usuario se convierten en
el mando que le permite interactuar con el sistema.
Este es el paradigma de interaccin en el que se basan tecnologas como
las pantallas capacitivas tan empleadas en telefona movil y tabletas, sistemas de
reconocimiento de voz como el implantado en algunos vehculos para permitir
hablar al coche y no despistarnos de la conduccin, o dispositivos como Kinect de
Xbox que nos permite sumergirnos en mundos virtuales empleando nuestro cuerpo.
Este captulo se centrar en la construccin de interfaces naturales que empleen
movimientos gestuales mediante la aplicacin de tcnicas de Visin por Computador
Figura 31.1: Las interfaces natura-
o Visin Articial y el uso de dispositivos como el mando de la Wii de Nintendo
les ya no pertenecen slo al mundo
de la ciencia ccin
y el Kinect de XBox, abordando temas como la identicacin del movimiento, el
seguimiento de objetos o el reconocimiento facial, que puedan emplearse en el diseo
y desarrollo de videojuegos que sigan este paradigma de interaccin.

31.1. Introduccin a la Visin por Computador


Quiz, lo primero que puede sugerir el trmino Visin por Computador es que
se trata de aquellas tcnicas que permiten ver a travs del ordenador, es decir, ver
Figura 31.2: La visin de uno de
como lo hara un ordenador. Lo cierto es que la ciencia ccin se ha encargado de
los computadores ms famosos del darnos algunas ideas al respecto, y de algn modo no va desencaminada.
cine: El Terminator

1007
[1008] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Las tcnicas de Visin por Computador permiten extraer y analizar ciertas


propiedades del mundo real a partir de un conjunto de imgenes obtenidas por medio
de una cmara. A da de hoy, cuenta con algoritmos sucientemente probados y
maduros, permitindo construir aplicaciones que se aprovechen de todo su potencial.
Adems, el abaratamiento de las cmaras digitales, por otro lado cada vez ms
potentes, permite obtener unos resultados cada vez ms sorprendentes en este campo.
En el caso que nos ocupa, las tcnicas de Visin por Computador pueden ser
empleadas para el tratamiento de aquellas propiedades que permitan identicar los
movimientos del usuario, dotando as a la aplicacin de la capacidad de actuar en
consecuencia.

31.2. Introduccin a OpenCV


OpenCV es una librera de cdigo abierto multiplataforma (GNU/Linux, Windows
y Mac OS X) escrita en los lenguajes de programacin C/C++ y distribuida bajo
licencia BSD. Sus orgenes parten de una iniciativa de Intel Reseach con caracter
libre, para ser empleada con nes comerciales, educativos y de investigacin.
Fue diseada para ser eciente en aplicaciones de tiempo real y proveer de un
framework de Visin por Computador que fuera sencillo de usar, permitiendo as la
construccin rpida de aplicaciones de Visin por Computador que fueran potentes y
robustas. Cuenta adems con interfaces para otros lenguajes de programacin como
Python, Ruby, Java, Matlab, entre otros.
A pesar de todo, si se desea un mayor rendimiento y optimizacin, pueden
adquirirse las libreras Intels IPP (Integraed Performance Primitives), un conjunto
de rutinas optimizadas de bajo nivel para arquitecturas Intel.
Las libreras OpenCV estn compuestas por cinco modulos:

CV: Contiene las funciones principales de OpenCV, tales como procesamiento


de imgenes, anlisis de la estructura de la imagen, deteccin del movimiento y
rastreo de objetos, reconocimiento de patrones, etc. Figura 31.3: Logo de OpenCV
Machine Learning: Implementa funciones para agrupacin (clustering), clasi-
cacin y anlisis de datos.
CXCORE: Dene las estructuras de datos y funciones de soporte para algebra
lineal, persistencia de objetos, tranformacin de datos, manejo de errores, etc.
HighGUI: Empleada para construccin de interfaces de usuario sencillas y muy
ligeras.
CVAUX: Formada por un conjunto de funciones auxiliares/experimentales de
OpenCV.

31.2.1. Instalacin de OpenCV


La librera OpenCV est disponible a travs de la direccin Web http://sourceforge.
net/projects/opencvlibrary/ y pueden encontrarse documentacin y ejem-
plos en http://opencv.willowgarage.com/wiki/ y http://code.opencv.
org/projects/OpenCV/wiki/WikiStart.
Para realizar la instalacin de la libreria, podemos descargar el cdigo fuente
directamente del enlace anterior y proceder posteriormente a su compilacin para la
plataforma correspondiente, o bien podemos emplear los repositorios de paquetes del
sistema. As, en un sistema basado en Debian los comandos a ejecutar seran:
31.3. Conceptos previos [1009]

# apt-get install build-essential


# apt-get install libavformat-dev
# apt-get install ffmpeg
# apt-get install libcv2.1 libcvaux2.1 libhighgui2.1 opencv-doc \
libcv-dev libcvaux-dev libhighgui-dev python-opencv

Con esto tendremos los requisitos mnimos para poder desarrollar, compilar y
ejecutar nuestras aplicaciones con OpenCV.
La distribucin viene acompaada de una serie de programitas de ejemplo en
diferentes lenguajes de programacin. Estos pueden resultar de mucha utilidad en
una primera toma de contacto. Si se ha realizado la instalacin mediante el sistema
de paquetes, el paquete opencv-doc es el encargado de aadir dichos ejemplos. Para
poder copiarlos y compilarlos haremos:

# cp -r /usr/share/doc/opencv-doc/examples .
# cd examples
# cd c
# sh build_all.sh

31.3. Conceptos previos


Antes de adentrarnos en los conceptos del procesamiento de imagen que nos ofrece
la Visin por Computador con OpenCV, es preciso introducir algunos conceptos como
aclaraciones sobre las nomenclaturas de las funciones, las estructuras de datos, cmo
construir sencillas interfaces de usuario para testear nuestros desarrollos, etc.

31.3.1. Nomenclatura
Como nemotcnico general para la interfaz de programacin en C, puede apreciar-
se cmo las funciones de OpenCV comienzan con el prejo cv seguido de la operacin
y el objeto sobre el que se aplicar. Veamos algunos ejemplos:

cvCreateImage: funcin OpenCV para crear una imagen.


cvCreateMat: funcin OpenCV para crear una matriz.
cvCaptureFromAVI: funcin OpenCV para realizar una captura de un chero
AVI.
cvLoadImage: funcin OpenCV para cargar una imagen.
cvShowImage: funcin OpenCV para mostrar una imagen.
cvConvertImage: funcin OpenCV para convertir una imagen.
cvSetImageROI: funcin OpenCV para establecer una regin de inters (ROI)
sobre una imagen.
...
C31
[1010] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

31.3.2. Interfaces ligeras con HighGUI


El mdulo HighGUI, permite la construccin de interfaces de usuario multiplata-
forma, ligeras y de alto nivel. Estas suelen estr destinadas a probar los procesamientos
que se realicen con OpenCV.
HighGUI tiene funciones para permitir a los desarrolladores interactuar con el
sistema operativo, el sistema de archivos, la WebCam, construir ventanas en las que
mostrar imgenes y vdeos, leer y escribir cheros (imgenes y vdeo) desde y hacia
cheros, gestin sencilla del ratn y teclado, etc.

Ventanas

Las primitivas ms interesantes y comunes en una aplicacin que necesite gestin


de ventanas son:

Crear: cvNamedWindow(window, CV_WINDOW_AUTOSIZE)


Redimensionar: cvResizeWindow(window, width, heigh);
Posicionar: cvMoveWindow(window, offset_x, offset_y)
Cargar imagen: IplImage* img=cvLoadImage(leName)
Visualizar imagen: cvShowImage(window,img);
liberar imagen: cvReleaseImage( &img )
Destruir: cvDestroyWindow(window);

Como puede apreciarse, ninguna de las anteriores primitivas devuelve una referen-
cia a la ventana o recibe por parmetro una referencia. Esto es debido a que HighGUI
accede a las ventanas que gestiona mediante su nombre. De esta manera, cada vez que
se desee acceder a una ventana concreta, habr que indicar su nombre. Esto evita la
necesidad de emplear y gestionar estructuras adicionales por parte de los programa-
dores.

Recibir eventos del teclado

Para el teclado no existe manejador. Tan slo se dispone de una primitiva


cvWaitKey(interval). Si interval vale 0, la llamada es bloqueante, y se quedar
esperando indenidamente hasta que se pulse una tecla. Si interval tiene un valor
positivo, este representar el nmero de milisegundos que esperar a recibir un evento
del teclado antes de continuar con la ejecucin.

Capturando eventos del ratn

Como en la mayora de las libreras para la construccin de interfaces de usuario,


los eventos del ratn son manipulados a travs del correspondiente manejador. Un
ejemplo de manejador del ratn puede verse en el siguiente cdigo:

Listado 31.1: Manejador del ratn


1 void mouseHandler(int event, int x, int y, int flags, void* param){
2 switch(event){
31.3. Conceptos previos [1011]

3 case CV_EVENT_LBUTTONDOWN:
4 if(flags & CV_EVENT_FLAG_CTRLKEY)
5 printf("Pulsado boton izquierdo con CTRL presionada\n");
6 break;
7
8 case CV_EVENT_LBUTTONUP:
9 printf("Boton izquierdo liberado\n");
10 break;
11 }
12 }

Como puede apreciarse, el manejador recibe una serie de parmetros:

x, y: Las coordenadas de los pixels en los que se produce el evento


event: El tipo de evento.
CV_EVENT_LBUTTONDOWN, CV_EVENT_RBUTTONDOWN,
CV_EVENT_MBUTTONDOWN, CV_EVENT_LBUTTONUP,
CV_EVENT_RBUTTONUP, CV_EVENT_RBUTTONDBLCLK,
CV_EVENT_MBUTTONDBLCLK, CV_EVENT_MOUSEMOVE,
CV_EVENT_MBUTTONUP, CV_EVENT_LBUTTONDBLCLK,

ags: El ag que modica al evento.


CV_EVENT_FLAG_CTRLKEY, CV_EVENT_FLAG_SHIFTKEY,
CV_EVENT_FLAG_ALTKEY, CV_EVENT_FLAG_LBUTTON,
CV_EVENT_FLAG_RBUTTON, CV_EVENT_FLAG_MBUTTON

Para registrar el manejador y as poder recibir los eventos del ratn para una
ventana concreta, deber emplearse la funcin cvSetMouseCallback(window,mou-
seHandler,&mouseParam);.

Barra de progreso

Otro elemento interesante en las interfaces construidas con HighGUI son las barras
de progreso o trackbar. Estas suelen ser muy tiles para introducir valores enteros
al vuelo en nuestra aplicacin. Por ejemplo, para posicionar la visualizacin de un
vdeo en un punto determinado de la visualizacin o para establecer los valores de
determinados umbrales en los procesados (brillos, tonos, saturacin, matices, etc.).
El manejador del evento del trackbar tiene el siguiete aspecto:

Listado 31.2: Manejador del trackbar


1 void trackbarHandler(int pos)
2 {
3 printf("Trackbar en la posicion: %d\n",pos);
4 }

Para registrarlo bastar con invocar a la primitiva cvCreateTrackbar(bar1, win-


dow, &trackbarVal ,maxVal , trackbarHandler). Si se desea establecer u obtener la
posicin del trackbar durante un momento dado de la ejecucin, se emplearn las
funciones cvGetTrackbarPos(bar1,window) y cvSetTrackbarPos(bar1, win-
dow, value), respectivamente
C31
[1012] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

31.3.3. Estructura de la imagen


Las imgenes son la piedra angular en la visin por computador. OpenCV
proporciona la estructura IplImage heredada de la Intel Image Processing Library.
Se encuentra denida en el chero cxtypes.h y la estructura es la que se muestra en el
listado:

Listado 31.3: Estructura IplImage


1 typedef struct _IplImage
2 {
3 int nSize; /* sizeof(IplImage) */
4 int ID; /* version (=0)*/
5 int nChannels; /* Most of OpenCV functions support
6 1,2,3 or 4 channels */
7 int alphaChannel; /* Ignored by OpenCV */
8 int depth; /* Pixel depth in bits: IPL_DEPTH_8U,
9 IPL_DEPTH_8S, IPL_DEPTH_16S,
10 IPL_DEPTH_32S, IPL_DEPTH_32F and
11 IPL_DEPTH_64F are supported. */
12 char colorModel[4]; /* Ignored by OpenCV */
13 char channelSeq[4]; /* ditto */
14 int dataOrder; /* 0 - interleaved color channels,
15 1 - separate color channels.
16 cvCreateImage can only create
17 interleaved images */
18 int origin; /* 0 - top-left origin,
19 1 - bottom-left origin (Windows
20 bitmaps style).*/
21 int align; /* Alignment of image rows (4 or 8).
22 OpenCV ignores it and uses widthStep instead.*/
23 int width; /* Image width in pixels.*/
24 int height; /* Image height in pixels.*/
25 struct _IplROI *roi; /* Image ROI.
26 If NULL, the whole image is selected. */
27 struct _IplImage *maskROI; /* Must be NULL. */
28 void *imageId; /* " " */
29 struct _IplTileInfo *tileInfo; /* " " */
30 int imageSize; /* Image data size in bytes
31 (==image->height*image->widthStep
32 in case of interleaved data)*/
33 char *imageData; /* Pointer to aligned image data.*/
34 int widthStep; /* Size of aligned image row
35 in bytes.*/
36 int BorderMode[4]; /* Ignored by OpenCV.*/
37 int BorderConst[4]; /* Ditto.*/
38 char *imageDataOrigin; /* Pointer to very origin of image data
39 (not necessarily aligned) -
40 needed for correct deallocation */
41 }
42 IplImage;

As, para acceder a un pixel concreto para una imagen multicanal puede emplearse
el siguiente cdigo:

Listado 31.4: Acceso a los pixels de una imagen


1 IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);
2
3 int height = img->height;
4 int width = img->width;
5 int step = img->widthStep/sizeof(float);
6 int channels = img->nChannels;
7 float * data = (float *)img->imageData;
8
9 data[i*step+j*channels+k] = 111;
31.3. Conceptos previos [1013]

Del mismo modo, para acceder a todos los pixels de una imagen, bastar con
implementar un bucle anidado, tal y como se muestra en el siguiente cdigo de
ejemplo encargado de convertir una imagen a escala de grises:

Listado 31.5: Operacin directa sobre pixels. Convirtiendo a escala de grises.


1 #include "highgui.h"
2 #include "cv.h"
3 #include "stdio.h"
4
5 int main(int argc, char **argv) {
6 IplImage *img = cvLoadImage( argv[1], CV_LOAD_IMAGE_COLOR );
7
8 if (!img)
9 printf("Error while loading image file\n");
10
11 int width = img->width;
12 int height = img->height;
13 int nchannels = img->nChannels;
14 int step = img->widthStep;
15 uchar *data = ( uchar* )img->imageData;
16
17 int i, j, r, g, b, byte;
18 for( i = 0 ; i < height ; i++ ) {
19 for( j = 0 ; j < width ; j++ ) {
20 r = data[i*step + j*nchannels + 0];
21 g = data[i*step + j*nchannels + 1];
22 b = data[i*step + j*nchannels + 2];
23
24 byte = ( r + g + b ) / 3;
25
26 data[i*step + j*nchannels + 0] = byte;
27 data[i*step + j*nchannels + 1] = byte;
28 data[i*step + j*nchannels + 2] = byte;
29 }
30 }
31
32 cvNamedWindow( "window", CV_WINDOW_AUTOSIZE );
33 cvShowImage("window",img);
34
35 cvWaitKey(0);
36
37 cvReleaseImage(&img);
38 cvDestroyWindow("window");
39
40 return 0;
41 }

31.3.4. Almacenes de memoria y secuencias


En algunas de las operaciones que realizan los algoritmos de OpenCV, ser
necesario hacer la correspondiente reserva de memoria, de manera que nuestra
aplicacin sea capaz de construir objetos de manera dinmica. Adems, se introducir
en este apartado la secuencia como una estructura de datos en la que almacenar
nuestros objetos dinmicos o donde recibir la salida de determinados procesamientos
realizados por las funciones de OpenCV.
C31
[1014] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Almacenes de memoria: MemStorage

Existen funciones en OpenCV que requieren de una reserva de memoria para


almacenar clculos intermedios o de salida. Por esto, OpenCV dispone de una
estructura especca a la que denomina almacn de memoria MemStorage para
permitir la gestin de memoria dinmica.
Las funciones principales para gestin de almacenes de memoria son:

Reservar:
CvMemStorage* cvCreateMemStorage(int block_size = 0)
void* cvMemStorageAlloc(CvMemStorage* storage, size_t size)
Liberar: void cvReleaseMemStorage(CvMemStorage** storage)
Vaciar: void cvClearMemStorage(CvMemStorage* storage)

Con esto se dispone de todo lo necesario para poder trabajar con memoria
dinmica en OpenCV.

Secuencias: CVSeq

Un tipo de objeto que puede ser guardado en los almacenes de memoria son las
secuencias. Estas son listas enlazadas de otras estructuras, de manera que se pueden
crear secuencias de cualquier tipo de objeto. Por su implementacin, las secuencias
permiten incluso el acceso directo a cada uno de los elementos.
Algunas de las principales primitivas para su manipulacin son:

Crear: CvSeq* cvCreateSeq(int seq_ags,


int header_size, int elem_size, CvMemStorage* storage)
Eliminar: void cvClearSeq(CvSeq* seq)
Acceder: char* cvGetSeqElem(seq,index)
Comprobar: int cvSeqElemIdx(const CvSeq* seq,
const void* element, CvSeqBlock** block = NULL)
Clonar: CvSeq* cvCloneSeq(const CvSeq* seq,
CvMemStorage* storage)
Ordenar: void cvSeqSort(CvSeq* seq, CvCmpFunc func,
void* userdata = NULL)
Buscar: char* cvSeqSearch(CvSeq* seq,
const void* elem, CvCmpFunc func, int is_sorted,
int* elem_idx, void* userdata = NULL)

31.4. Primera toma de contacto con OpenCV: mostran-


do vdeo
A modo de una primera toma de contacto, se ver un pequeo ejemplo que
muestra en una ventana el contenido de un archivo de video pasado por parmetro
a la aplicacin, o bien la captura de la WebCam en su ausencia.
31.5. Introduccin a los ltros [1015]

Listado 31.6: Mostrando imgenes de una captura


1 #include "highgui.h"
2 #include "cv.h"
3
4 int main( int argc, char** argv ) {
5 CvCapture* capture;
6 if( argc != 2 || !(capture = cvCreateFileCapture(argv[1])) )
7 capture = cvCreateCameraCapture(0);
8
9 cvNamedWindow( "Window", CV_WINDOW_AUTOSIZE );
10
11 IplImage* frame;
12 while(1) {
13 frame = cvQueryFrame(capture);
14 if(!frame) break;
15
16 cvShowImage("Window", frame);
17
18 char c = cvWaitKey(33);
19 if(c==27) break;
20 }
21
22 cvReleaseCapture(&capture);
23 cvDestroyWindow("Window");
24
25 return 0;
26 }

Aclarado
esto, puede que el cdigo resulte bastante autodescriptivo. As, las lneas

1-2 muestran la inclusin de los cheros con las deniciones de las funciones tanto
para OpenCV como para Highgui. Lo primero que hace la aplicacin es una mnima
comprobacin de parmetros 6-7 . Si se ha introducido un parmetro, se entender
que es la ruta al chero a visualizar y se crear una captura para extraer la informacin
del mismo mediante la llamada a cvCreateFileCapture. En caso de que no se hayan
introducido parmetros o el parmetro no sea un chero de video, se realizar la

captura desde la WebCam empleando la llamada a cvCreateCameraCapture.
A continuacin, en la lnea 9 se crear una ventana llamada Window mediante
la llamada a cvNamedWindow. Como puede verse, esta funcin no devuelve ningn
puntero a la estructura que permita acceder a la ventana, sino que construye una
ventana con nombre. As, cada vez que deseemos acceder a una ventana concreta,
bastar con indicar su nombre, evitando emplear estructuras adicionales.

Los frames de los que consta el vdeo sern mostrados en la ventana mediante
el bucle de las lneas 12-20 . En l, se realiza la extraccin de los frames desde la
captura (chero de video o cmara) y se muestran en la ventana. El bucle nalizar

porque se haya
extraido el ltimo frame de la captura nea14 o se haya pulsado la tecla
de escape 18-19 por parte del usuario. Finalmente,se liberan
las estructuras mediante
las correspondientes llamadas del tipo cvRelease* 22-23 .
Como puede verse, en slo unas pocas lneas de cdigo se ha implementado un
reproductor de vdeo empleando OpenCV. En las siguientes secciones se mostrar
cmo sacarle algo de partido al potencial que nos ofrece la Visin por Computador.

31.5. Introduccin a los ltros


En la mayora de las ocasiones, al trabajar con visin por computador no nos
limitaremos a mostrar video por pantalla, sino que se querr realizar algn tipo de
procesamiento sobre las imgenes que estemos capturando.
C31
[1016] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

En esta seccin veremos cmo realizar unas pequeas modicaciones sobre el


ejemplo anterior, pasando una serie de ltros a cada uno de los frames recogidos de
un vdeo, a n de mostrar slo la silueta de las guras. El cdigo que lo implementa
es el del siguiente listado:

Listado 31.7: Identicando bordes y siluetas


1 #include "highgui.h"
2 #include "cv.h"
3
4 int main( int argc, char** argv ) {
5 CvCapture* capture;
6 if( argc != 2 || !(capture = cvCreateFileCapture(argv[1])) )
7 capture = cvCreateCameraCapture(0);
8
9 cvNamedWindow( "Window_1", CV_WINDOW_AUTOSIZE );
10 cvNamedWindow( "Window_2", CV_WINDOW_AUTOSIZE );
11
12 IplImage* frame;
13 while(1) {
14 frame = cvQueryFrame( capture );
15 if( !frame ) break;
16
17 IplImage *smooth, *edge, *out;
18 smooth = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 3);
19 edge = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 1);
20 out = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 1);
21
22 cvSmooth (frame, smooth, CV_GAUSSIAN, 11, 11, 0, 0);
23 cvCvtColor(smooth, edge, CV_BGR2GRAY);
24 cvCanny (edge, out, 10, 50, 3 );
25
26 cvShowImage( "Window_1", frame );
27 cvShowImage( "Window_2", out );
28
29 cvReleaseImage(&out);
30 cvReleaseImage(&edge);
31 cvReleaseImage(&smooth);
32
33 char c = cvWaitKey(33);
34 if( c == 27 ) break;
35 }
36
37 cvReleaseImage(&frame);
38 cvReleaseCapture( &capture );
39 cvDestroyWindow( "Window_1" );
40 cvDestroyWindow( "Window_2" );
41
42 return 0;
43 }


En las lneas 9-10 se han creado dos ventanas, de manera que se puedan apreciar
los frames de entrada y de salida del ejemplo. Para almacenar las transformaciones

intermedias, se crearn una serie de imgenes mediante la primitiva cvCreateImage
17-20 , cuyo tamao es siempre el tamao del frame original.
La primitiva cvCreateImage ser una de las que ms se empleen cuando desa-
rrollemos aplicaciones con OpenCV. Esta crea la cabecera de la imagen y reserva la
memoria para los datos de la misma. Para ello toma tres parmetros: el tamao (si-
ze) que a su vez se compone de alto y ancho, la profundidad (depth) y el nmero de

canales (channels).
El proceso de ltrado est codicado en las lneas 22-24 . Como puede apreciarse
son tres los ltros que se le aplican:
31.5. Introduccin a los ltros [1017]

1. Smooth o suavizado. El primer paso es el de aplicar a cada frame capturado una


operacin de suavizado Gaussiano 11x11. Este ltro producir una imagen ms
suave a la que se le ha eliminado el posible ruido y cambios bruscos en el
color.
2. Escala de grises. Esta transformacin permite pasar la imagen de RGB a escala
esta transformacin
de grises. De ah que la imagen creada para almacenar
intermedia tenga una profundidad de 1 en la lnea 19 . Este es en realidad un
paso intermedio requerido para generar la entrada al algoritmo de deteccin de
contornos.
3. Canny o deteccin de contornos. El cul permite aplicar el algoritmo Canny
para la identicacin de contornos en una imagen. Este algoritmo toma como
entrada una imagen de un solo canal (la imagen en escala de grises del paso
anterior), y genera otra imagen de un solo canal en la que se almacenan las
siluetas o contornos identicados.

En la gura 31.4 puede verse cul ser la salida nal de la ejecucin de nuestra
aplicacin.

Figura 31.4: Salida del ejemplo. A la izquierda la imagen antes del proceso de ltrado. A la derecha los
bordes detectados tras el proceso de ltrado.

Lo que se consigue aplicando el algoritmo Canny es identicar aquellos pixels


que conforman el borde de los objetos, es decir, aquellos que separan segmentos
diferentes de la imagen. Se trata de pixels aislados, sin una entidad propia. En realidad,
un contorno es una lista de puntos que representan una curva en una imagen.
En OpenCV un contorno se representa mediante secuencias de pixels, y la
estructura de datos que la implementa es CvSeq. A travs de esta estructura podr
recorrerse el contorno completo tan slo siguiendo la secuencia.

Listado 31.8: Uso de cvFindContours


1 IplImage* image = /*inicializada previamente*/;
2 int thresh = 100;
3
4 IplImage* gray = cvCreateImage( cvGetSize(image), 8, 1 );
5 CvMemStorage* storage = cvCreateMemStorage(0);
6
7 cvCvtColor(image, gray, CV_BGR2GRAY);
8 cvThreshold(gray, gray, thresh, 255, CV_THRESH_BINARY);
9 CvSeq* contours = 0;
10 cvFindContours(gray, storage, &contours, sizeof(CvContour),
C31
[1018] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

11 CV_RETR_TREE,
12 CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );
13 cvZero(gray);
14 if( contours )
15 cvDrawContours(gray, contours, cvScalarAll(255),
16 cvScalarAll(255), 100, 3, CV_AA, cvPoint(0,0) );
17 cvShowImage( "Contours", gray );

Un modo de ensamblar todos esos pixels aislados en contornos es mediante la


aplicacin de la funcin cvFindContours. Esta funcin calcula los contornos a partir
de imgenes binarias. As, toma como entrada imgenes creadas con cvCanny, cvTh-
reshold o cvAdaptiveThreshold y devuelve todos los contornos que ha identicado y
los cuales pueden ser accedidos mediante la correspondiente estructura CvSeq.

31.6. Deteccin de objetos mediante reconocimiento


A la hora de proporcionar una interaccin natural, uno de los elementos que
deben tenerse en cuenta es el de detectar que aquello con lo que interactua nuestro
computador es un rostro, de manera que pueda actuar en consecuencia.
OpenCV reconoce (o clasica) regiones que cumplan con determinadas caracte-
rsticas aprendidas, tales como rostros, cuerpos, etc.
Para esto se har uso del mdulo de aprendizaje automtico (Machine Learning)
que incluye OpenCV. As, el procedimiento habitual es el de entrenar un clasicador
con aquellos patrones que se deseen reconocer. Tras el entrenamiento y la validacin
del mismo, el clasicador estar listo para identicar los patrones para los que fue
entrenado.
En las siguientes subsecciones veremos cmo realizar el reconocimiento de
objetos mediante el uso de clasicadores, y cmo pueden ser empleados para
proporcionar interaccin con el usuario.

31.6.1. Trabajando con clasicadores


Tal y como se ha mencionado, el empleo de clasicadores implica un proceso de
aprendizaje de los mismos. En dicho proceso, se entrena al clasicador con una
serie de ejemplos (tanto buenos como malos) ya clasicados, y al que se le conoce
comnmente como conjunto de entrenamiento. Posteriormente se valida dicho
entrenamiento analizando y contrastando la salida con un conjunto de validacin
tambien clasicado de antemano. Contrastar en qu medida ha clasicado los casos
del conjunto de validacin, dar una idea de la precisin del clasicador.
Tanto el conjunto de entrenamiento como el de validacin, deben contar un
nmero importante de elementos clasicados a n de que el proceso de aprendizaje
proporcione una precisin razonable. Esto hace que el proceso de entrenamiento suela
ser costoso en tiempo, tanto en la construccin de los conjuntos para el aprendizaje
como en el cmputo a realizar por los diferentes algoritmos de clasicacin.
Afortunadamente, existen diferentes especicaciones para los clasicadores ya en-
trenados de uso ms comn para OpenCV, que pueden ser integrados directamente en
nuestras aplicaciones. Si se ha hecho la instalacin a travs del sistema de gestin de
paquetes de nuestro sistema, en aquellos basados en Debian podrn encontrarse en
/usr/share/doc/opencv-doc/examples/haarcascades/. Si se ha descargado directamen-
31.6. Deteccin de objetos mediante reconocimiento [1019]

te el chero tarball, una vez descomprimido, estos pueden encontrarse en el directorio


OpenCV-X.X.X/data/haarcascades. En este directorio se pueden encontrar las especi-
caciones de clasicadores para reconocimiento facial, reconocimiento de ojos, nariz,
boca, etc.
Analicemos un ejemplo donde se muestra el uso de un reconocedor facial para una
imagen.

Listado 31.9: Aplicacin de clasicadores para detectar objetos


1 #include <cv.h>
2 #include <highgui.h>
3
4 int main(int argc, char** argv ){
5 static CvScalar colors[] = { {{0,0,255}} , {{0,128,255}},
6 {{0,255,255}}, {{0,255,0}},
7 {{255,128,0}}, {{255,255,0}},
8 {{255,0,0}} , {{255,0,255}} };
9
10 IplImage* img = cvLoadImage("./media/The-Beatles.jpg", 1);
11 CvMemStorage* storage = cvCreateMemStorage(0);
12 CvHaarClassifierCascade* classifier = cvLoadHaarClassifierCascade
13 ("./media/haarcascade_frontalface_alt.xml", cvGetSize(img));
14
15 cvClearMemStorage(storage);
16 CvSeq* faces = cvHaarDetectObjects
17 (img, classifier, storage, 1.1, 4, 0, cvSize( 40, 50 ), cvSize(
40, 50 ));
18
19 CvRect* r; int i;
20 for( i = 0; i < (faces? faces->total : 0); i++ ){
21 r = (CvRect*)cvGetSeqElem(faces, i);
22 cvRectangle(img, cvPoint(r->x, r->y),
23 cvPoint(r->x + r->width, r->y + r->height),
24 colors[i %8], 3, 10, 0);
25 }
26
27 cvNamedWindow("Window", CV_WINDOW_AUTOSIZE );
28 cvShowImage("Window", img );
29 cvWaitKey(0);
30
31 cvReleaseImage(&img);
32 cvDestroyWindow("Window");
33
34 return 0;
35 }


En la lnea 5 se denen una serie de colores que sern

empleados para marcar
con un rectngulo los objetos detectados. En las lneas 10-12 , cargamos la imagen
desde un chero, reservamos memoria para llevar a cabo el proceso de clasicacin y

cargamos la especicacin del clasicador.
Seguidamente, en las lneas 15-17 se limpia la memoria donde se realizar el
proceso de deteccin, y se pone a trabajar al clasicador sobre la imagen mediante
la llamada a la funcin cvHaarDetectObjects. Finalizado el proceso de deteccin por
parte del clasicador, se obtiene una secuencia de objetos (en nuestro caso caras)
almacenada en faces de tipo CvSeq (estructura de datos para almacenar secuencias en
OpenCV).
Bastar con recorrer dicha secuencia e ir creando rectngulos de diferentes colores
(empleando cvRectangle) sobre cada una de las caras identicadas. Esto es lo que se
realiza en el bucle de las lneas 19-25 . Finalmente se muestra la salida en una ventana
espera
y a que el usuario pulse cualquier tecla antes de liberar la memoria reservada
27-32 .
C31
[1020] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

La salida de la ejecucin puede apreciarse en la gura 31.5, donde puede verse la


precisin del clasicador. Incluso la cara de la chica del fondo ha sido identicada.
La del hombre del fondo a la izquierda no ha sido reconocida como tal, porque
no corresponde con una cara completa y por tanto no cumple con los patrones
aprendidos.

31.6.2. Interaccin con el usuario


Una vez visto cmo realizar deteccin de objetos con OpenCV, a nadie se le
escapa que el hecho de identicar un cuerpo humano, un rostro (o cualquier otra parte
del cuerpo humano), etc., puede permitir a nuestras aplicaciones realizar acciones en
funcin de dnde se encuentren dichos objetos.
As, imagne que pudieramos ejecutar unas acciones u otras segn la posicin
del usuario ante la cmara, o que atendiendo al parpadeo de ste fuera posible
desencadenar la realizacin de unas acciones u otras en el computador. Introducir
los conceptos asociados a estas cuestiones es el objeto de esta subseccin.

Figura 31.5: Salida del ejemplo de reconocimiento facial con OpenCV.

Veamos un sencillo ejemplo en el que se realiza la deteccin del rostro en los


frames extraidos de un chero de video pasdo por parmetro a la aplicacin, o de la
WebCam en ausencia de este parmetro. En funcin de dnde se encuentre el rostro,
se mostrar un mensaje identicando en qu esquina se encuentra.
Como aclaracin previa para entender el ejemplo, cabe matizar que el proceso
de identicacin resulta costoso en cuanto a memoria y CPU. Para optimizar dicho
proceso, una tcnica a emplear es la de manipular imgenes en escala de grises y que
adems sean de menor tamao que la original. Con ello, el tiempo de cmputo se
reduce considerablemente, permitiendo una interaccin ms cmoda.
31.6. Deteccin de objetos mediante reconocimiento [1021]

Llegados a este punto, el lector entender gran parte del cdigo, de modo que
nos centraremos en aquellas partes que resultan nuevas. As, en el bucle que
recoge
los frames de la captura, el clasicador es cargado slo la primera vez 32-34 . A
para
continuacin,
cada frame capturado, se realizar una transformacin a escala
de grises 36-37 , se reducir
la imagen a otra ms pequea y nalmente
se ecualizar el histograma 43 . Este ltimo paso de ecualizar el histograma, sirve
39-42

para tratar de resaltarn detalles que pudieran haberse perdido en los procesos de
transformacin a escala de grises y reduccin de tamao. Tras nalizar el proceso
de identicacin de rostros invocando la primitiva cvHaarDetectObjects 45-47 , si no
se ha encontrado ninguno se muestra por pantalla el mensaje Buscando objeto...
empleando para ello la primitiva de cvPutText 39 , la cual recibe la imagen donde
propiamente dicho, la fuente que se emplear y que fue
se pondr el texto, el texto
inicializada en la lnea 25 , y el color con el que se pintar. Si se han identicado
rostros en la escena, mostrar el mensaje Objetivo encontrado! 53 . Si el rostro

est en el cuarto derecho o izquierdo de la pantalla,mostrar
el mensaje oportuno
59-64
, adems de identicarlo mediante un recuadro
66 .

Listado 31.10: Interaccin mediante reconocimiento


1 #include "highgui.h"
2 #include "cv.h"
3 #include "stdio.h"
4
5 int main( int argc, char** argv ) {
6 CvCapture* capture;
7 if( argc != 2 || !(capture = cvCreateFileCapture(argv[1])) )
8 capture = cvCreateCameraCapture(0);
9
10 static CvScalar colors[] = { {{0,0,255}}, {{0,128,255}},
11 {{0,255,255}}, {{0,255,0}},
12 {{255,128,0}}, {{255,255,0}},
13 {{255,0,0}}, {{255,0,255}} };
14
15 cvNamedWindow( "Window", CV_WINDOW_AUTOSIZE );
16
17 IplImage *frame, *gray, *small_img;
18 double scale = 2;
19
20 CvMemStorage* storage = cvCreateMemStorage(0);
21 cvClearMemStorage( storage );
22 CvHaarClassifierCascade* classifier = NULL;
23
24 CvFont font;
25 cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX,
26 0.75, 0.75, 0, 2, CV_AA);
27
28 while(1) {
29 frame = cvQueryFrame(capture);
30 if( !frame ) break;
31
32 if (classifier == NULL)
33 classifier = cvLoadHaarClassifierCascade
34 ("./media/haarcascade_frontalface_alt.xml", cvGetSize(frame));
35
36 gray = cvCreateImage( cvGetSize(frame), 8, 1 );
37 cvCvtColor( frame, gray, CV_BGR2GRAY );
38
39 small_img = cvCreateImage( cvSize( cvRound (gray->width/scale),
40 cvRound (gray->height/scale)),
41 8, 1 );
42 cvResize( gray, small_img, CV_INTER_LINEAR );
43 cvEqualizeHist( small_img, small_img );
44
45 CvSeq* faces = cvHaarDetectObjects
46 (small_img, classifier, storage, 1.1, 4, 0,
C31
[1022] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

47 cvSize( 40, 50 ), cvSize( 40, 50 ));


48
49 if (!faces->total)
50 cvPutText(frame, "Buscando objetivo...",
51 cvPoint(10, 50), &font, cvScalar(255, 255, 255, 0));
52 else
53 cvPutText(frame, "Objetivo encontrado!",
54 cvPoint(10, 50), &font, cvScalar(0, 0, 255, 0));
55
56 int i; CvRect* r;
57 for( i = 0; i < (faces ? faces->total : 0 ); i++ ){
58 r = (CvRect*)cvGetSeqElem(faces, i);
59 if (r->x < (small_img->width*0.25))
60 cvPutText(frame, "En la izquierda!",
61 cvPoint(10, 100), &font, cvScalar(0, 255, 255, 0));
62 if ((r->x+r->width) > (small_img->width*0.75))
63 cvPutText(frame, "En la derecha!",
64 cvPoint(10, 100), &font, cvScalar(0, 255, 255, 0));
65
66 cvRectangle(frame, cvPoint( r->x * scale, r->y * scale ),
67 cvPoint((r->x + r->width) * scale,
68 (r->y + r->height) * scale),
69 colors[i %8],3,10,0);
70 }
71
72 cvShowImage( "Window", frame );
73 cvReleaseImage( &gray );
74 cvReleaseImage( &small_img );
75
76 char c = cvWaitKey(5);
77 if(c == 27) break;
78 }
79
80 cvReleaseCapture(&capture);
81 cvDestroyWindow("Window");
82
83 return 0;
84 }

En la gura 31.6 puede verse la salida de la aplicacin cuando identica un rostro


a la izquierda o a la derecha.

Figura 31.6: La aplicacin muestra cundo ha identicado un rostro y si este se encuentra a la izquierda o
a la derecha.
31.7. Interaccin mediante deteccin del color de los objetos [1023]

31.7. Interaccin mediante deteccin del color de los


objetos
La deteccin de objetos es un proceso costoso en tiempo y cmputos. Adems,
si es necesaria una interaccin mediante objetos de los cuales no disponemos de un
clasicador ni de ejemplos sucientes con los que realizar su entrenamiento para la
posterior fase de reconocimiento, existen otras alternativas. Una de ellas es identicar
objetos que tengan un determinado color nico en la escena.
Para ello, debern realizarse diferentes transformaciones sobre la imagen, de modo
que se ltre todo aquello que tenga un color diferente al del objeto que se desea
detectar.
A modo de ejemplo, se construir una sencilla aplicacin en la que se ir pintando
el trazo por el lugar por donde va pasando un objeto rastreado. El cdigo es el que se
lista a continuacin:

Listado 31.11: Deteccin de objeto con determinado color


1 #include <cv.h>
2 #include <highgui.h>
3
4 int main(){
5 CvCapture *capture = cvCaptureFromCAM(0);
6
7 if(!capture) return -1;
8
9 cvNamedWindow("Window", CV_WINDOW_FULLSCREEN);
10
11 IplImage* frame = NULL;
12 IplImage* imgScribble = NULL;
13 while(1){
14 frame = cvQueryFrame(capture);
15 if(!frame) break;
16
17 if(imgScribble == NULL)
18 imgScribble = cvCreateImage(cvGetSize(frame), 8, 3);
19
20 cvSmooth(frame, frame, CV_GAUSSIAN, 11, 11, 0, 0);
21
22 IplImage* imgHSV = cvCreateImage(cvGetSize(frame), 8, 3);
23 cvCvtColor(frame, imgHSV, CV_BGR2HSV);
24
25 IplImage* imgThreshed = cvCreateImage(cvGetSize(frame), 8, 1);
26 cvInRangeS(imgHSV, cvScalar(25, 100, 100, 0),
27 cvScalar(40, 255, 255, 0), imgThreshed);
28
29 CvMoments *moments = (CvMoments*)malloc(sizeof(CvMoments));
30 cvMoments(imgThreshed, moments, 0);
31
32 double moment10 = cvGetSpatialMoment(moments, 1, 0);
33 double moment01 = cvGetSpatialMoment(moments, 0, 1);
34 double area = cvGetCentralMoment(moments, 0, 0);
35
36 static int posX = 0; static int posY = 0;
37 int lastX = posX; int lastY = posY;
38 posX = moment10/area; posY = moment01/area;
39
40 if(lastX>0 && lastY>0 && posX>0 && posY>0)
41 cvLine(imgScribble,
42 cvPoint(posX, posY), cvPoint(lastX, lastY),
43 cvScalar(255, 255, 0, 0), 5, 1, 0);
44
45 cvAdd(frame, imgScribble, frame, 0);
46 cvShowImage("Window", frame);
47
C31
[1024] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

48 char c = cvWaitKey(5);
49 if(c==27) break;
50
51 cvReleaseImage(&imgHSV);
52 cvReleaseImage(&imgThreshed);
53 free(moments);
54 }
55
56 cvReleaseCapture(&capture);
57 cvDestroyWindow("Window");
58
59 return 0;
60 }

En l puede verse cmo, una vez seleccionado el color del objeto a detectar, el
procedimiento a llevar a cabo es el siguiente (la salida de su ejecucin aparece en la
gura 31.9):


1. Realizar un proceso de suavizado para eliminar el posible ruido en el color
21 .
2. Convertir la imagen RGB del frame capturado a una imagen HSV (Hue,

Saturation, Value) (Matiz, Saturacin, Valor) de modo que sea ms fcil
identicar el color a rastrear 23-24 . La salida de este ltro es la que se muestra
en la parte izquierda de la gura 31.8.
3. Eliminar todos aquellos pixels cuyo matiz no corresponda con el del objeto a
detectar. Esto devolver una imagen en la que sern puestos a 0 todos los pixels

que no estn dentro de nuestro criterio. En denitiva, la imagen slo contendr
aquellos pixels cuyo color deseamos detectar 25-27 en color blanco y el resto
todo en negro. La salida de este proceso es la que se muestra en la parte derecha
de la gura 31.8.
4. Identicar el momento
de los objetos en movimiento, es decir, su posicin en
coordenadas 29-34 .

5. Pintar una linea desde el anterior momento hasta el momento actual 29-43 Figura 31.7: Tringulo HSV donde
en una capa (en el cdigo es una imagen denominada imgScribble). se representa el matiz (en la ruleta),
6. Unir la capa que contiene
el trazo, con aquellaque contiene la imagen capturada
la saturacin y el valor (en el trin-

desde la cmara 45 y mostrarlo por pantalla 46 .


gulo)

Figura 31.8: A la izquierda, se muestra la imagen HSV del Tux capturado con la cmara. A la derecha slo
aquello que coincida con el valor del color de las patas y el pico (amarillo en nuestro caso)
31.8. Identicacin del movimiento [1025]

Figura 31.9: Salida de la ejecucin del ejemplo de deteccin de objeto con determinado color. Puede verse
cmo se superpone a la imagen el trazo realizado por el rastreo de un pequeo objeto amarillo uorescente.

31.8. Identicacin del movimiento


Para explicar la identicacin del movimiento, tomaremos uno de los ejemplos
que se proporcionan junto a la librera OpenCV. Se trata del ejemplo motempl, del
cual se dispone de implementacin en C y Python. En l se analiza tanto el proceso de
identicacin del movimiento, como el de la deteccin de la direccin y sentido del
mismo.

Listado 31.12: Bucle principal del ejemplo motempl.c


1 for(;;)
2 {
3 IplImage* image = cvQueryFrame( capture );
4 if( !image )
5 break;
6
7 if( !motion )
8 {
9 motion = cvCreateImage( cvSize(image->width,image->height), 8,
3 );
10 cvZero( motion );
11 motion->origin = image->origin;
12 }
13
14 update_mhi( image, motion, 30 );
15 cvShowImage( "Motion", motion );
16
17 if( cvWaitKey(10) >= 0 )
C31
[1026] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

18 break;
19 }

Para realizar el anlisis de dicho cdigo debemos comenzar por el bucle principal.
En l puede verse cmo se captura la imagen, y si an no existe movimiento

detectado, se crea una nueva imagen cuyo origen se har coincidir con el del primer
frame capturado 7-12 . A continuacin, se actualiza el historial de movimiento
(MHI (Motion History
Image)) en la funcin update_mhi y se muestra la imagen
por pantalla 15 . La funcin update_mhi recibe tres parmetros: la imagen actual
procedente de la captura, la imagen resultante del movimiento y un umbral que
establece a partir de cundo la diferencia entre dos frames se considerar movimiento.

Listado 31.13: Identicacin de silueta en motempl.c:


1 void update_mhi(IplImage* img, IplImage* dst, int diff_threshold){
2 ...
3 // allocate images at the beginning or
4 // reallocate them if the frame size is changed
5 ...
6 cvCvtColor( img, buf[last], CV_BGR2GRAY ); // convert frame to
grayscale
7
8 idx2 = (last + 1) % N; // index of (last - (N-1))th frame
9 last = idx2;
10
11 silh = buf[idx2];
12 cvAbsDiff( buf[idx1], buf[idx2], silh ); // get difference
between frames
13
14 cvThreshold( silh, silh, diff_threshold, 1, CV_THRESH_BINARY );
// and threshold
it
15 cvUpdateMotionHistory( silh, mhi, timestamp, MHI_DURATION ); //
update MHI
16
17 // convert MHI to blue 8u image
18 cvCvtScale( mhi, mask, 255./MHI_DURATION,
19 (MHI_DURATION - timestamp)*255./MHI_DURATION );
20 cvZero( dst );
21 cvMerge( mask, 0, 0, 0, dst );
22 ...
23 }

En el cdigo de la funcin update_mhi puede verse cmo se inicializan algunas



variables y se reserva la memoria para las imgenes. Tras esto lo interesante viene a
continuacin. En la lnea 6 puede verse cmo la imagen de entrada es transformada a
escala de grises y almacenada al nal de una lista circular (en este caso de slo cuatro
elementos dado que N est declarado como const int N = 4). Tras actualizar los ndices
de la lista circular, se obtiene la diferencia entre los frames 12 mediante la llamada a
la primitiva cvAbsDiff.
sta proporciona una imagen donde la salida es la diferencia de los pixels entre una
imagen y la siguiente. Aquellos pixels con diferencia distinta de cero, son los que han
cambiado y por tanto los que se han movido. Bastar con aplicar un ltrado threshold
binario 14 al resultado de la operacin anterior, de manera que todos aquellos pixels
cuya diferencia sea mayor que un determinado umbral tomarn el valor 1 y en caso
contrario el valor 0. En este punto se dispone de la silueta de aquella parte de la
imagen que se ha movido (aqu la imagen llamada silh).
El siguiente paso es el de actualizar la imagen del movimiento con la silueta
obtenida. Para ello se emplea la primitiva cvUpdateMotionHistory que recibe como
parmetros:
31.8. Identicacin del movimiento [1027]

Mscara de la silueta que contiene los pixels que no estn puestos a 0 donde
ocurri el movimiento.
Historia de imgenes del movimiento, que se actualiza por la funcin y debe ser
de un nico canal y 32 bits.
Hora actual.
Duracin mxima para el rastreo del movimiento.

Una vez obtenida la silueta en movimiento


(el MHI), se crea una imagen donde se
mostrar la misma en color azul 17-21 .
El siguiente reto del cdigo incluido en motempl.c es el de determinar cul es la
direccin y sentido del movimiento realizado. El fragmento de cdigo encargado de
esta funcionalidad puede verse en el siguiente listado.

Listado 31.14: Calculando direccin del movimiento en motempl.c


1 // calculate motion gradient orientation and valid orientation
mask
2 cvCalcMotionGradient( mhi, mask, orient, MAX_TIME_DELTA,
MIN_TIME_DELTA, 3 );
3
4 if( !storage )
5 storage = cvCreateMemStorage(0);
6 else
7 cvClearMemStorage(storage);
8
9 // segment motion: get sequence of motion components
10 // segmask is marked motion components map. It is not used
further
11 seq = cvSegmentMotion( mhi, segmask, storage, timestamp,
MAX_TIME_DELTA );
12
13 // iterate through the motion components,
14 // One more iteration (i == -1) corresponds to the whole image
(global motion)
15 for( i = -1; i < seq->total; i++ ) {
16
17 if( i < 0 ) { // case of the whole image
18 comp_rect = cvRect( 0, 0, size.width, size.height );
19 color = CV_RGB(255,255,255);
20 magnitude = 100;
21 }
22 else { // i-th motion component
23 comp_rect = ((CvConnectedComp*)cvGetSeqElem( seq, i ))->rect;
24 if( comp_rect.width + comp_rect.height < 100 ) // reject very
small components
25 continue;
26 color = CV_RGB(255,0,0);
27 magnitude = 30;
28 }
29
30 // select component ROI
31 cvSetImageROI( silh, comp_rect );
32 cvSetImageROI( mhi, comp_rect );
33 cvSetImageROI( orient, comp_rect );
34 cvSetImageROI( mask, comp_rect );
35
36 // calculate orientation
37 angle = cvCalcGlobalOrientation( orient, mask, mhi, timestamp,
MHI_DURATION);
38 angle = 360.0 - angle; // adjust for images with top-left
origin
39
40 count = cvNorm( silh, 0, CV_L1, 0 ); // calculate number of
points within silhouette ROI
C31
[1028] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

41
42 cvResetImageROI( mhi );
43 cvResetImageROI( orient );
44 cvResetImageROI( mask );
45 cvResetImageROI( silh );
46
47 // check for the case of little motion
48 if( count < comp_rect.width*comp_rect.height * 0.05 )
49 continue;
50
51 // draw a clock with arrow indicating the direction
52 center = cvPoint( (comp_rect.x + comp_rect.width/2),
53 (comp_rect.y + comp_rect.height/2) );
54
55 cvCircle( dst, center, cvRound(magnitude*1.2), color, 3, CV_AA,
0 );
56 cvLine( dst, center, cvPoint( cvRound( center.x + magnitude*cos
(angle*CV_PI/180)),
57 cvRound( center.y - magnitude*sin(angle*CV_PI
/180))), color, 3, CV_AA, 0 );
58 }

En l puede apreciarse cmo para obtener la direccin comienza calculando el


gradiente de la orientacin del MHI con cvCalcMotionOrientation 2 . A continua-
los diferentes
cin trocea todo el movimiento en partes separadas, a n de identicar
componentes en movimientos con la funcin cvSegmentMotion 11 . As, iterando so-
bre cada uno de los componentes del movimiento identicados, puede calcularse la
del movimiento de cada componente con la funcin cvCalcGloba-
direccin concreta
lOrientation 37 empleando la mscara extrada para cada componente concreto. Para

pasar a la funcin slo aquellas partes de la imagen que intervienen en cada compo-
nente, en el cdigo se extraen las regiones de inters (ROI) 30-34 .

Figura 31.10: Deteccin del movimiento capturado. En azul aquellos pixels donde se ha localizado
movimiento. En negro el fondo esttico.
31.9. Comentarios nales sobre Visin por Computador [1029]

Finalmente marcar con un reloj la direccin de cada componente del movimiento.



Para ello construye un crculo en torno a cada componente y dibuja una lnea en
su interior para indicar el ngulo del movimiento 51-57 . En la gura 31.10 puede
encontrarse una salida con la imagen en azul del MHI y el resto de elementos estticos
en negro.

31.9. Comentarios nales sobre Visin por Computador


A lo largo de las secciones anteriores se han introducido algunos de los conceptos
bsicos de las tcnicas de visin por computador y de cmo estas pueden ayudarnos
a construir interfaces naturales que nos permitan interactuar con nuestras aplicaciones
y juegos.
Para ello se ha abordado la librera OpenCV, centrndonos desde el comienzo en
el procesamiento de vdeo y en la captura del mismo procedente de una WebCam. As,
tras hacer una introduccin al ltrado y procesamiento de los frames que componen
el vdeo, se han mostrado algunos conceptos bsicos sobre interaccin a partir de
identicacin de objetos como rostros, ojos, etc., reconocimiento de objetos con un
determinado color y deteccin del movimiento. Si desea profundizar ms en algunos
temas relacionados con la visin por computador, animamos a la lectura de [15].
Con esto esperamos que el alumno haya adquirido una panormica general sobre
las capacidades que la visin por computador puede ofrecer al paradigma de la
interaccin natural aplicada al mundo de los videojuegos.

31.10. Caso de estudio: Wiimote


En el ao 2006, Nintendo lanz su consola Wii con un dispositivo hardware que
permita realizar interaccin natural con el usuario: el Wiimote o mando a distancia
de Wii.
El Wiimote realiza la deteccin del movimiento del usuario y transere esta
informacin al computador o la consola. La correcta interpretacin de los datos que
se reciben desde este dispositivo, permitir que las aplicaciones puedan implementar
interfaces de usuario naturales, en las que un elemento externo al juego o a la
aplicacin, pueda interaccionar con el mundo virtual.
Entender el funcinamiento interno del mando y conocer el funcionamiento de
libreras que nos permitan manipular el mando ser el objetivo de las siguientes
secciones.

31.11. Descripcin del mando de Wii


Aunque a simple vista el Wiimote tenga un aspecto parecido al del mando a
distancia de la televisin, ste cuenta con multitud de elementos sensores, actuadores
y de comunicacin que permiten detectar e identicar los movimientos gestuales de
los usuarios. Enumerndolos, los elementos de los que consta el WiiMote son:

1. Elementos sensores
Tres acelermetros para medir la fuerza ejercida en cada uno de los ejes.
C31
[1030] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Una cmara infrarroja (IR) de cuatro puntos sencibles situada en el frontal


superior para determinar el lugar al que el Wiimote est apuntando.
Siete botones pulsables distribuidos a lo largo del mando (A, 1, 2,
+, -, HOME y POWER).
Cuatro botones de direccin en cruz (arriba, abajo, izquierda, derecha)
situados en la zona superior.
2. Elementos actuadores
Cuatro luces LED (Light Emitter Diode) localizados en la zona inferior.
Un pequeo altavoz para emitir sonidos al usuario en base a respuestas de
la aplicacin, y que se encuentra en la zona central del mando.
Un motor de vibracion para enviar zumbidos al usuario en funcin de la
interaccin de este.
3. Interfaces de comunicacin
Un puerto de expansion para permitir conectar otros dispositivos (como el
Nunchuk) en el frontal inferior.
Una interfaz de conectividad Bluetooth, gracias a la cul es posible
emparejarlo con cualquier computador e implementar aplicaciones que
interaccionen con l.

Figura 31.11: Una fotograra de un WiiMote (a la derecha) y el Nunchunk (a la izquierda).

Adicionalmente, el Wiimote viene acompaado de una barra de emisin de luz


infrarroja (IR). Esta barra se coloca en la parte superior o inferior de la pantalla con la
que se desee interactuar. Tiene 20 cm de longitud y cuenta con diez LED infrarrojos
divididos en dos grupos, cinco en cada extremo de la barra. En cada grupo de cinco, los
que se encuentran ms al exterior estn apuntando ligeramente hacia afuera, mientras
que los ms interiores apuntan ligeramente al centro. Los cuatro restantes apuntan
hacia adelante. Esto permite conformar dos fuentes de luz IR bien diferenciadas.
Como se ha mencionado, el Wiimote cuenta con un sensor de IR localizado en el
frontal superior del mando. As, este sensor es capaz de localizar las dos fuentes de
luz IR procedentes de los extremos de la barra y saber a qu posicin concreta de la
pantalla est apuntando el usuario hasta una distancia de unos cinco metros respecto
de la barra.
31.12. Libreras para la manipulacin del Wiimote [1031]

A travs del puerto de expansin situado en la parte inferior del mando, pueden
conectarse una serie de dispositivos adicionales para permitir incrementar las posi-
bilidades de interaccin que proporciona. Entre estos dispositivos caben destacar el
Nunchunk, el cul consiste en un joystick analgico con dos botones ms (comn-
mente utilizado para controlar el movimiento de los personajes), la Wii Wheel, que
transforma el mando en un volante para aquellos juegos de simulacin de vehculos,
o el Wii Zapper, que convierte al Wiimote+Nunchuk en una especie de pistola.
El objetivo de todos estos elementos es siempre el mismo: proporcionar una in-
teraccin lo ms cmoda y natural posible al usuario a partir de sensores y actuadores
que obtengan informacin acerca del movimiento que ste est realizando.
Interpretar esta informacin de forma correcta y crear los manejadores de eventos
apropiados para programar la aplicacin en funcin de las necesidades de interaccin,
corre a cargo de los programadores de dichas apliciaciones. Para podeer capturar
desde nuestras aplicaciones la informacin procedente de un dispositivo Wiimote, este
dispone del puerto Bluetooth. As, podrn programarse sobre la pila del protocolo
Bluetooth las correspondientes libreras para comunicarse con el mando. En el
siguiente apartado se abordar cmo trabajar con ste tipo de libreras.

31.12. Libreras para la manipulacin del Wiimote


Lo cierto es que dependiendo del sistema operativo en el que nos encontremos
deberemos optar por unas libreras u otras. Sin embargo, existen similitudes muy
estrechas entre todas ellas, por lo que no resulta nada complicado migrar de una a
otra.
Para los siguientes ejemplos se har uso de la librera libwiimote disponible bajo
licencia GPL en http://libwiimote.sourceforge.net/. Esta se encuentra
escrita en C y proporciona un API para sistemas GNU/Linux.
Libwiimote aporta mecanismos para leer los datos de los acelermetros, del sensor
de IR de los estados de pulsacin de los botones, del estado de la batera y del nunchuk
(su botn, acelermetros y estados del joystick). Asimismo, permite enviar seales
o eventos hacia el mando para activar y desactivar los actuadores, de modo que se
puedan establecer los LEDs a encendido/apagado, lanzar zumbidos o reproducir
sonidos en el pequeo altavoz.
En las siguientes subsecciones se mostrar como manipular dicha librera.

Estructura wiimote_t

Libwiimote dene una estructura directamente accesible desde nuestra aplicacin


en la que se almacena el estado de los distintos elementos del mando. En nuestras
aplicaciones necesitaremos una estructura de este tipo por cada uno de los Wiimotes
que se encuentren conectados.
Esta estructura es la wiimote_t que se encuentra denida en el chero wiimote.h.
Todos los elementos del mando se encuentran directamente accesibles a travs de los
campos de esta estructura. Su denicin es la que se muestra en el siguiente listado:

Listado 31.15: Estructura wiimote_t


1 typedef struct {
2 wiimote_mode_t mode; /* Current report mode of wiimote. */
3 wiimote_keys_t keys; /* Current key state. */
4 wiimote_point3_t axis; /* Current accelerometer data. */
C31
[1032] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

5 wiimote_ir_t ir1; /* First detected IR source. */


6 wiimote_ir_t ir2; /* Second detected IR source. */
7 wiimote_ir_t ir3; /* Third detected IR source. */
8 wiimote_ir_t ir4; /* Fourth detected IR source. */
9
10 wiimote_cal_t cal; /* Wiimote calibration data */
11 wiimote_ext_port_t ext; /* Current extension port state. */
12
13 wiimote_link_t link; /* Current link state. */
14 wiimote_led_t led; /* Current state of the leds. */
15 uint8_t rumble; /* Current state of rumble. */
16 uint8_t speaker; /* ... */
17 uint8_t battery; /* Current battery status. */
18
19 wiimote_float3_t tilt; /* The tilt of the wiimote in degrees*/
20 wiimote_float3_t force; /* The force in g on each axis. */
21
22 struct {
23 wiimote_mode_t mode;
24 wiimote_keys_t keys;
25 wiimote_led_t led;
26 uint8_t rumble;
27 } old;
28 } __attribute__((packed)) wiimote_t;

Puede verse cmo quedan representados los botones, los acelermetros, ngulos
de inclinacin, fuerzas de gravedad, el altavoz, la batera, etc.

Bucle principal de captura de datos y comunicacin con el mando

Toda aplicacin que manipule el Wiimote deber contar con un bucle de captu-
ra/envo de datos desde y hacia el mando. Este tiene el aspecto que se muestra en el
listado analizado a continuacin.

En l cdigo se incluye la cabecera de la librera y en un momento dado se

inicializa la estructura wiimote_t 5 y se conecta con el mando mediante la primitiva
wiimote_connect 6 , a la que se le pasa una referencia a la anterior estructura y la
direccin MAC del mando. Si desconocemos la direccin del mando, en GNU/Linux
podemos emplear el comando hcitool mientras pulsamos simultaneamente los botones
1 y 2 del mando.

# hcitool scan

En caso de error, mediante la primitiva wiimote_get_error, podr obtenerse


Si todo ha ido bien, mientras la conexin siga abierta
informacin de la causa.
(wiimote_is_open) 13 , la aplicacin permanecer dentro un bucle en el que se
de
resincronizar con el mando mediante wiimote_update 14 , capturando informacin
del mando y provocando la activacin de los actuadores si procede. Como se
el usuario puede desconectarse del mando mediante la primitiva
muestra en el cdigo,
wiimote_disconect 16 , que en el caso del ejemplo se ejecuta cuando se pulsa el botn
home.

Listado 31.16: Bucle principal de captura/envo de datos


1 #include "wiimote_api.h"
2 ...
3
4 wiimote_t wiimote = WIIMOTE_INIT;
5 if (wiimote_connect(&wiimote, "XX:XX:XX:XX:XX:XX")<0){
6 fprintf(stderr, "Error al conectar con el mando: %s\n",
wiimote_get_error());
31.12. Libreras para la manipulacin del Wiimote [1033]

7 exit(1);
8 }
9
10 //Establecer el modo de captura
11 while (wiimote_is_open(&wiimote)) {
12 wiimote_update(&wiimote);// sincronizar con el mando
13 if (wiimote.keys.home) { // alguna comprobacin para salir
14 wiimote_disconnect(&wiimote);
15 }
16
17 //Realizar captura de datos segn el modo
18 //Realizar envo de datos
19 }

Captura de pulsacin de botones

Para detectar si el usuario est pulsando alguno de los botones del mando o del
Nunchuk (incluido el joystick), podemos hacerlo comprobando directamente sus va-
lores a travs de los campos que se encuentran en wiimote.keys y wiimote.ext.nunchuk
de la variable de tipo wiimote_t, tal y como se muestra en el ejemplo.

Listado 31.17: Deteccin de botores y joystick


1 #include <stdio.h>
2 #include <stdlib.h>
3 #include "wiimote_api.h"
4
5 int main(int argc, char **argv) {
6 if (argc!=2){
7 printf("Introduzca la mac del mando");
8 exit(0);
9 }else{
10 printf("Pulse 1+2 para sincronizar");
11 }
12
13 wiimote_t wiimote = WIIMOTE_INIT;
14 wiimote_connect(&wiimote, argv[1]);
15 while (wiimote_is_open(&wiimote)) {
16 wiimote_update(&wiimote);
17 if (wiimote.keys.home) {
18 wiimote_disconnect(&wiimote);
19 }
20
21 printf("WIIMOTE: KEYS %04x 1= %d 2= %d A= %d B= %d <= %d >= %d
^= %d v= %d += %d -= %d home= %d ",
22 wiimote.keys.bits,
23 wiimote.keys.one, wiimote.keys.two,
24 wiimote.keys.a, wiimote.keys.b,
25 wiimote.keys.left, wiimote.keys.right,
26 wiimote.keys.up, wiimote.keys.down,
27 wiimote.keys.plus, wiimote.keys.minus,
28 wiimote.keys.home);
29
30 printf("NUNCHUK: JOY1 joyx= %03d joyy= %03d keys.z= %d keys.c
= %d\n",
31 wiimote.ext.nunchuk.joyx,
32 wiimote.ext.nunchuk.joyy,
33 wiimote.ext.nunchuk.keys.z,
34 wiimote.ext.nunchuk.keys.c);
35 }
36
37 return 0;
38 }
C31
[1034] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Captura de datos de los acelermetros

Para cada uno de los ejes, el Wiimote proporciona valores recogidos por un
acelermetro. De este modo, si ponemos el Wiimote en horizontal sobre una mesa
los valores recogidos por los acelermetros tendern a cero.
Es preciso tener en cuenta que los acelermetros, como su nombre indica, miden
la aceleracin, es decir, la variacin de la velocidad con respecto del tiempo. No
miden la velocidad ni la posicin. Valores altos de uno de los acelermetros indicar
que el mando est variando muy deprisa su velocidad en el eje correspondiente. Que
el acelermetro proporcione un valor cero, indicar que la velocidad permaneciera
constante, no que el mando est detenido.
Adems de la aceleracin, resulta interesante medir los ngulos de inclinacin del
mando, y en consecuencia, la direccin en la que se ha realizado un movimiento. En
el Wiimote, estos ngulos se conocen como cabeceo (Pitch) arriba y abajo en el eje x,
balanceo o alabeo (Roll) en el eje y, y guiada o viraje a izquierda y derecha (Yaw) en
el eje z (ver gura 31.12).
Los movimientos de cabeceo (Pitch) y balanceo (Roll) pueden medirse directa-
mente por los acelermetros, pero la guiada (Yaw) precisa de la barra de LEDs IR
para permitir estimar el ngulo de dicho movimiento.

Yaw (guiada)
+Z
+X
+Y

Pitch (cabeceo)
W
II-
M
ot
e

-X

-Z
-Y
Roll (balanceo)

Figura 31.12: Ejes y ngulos de balanceo, viraje y cabeceo en el Wiimote.

Afortunadamente, el clculo para la obtencin de datos como la aceleracin,


las fuerzas G de la gravedad, y los ngulos de orientacin, son funciones que
habitualmente implementan las APIs que controlan el Wiimote. As, para capturar
los datos procedentes del acelermetro, el modelo de trabajo es identico al mostrado
con anterioridad. La salvedad es que antes de comenzar la captura de los datos,
deber activarse el ag wiimote.mode.acc. Una vez hecho esto, la librera pone a
nuestra disposicin tres grupos de valores x, y y z accesibles a travs de la estructura
wiimote_t:

Ejes (axis): para proporcionar la aceleracin en cada uno de los ejes, es decir,
su vector aceleracin.
ngulos (tilt): para indicar el ngulo de inclinacin del acelermetro para el
balanceo, el cabeceo y el viraje.
Fuerzas (force): para representar al vector fuerza de la gravedad en sus
componentes (x, y, z).
31.12. Libreras para la manipulacin del Wiimote [1035]

El siguiente ejemplo muestra la recogida de estos datos.

Listado 31.18: Recoger datos del acelermetro


1 #include <stdio.h>
2 #include <stdlib.h>
3 #include "wiimote_api.h"
4
5 int main(int argc, char **argv) {
6 if (argc!=2){
7 printf("Introduzca la mac del mando");
8 exit(0);
9 }else{
10 printf("Pulse 1+2 para sincronizar");
11 }
12
13 wiimote_t wiimote = WIIMOTE_INIT;
14 wiimote_connect(&wiimote, argv[1]);
15 wiimote.mode.acc = 1; // habilitar acelerometro
16
17 while (wiimote_is_open(&wiimote)) {
18 wiimote_update(&wiimote);
19 if (wiimote.keys.home) {
20 wiimote_disconnect(&wiimote);
21 }
22
23 printf("AXIS x= %03d y= %03d z= %03d "
24 "FORCE x= %.3f y= %.3f z= %.3f "
25 "TILT x= %.3f y= %.3f z= %.3f\n",
26 wiimote.axis.x, wiimote.axis.y, wiimote.axis.z,
27 wiimote.force.x,wiimote.force.y,wiimote.force.z,
28 wiimote.tilt.x, wiimote.tilt.y, wiimote.tilt.z);
29 }
30
31 return 0;
32 }

Envio de eventos al mando

Para enviar eventos al mando, tales como activar y desactivar los LEDs, provocar
zumbidos o reproducir sonidos en el altavz, bastar con activar el correspondiente
ag de la estructura wiimote_t, lo que accionar el actuador correspondiente en el
Wiimote.
En el siguiente cdigo se muestra cmo provocar determinados eventos en el
mando al pulsar el correspondiente botn del mismo.

Listado 31.19: Enviando acciones al mando


1 #include <stdio.h>
2 #include <stdlib.h>
3 #include "wiimote_api.h"
4
5 int main(int argc, char **argv) {
6 if (argc!=2){
7 printf("Introduzca la mac del mando");
8 exit(0);
9 }else{
10 printf("Pulse 1+2 para sincronizar");
11 }
12
13 wiimote_t wiimote = WIIMOTE_INIT;
14 wiimote_connect(&wiimote, argv[1]);
15
C31
[1036] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

16 while (wiimote_is_open(&wiimote)) {
17 wiimote_update(&wiimote);
18 if (wiimote.keys.home) {
19 wiimote_disconnect(&wiimote);
20 }
21
22 if (wiimote.keys.up)wiimote.led.one = 1;
23 else wiimote.led.one = 0;
24
25 if (wiimote.keys.down) wiimote.led.two = 1;
26 else wiimote.led.two = 0;
27
28 if (wiimote.keys.left) wiimote.led.three = 1;
29 else wiimote.led.three = 0;
30
31 if (wiimote.keys.right) wiimote.led.four = 1;
32 else wiimote.led.four = 0;
33
34 if (wiimote.keys.a) wiimote.rumble = 1;
35 else wiimote.rumble = 0;
36 }
37
38 return 0;
39 }

Datos a interpretar del Wiimote segn la interaccin

Con lo que se ha expuesto hasta el momento, es fcil ver cmo programar


aplicaciones que tengan en cuenta los datos procedentes del mando de Wii. Veamos
algunas lneas gua:

Para empelar el mando como puntero sobre la pantalla de modo que se puedan
acceder a los diferentes menus de una aplicacin o apuntar a un objetivo
concreto en la pantalla, solo tendr que conocerse la posicin a la que se seala.
Si se desea emplear el mando a modo de volante para una aplicacin de
simulacin automovilstica, bastar procesar la variacin de la fuerza sobre el
eje y y quiz implementar algunas funciones mediante los botones para frenar,
acelerar, etc.

Listado 31.20: Usando el mando como volante


1 while (wiimote_is_open(&wiimote)) {
2 wiimote_update(&wiimote);
3 if (wiimote.keys.home) {
4 wiimote_disconnect(&wiimote);
5 }
6
7 if (wiimote.force.y>0)
8 printf ("< %.3f grados ", wiimote.force.y);
9 else if (wiimote.force.y<0)
10 printf ("> %.3f grados ", wiimote.force.y);
11 else
12 printf ("= %.3f grados ", wiimote.force.y);
13
14 if (wiimote.keys.one){
15 printf ("frenando");
16 wiimote.rumble = 1;
17 }else{
18 wiimote.rumble = 0;
19 }
20 if (wiimote.keys.two) printf ("acelerando");
21 if (wiimote.keys.a) printf ("nitro");
31.12. Libreras para la manipulacin del Wiimote [1037]

22
23 printf ("\n");
24 }

Suponga que desea implementar un juego donde se deba lanzar una caa de
pescar o simular el golpe de un palo de golf. Deber tomar e interpretar del
modo adecuado el viraje de la mano. As, para el caso de la caa de pescar se
monitorizar la fuerza de la gravedad ejercida en el eje z para saber si est hacia
adelante y hacia atrs, siempre y cuando la inclinacin en el eje y sea negativo
(la caa debe estar arriba). Adems, deber calcularse la fuerza a partir de las
componentes del vector gravedad.

Listado 31.21: Usando el mando como caa de pescar


1 while (wiimote_is_open(&wiimote)) {
2 wiimote_update(&wiimote);
3 if (wiimote.keys.home) {
4 wiimote_disconnect(&wiimote);
5 }
6
7 if (wiimote.tilt.y>0){ //Balanceo positivo ==>caa
arriba
8 printf ("ca~na abajo %.3f\n", wiimote.tilt.y);
9 }else{
10 if (wiimote.force.z<0)
11 printf ("atras %.3f\n", wiimote.force.z);
12 else
13 printf ("adelante %.3f\n", wiimote.force.z);
14 }
15 }

Si lo que se desea implementar es la simulacin de un gesto para golpear algo


(quiz a un adversario en un combate de boxeo), ser preciso analizar no slo la
fuerza, sino tambin la informacin para posicionar el puo.
Emular objetos como espadas, baras, etc. implica que deber procesarse tanto
la fuerza como el valor de la inclinacin en cada uno de los ejes para poder
posicionar perfectamente el objeto en el mundo virtual.

Figura 31.13: Captura de la aplicacin WmGui.


C31
[1038] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Como puede verse las posibilidades son mltiples, solo es cuestin de identicar
cules son los datos procedentes del mando que son imprescindibles para nuestra apli-
cacin, posibilitndo de este modo la construccin de interfaces de usuario naturales,
donde un elemento del mundo real externo a la aplicacin, puede interaccionar con el
mundo virtual dentro del computador.
Si en ocasiones no resulta evidente el conocer qu elementos deben procesarse
y con qu valores, existen aplicaciones que pueden ayudar a testear cules son las
variables que intervienen en un movimiento. En la gura 31.13 puede verse el aspecto
de una de ellas: WmGui. Se trata de una aplicacin para GNU/Linux donde podemos
monitorizar parmetros como los botones pulsados, la variacin sobre los ejes x, y, z,
la acceleracin como composicin del vector gravedad, la rotacin, el balanceo, etc.

31.13. Caso de estudio: Kinect


Siguiendo en la linea de dispositivos que permiten al usuario realizar una
interaccin natural con la consola o el computador, se encuentra Kinect. Se trata de un
dispositivo desarrollado por Microsoft para su videoconsola Xbox 360, el cual permite
a los programadores implementar aplicaciones capaces de reconocer gestos y emplear
comandos mediante la voz.
El dispositivo Kinect est diseado para poder localizarlo encima o debajo
del monitor o del televisor con el que se interactuar. Como puede verse en la
imagen 31.14, Kinect tiene una composicin horizontal en la que se albergan:

Una cmara RGB que proporciona una resolucin VGA (Video Graphics
Adapter) de 8 bits (640x480).
Un sensor de profuncidad monocromo de resolucin VGA de 11 bist que
proporcina 211 = 2048 niveles de sensibilidad.
Un array de cuatro micrfonos.

Figura 31.14: El dispositivo Kinect para XBox 360


31.14. Comunidad OpenKinect [1039]

El sensor de profuncidad consiste en una convinacin de un laser infrarrojo


y un sensor monocromo CMOS (Complementary Metal-Oxide-Semiconductor) de
infrarrojos. Esto le permite capturar video 3D bajo culquier tipo de luz ambiental. El
laser infrarrojo se proyecta sobre cada uno de los objetos que se encuentre a su alcance.
El sensor recibe el infrarrojo reejado por los objetos. Analizando las longitudes de
onda de la seal reejada, el dispositivo calcula la distancia a la que se encuentran los
objetos.
Kinect cuenta adems con un motor en su base, el cual le permite modicar su
inclinacin. Adems dispone de una conexin USB (Universal Serial Bus), gracias a
la cul es posible comunicarnos con l e implementar aplicaciones que manipulen los
datos que proporciona.
Hasta ahora Kinect no haba tenido muchos rivales, sin embargo, cabe mencionar
un par de competidores interesantes: ASUS Xtion Motion y PrimeSense Carmine. El
primero ha sido desarrollado por ASUS y PrimeSense; el segundo, ntegramente por
PrimeSense, la empresa que dise el sensor de profundidad de Kinect.
Desde el punto de vista de la programacin, Kinect cuenta con un API desarrollada
por Microsoft capaz de realizar deteccin y seguimiento de objetos como cuerpo,
cabeza, manos, identicacin de gestos, etc. Adems, desde su aparicin, tanto la
liberacin por parte de PrimeSense del cdigo de los drivers del sensor de profuncidad,
como la labor de ingeniera inversa desarrollada por algunas comunidades, han
permitido que los desarrolladores puedan implementar APIs con las que poder
manipular el dispositivo Kinect empleando alternativas libres.
En las siguientes secciones se abordarn algunas de las principales APIs y
Middlewares de libre disposicin ms empleadas y robustas.

31.14. Comunidad OpenKinect

OpenKinect 1 es una comunidad interesada en manipular Kinect en sus aplica-


ciones. Desarrollan libreras de cdigo abierto bajo licencia Apache2.0 y GPL2, para
poder implementar aplicaciones que hagan uso de Kinect desde sistemas Windows,
Linux y Mac. Su principal desarrollo es la librera libfreenect.
El cdigo de libfreenect se encuentra disponible en la direccin https://
github.com/OpenKinect/libfreenect, aunque se pueden encontrar los
paquete para sistemas Ubuntu/Debian a travs del correspondiente sistema de gestin
de paquetes. As, para instalar libfreenect, el paquete de desarrollo con sus cabeceras
y las demos, bastar escribir desde consola:

# sudo add-apt-repository ppa:floe/libtisch


# sudo apt-get update
# sudo apt-get install freenect libfreenect libfreenect-dev
libfreenect-demos

libfreenect requiere que el usuario que accede a Kinect pertenezca a los grupos
plugdev y video. Deber garantizarse que el usuario pertenece a estos grupos mediante
los comandos:

# sudo adduser <USER> plugdev


# sudo adduser <USER> video

1 http://openkinect.org/wiki/Main_Page
C31
[1040] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Tras esto, tendr que cerrarse la sesin para volver a acceder de nuevo y hacer as
efectivos los grupos a los que se ha aadido el usuario. Con ello, podrn ejecutarse
las aplicaciones que manipulen Kinect, como las que se incluyen en el paquete de
demostracin para probar la captura realizada por las cmaras de Kinect:

# freenect-glview

En la gura 31.15 puede verse la salida de dicha aplicacin. A la derecha se aprecia


la imagen procedente de la cmara RGB, mientras que a la izquierda se muestran los
niveles de profundidad representados por la escala de color.

Figura 31.15: Catura de pantalla de la ejecucin de freenect-glview.

Con todo, lo que OpenKinect proporciona con libfreenect es un API para el acceso
a los datos procedentes de Kinect y para el control del posicionamiento del motor.
Sin embargo, procesar los datos ledos es responsabilidad de la aplicacin, por lo
que es comn su empleo junto a libreras como OpenCV para la identicacin del
movimiento, reconocimiento de personas, etc.
Si lo que se desea es poder disponer de un API de ms alto nivel, deberemos hacer
uso de algn framework, como el de OpenNI que pasaremos a describir en la siguiente
seccin.

31.15. OpenNI
OpenNI (Open Natural Interaction) es una organizacin sin nimo de lucro
promovida por la industria para favorecer la compatibilidad e interoperabilidad de
dispositivos, aplicaciones y middlewares que permitan la construccin de Interfaces
Naturales.
La organizacin ha liberado un framework para el desarrollo de aplicaciones, el
cual permite tanto la comunicacin a bajo nivel con los dispositivos de video y audio
de Kinect, como el uso de soluciones de alto nivel que emplean visin por computador
para realizar seguimiento de objetos como manos, cabeza, cuerpo, etc.
El framework OpenNI se distribuy bajo licencia GNU Lesser General Public
License (LGPL (Lesser General Public License)) hasta la versin 1.5, pasando a
distribuirse bajo Apache License desde la versin 2. Proporciona un conjunto de APIs
para diferentes plataformas con wrappers para diversos lenguajes de programacin
entre los que estn C, C++, Java y C#.
31.15. OpenNI [1041]

Capa de aplicacin Juegos Navegadores etc.

Libreras del Middleware

Middleware OpenNI Componentes de procesamiento de datos

Componentes de acceso a sensores

Capa de Hardware
Dispositivos y sensores Micrfonos Videocmaras Kinect

Figura 31.16: Arquitectura de OpenNI.

La arquitectura actual del framework de OpenNI (v2.0) es la que se muestra en la


gura 31.16. En ella pueden verse cuatro capas bien diferenciadas:

La capa en la que se encuentra el hardware o dispositivos sensores de audio y


video.
El middleware de OpenNI que permite:
Acceso a sensores de audio y vdeo, de modo que puedan recogerse los
datos procedentes de los mismos.
Procesado de los datos procedentes de los sensores, de manera que puedan
implementarse de forma cmoda determinados algoritmos de uso ms
habitual como identicacin y rastreo de personas, manos, cabeza, etc.
Las libreras del middleware ofrecidas por terceros para identicar y seguir
partes del cuerpo, realizar reconocimiento de objetos, permitir reconstrucciones
tridimensionales, y un largo etctera. Estas hacen uso de middleware OpenNI.
La capa de aplicacin donde residen los programas que implementan interac-
cin natural mediante el uso del middleware de OpenNI y las libreras propor-
cionadas por terceros.

El middleware OpenNI est formado por una serie de mdulos o componentes


que pueden ser registrados en el mismo. Estos permiten la lectura de los datos de
los sensores (concretamente del sensor 3D, de la cmara RGB, de la cmara IR y
de los micrfonos), y el procesamiento de los mismos para deteccin de cuerpos,
manos, gestos, anlisis de escena para identicar primer y segundo plano, separacin
de objetos, etc. Para esto ltimo, destaca el middleware NiTE de Primesense que
implementa multitud de detectores de gestos con una carga de CPU mnima, para
plataformas Windows, Linux, Mac OS y Android.
C31
[1042] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

31.15.1. Instalacin
Para poder emplear OpenNI, deber descargarse el software OpenNI, el midd-
leware NiTE de PrimeSense y el driver del Sensor de la direccin http://www.
openni.org/openni-sdk/, donde pueden encontrarse los paquetes preparados
para Windows, Linux y Mac, as como el acceso a los repositorios github de los fuen-
tes para ser compilados e instalados.
Sin embargo, si se desea desarrollar aplicaciones que empleen OpenNI en otras
plataformas, una opcin interesante y sencilla puede pasar por seguir el proyecto
Simple-OpenNI2 , donde se pone a nuestra disposicin una versin algo reducida del
cdigo de OpenNI con un wrapper para Processing, pero con funcionalidad suente
para realizar una primera inmersin en el potencial de OpenNI.
As, desde la pgina de Simple-OpenNI pueden descargarse cheros zip con el
cdigo preparado para compilar e instalar en sistemas Linux de 64 bits, MacOSX
y Windows de 32 y 64 bits. Una vez descargado el chero zip para la plataforma
correspondiente, bastar descomprimirlo y se dispondr de directorios llamados
OpenNI-Bin-Dev-Linux-x64-v1.5.2.23, NITE-Bin-Dev-Linux-x64-v1.5.2.21 y Sensor-
Bin-Linux-x64-v5.1.0.41 si nos hemos descargado el cdigo para una arquitectura de
64 bits.
En cualquiera de los casos, si se ha descargado el zip completo desde Simple-
OpenNI o si se ha descargado desde los correspondientes repositorios github, para la
instalacin debern compilarse e instalarse por separado cada uno de los anteriores
paquetes, invocando al ./install que se encuentra en cada uno de ellos:

1. OpenNI

# cd OpenNI-Bin-Dev-Linux-x64-v1.5.2.23
# sudo ./install

2. Nite

# cd NITE-Bin-Dev-Linux-x64-v1.5.2.21
# sudo ./install

3. Driver Kinect

# cd Sensor-Bin-Linux-x64-v5.1.0.41
# sudo ./install

31.15.2. Conceptos iniciales


Una vez instalado el software, antes de comenzar adentrndonos en las particula-
ridades del framework OpenNI, deben denirse una serie de conceptos iniciales nece-
sarios para comprender cmo programar aplicaciones que lo utilicen.

Mdulos

Como se ha mencionado, el framework OpenNI proporciona una capa de abs-


traccin tanto para dispositivos fsicos como para otros componentes del middleware.
Estos son conocidos como mdulos. OpenNI distingue dos grupos de mdulos:
2 http://code.google.com/p/simple-openni/
31.15. OpenNI [1043]

Mdulos sensores: encargados del sensor 3D, de la cmara RGB, de la cmara


IR y de los micrfonos.
Componentes del middleware: destinados al anlisis y deteccin del cuerpo,
puntos de la mano, deteccin de gestos, anlisis de escena, etc.

Nodos de produccin y generadores

Los nodos de produccin son componentes que encapsulan una funcionalidad muy
concreta. Estos toman unos datos de un tipo concreto en su entrada, para producir unos
datos especcos de salida como resultado de un procesamiento concreto. Esta salida
puede ser aprovechada por otros nodos de produccin o por la aplicacin nal.
Existen nodos de produccin tando de los sensores como del middleware.
Aquellos nodos de produccin que proporcionan datos que pueden ser manipulados
directamente por nuestra aplicacin son los conocidos como generadores. Ejemplos
de generadores son: el generador de imagen, que proporciona la imagen capturada
por la cmara; el generador de profundidad, que proporciona el mapa de profundidad
capturado por el sensor 3D; el generador de usuario, que proporciona datos sobre
los usuarios identicados; el generador de puntos de la mano, que proporciona
informacin acerca del muestreo de las manos; el generador de alertas de gestos, que
proporciona datos a cerca de los gestos identicados en los usuarios, etc.

Cadenas de produccin

Es la encadenacin de varios nodos de produccin de manera que la salida del


procesamiento de uno es empleada como entrada para el procesamiento de otro, a n
de obtener el resultado nal deseado.

31.15.3. Manipulando Kinect con OpenNI


Para conocer cul es el modo habitual de proceder cuando se codiquen aplica-
ciones que manipulen Kinect empleando OpenNI, veremos paso a paso un sencillo
ejemplo donde se leen algunos datos del esqueleto de una persona. Comenzaremos
analizando el cdigo que se muestra en el listado.

Listado 31.22: Ej. sencillo para reconocimiento de la mano izquierda


1 int main(){
2 XnStatus nRetVal = XN_STATUS_OK;
3
4 //Inicializacin del contexto
5 xn::Context context;
6 nRetVal = context.Init();
7
8 // Creacin de los generadores necesarios
9 nRetVal = g_UserGenerator.Create(context);
10
11 // Establecer las opciones de los generadores
12 XnCallbackHandle h1, h2, h3;
13 g_UserGenerator.RegisterUserCallbacks(User_NewUser,
14 User_LostUser, NULL, h1);
15 g_UserGenerator.GetPoseDetectionCap().RegisterToPoseCallbacks(
16 Pose_Detected, NULL, NULL, h2);
17 g_UserGenerator.GetSkeletonCap().RegisterCalibrationCallbacks(
18 Calibration_Start,
19 Calibration_End, NULL, h3);
20
C31
[1044] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

21 g_UserGenerator.GetSkeletonCap().SetSkeletonProfile(
22 XN_SKEL_PROFILE_ALL);
23
24 // Hacer que los generadores comiencen su trabajo
25 nRetVal = context.StartGeneratingAll();
26
27 while (TRUE){
28 // Tomamos el siguiente frame
29 nRetVal = context.WaitAndUpdateAll();
30
31 // Para cada usuario detectado por el sistema
32 XnUserID aUsers[15];
33 XnUInt16 nUsers = 15;
34 g_UserGenerator.GetUsers(aUsers, nUsers);
35 for (int i = 0; i < nUsers; ++i){
36 // Recogemos los datos deseados del esqueleto
37 if (g_UserGenerator.GetSkeletonCap().IsTracking(aUsers[i])){
38 XnSkeletonJointPosition LeftHand;
39 g_UserGenerator.GetSkeletonCap().GetSkeletonJointPosition(
40 aUsers[i], XN_SKEL_LEFT_HAND, LeftHand);
41
42 // Procesamos los datos ledos
43 if (LeftHand.position.X<0) printf("RIGHT ");
44 else printf("LEFT ");
45
46 if (LeftHand.position.Y>0) printf("UP");
47 else printf("DOWN");
48
49 printf("\n");
50 }
51 }
52 }
53
54 context.Shutdown();
55
56 return 0;
57 }


En toda aplicacin que manipule Kinect con el framework de OpenNI, se debe
comenzar con la inicializacin de un objeto Contexto 5-6 . ste contiene todo el
OpenNI. Cuando el Contexto no vaya a ser usado
estado del procesamiento usando
ms, este deber ser liberado 54 .
Una vez que se tiene el objeto Contexto construido, se pueden crear los nodos
de produccin. Para ello ser preciso invocar al constructor apropiado. As se
dispone de xn::DepthGenerator::Create() para generar datos de profundidad, xn::-
ImageGenerator::Create() para generar imagen RGB, xn::IRGenerator::Create(),
para generar datos del receptor de infrarrojos, xn::AudioGenerator::Create() para
generar datos de audio procedente de los micrfonos, xn::GestureGenerator::Create()
para generar datos de gestos, xn::SceneAnalyzer::Create() para generar datos de
la escena, xn::HandsGenerator::Create() para generar datos de las manos, xn::-
UserGenerator::Create() para generar datos del usuario, etc.
En el caso que nos ocupa, en el ejemplo se ha empleado un generador de usuario
9 , el cual producir informacin asociada a cada uno de los usuarios identicados en
una escena, tales como el esqueleto o la posicin de la cabeza, las manos, los pies, etc.
Tras esto se debern establecer las opciones que sean convenientes para procesar los
datos del generador elegido. Volveremos a las opciones establecidas para el generador
de usuario en breve, pero de momento continuaremos nuestra explicacin obviando
los detalles concretos de la inicializacin de este.
Una vez establecidos las opciones de los generadores, deber hacerse que estos
comiencen a producir la informacin de la que son responsables. Esto se har
con la funcin StartGeneratingAll() del Contexto 25 . Tras esto se podr entrar
en el bucle principal de procesamiento de la informacin para recoger los datos
31.15. OpenNI [1045]

producidos por los generadores. En dicho bucle, lo primero que se har es actualizar
mediante alguna de las funciones de la
la informacin procedente de los generadores
familia WaitAndUpdateAll() del Contexto 29 . A continuacin, se tendr disponible la
informacin proporcionada por los generadores, y que podr recogerse con mtodos

Get, los cuales dependern del generador o generadores que se estn manipulando.
En el ejemplo, lo que se hace es invocar al mtodo GetUsers 34 que devuelve el Id
para cada uno de los usuarios que ha reconocido el generador, as como el nmero de
usuarios detectados en la escena. Con esta informacin, para cada uno de los usuarios
identicados, se va recogiendo su esqueleto mediante el mtodo GetSkeletonCap(),
y a partir de este, la posicin en la que se encuentra la mano izquierda con el
mtodo GetSkeletonJointPosition() 38 al que se le ha indicado qu parte del esqueleto
se desea extraer (mediante el parmetro XN_SKEL_LEFT_HAND) y dnde se

almacenar la informacin (en la estructura LeftHand). partir de aqu, bastar con
A
procesar la informacin de la estructura reportada 42-49 .
Es preciso tener en cuenta que, cuando le pedimos datos del esqueleto al
generador, es desde el punto de vista de la cmara. Es decir, la mano izquierda que
ve Kinect se corresponder con mano la derecha del usuario.
Como se ha mencionado con anterioridad, para poder disponer de los datos
adecuados procedentes de los generadores, en ocasiones ser preciso establecer ciertos

parmetros de stos y personalizar determinada funcionalidad. Ha llegado el momento
de abordar de nuevo este fragmento del cdigo. As, en el ejemplo, en las lneas 11-22
puede verse cmo se han registrado determinados manejadores XnCallbackHandle
que indican al generador de usuario qu accin debe realizar cuando se ha detectado
un nuevo usuario o cuando se ha perdido uno (empleando para ello el mtodo
RegisterUserCallbacks), qu hacer cuando se ha registrado una determinada pose del
usuario (mediante RegisterToPoseCallbacks), y las acciones a realizar al comenzar y
terminar el proceso de calibracin (usando RegisterCalibrationCallbacks).
Una implementacin de estos manejadores o callbacks es la que se muestra en el
siguiente cdigo:

Listado 31.23: Callback para el generador de usuario


1 #define POSE_TO_USE "Psi"
2 xn::UserGenerator g_UserGenerator;
3
4 void XN_CALLBACK_TYPE
5 User_NewUser(xn::UserGenerator& generator,
6 XnUserID nId, void* pCookie){
7 printf("Nuevo usuario: %d\n", nId);
8 g_UserGenerator.GetPoseDetectionCap().
9 StartPoseDetection(POSE_TO_USE, nId);
10 }
11
12 void XN_CALLBACK_TYPE
13 User_LostUser(xn::UserGenerator& generator, XnUserID nId,
14 void* pCookie){}
15
16 void XN_CALLBACK_TYPE
17 Pose_Detected(xn::PoseDetectionCapability& pose,
18 const XnChar* strPose, XnUserID nId, void* pCookie){
19 printf("Pose %s para el usuario %d\n", strPose, nId);
20 g_UserGenerator.GetPoseDetectionCap().StopPoseDetection(nId);
21 g_UserGenerator.GetSkeletonCap().RequestCalibration(nId, TRUE);
22 }
23
24 void XN_CALLBACK_TYPE
25 Calibration_Start(xn::SkeletonCapability& capability, XnUserID nId,
26 void* pCookie){
27 printf("Comenzando calibracion para el usuario %d\n", nId);
28 }
C31
[1046] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

29
30 void XN_CALLBACK_TYPE
31 Calibration_End(xn::SkeletonCapability& capability, XnUserID nId,
32 XnBool bSuccess, void* pCookie){
33 if (bSuccess){
34 printf("Usuario calibrado\n");
35 g_UserGenerator.GetSkeletonCap().StartTracking(nId);
36 } else {
37 printf("Fallo al calibrar al usuario %d\n", nId);
38 g_UserGenerator.GetPoseDetectionCap().
39 StartPoseDetection(POSE_TO_USE, nId);
40 }
41 }

Cuando el generador detecte un nuevo usuario en escena, se invocar al callback


User_NewUser. En el anterior listado, puede verse cmo cuando se detecta un nuevo
usuario por parte del generador, lo que se har es esperar a identicar una determinada

pose mismo, de modo que esta pose sirva para comenzar el proceso de calibracin
del
. Por su parte, cuando el usuario
desaparece de escena y se invoca al callback
User_LostUser no se hace nada 12-14 .
4-10

Una vez que se identica la pose, se invocar el callback llamado PoseDetected,


en la pose
donde se detendr el proceso de deteccin de pose ya tenemos al usuario
deseada y se solicitar que comience el proceso de calibracin 16-22 .
ste invocar al callback
Cuando el generador comience el proceso de calibracin,
Calibration_Start, que smplemente producir un mensaje 24-28 por consola.
Cuando el proceso de calibracin nalice, el generador invocar el callback
Calibration_End donde, si todo ha ido bin, se indicar al generador que comience
a rastrear el esqueleto del usuario con el mtodo StartTracking(nId). Sin embargo, si
se ha producido un error en la callibracin, se volver a intentar identicar una pose
preestablecida del usuario.

Los lmites estn en nuestra imaginacin


Obviamente puede irse mucho ms lejos de lo que se ha llegado con este sencillo
ejemplo. As, en la gura 31.17 puede verse la captura de una de las aplicaciones de
ejemplo que acompaan OpenNI. En ella se superpone a la salida de los datos de un
generador de profundidad, aquella producida por un generador de usuario del que se
han leido los datos del esqueleto. El resultado muestra el esqueleto a partir de los
puntos representativos identicados por el generador de usuario.
Piense ahora en lo que se puede conseguir empleando un motor como Ogre 3D.
Los datos del esqueleto procedentes del generador de usuario, pueden conectarse al
esqueleto de un personaje virtual o abatar. De este modo, las acciones del usuario
tendrn una repercusin directa en las acciones del abatar. En la gura 31.18 puede
verse una captura en la que un personaje dentro de un mundo virtual es manipulado
directamente por un usuario. Fgese en la esquina inferior izquierda de la gura, donde
se representa la silueta del usuario. El cdigo de ejemplo puede descargarse libremente
de https://github.com/ttair/TuxSinbad.
31.16. Realidad Aumentada [1047]

Figura 31.17: El esqueleto de una persona.

Figura 31.18: Integracin en Ogre.

31.16. Realidad Aumentada


La Realidad Aumentada ofrece diversas posibilidades de interaccin que pueden
ser explotadas en diferentes mbitos de aplicacin, como en los videojuegos. En esta
seccin comenzaremos deniendo qu se entiende por Realidad Aumentada, as como
algunas de las soluciones tecnolgicas ms empleadas.
C31
[1048] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Este nuevo paradigma de interaccin se est utilizando en los ltimos aos en


videojuegos comerciales, como veremos en la seccin 31.16.1, con algunos ttulos
que han sido grandes xitos a nivel mundial.
La Realidad Aumentada se encarga de estudiar las tcnicas que permiten integrar
en tiempo real contenido digital con el mundo real. Segn la taxonoma descrita
por Milgram y Kishino, los entornos de Realidad Mixta son aquellos en los que se
presentan objetos del mundo real y objetos virtuales de forma conjunta en una nica
pantalla. Esto abre un abanico de deniciones en la que se sitan las aplicaciones de
Realidad Aumentada (ver Figura 31.22).
A diferencia de la Realidad Virtual donde el usuario interacta en un mundo
totalmente virtual, la Realidad Aumentada se ocupa de generar capas de informacin
virtual que deben ser correctamente alineadas con la imagen del mundo real para
lograr una sensacin de correcta integracin.
El principal problema con el que deben tratar los sitemas de Realidad Aumentada
es el denominado registro, que consiste en calcular la posicin relativa de la cmara
real respecto de la escena para poder generar imgenes virtuales correctamente
alineadas con esa imagen real. Este registro debe ser preciso (errores de muy pocos
milmetros son muy sensibles en determinadas aplicaciones, como en medicina o en
soporte a las aplicaciones industriales) y robusto (debe funcionar correctamente en
todo momento). En la gura 31.19 puede verse un ejemplo de una imagen obtenida
con un registro correcto (centro) e incorrecto (abajo). Este registro puede realizarse
empleando diferentes tipos de sensores y tcnicas (las ms extendidas son mediante el Figura 31.19: Arriba: Escena real.
El problema del registro trata de
uso tracking visual). calcular la posicin de la cma-
As, la Realidad Aumentada se sita entre medias de los entornos reales y los ra real para poder dibujar objetos
virtuales, encargndose de construir y alinear objetos virtuales que se integran en un virtuales correctamente alineados.
escenario real. Centro: Registro correcto. Abajo:
Registro incorrecto.

31.16.1. Un poco de historia


En esta breve introduccin histrica nos centraremos en los principales hitos
de la Realidad Aumentada que tienen relacin directa con el mundo del desarrollo
de videojuegos, aunque obviamente hay multitud de hitos importantes que deberan
aparecer en una introduccin histrica ms general.
El primer sistema de Realidad Aumentada fue creado por Ivan Sutherland en 1968,
empleando un casco de visin que permita ver sencillos objetos 3D renderizados en
wireframe en tiempo real. Empleaba dos sistemas de tracking para calcular el registro
de la cmara; uno mecnico y otro basado en ultrasonidos (ver Figura 31.20).
Sin embargo no fue hasta 1992 cuando se acu el trmino de Realidad Aumen-
tada por Tom Caudell y David Mizell, dos ingenieros de Boeing que proponan el Figura 31.20: Primer Sistema de
uso de esta novedosa tecnologa para mejorar la eciencia de las tareas realizadas por Realidad Aumentada de Suther-
operarios humanos asociadas a la fabricacin de aviones. land.
En 1998, el ingeniero de Sony Jun Rekimoto crea un mtodo para calcular
completamente el tracking visual de la cmara (con 6 grados de libertad) empleando
marcas 2D matriciales (cdigos de barras cuadrados, ver Figura 31.21). Esta tcnica
sera la precursora de otros mtodos de tracking visuales en los prximos aos.
Un ao ms tarde en 1999, Kato y Billinghurst presentan ARToolKit, una
biblioteca de tracking visual de 6 grados de libertad que reconoce marcas cuadradas
mediante patrones de reconocimiento. Debido a su liberacin bajo licencia GPL se
hace muy popular y es ampliamente utilizada en el mbito de la Realidad Aumentada.
Figura 31.21: Marcas matriciales
de Rekimoto.
31.16. Realidad Aumentada [1049]

Entorno Realidad Mixta Entorno


Real Virtual

Mundo Real Realidad Aumentada Virtualidad Aumentada Realidad Virtual


El usuario interacta La Realidad Aumentada aade elemen- La Virtualidad Aumentada permite La Realidad Virtual se
exclusivamente en el tos generados por computador a la aadir objetos reales a un entorno encarga de construir
mundo real y el informacin capturada del mundo real. generado por computador. entornos totalmente
resultado tiene su virtuales donde el usuario
equivalencia en el interacta.
mundo virtual.

Interfaces Realidad Realidad Aumentada Realidad


de Usuario Realidad
Aumentada Basada en Virtual Virtual
Tangibles Espacial Dispositivos de Visin SemiInmersiva Inmersiva

Utilizacin de objetos fsicos Ejemplo de proyeccin Ejemplo tpico de utilizacin Sistema de Realidad Virtual Sistema de Realidad Virtual
para crear modelos virtuales. espacial de Realidad de la librera ARToolKit. Semi-Inmersivo empleando tipo cueva Cave totalmente
Aumentada Bubble Cosmos el sistema Barco Baron. inmersivo.
Diagrama Adaptado de (Milgram y Kishino 94) y Material Fotogrfico de Wikipedia.

Figura 31.22: Taxonoma de Realidad Mixta segn Milgram y Kishino.

En 2000, un grupo de investigadores de la University of South Australia presentan


una extensin de Quake (AR-Quake, Figura 31.23) que permite jugar en primera
persona en escenarios reales. El registro se realizaba empleando una brjula digital, un
receptor de GPS y mtodos de visin basados en marcas. Los jugadores deban llevar
un sistema de cmputo porttil en una mochila, un casco de visin estereoscpica y
un mando de dos botones.
En el 2003, Siemens lanza al mercado Mozzies, el primer juego de Realidad
Aumentada para telfonos mviles. El juego superpone mosquitos a la visin obtenida
Figura 31.23: AR-Quake. del mundo mediante una cmara integrada en el telfono. Este juego fue premiado
como el mejor videojuego para telfonos mviles en dicho ao.
En 2004 investigadores de la Universidad Nacional de Singapur presentan Human
Pacman, un juego que emplea GPS y sistemas inerciales para registrar la posicin
de los jugadores. El PacMan y los fantasmas son en realidad jugadores humanos que
corren por la ciudad portando ordenadores y sistemas de visin (Figura 31.24).
Tambin en el 2004, la Universidad Tcnica de Viena presenta el proyecto Invisible
Train (ver Figura 31.26, el primer juego multi-usuario para PDAs. Esta aplicacin se
ejecutaba totalmente en las PDAs, sin necesidad de servidores adicionales para realizar
procesamiento auxiliar. Los jugadores controlan trenes virtuales y deben intentar
Figura 31.24: H-Pacman. evitar que colisione con los trenes de otros jugadores. El proyecto utiliza la biblioteca
Studierstube desarrollada en la misma universidad.
En 2005 A. Henrysson adapta la biblioteca ARToolKit para poder funcionar en
Symbian, y crea un juego de Tenis (ver Figura 31.25) que gana un premio internacional
el mismo ao.
C31
[1050] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Figura 31.26: Interfaz de Invisible Train, de la Universidad de Viena.

El 2009 se presenta ARhrrrr! (ver Figura 31.27), el primer juego con contenido de
alta calidad para smartphones con cmara. El telfono utiliza la metfora de ventana
virtual para mostrar un mapa 3D donde disparar a Zombies y facilitar la salida a los
humanos que estn atrapados en l. El videojuego emplea de forma intensiva la GPU
del telfono delegando en la tarjeta todos los clculos salvo el tracking basado en
caractersticas naturales, que se realiza en la CPU. As es posible distribuir el clculo
optimizando el tiempo nal de ejecucin.
El videojuego de PSP Invizimals (ver Figura 31.28), creado por el estudio espaol Figura 31.25: AR-Tennis.
Novorama en 2009, alcanza una distribucin en Europa en el primer trimestre de
2010 superior a las 350.000 copias, y ms de 8 millones de copias a nivel mundial,
situndose en lo ms alto del rnking de ventas. Este juego emplea marcas para
registrar la posicin de la cmara empleando tracking visual.

31.16.2. Caractersticas Generales


Segn R. Azuma, un sistema de Realidad Aumentada debe cumplir las siguientes
caractersticas: Figura 31.27: ARhrrrr!

1. Combina mundo real y virtual. El sistema incorpora informacin sinttica a


las imgenes percibidas del mundo real.
2. Interactivo en tiempo real. As, los efectos especiales de pelculas que integran
perfectamente imgenes 3D fotorrealistas con imagen real no se considera
Realidad Aumentada porque no son calculadas de forma interactiva. Fotografa de ElPais.com 11-10-2009

3. Alineacin 3D. La informacin del mundo virtual debe ser tridimensional Figura 31.28: Invizimals.
y debe estar correctamente alineada con la imagen del mundo real. As,
estrictamente hablando las aplicaciones que superponen capas grcas 2D sobre
la imagen del mundo real no son consideradas de Realidad Aumentada.

Siendo estrictos segn la denicin de Azuma, hay algunas aplicaciones que co-
mercialmente se venden como de realidad aumentada que no deberan ser consideradas
como tal, ya que el registro del mundo real no se realiza en 3D (como en el caso de
Wikitude o Layar por ejemplo).
31.16. Realidad Aumentada [1051]

La combinacin de estas tres caractersticas hacen que la Realidad Aumentada sea


muy interesante para el usuario ya que complementa y mejora su visin e interaccin
del mundo real con informacin que puede resultarle extremadamente til a la hora
de realizar ciertas tareas. De hecho la Realidad Aumentada es considerada como una
forma de Amplicacin de la Inteligencia que emplea el computador para facilitar el
trabajo al usuario.
La importancia de la Realidad Aumentada queda patente con el enorme inters
que ha generado en los ltimos meses. La prestigiosa publicacin britnicac The
Economist asegur en Septiembre de 2009 que intentar imaginar como se utilizar
la Realidad Aumentada es como intentar predecir el futuro de la tecnologa web en
1994.
Segn la consultora Juniper Research, la Realidad Aumentada en dispositivos
mviles generar ms de 700 millones de dlares en el 2014, con ms de 350 millones
Figura 31.29: W. Ross Ashby de terminales mviles con capacidad de ejecutar este tipo de aplicaciones.
(1903-1972), el padre de la Ciber-
ntica moderna.
Un indicador que puede ser signicativo es la tendencia de bsqueda en la web.
Desde Junio de 2009 la bsqueda por Augmented Reality supera el nmero de
bsquedas realizadas con la cadena Virtual Reality.
Amplicacin Inteligencia
Una de las principales causas de este crecimiento en el uso de la Realidad
El trmino Amplicacin de la In- Aumentada es debido a que mediante esta tecnologa se amplan los espacios
teligencia comenz a utilizarse des-
de la Introduccin a la Cibern- de interaccin fuera del propio ordenador. Todo el mundo puede ser un interfaz
tica de William Ross Ashby, re- empleando Realidad Aumentada sobre dispositivos mviles.
rindose al uso de las tecnologas
de la informacin para aumentar
la inteligencia humana. Tambin se
denomina habitualmente Aumenta- 31.16.3. Alternativas tecnolgicas
cin Cognitiva.
Augmented Reality En el mbito de la Realidad Aumentada existen varios toolkits que facilitan
Virtual Reality
1.5 la construccin de aplicaciones. Sin embargo, para sacar el mximo partido a la
1.0
tecnologa es necesario dominar ciertos conceptos tericos y de representacin
grca. La mayora de sistemas estn destinados a programadores con experiencia
0.5
en desarrollo grco. A continuacin enumeraremos algunas de las bibliotecas ms
0.0 famosas.
Julio 09 Octubre 09 Enero 2010

Figura 31.30: Porcentaje de bs-


quedas en Google por las cade- ARToolKit: Es probablemente la biblioteca ms famosa de Realidad Aumenta-
nas Augmented Reality y Virtual da. Con interfaz en C y licencia libre permite desarrollar fcilmente aplicaciones
Reality. de Realidad Aumentada. Se basa en marcadores cuadrados de color negro.
ARTag: Es otra biblioteca con interfaz en C. Est inspirado en ARToolKit.
El proyecto muri en el 2008, aunque es posible todava conseguir el cdigo
fuente. El sistema de deteccin de marcas es mucho ms robusto que el de
ARToolKit.
OSGART: Biblioteca en C++ que permite utilizar varias libreras de tracking
(como ARToolKit, SSTT o Bazar).
FLARToolKit: Implementacin para Web (basada en Flash y ActionScript) del
ARToolKit portado a Java NyARToolKit.
Otros ports de ARToolKit: Existen multitud de versiones de ARToolKit
portados en diferentes plataformas, como AndAR (para telfonos Android),
SLARToolkit, etc...

En esta seccin nos basaremos en ARToolKit por ser actualmente el principal


referente en tracking basado en marcas, y por existir multitud de versiones portadas a
diferentes plataformas mviles.
C31
[1052] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

31.17. ARToolKit
ARToolKit es una biblioteca de funciones para el desarrollo rpido de aplicaciones
de Realidad Aumentada. Fue escrita originalmente en C por H. Kato, y mantenida por
el HIT Lab de la Universidad de Washington, y el HIT Lab NZ de la Universidad de
Canterbury (Nueva Zelanda).
ARToolKit facilita el problema del registro de la cmara empleando mtodos
de visin por computador, de forma que obtiene el posicionamiento relativo de 6
grados de libertad haciendo el seguimiento de marcadadores cuadrados en tiempo real,
incluso en dispositivos de baja capacidad de cmputo. Algunas de las caractersticas
ms destacables son:

Tracking de una cmara. ARToolKit en su versin bsica soporta de forma


nativa el tracking de una cmara, aunque puede utilizarse para tracking multic-
mara (si el programador se hace cargo de calcular el histrico de percepciones).
La biblioteca soporta gran variedad de modelos de cmaras y modelos de color.
Marcas negras cuadradas. Emplea mtodos de tracking de supercies planas
de 6 grados de libertad. Estas marcas pueden ser personalizadas, siempre que el
patrn no sea simtrico en alguno de sus ejes.
Rpido y Multiplataforma. Funciona en gran variedad de sistemas operativos
(Linux, Mac, Windows, IRIX, SGI...), y ha sido portado a multitud de disposi-
tivos porttiles y smartphones (Andorid, iPhone, PDAs...).
Comunidad Activa. A travs de los foros3 y listas de correo se pueden resolver
problemas particulares de uso de la biblioteca.
Figura 31.31: Uso de ARToolKit
Licencia libre. Esto permite utilizar, modicar y distribuir programas realiza- en un dispositivo de visin estereo.
dos con ARToolKit bajo la licencia GPL v2.

31.17.1. Instalacin y conguracin


En esencia ARToolKit es una biblioteca de funciones para el desarrollo de
aplicaciones de Realidad Aumentada, que utiliza a su vez otras bibliotecas. En Debian
necesitaremos instalar los siguientes paquetes:

# apt-get install freeglut3-dev libgl1-mesa-dev libglu1-mesa-dev


libxi-dev libxmu-dev libjpeg-dev

A continuacin ejecutamos ./Configure para obtener un Makele adaptado a


nuestro sistema. Elegimos Video4Linux2 en el driver de captura de video porque
disponemos de una cmara integrada de este tipo (la versin de ARToolKit con la que
vamos a trabajar est parcheada para soportar este tipo de dispositivos), en la segunda
pregunta no utilizaremos las instrucciones de ensamblador en ccvt (por disponer de
una arquitectura ia64), habilitaremos los smbolos de depuracin, y activaremos el
soporte hardware para GL_NV_texture_rectangle, ya que la tarjeta grca del
equipo los soporta:

carlos@kurt:ARToolKit$ ./Configure Figura 31.32: Aunque las bibliote-


Select a video capture driver. cas con las que trabajaremos en es-
1: Video4Linux te documento son libres, slo des-
2: Video4Linux+JPEG Decompression (EyeToy)
3: Video4Linux2 cribiremos el proceso de instalacin
y conguracin bajo GNU/Linux.
3 Foros de ARToolKit: http://www.hitlabnz.org/wiki/Forum Imagen original de sfgate.com.
31.18. El esperado Hola Mundo! [1053]

4: Digital Video Camcoder through IEEE 1394 (DV Format)


5: Digital Video Camera through IEEE 1394 (VGA NC Image Format)
6: GStreamer Media Framework
Enter : 3

Color conversion should use x86 assembly (not working for 64bit)?
Enter : n
Do you want to create debug symbols? (y or n)
Enter : y
Build gsub libraries with texture rectangle support? (y or n)
GL_NV_texture_rectangle is supported on most NVidia graphics cards
and on ATi Radeon and better graphics cards
Enter : y
create ./Makefile
create lib/SRC/Makefile
...
create include/AR/config.h
Done.

Finalmente compilamos las bibliotecas desde el mismo directorio, ejecutando


make. Si todo ha ido bien, ya tenemos compiladas las bibliotecas de ARToolKit. Estas
bibliotecas no requieren estar instaladas en ningn directorio especial del sistema,
ya que se compilan como bibliotecas estticas, de forma que estn incluidas en cada
ejecutable que se construye. Los cheros makefile que utilizaremos en los ejemplos
tendrn denido un camino (relativo o absoluto) hasta la localizacin en disco de estas
bibliotecas. A continuacin veremos un ejemplo de funcionamiento bsico.

31.18. El esperado Hola Mundo!


Aunque todava quedan muchos aspectos que estudiar, comenzaremos con una
aplicacin mnima que dibuja una tetera 3D localizada en el centro de una marca de
ARToolKit. El siguiente listado muestra el cdigo completo (menos de 110 lneas!)
del Hola Mundo!. Al nal de la seccin se mostrar el listado del makefile necesario
para compilar este ejemplo.

Listado 31.24: Hola Mundo! con ARToolKit.


1 #include <GL/glut.h>
2 #include <AR/gsub.h>
3 #include <AR/video.h>
4 #include <AR/param.h>
5 #include <AR/ar.h>
6 // ==== Definicion de variables globales =========================
7 int patt_id; // Identificador unico de la marca
Figura 31.33: La impresionante sa- 8 double patt_trans[3][4]; // Matriz de transformacion de la marca
lida en pantalla del Hola Mundo 9 // ======== print_error ==========================================
de Realidad Aumentada. 10 void print_error (char *error) { printf(error); exit(0); }
11 // ======== cleanup ==============================================
12 static void cleanup(void) {
13 arVideoCapStop(); // Libera recursos al salir ...
14 arVideoClose();
15 argCleanup();
16 }
17 // ======== draw =================================================
18 static void draw( void ) {
19 double gl_para[16]; // Esta matriz 4x4 es la usada por OpenGL
20 GLfloat mat_ambient[] = {0.0, 0.0, 1.0, 1.0};
21 GLfloat light_position[] = {100.0,-200.0,200.0,0.0};
22
23 argDrawMode3D(); // Cambiamos el contexto a 3D
24 argDraw3dCamera(0, 0); // Y la vista de la camara a 3D
25 glClear(GL_DEPTH_BUFFER_BIT); // Limpiamos buffer de profundidad
26 glEnable(GL_DEPTH_TEST);
C31
[1054] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

27 glDepthFunc(GL_LEQUAL);
28
29 argConvGlpara(patt_trans, gl_para); // Convertimos la matriz de
30 glMatrixMode(GL_MODELVIEW); // la marca para ser usada
31 glLoadMatrixd(gl_para); // por OpenGL
32 // Esta ultima parte del codigo es para dibujar el objeto 3D
33 glEnable(GL_LIGHTING); glEnable(GL_LIGHT0);
34 glLightfv(GL_LIGHT0, GL_POSITION, light_position);
35 glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
36 glTranslatef(0.0, 0.0, 60.0);
37 glRotatef(90.0, 1.0, 0.0, 0.0);
38 glutSolidTeapot(80.0);
39 glDiable(GL_DEPTH_TEST);
40 }
41 // ======== init =================================================
42 static void init( void ) {
43 ARParam wparam, cparam; // Parametros intrinsecos de la camara
44 int xsize, ysize; // Tamano del video de camara (pixels)
45
46 // Abrimos dispositivo de video
47 if(arVideoOpen("") < 0) exit(0);
48 if(arVideoInqSize(&xsize, &ysize) < 0) exit(0);
49
50 // Cargamos los parametros intrinsecos de la camara
51 if(arParamLoad("data/camera_para.dat", 1, &wparam) < 0)
52 print_error ("Error en carga de parametros de camara\n");
53
54 arParamChangeSize(&wparam, xsize, ysize, &cparam);
55 arInitCparam(&cparam); // Inicializamos la camara con param"
56
57 // Cargamos la marca que vamos a reconocer en este ejemplo
58 if((patt_id=arLoadPatt("data/simple.patt")) < 0)
59 print_error ("Error en carga de patron\n");
60
61 argInit(&cparam, 1.0, 0, 0, 0, 0); // Abrimos la ventana
62 }
63 // ======== mainLoop =============================================
64 static void mainLoop(void) {
65 ARUint8 *dataPtr;
66 ARMarkerInfo *marker_info;
67 int marker_num, j, k;
68
69 double p_width = 120.0; // Ancho del patron (marca)
70 double p_center[2] = {0.0, 0.0}; // Centro del patron (marca)
71
72 // Capturamos un frame de la camara de video
73 if((dataPtr = (ARUint8 *)arVideoGetImage()) == NULL) {
74 // Si devuelve NULL es porque no hay un nuevo frame listo
75 arUtilSleep(2); return; // Dormimos el hilo 2ms y salimos
76 }
77 argDrawMode2D();
78 argDispImage(dataPtr, 0,0); // Dibujamos lo que ve la camara
79
80 // Detectamos la marca en el frame capturado (ret -1 si error)
81 if(arDetectMarker(dataPtr, 100, &marker_info, &marker_num) < 0){
82 cleanup(); exit(0); // Si devolvio -1, salimos del programa!
83 }
84 arVideoCapNext(); // Frame pintado y analizado... A por otro!
85 // Vemos donde detecta el patron con mayor fiabilidad
86 for(j = 0, k = -1; j < marker_num; j++) {
87 if(patt_id == marker_info[j].id) {
88 if (k == -1) k = j;
89 else if(marker_info[k].cf < marker_info[j].cf) k = j;
90 }
91 }
92 if(k != -1) { // Si ha detectado el patron en algun sitio...
93 // Obtenemos transformacion entre la marca y la camara real
94 arGetTransMat(&marker_info[k], p_center, p_width, patt_trans);
95 draw(); // Dibujamos los objetos de la escena
96 }
97 argSwapBuffers(); // Cambiamos el buffer con lo que dibujado
31.18. El esperado Hola Mundo! [1055]

98 }
99 // ======== Main =================================================
100 int main(int argc, char **argv) {
101 glutInit(&argc, argv); // Creamos la ventana OpenGL con Glut
102 init(); // Llamada a nuestra funcion de inicio
103 arVideoCapStart(); // Creamos un hilo para captura video
104 argMainLoop( NULL, NULL, mainLoop ); // Asociamos callbacks...
105 return (0);
106 }

El ciclo de desarrollo puede resumirse en tres grandes etapas: 1. Inicializacin:


Consiste en leer los parmetros asociados a la cmara y la descripcin de las marcas
que se van a utilizar. 2. Bucle Principal (Main Loop): Es la etapa principal y est
formada por un conjunto de subetapas que veremos a continuacin. 3. Finalizacin:
Libera los recursos requeridos por la aplicacin.
La etapa del Bucle Principal est formada por 4 subetapas funcionales que se
realizan repetidamente hasta que el usuario decide nalizar la aplicacin:

Mundo! se
1. Captura. Se obtiene un frame de la cmara de vdeo. En el Hola
realiza llamando a la funcin arVideoGetImage en la lnea 73 .

anterior. En el ejemplo se llama


2. Deteccin. Se identican las marcas en el frame
a la funcin arDetectMarker en la lnea 81 .
Marcas Personalizadas 3. Transformacin. Se calcula la posicin relativa entre las marcas detectadas y
cmara
la fsica. Se realiza llamando a la funcin arGetTransMat en la lnea
94 .
ARToolKit permite crear fcilmen-
te marcadores propios. En este pri-
mer ejemplo utilizaremos una mar-
ca previamente entrenada. 4. Dibujado. Se dibujan los objetos virtuales situando la cmara virtual en la
posicin relativa anteriormente calculada. En el Hola Mundo se hacreado
una
funcin propia draw que es llamada desde el mainLoop en la lnea 95 .

En los ejemplos de este documento nos basaremos en el uso de las bibliotecas


GLUT (OpenGL Utility Toolkit) que nos facilitan el desarrollo de aplicaciones
OpenGL proporcionando sencillas funciones de callback para el manejo
de eventos de teclado y ratn, as como la fcil apertura de ventanas
multiplataforma. Estas bibliotecas estn pensadas para el aprendizaje de
OpenGL, y para la construccin de pequeas aplicaciones. Para facilitar la
escritura de cdigo en estos primeros ejemplos se utilizarn algunas variables
globales (por la imposibilidad de pasar parmetros adicionales a las funciones
de callback de GLUT).

En un primer estudio, se pueden identicar algunos de los bloques funcionales


descritos anteriormente. Por ejemplo, en las lneas 43-61 se encuentra denido el
es llamado desde main (lnea 102 ). El
bloque de inicializacin (funcin init), que
bucle principal est denido en las lneas 65-97 y la liberacin de recursos

Uso de /dev/video*
se realiza
Si tuviramos dos cmaras en el
sistema, y quisiramos abrir la se-
con la llamada a funciones propias de ARToolKit en las lneas 13-15 .
gunda (con interfaz V4L) llamara- En la ltima seccin del captulo se estudiar la integracin de ARToolKit con
mos a la funcin arVideoOpen con
la cadena -dev=/dev/video1. Po- OpenCV y Ogre, creando clases especcas para el tracking y la gestin de vdeo.
demos ver una descripcin com-
pleta de los parmetros soportados
por esta funcin en el directorio de
documentacin doc/video/ de AR-
ToolKit.
C31
[1056] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

31.18.1. Inicializacin
El bloque de inicializacin (funcin init), comienza abriendo el dispositivo de
video con arVideoOpen. Esta funcin admite parmetros de conguracin en una

cadena de caracteres.
A continuacin, lnea 48 , se obtiene el tamao de la fuente de vdeo mediante
arVideoInqSize. Esta funcin escribe en las direcciones de memoria reservadas
para dos enteros el ancho y alto del vdeo. En las siguientes lneas cargamos los
parmetros intrnsecos de la cmara obtenidos en la etapa de calibracin. En este
ejemplo utilizaremos un chero genrico camera_para.dat vlido para la mayora de
webcams. Sin embargo, para obtener resultados ms precisos, lo ideal es trabajar con
un chero adaptado a nuestro modelo de cmara concreto. Veremos cmo crear este
chero especco para cada cmara posteriormente.
As, con la llamada a arParamLoad se rellena la estructura ARParam especicada
como tercer parmetro con las caractersticas de la cmara. Estas caractersticas son
independientes de la resolucin a la que est trabajando la cmara, por lo que tenemos

que instanciar los parmetros concretamente a la resolucin en pxeles. Para ello, se
utiliza la llamada a arParamChangeSize (lnea 54 ) especicando la resolucin
concreta con la que trabajar la cmara en este ejemplo. Una vez obtenidos los
parmetros de la cmara instanciados al caso concreto de este ejemplo, se cargan en las

estructuras de datos internas de ARToolKit, mediante la llamada a arInitCparam.
En la ltima parte cargamos el patrn asociado a la marca (lnea 58 ). Finalmente

abrimos la ventana deOpenGL (mediante la biblioteca auxiliar Gsub de ARToolKit)
argInit en la lnea 61 , pasndole como primer parmetro la conguracin de la
cmara. El segundo parmetro indica el factor de zoom (en este caso, sin zoom).

31.18.2. Bucle Principal


El primer paso a realizar en el bucle principal es recuperar un frame de la cmara
de vdeo. Mediante la funcin arVideoGetImage obtenemos una imagen (la llamada
devuelve un puntero a un buffer donde se encuentra la imagen capturada). La llamada
devuelve NULL si no hay una nueva imagen (en el caso de que llamemos de nuevo Figura 31.34: Aunque es posible


muy pronto; con mayor frecuencia que la soportada por la cmara). Si esto ocurre, denir marcas personalizadas, las
simplemente dormimos el hilo 2ms (lnea 75 )y volvemos a ejecutar el mainLoop.
que ofrecen mejores resultados son

las basadas en patrones sencillos,
modo 2D, lnea 77 ) el buffer que
(en
A continuacin dibujamos en la ventana como la empleada en este ejemplo.
acabamos de recuperar de la cmara (lnea 78 ).
a arDetectMarker localiza las marcas en el buffer de entrada. En
La llamada
la lnea 81 el segundo parmetro de valor 100 se corresponde con el valor umbral
de binarizacin (a blanco y negro, ver Figura 31.35) de la imagen. El propsito
de este valor umbral (threshold) se explicar en detalle ms adelante. Esta funcin
nos devuelve en el tercer parmetro un puntero a una lista de estructuras de tipo
ARMarkerInfo, que contienen informacin sobre las marcas detectadas (junto con
un grado de abilidad de la deteccin), y como cuarto parmetro el nmero de marcas
detectadas.
De esta forma, ARToolKit nos devuelve posibles posiciones para cada una
de las marcas detectas. Incluso cuando estamos trabajando con una nica marca, es
comn que sea detectada en diferentes posiciones (por ejemplo, si hay algo parecido
a un cuadrado negro en la escena). Cmo elegimos la correcta en el caso de que
tengamos varias detecciones? ARToolKit asocia a cada percepcin una probabilidad
Figura 31.35: Ejemplo de proceso
de binarizacin de la imagen de
entrada para la deteccin de marcas.
31.19. Las Entraas de ARToolKit [1057]

de que lo percibido sea una marca, en el campo cf (condence value). En la tabla 31.1
se muestra la descripcin completa de los campos de esta estructura. Como se puede
comprobar, todos los campos de la estructura ARMarkerInfo se reeren a coordenadas

2D, por lo que an no se ha calculado la posicin relativa de la marca con la cmara.
As, en las lneas 86-91 guardamos en la variable k el ndice de la lista de marcas
detectadas aquella percepcin que tenga mayor probabilidad de ser la marca (cuyo

valor de abilidad sea mayor).
Mediante la llamada a arGetTransMat (lnea 94 ) obtenemos en una matriz
la transformacin relativa entre la marca y la cmara (matriz 3x4 de doubles); es
decir, obtiene la posicin y rotacin de la cmara con respecto de la marca detectada.
Para ello es necesario especicar el centro de la marca, y el ancho. Esta matriz ser

nalmente convertida al formato de matriz homogenea de 16 componentes utilizada
por OpenGL mediante la llamada a argConvGlpara en la lnea 29 .

Cuadro 31.1: Campos de la estructura ARMarkerInfo

Tipo Campo Descripcin


int area Tamao en pxeles de la regin detectada.
int id Identicador (nico) de la marca.
int dir Direccin. Codica mediante un valor numrico (0..3)
la rotacin de la marca detectada. Cada marca puede
tener 4 rotaciones distintas.
double cf Valor de conanza. Probabilidad de ser una marca
(entre 0 y 1).
double pos[2] Centro de la marca (en espacio 2D).
double line[4][3] Ecuaciones de las 4 aristas de la marca. Las aristas se
denen con 3 valores (a,b,c), empleando la ecuacin
implcita de la recta ax + by + c = 0.
double vertex[4][2] Posicin de los 4 vrtices de la marca (en espacio 2D).

31.18.3. Finalizacin y funcin Main


En la funcin cleanup se liberan los recursos al salir de la aplicacin. Se hace uso
de funciones de ARToolKit para detener la cmara de vdeo, y limpiar las estructuras

de datos internas de ARToolKit.
En la funcin main se registran los callbacks en la lnea 104 mediante la funcin
argMainLoop. En este ejemplo, se pasa como primer y segundo parmetro NULL
(correspondientes a los manejadores de ratn y teclado respectivamente). Por su parte,
se asocia la funcin que se estar llamando constantemente en el bucle principal. En
el caso de este ejemplo se llama mainLoop.

31.19. Las Entraas de ARToolKit


En este apartado veremos algunos detalles sobre cmo funciona internamente
ARToolKit. ARToolKit est formado por tres mdulos (ver Figura 31.36):
C31
[1058] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Bsqueda de Marcas

Dibujado Objetos 3D
Registro Cmara 3D
Identificacin Patrn
Posicin 2D Marcas

Pos. + Rotacin 3D
Fuente de vdeo de En la imagen binaria se Los objetos virtuales 3D
entrada.La imagen ser 1 detectan contornos y se 2 3 4 5 6 se dibujan superpuestos a
binarizada a blanco y extraen las aristas y las la imagen real. En este
negro para buscar marcas esquinas del cuadrado. caso coincidiendo con el
cuadradas. centro de la marca.

Figura 31.37: Esquema funcional de ARToolKit.

Video: Este mdulo contiene las funciones para obtener frames de vdeo de los
dispositivos soportados por el Sistema Operativo. El prototipo de las funciones
de este mdulo se encuentran el chero de cabecera video.h.
Aplicacin Realidad Aumentada
AR: Este mdulo principal contiene las funciones principales de tracking de
marcas, calibracin y estructuras de datos requeridas por estos mtodos. Los ARToolKit
cheros de cabecera ar.h, arMulti.h (subrutinas para gestin multi-patrn) Gsub
y param.h describen las funciones asociadas a este mdulo. AR Video
Gsub_Lite
Gsub y Gsub_Lite: Estos mdulos contienen las funciones relacionadas con la GLUT
etapa de representacin. Ambas utilizan GLUT, aunque la versin _Lite es Bibs.
ms eciente y no tiene dependencias con ningn sistema de ventanas concreto. OpenGL APIs Vdeo
En estos mdulos se describen los cheros de cabecera gsub.h, gsub_lite.h Driver Tarjeta 3D Driver Cmara
y gsubUtil.h. Sistema Operativo

Estos mdulos estn totalmente desacoplados, de forma que es posible utilizar Figura 31.36: Arquitectura de AR-
ARToolKit sin necesidad de emplear los mtodos de captura de vdeo del primer ToolKit.
mdulo, o sin emplear los mdulos de representacin Gsub o Gsub_Lite, como
veremos en la posterior integracin con Ogre y OpenCV.

31.19.1. Principios Bsicos


ARToolKit est basado en un algoritmo de deteccin de bordes y un mtodo
rpido de estimacin de la orientacin. La gura 31.37 resume el principio bsico
de funcionamiento de ARToolKit. Inicialmente las funciones de ARToolKit nos aislan
de la complejidad de tratar con diferentes dispositivos de vdeo, por lo que la captura
del frame actual es una simple llamada a funcin. Con esta imagen se inicia el primer
paso de bsqueda de marcas. La imagen se convierte a blanco y negro para facilitar
la deteccin de formas cuadradas (en realidad este paso se realiza en dos etapas (ver
Figura 31.38); primero se convierte a escala de grises (b), y despus se binariza (c)
eligiendo un parmetro de umbral threshold que elige a partir de qu valor de gris
(de entre 256 valores distintos) se considera blanco o negro (ver Figura 31.39).
A continuacin el algoritmo de visin por computador extrae componentes
conectados de la imagen previamente binarizada, Figura 31.38 (d), cuya area es
sucientemente grande como para detectar una marca. A estas regiones se les aplica un
rpido algoritmo de deteccin de contornos (e), obteniendo a continuacin los vrtices
y aristas que denen la regin de la marca en 2D (f).
31.19. Las Entraas de ARToolKit [1059]

a) Imagen en escala de b) Threshold = 100 c) Threshold = 50 d) Threshold = 150


grises.

Figura 31.39: Ejemplo de binarizacin con diferentes valores de Threshold.

Con la regin denida, se procede a una fase de normalizacin en la que se extrae


a el contenido de la marca (la zona central que la diferencia de otras marcas) y se
compara con los patrones de las marcas conocidas (etapa 4 de la Figura 31.37).
Conociendo las posiciones 2D de las aristas y vrtices que denen el marcador
2D, y el modelo de proyeccin de la cmara es posible estimar la posicin y rotacin
3D de la cmara relativamente a la marca.
b En el mdulo 2 estudiamos qu supona el clculo de la posicin y la rotacin de la
cmara en trminos de transformaciones en OpenGL. El uso de marcas cuadradas de
un tamao previamente conocido nos permite denir un sistema de coordenadas local
a cada marca, de modo que empleando mtodos de visin por computador obtengamos
la matriz de transformacin 4x4 del sistema de coordenadas de la marca al sistema de
c coordenadas de la cmara Tcm (Ecuacin 31.1).


Xc R11 R12 R13 Tx Xm Xm
Yc R21 R22 R23 Ty Y m Ym
Z = R R32 R33 Tz Zm = Tcm Zm (31.1)
c 31
d 1 0 0 0 1 1 1

De este modo, conociendo la proyeccin de cada esquina de la marca (e) sobre las
coordenadas de pantalla (xc, yc), y las restricciones asociadas al tamao de las marcas
y su geometra cuadrada, es posible calcular la matriz Tcm . La Figura 31.40 resume
esta transformacin.
e

El clculo aproximado de la matriz de transformacin que representa la


rotacin R y la traslacin T desde las coordenadas de la marca a las
coordenadas de la cmara se denominan en ingls pose estimation y position
estimation.
f

Finalmente es posible dibujar cualquier objeto 3D correctamente alineado con la


escena. Si conocemos la posicin absoluta de las marcas respecto de algn sistema
Figura 31.38: Pasos seguidos por
de coordenadas global, es posible representar los objetos posicionados globalmente.
ARToolKit para la deteccin e iden- Veremos ms detalles sobre el posicionamiento global en los prximos captulos.
ticacin de marcas.
C31
[1060] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Ym Coordenadas
Convenio de xc de la Cmara
Xc
ARToolKit:
el eje Z sale
Xm desde el papel. Coordenadas (xce,yce)
en Pantalla
(xc, yc) Yc
Coordenadas
Zm
de la Marca
e
Xm Zc
yc

Ym

Figura 31.40: Sistema de coordenadas de ARToolKit

31.19.2. Calibracin de la Cmara


Como vimos en el ejemplo del Hola Mundo!, los parmetros de la cmara
se cargan en tiempo de ejecucin de un archivo, mediante la llamada a la funcin
arParamLoad (ver Listado del ejemplo). Aunque en el primer ejemplo trabajamos
con un chero de descripcion genrico de cmaras (vlido para multitud de webcams),
es preferible realizar un proceso previo de calibracin que obtenga los parmetros
intrnsecos de la cmara.
ARToolKit proporciona dos etapas de calibracin; la primera (que es la que
estudiaremos en esta seccin) nos permite obtener un chero de cmara vlido para
la mayora de aplicaciones que contiene informacin sobre el punto central de la
imagen y el factor de distorsin de las lentes. Si se quiere una mayor precisin (que
contenga la distancia focal de la cmara), es necesario realizar la segunda etapa (ver
documentacin ocial de ARToolKit).
Para realizar la calibracin de una etapa de la cmara, necesitaremos imprimir
el patrn de 24 puntos separados entre s 4cm (ver Figura 31.41). Hecho esto, Figura 31.41: Patrn de calibracin
ejecutaremos la aplicacin calib_camera2 de la distribucin de ARToolKit. La de la cmara.
aplicacin nos pedir la longitud en milmetros entre cada punto del patrn de
calibracin (deber ser 40 si hemos imprimido el patrn a tamao 1:1, en otro caso
mediremos el espacio fsico entre el centro de los puntos).

carlos@kurt:calib_camera2$ ./calib_camera2
Input the distance between each marker dot, in millimeters: 40
-----------
Press mouse button to grab first image,
or press right mouse button or [esc] to quit.

Hecho esto nos aparecer una ventana con la imagen que percibe la cmara. Mo-
veremos el patrn para que todos los puntos aparezcan en la imagen y presionaremos
el botn izquierdo del ratn una vez sobre la ventana para congelar la imagen. Ahora
tenemos que denir un rectngulo que rodee cada crculo del patrn (ver Figura 31.42
empleando el siguiente orden: primero el crculo ms cercano a la esquina superior
izquierda, y a continuacin los de su misma la. Luego los de la segunda la co-
menzando por el de la izquierda y as sucesivamente. Es decir, los crculos del patrn
Figura 31.42: Marcado de crculos
deben ser recorridos en orden indicado en la Figura 31.43. del patrn.
31.19. Las Entraas de ARToolKit [1061]

El programa marcar una pequea cruz en el centro de cada crculo que hayamos
marcado (ver Figura 31.42), y aparecer una lnea indicando que ha sido sealada
como se muestra a continuacin. Si no aparece una cruz en rojo es porque el crculo
no se ha detectado y tendr que ser de nuevo sealado.
1 2 3 4 5 6
Grabbed image 1.
7 8 9 10 11 12 -----------
Press mouse button and drag mouse to rubber-bound features (6 x 4),
or press right mouse button or [esc] to cancel rubber-bounding & retry
13 14 15 16 17 18 grabbing.
Marked feature position 1 of 24
19 20 21 22 23 24 Marked feature position 2 of 24
Marked feature position 3 of 24
Figura 31.43: Orden de marcado ...
de los crculos del patrn de calibra- Marked feature position 24 of 24
cin. -----------
Press mouse button to save feature positions,
or press right mouse button or [esc] to discard feature positions &
retry grabbing.

Una vez que se hayan marcado los 24 puntos, se pulsa de nuevo el botn izquierdo
del ratn sobre la imagen. Esto almacenar la posicin de las marcas para la primera
imagen, y descongelar la cmara, obteniendo una salida en el terminal como la
siguiente.
Precisin en calibracin
### Image no.1 ###
Como es obvio, a mayor nmero 1, 1: 239.50, 166.00
de imgenes capturadas y marca- 2, 1: 289.00, 167.00
das, mayor precisin en el proceso ...
de calibracin. Normalmente con 5 6, 4: 514.00, 253.50
6 imgenes distintas suele ser su- -----------
ciente. Press mouse button to grab next image,
or press right mouse button or [esc] to calculate distortion param.

Figura 31.44: Ejemplo de posicionamiento de patrones de calibracin.

Como se indica en el manual de ARToolKit, es necesario capturar entre 5 y 10


imgenes siguiendo el mismo proceso, variando el ngulo y la posicin en la que
se presenta el patrn de calibracin. En la Figura 31.44 se muestran 6 ejemplos de
diferentes imgenes capturadas para la calibracin de una cmara.
C31
[1062] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Cuando te haya realizado un nmero de capturas adecuado pulsaremos el botn


derecho del ratn (o la tecla ESC) para calcular el parmetro de distorsin. En el
terminal aparecer una salida como la siguiente, indicando al nal el centro (X,Y)
de la cmara, y el factor de distorsin. El clculo de estos parmetros puede requerir
varios segundos.

Press mouse button to grab next image,


or press right mouse button or [esc] to calculate distortion param.
[320.0, 240.0, -13.5] 0.459403
[370.0, 190.0, -15.3] 0.457091
...
[375.0, 211.0, -16.4] 0.456635
--------------
Center X: 375.000000
Y: 211.000000
Dist Factor: -16.400000
Size Adjust: 0.978400
--------------

A continuacin la aplicacin nos muestra el resultado del clculo de estos


parmetros, para que el usuario realice una comprobacin visual. Cada vez que
pulsemos con el botn izquierdo del ratn se mostrar una imagen con lneas de
color rojo que deben pasar por el centro de cada crculo de calibracin (como las
dos imgenes mostradas en la Figura 31.45).

Checking fit on image 1 of 6.


Press mouse button to check fit of next image.

Finalmente, tras aceptar los resultados mostrados por la aplicacin, se calcularn


todos los parmetros que se guardarn en el chero que le indiquemos al nal del
proceso.
Este nuevo chero creado ser el que le indicaremos a ARToolKit que utilice en la
llamada a la funcin arParamLoad.

-- loop:-50 --
F = (816.72,746.92), Center = (325.0,161.0): err = 0.843755
-- loop:-49 --
F = (816.47,747.72), Center = (325.0,162.0): err = 0.830948
...
Calibration succeeded. Enter filename to save camera parameter.
--------------------------------------
SIZE = 640, 480
Distortion factor = 375.000000 211.000000 -16.400000 0.978400 Figura 31.45: Dos imgenes resul-
770.43632 0.00000 329.00000 0.00000 tado del clculo del centro y el fac-
0.00000 738.93605 207.00000 0.00000 tor de distorsin.
0.00000 0.00000 1.00000 0.00000
--------------------------------------
Filename: logitech_usb.dat

31.19.3. Deteccin de Marcas


En la seccin 31.19.1 hemos visto que ARToolKit extrae los vrtices que denen
las cuatro esquinas de una marca. En la etapa de deteccin de la marca, es necesario
adems identicar qu patrn est siendo detectado. Para ello ARToolKit primero
normaliza la imagen detectada (eliminando la distorsin debida a la perspectiva)
para posteriormente comparar el patrn con las plantillas de los patrones que
puede reconocer la aplicacin (aquellas que hayan sido cargadas con la funcin
arLoadPatt).
31.19. Las Entraas de ARToolKit [1063]

2 Normalizacin 3 Resampling 4
1 Identificacin
de la marca
Comparacin
con datos

identic.patt
de ficheros
de patrones

Fichero
Figura 31.46: Pasos para la identicacin de patrones.

El proceso de normalizacin de los patrones requiere mucho tiempo, por lo que es


un problema cuando se utilizan muchos marcadores. Tras la normalizacin, se reduce
la resolucin del patrn normalizado antes de pasar a la fase de comparacin. Cuanto
mayor es la resolucin del patrn reescalado, mayor es la precisin de ARToolKit,
pero require ms capacidad de cmputo para realizar las operaciones.
En el chero config.h de ARToolKit pueden derse algunos parmetros relacio-
nados con la deteccin de marcas. A continuacin se indican los valores que trae por
defecto la distribucin de ARToolKit:

#define AR_SQUARE_MAX 50: Este parmetro dene el nmero mximo de


marcadores que sern detectados en cada imagen.
#define AR_PATT_NUM_MAX 50: Nmero mximo de patrones que pueden
ser cargados por el usuario.
#define AR_PATT_SIZE_X 16: Resolucin (nmero de pxeles) en horizon-
tal del patrn cuando es resampleado.
#define AR_PATT_SIZE_Y 16: Resolucin (nmero de pxeles) en horizon-
tal del patrn cuando es resampleado.
#define AR_PATT_SAMPLE_NUM 64: Nmero mximo de pasos empleados
para resamplear el patrn.

De esta forma, el patrn reconocido incialmente es normalizado y resampleado


para obtener una imagen como se muestra en la Figura 31.46. Por defecto, esta
representacin se realizar en 64 pasos, generando una imagen de 16x16 pxeles.
Esta matriz de 16x16 pxeles se compar con los datos contenidos en los cheros
de patrones. Estos cheros simplemente contienen 4 matrices con el valor de gris
de cada pxel del patrn. Cada matriz se corresponde con el patrn rotado 90o (ver
Figura 31.46). Podemos abrir cualquier chero .patt (como el que utilizamos en el
Hola Mundo!) y veremos los valores en ASCII correspondientes a las 4 matrices.

Limitaciones

Si utilizamos nicamente mtodos de registro basados en marcas como en el caso


de ARToolKit, la principal limitacin viene cuando ninguna marca es visible en un
momento dado, ya que no es posible obtener la posicin de la cmara virtual. Adems,
si se oculta alguna parte de la marca, el mtodo de deteccin, tal y como hemos visto,
no ser capaz de identicar las 4 esquinas de la regin y el mtodo de identicacin
fallar.
C31
[1064] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Entre los factores que afectan a la deteccin de los patrones podemos destacar su
tamao, complejidad, orientacin relativa a la cmara y condiciones de iluminacin
de la escena (ver Figura 31.47).
150
El tamao fsico de la marca afecta directamente a la facilidad de deteccin; a

Rango Efectivo (cm)


125
mayor tamao de marca, mayor distancia puede ser cubierta. Por ejemplo, marcas 100
de 7 cm de lado pueden ser detectadas hasta una distancia mxima de 40 cm (a una 75
resolucin de 640x480 pxeles). Si aumentamos el tamao de la marca a 18cm, sta 50
25
ser detectada hasta una distancia de 125cm. 0
5 7.5 10 12.5 15 17.5
Este rango de deteccin se ve tambin afectado por la complejidad de la marca. Tamao Marca (cm)
Los patrones simples (con grandes reas de color blanco o negro) son detectados
3
mejor.

Rotacin
0
2
La orientacin relativa de la marca con respecto a la cmara afecta a la calidad 30

Error (cm)
1 45
de la deteccin. A mayor perpendicularidad entre el eje Z de la marca y el vector look
de la cmara, la deteccin ser peor. 0
-1
Finalmente las condiciones de iluminacin afectan enormemente a la deteccin
de las marcas. El uso de materiales que no ofrezcan brillo especular, y que disminuyan 0 10 20 30 40 50 60
el reejo en las marcas mejoran notablemente la deteccin de las marcas. Distancia (cm)

Figura 31.47: Arriba Relacin en-


tre el tamao de la marca y el ran-
31.20. Histrico de Percepciones go mximo de deteccin efectiva.
Abajo Relacin entre el error co-
metido, la distancia de deteccin, y
ARToolKit incorpora una funcin de tratamiento del histrico de percepciones el ngulo relativo entre la marca y
para estabilizar el tracking. Este histrico se implementa en una funcin alternativa a la cmara.
arGetTransMat que, en realidad, utiliza nicamente la percepcin anterior, llamada
arGetTransMatCont. Mediante el uso de esta funcin se elimina gran parte del
efecto de registro tembloroso (ver Figura 31.48). En esta gura se muestran diferentes
valores de la componente de la posicin en X en 40 frames de captura. Lo interesante
de esta gura no son los valores de estas posiciones (intencionadamente distintos),
sino la forma de la trayectoria. Empleando el histrico, la trayectoria resultante es
mucho ms suave.
utilizacin
En el listado de esta seccin se muestra un ejemplo de de esta funcin Histrico y precisin
de histrico. La funcin de callback de teclado (lneas 8-16 ) permite activar el uso
La utilizacin del histrico suaviza
del histrico (mediante la tecla h). la captura y consigue mayor estabi-
lidad, aunque el resultado del trac-
king cuenta con menor precisin.
e75
Con histrico
270 Sin histrico
265
260
255
250
5 10 15 20 25 30 35

Figura 31.48: Comparativa de la trayectoria (en eje X) de 40 frames sosteniendo la marca manualmente
activando el uso del histrico de percepciones. Se puede comprobar cmo la grca en el caso de activar el
uso del histrico es mucho ms suave.
31.20. Histrico de Percepciones [1065]

Como el histrico requiere como parmetro la percepcin anterior, no podr


utilizarse hasta que no dispongamos (al menos) de una percepcin. Por esta razn,
es necesario tener en cuenta este aspecto para llamar a la funcin de histrico
arGetTransMatCont (cuando tengamos una percepcin) o a la funcin sin histrico

arGetTransMat la primera vez. Para esto, se utiliza otra variable llamada useCont
(lnea 4 ), que nos indica si ya podemos utilizar la funcin con histrico o no. Esta
variable de comportamiento booleano
se tendr que poner a false (valor 0) cuando no
se detecte la marca (lnea 65 ).

C31
[1066] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Listado 31.25: Uso del histrico de percepciones.


1 // ==== Definicion de constantes y variables globales =============
2 int patt_id; // Identificador unico de la marca
3 double patt_trans[3][4]; // Matriz de transformacion de la marca
4 int useCont = 0; // Inicialmente no puede usar historico!
5 int contAct = 0; // Indica si queremos usar el historico
6
7 // ======== keyboard ==============================================
8 static void keyboard(unsigned char key, int x, int y) {
9 switch (key) {
10 case H: case h:
11 if (contAct) {contAct = 0; printf("Historico Desactivado\n");}
12 else {contAct = 1; printf("Historico Activado\n");} break;
13 case 0x1B: case Q: case q:
14 cleanup(); exit(1); break;
15 }
16 }
17
18 // ======== mainLoop ==============================================
19 static void mainLoop(void) {
20 ARUint8 *dataPtr;
21 ARMarkerInfo *marker_info;
22 int marker_num, j, k;
23
24 double p_width = 120.0; // Ancho del patron (marca)
25 double p_center[2] = {0.0, 0.0}; // Centro del patron (marca)
26
27 // Capturamos un frame de la camara de video
28 if((dataPtr = (ARUint8 *)arVideoGetImage()) == NULL) {
29 // Si devuelve NULL es porque no hay un nuevo frame listo
30 arUtilSleep(2); return; // Dormimos el hilo 2ms y salimos
31 }
32
33 argDrawMode2D();
34 argDispImage(dataPtr, 0,0); // Dibujamos lo que ve la camara
35
36 // Detectamos la marca en el frame capturado (return -1 si error)
37 if(arDetectMarker(dataPtr,100,&marker_info,&marker_num) < 0) {
38 cleanup(); exit(0); // Si devolvio -1, salimos del programa!
39 }
40
41 arVideoCapNext(); // Frame pintado y analizado...
42
43 // Vemos donde detecta el patron con mayor fiabilidad
44 for(j = 0, k = -1; j < marker_num; j++) {
45 if(patt_id == marker_info[j].id) {
46 if (k == -1) k = j;
47 else if(marker_info[k].cf < marker_info[j].cf) k = j;
48 }
49 }
50
51 if(k != -1) { // Si ha detectado el patron en algun sitio...
52 // Transformacion relativa entre marca y la camara real
53 if (useCont && contAct) {
54 arGetTransMatCont(&marker_info[k], patt_trans, p_center,
55 p_width, patt_trans);
56 printf ("Usando historico!!!\n");
57 }
58 else {
59 useCont = 1; // En la siguiente iteracion lo podemos usar!
60 arGetTransMat(&marker_info[k],p_center,p_width,patt_trans);
61 printf ("Sin historico...\n");
62 }
63 draw(); // Dibujamos los objetos de la escena
64 } else {
65 useCont = 0; printf ("Reset Historico (fallo deteccion)\n");
66 }
67
68 argSwapBuffers(); // Cambiamos el buffer con lo dibujado
69 }
31.21. Utilizacin de varios patrones [1067]

Si el usuario activ el uso


del histrico (y ya tenemos al menos una
percepcin
previa de la marca, lnea 53 ), utilizamos la funcin con
histrico 54 . En otro caso,
de deteccin sin histrico 60 y activaremos su uso para la
llamaremos a la funcin
siguiente llamada 59 .

31.21. Utilizacin de varios patrones


En muchas ocasiones querremos trabajar con ms de un patrn. Un ejemplo tpico
es asociar cada patrn con un objeto 3D, o utilizarlos (como veremos en la siguiente
seccin) como mecanismos de interaccin para realizar otras operaciones.
ARToolKit no proporciona ningn mecanismo directo para trabajar con varios
patrones, por lo que es responsabilidad del programador mantener alguna estructura
para guardar las matrices asociadas a cada marca detectada. A continuacin veremos
un sencillo ejemplo que dene un tipo de datos propio TObject para guardar este tipo
de informacin.

Listado 31.26: Utilizacin de varios patrones.


1 // ==== Definicion de estructuras =================================
2 struct TObject{
3 int id; // Identificador del patron
4 int visible; // Es visible el objeto?
5 double width; // Ancho del patron
Figura 31.49: Asociacin de dife- 6 double center[2]; // Centro del patron
rentes modelos a varios patrones. 7 double patt_trans[3][4]; // Matriz asociada al patron
8 void (* drawme)(void); // Puntero a funcion drawme
9 };
10
11 struct TObject *objects = NULL;
12 int nobjects = 0;
13
14 // ==== addObject (Anade objeto a la lista de objetos) ============
15 void addObject(char *p, double w, double c[2], void(*drawme)(void))
16 {
17 int pattid;
18
19 if((pattid=arLoadPatt(p)) < 0) print_error ("Error en patron\n");
20 nobjects++;
21 objects = (struct TObject *)
22 realloc(objects, sizeof(struct TObject)*nobjects);
23
24 objects[nobjects-1].id = pattid;
25 objects[nobjects-1].width = w;
26 objects[nobjects-1].center[0] = c[0];
27 objects[nobjects-1].center[1] = c[1];
28 objects[nobjects-1].drawme = drawme;
29 }
30 // ==== draw****** (Dibujado especifico de cada objeto) ===========
31 void drawteapot(void) {
32 GLfloat material[] = {0.0, 0.0, 1.0, 1.0};
33 glMaterialfv(GL_FRONT, GL_AMBIENT, material);
34 glTranslatef(0.0, 0.0, 60.0);
35 glRotatef(90.0, 1.0, 0.0, 0.0);
36 glutSolidTeapot(80.0);
37 }
38
39 void drawcube(void) {
40 GLfloat material[] = {1.0, 0.0, 0.0, 1.0};
41 glMaterialfv(GL_FRONT, GL_AMBIENT, material);
42 glTranslatef(0.0, 0.0, 40.0);
43 glutSolidCube(80.0);
C31
[1068] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

44 }
45
46 // ======== cleanup ===============================================
47 static void cleanup(void) { // Libera recursos al salir ...
48 arVideoCapStop(); arVideoClose(); argCleanup();
49 free(objects); // Liberamos la memoria de la lista de objetos!
50 exit(0);
51 }
52
53 // ======== draw ==================================================
54 void draw( void ) {
55 double gl_para[16]; // Esta matriz 4x4 es la usada por OpenGL
56 GLfloat light_position[] = {100.0,-200.0,200.0,0.0};
57 int i;
58
59 argDrawMode3D(); // Cambiamos el contexto a 3D
60 argDraw3dCamera(0, 0); // Y la vista de la camara a 3D
61 glClear(GL_DEPTH_BUFFER_BIT); // Limpiamos buffer de profundidad
62 glEnable(GL_DEPTH_TEST);
63 glDepthFunc(GL_LEQUAL);
64
65 for (i=0; i<nobjects; i++) {
66 if (objects[i].visible) { // Si el objeto es visible
67 argConvGlpara(objects[i].patt_trans, gl_para);
68 glMatrixMode(GL_MODELVIEW);
69 glLoadMatrixd(gl_para); // Cargamos su matriz de transf.
70
71 // La parte de iluminacion podria ser especifica de cada
72 // objeto, aunque en este ejemplo es general para todos.
73 glEnable(GL_LIGHTING); glEnable(GL_LIGHT0);
74 glLightfv(GL_LIGHT0, GL_POSITION, light_position);
75 objects[i].drawme(); // Llamamos a su funcion de dibujar
76 }
77 }
78 glDisable(GL_DEPTH_TEST);
79 }
80
81 // ======== init ==================================================
82 static void init( void ) {
83 ARParam wparam, cparam; // Parametros intrinsecos de la camara
84 int xsize, ysize; // Tamano del video de camara (pixels)
85 double c[2] = {0.0, 0.0}; // Centro de patron (por defecto)
86
87 // Abrimos dispositivo de video
88 if(arVideoOpen("") < 0) exit(0);
89 if(arVideoInqSize(&xsize, &ysize) < 0) exit(0);
90
91 // Cargamos los parametros intrinsecos de la camara
92 if(arParamLoad("data/camera_para.dat", 1, &wparam) < 0)
93 print_error ("Error en carga de parametros de camara\n");
94
95 arParamChangeSize(&wparam, xsize, ysize, &cparam);
96 arInitCparam(&cparam); // Inicializamos la camara con param"
97
98 // Inicializamos la lista de objetos
99 addObject("data/simple.patt", 120.0, c, drawteapot);
100 addObject("data/identic.patt", 90.0, c, drawcube);
101
102 argInit(&cparam, 1.0, 0, 0, 0, 0); // Abrimos la ventana
103 }
104
105 // ======== mainLoop ==============================================
106 static void mainLoop(void) {
107 ARUint8 *dataPtr;
108 ARMarkerInfo *marker_info;
109 int marker_num, i, j, k;
110
111 // Capturamos un frame de la camara de video
112 if((dataPtr = (ARUint8 *)arVideoGetImage()) == NULL) {
113 // Si devuelve NULL es porque no hay un nuevo frame listo
114 arUtilSleep(2); return; // Dormimos el hilo 2ms y salimos
31.21. Utilizacin de varios patrones [1069]

115 }
116
117 argDrawMode2D();
118 argDispImage(dataPtr, 0,0); // Dibujamos lo que ve la camara
119
120 // Detectamos la marca en el frame capturado (ret -1 si error)
121 if(arDetectMarker(dataPtr, 100, &marker_info, &marker_num) < 0){
122 cleanup(); exit(0); // Si devolvio -1, salimos del programa!
123 }
124
125 arVideoCapNext(); // Frame pintado y analizado...
126
127 // Vemos donde detecta el patron con mayor fiabilidad
128 for (i=0; i<nobjects; i++) {
129 for(j = 0, k = -1; j < marker_num; j++) {
130 if(objects[i].id == marker_info[j].id) {
131 if (k == -1) k = j;
132 else if(marker_info[k].cf < marker_info[j].cf) k = j;
133 }
134 }
135
136 if(k != -1) { // Si ha detectado el patron en algun sitio...
137 objects[i].visible = 1;
138 arGetTransMat(&marker_info[k], objects[i].center,
139 objects[i].width, objects[i].patt_trans);
140 } else { objects[i].visible = 0; } // El objeto no es visible
141 }
142
143 draw(); // Dibujamos los objetos de la escena
144 argSwapBuffers(); // Cambiamos el buffer
145 }


La estructura de datos base del ejemplo es TObject, denida en las lneas 2-9 .
En esta estructura, el ltimo campo drawme es un puntero a funcin, que deber ser
asignado a alguna funcin que no reciba ni devuelva argumentos, y que se encargar

de dibujar el objeto.
Mantendremos en objects (lnea 11 ) una lista de objetos reconocibles en la
escena. La
memoria en la funcin addObject
para cada objeto de la lista se reservar
(lneas 15-29 ) mediante una llamada a realloc en 21-22 . Esta memoria ser
liberada cuando nalice el programa en cleanup (lnea 49 ).
De este modo, en la funcin de inicializacin init, se llama a nuestra funcin
addObject para cada objeto que deba ser reconocido, pasndole la ruta al chero
del patrn, el ancho, centro y el nombre de la funcin de dibujado asociada a esa
marca (lneas 99-100 ). En este
ejemplo se han denido dos sencillas funciones de
dibujado de una tetera 31-37 y un cubo 39-44 . En el caso de que una marca no tenga
asociado el dibujado de un objeto (como veremos en los ejemplos de la seccin 31.22),
simplemente podemos pasar NULL.
En el bucle principal se han incluido pocos cambios respecto de los ejemplos

anteriores. nicamenteen las lneas de cdigo donde se realiza la comprobacin de
las marcas detectadas 129-142 , se utiliza la lista de objetos global y se realiza la
comparacin con cada marca. As, el bucle for externo se encarga
de recorrer todos
los objetos que pueden ser reconocidos por el programa 129 , y se compara el factor
de conanza de cada objeto con el que est siendo estudiado 133 .A continuacin se
obtiene la matriz de transformacin asociada al objeto en 139-140 .

La funcin de dibujado (llamada en 144 y denida en 54-79
) utiliza igualmente
objetos. Para cada objeto visible 65-66 se carga
la informacin de la lista de su matriz
de transformacin 67-69 y se llama a su funcin propia de dibujado 75 .
C31
[1070] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Orientacin a Objetos. Estos ejemplos estn realizados en C intentando


crear ejemplos de cdigo mnimos. En la integracin con Ogre sera con-
veniente crear objetos para el tracking y despliegue con la informacin nece-
saria.

31.22. Relacin entre Coordenadas


Como hemos visto en los ejemplos de los captulos anteriores, ARToolKit calcula
la posicin de la marca en coordenadas de la cmara.
Puede entenderse como un posicionamiento de la cmara en una posicin del Coordenadas Marca
espacio (correspondiente a la distancia y rotacin relativa entre la marca y la cmara Como vimos en el captulo de
real), conservando el origen del mundo en el centro de la marca. Esta idea es OpenGL del mdulo 2, el sistema
nicamente una ayuda para entender el posicionamiento real, ya que internamente de coordenadas de la marca sigue el
es una transformacin que se realiza en la pila ModelView, siendo equivalente mover mismo convenio que OpenGL (ver
la cmara y mantener el objeto (la marca) esttica o a la inversa. Figura 31.50), por lo que si utiliza-
mos la idea intuitiva de que el ori-
gen del SRU est en la marca, pode-
mos posicionar fcilmente los obje-
tos con OpenGL, ya que las trans-
Y formaciones se aplican siguiendo la
X misma notacin.
Z

X
arGetT
ransM
at

Figura 31.50: Relacin entre el sistema de coordendas de la marca y el sistema de coordenadas de la


cmara.

De este modo, como se representa en la Figura 31.50, la llamada a arGetTransMat


nos posiciona la cmara en relacin a la marca. Es como si aplicramos la transfor-
macin marcada por la echa de color rojo, con origen del sistema de referencia en la
marca.
De hecho, si en el ejemplo del Hola Mundo imprimimos la columna de ms a
la derecha de la matriz de transformacin de la marca, podemos comprobar cmo
si movemos la marca hacia arriba (en direccin a su eje Y), el valor de posicin en
ese Y decrecer (debido a que el sentido est invertido respecto de la cmara). De
forma anloga ocurrir con el eje Z. Por el contrario, si desplazamos la marca hacia la
derecha, el eje X de la matriz crecer, debido a que ambos sistemas de coordenadas
emplean el mismo convenio (ver Figura 31.50).
Continuaremos trabajando con la idea mucho ms intuitiva de considerar que la
marca es esttica y que lo que se desplaza es la cmara (aunque, como veremos, la
marca puede en realidad desplazarse y la idea de considerar que la cmara es la mvil
seguir siendo ltil).
31.22. Relacin entre Coordenadas [1071]

X Y
d? X Y
B
ar
Ge A-1
tTr
a ns
Ma
t X

t
sMa
B d

Tran et
A

ArG
SRU A

Figura 31.53: Utilizacin de la transformacin inversa a la marca de Identic A1 para calcular la


distancia entre marcas. Primero cargamos la matriz asociada a la marca de marca de Identic, (estableciendo
implcitamente el origen del SRU ah) y de ah aplicamos la transformacin inversa A1 . A continuacin
aplicamos la matriz B asociada a la segunda marca. La distancia al origen del SRU es la distancia entre
marcas.

Veamos a continuacin un ejemplo que trabajar con las relaciones entre la cmara
y la marca. Tener claras estas transformaciones nos permitir cambiar entre sistemas
de coordenadas y trabajar con las relaciones espaciales entre las marcas que no son
ms que transformaciones entre diferentes sistemas de coordenadas. En este programa
queremos variar la intensidad de color rojo de la tetera segn la distancia de una marca
auxiliar (ver Figura 31.51).
El problema nos pide calcular la distancia entre dos marcas. ARToolKit, mediante
la llamada a la funcin arGetTransMat nos devuelve la transformacin relativa entre
la marca y la cmara. Para cada marca podemos obtener la matriz de transformacin
relativa hacia la cmara. Podemos verlo como la transformacin que nos posiciona la
cmara en el lugar adecuado para que, dibujando los objetos en el origen del SRU,
se muestren de forma correcta. Cmo calculamos entonces la distancia entre ambas
marcas?
En el diagrama de la Figura 31.53 se resume el proceso. Con la llamada a
arGetTransMat obtenemos una transformacin relativa al sistema de coordenadas
de cada cmara (que, en el caso de una nica marca, podemos verlo como si fuera el
origen del SRU donde dibujaremos los objetos). En este caso, tenemos las echas que
parten de la marca y posicionan relativamente la cmara sealadas con A y B.
Podemos calcular la transformacin entre marcas empleando la inversa de una
transformacin, que equivaldra a viajar en sentido contrario. Podemos imaginar
realizar la transformacin contraria; partiendo del origen del SRU, viajamos hasta la
Figura 31.51: Salida del ejemplo
marca de Identic (aplicando A1 , y de ah aplicamos la transformacin B (la que nos
de clculo de distancias entre mar- posicionara desde la segunda marca hasta la cmara). As, llegamos al punto nal
cas. La intensidad de la componen- (sealado con el crculo de color negro en la Figura 31.53.derecha). Su distancia al

te roja del color vara en funcin de origen del SRU ser la distancia entre marcas.
la distancia entre marcas.

La codicacin de esta operacin es directa. En la lnea 33 del listado anterior, si
ambos objetos son visibles 32 se calcula la inversa de la transformacin a la marca
de Identic (que est en la posicin 0 de la lista de objetos). Esta nueva matriz m se
multiplica a continuacin con la matriz de transformacin de la segunda marca 34 .
C31
[1072] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

Listado 31.27: Ejemplo de clculo de distancias entre marcas. Z


1 // ======== drawteapot ============================================
2 void drawteapot(void) {
3 GLfloat material[] = {0.0, 0.0, 0.0, 1.0}; M
4 float value = 0.0; // Intensidad del gris a dibujar X
5
6 // La intensidad se asigna en funcion de la distancia "dist01" Y
7 // dist01 es la distancia entre objeto 1 y 2 (global) C-1
8 // Mapear valor intensidad linealmente entre 160 y 320 ->(1..0) X
9 value = (320 - dist01) / 160.0; Z
10 if (value < 0) value = 0; if (value > 1) value = 1;
11 material[0] = value;
12 Y
13 glMaterialfv(GL_FRONT, GL_AMBIENT, material);
14 glTranslatef(0.0, 0.0, 60.0); Figura 31.52: Esquema de posicio-
15 glRotatef(90.0, 1.0, 0.0, 0.0);
namiento global utilizando transfor-
16 glutSolidTeapot(80.0);
17 } maciones inversas.
18
19 // ======== draw ==================================================
20 void draw( void ) {
21 double gl_para[16]; // Esta matriz 4x4 es la usada por OpenGL
22 GLfloat light_position[] = {100.0,-200.0,200.0,0.0};
23 double m[3][4], m2[3][4];
24 int i;
25
26 argDrawMode3D(); // Cambiamos el contexto a 3D
27 argDraw3dCamera(0, 0); // Y la vista de la camara a 3D
28 glClear(GL_DEPTH_BUFFER_BIT); // Limpiamos buffer de profundidad
29 glEnable(GL_DEPTH_TEST);
30 glDepthFunc(GL_LEQUAL);
31
32 if (objects[0].visible && objects[1].visible) {
33 arUtilMatInv(objects[0].patt_trans, m);
34 arUtilMatMul(m, objects[1].patt_trans, m2);
35 dist01 = sqrt(pow(m2[0][3],2)+pow(m2[1][3],2)+pow(m2[2][3],2));
36 printf ("Distancia objects[0] y objects[1]= %G\n", dist01);
37 }
38
39 for (i=0; i<nobjects; i++) {
40 if ((objects[i].visible) && (objects[i].drawme != NULL)) {
41 argConvGlpara(objects[i].patt_trans, gl_para);
42 glMatrixMode(GL_MODELVIEW);
43 glLoadMatrixd(gl_para); // Cargamos su matriz de transf.
44
45 glEnable(GL_LIGHTING); glEnable(GL_LIGHT0);
46 glLightfv(GL_LIGHT0, GL_POSITION, light_position);
47 objects[i].drawme(); // Llamamos a su funcion de dibujar
48 }
49 }
50 glDisable(GL_DEPTH_TEST);
51 }
52
53 // ======== init ==================================================
54 static void init( void ) {
55 // La parte inicial de init es igual que en ejemplos anteriores
56
57 // Inicializamos la lista de objetos Posicionamiento Global
58 addObject("data/identic.patt", 120.0, c, drawteapot);
59 addObject("data/simple.patt", 90.0, c, NULL); El uso de transformaciones inversas
60 nos permiten obtener coordenadas
61 argInit(&cparam, 1.0, 0, 0, 0, 0); // Abrimos la ventana de posicionamiento global, como se
62 } muestra en la Figura 31.52. En ese
ejemplo, bastar con conocer la ma-
triz de transformacin entre la mar-
ca y el origen del SRU (expresada
como M ), para obtener el posico-
namiento global de la cmara res-
pecto del SRU, multiplicando la in-
versa de la transformacin relativa
de la marca C 1 con la matriz M .
31.23. Integracin con OpenCV y Ogre [1073]

Recordemos que la ltima columna de las matrices netas de transformacin


codican la traslacin. De este modo, basta con calcular el mdulo del vector que
une el punto trasladado con el origen para calcular la distancia, que se guarda en la
variable global dist01 35 (distancia entre los objetos 0 y 1). Para cambiar el color

de la tetera en funcin de esta distancia usamos una funcin lineal que asigna el nivel
de rojo (entre 0 y 1) segn la distancia en las lneas 9-11 (si d 16cm, r = 1, si
d > 32cm, r = 0, y linealmente los intermedios).

31.23. Integracin con OpenCV y Ogre


En esta seccin estudiaremos primero cmo realizar una captura de vdeo y
despliegue en el fondo de una ventana de Ogre para, posteriormente, convertir el tipo
de datos relativos a un Frame en OpenCV para ser utilizados en ARToolKit (ver Figura
31.54).

Figura 31.54: Ejemplo de integracin que se describir en esta seccin.

La clase VideoManager se encarga de la gestin de las fuentes de vdeo.


Podran instanciarse tantos objetos de esta clase como fueran necesarios, pero
habra que modicar la implementacin de la llamada a createBackground y a

DrawCurrentFrame. El VideoManager abstrae a la aplicacin de Ogre del uso de
OpenCV. nicamente el VideoManager conoce los dispositivos de captura (lnea 10
del archivo de cabecera). El puntero al sceneManager de Ogre es necesario como
veremos ms adelante para la creacin y actualizacin del plano que utilizaremos para
desplegar la salida del dispositivo de vdeo en la escena.
C31
[1074] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS


El VideoManager trabaja internamente con dos tipos de datos para la representa-
cin de los frames (ver lneas 11-12 ), que pueden ser convertidos entre s fcilmente.
El IplImage ha sido descrito anteriormente en la seccin. cv::Mat es una extensin
para trabajar con matrices de cualquier tamao y tipo en C++, que permiten acceder
cmodamente a los datos de la imagen. De este modo, cuando se capture un frame, el
VideoManager actualizar los punteros anteriores de manera inmediata.

Listado 31.28: VideoManager.h


1 #include <Ogre.h>
2 #include <iostream>
3 #include "cv.h"
4 #include "highgui.h"
5
6 class VideoManager {
7 private:
8 void createBackground(int cols, int rows);
9 void ReleaseCapture();
10 CvCapture* _capture;
11 IplImage* _frameIpl;
12 cv::Mat* _frameMat;
13 Ogre::SceneManager* _sceneManager;
14
15 public:
16 VideoManager(int device, int w, int h,
17 Ogre::SceneManager* sm);
18 ~VideoManager();
19 void UpdateFrame();
20 IplImage* getCurrentFrameIpl();
21 cv::Mat* getCurrentFrameMat();
22 void DrawCurrentFrame();
23 };


El constructor de la clase (lneas 4-8 del siguiente listado) se encarga de obtener
el dispositivo de captura e inicializarlo con las dimensiones especicadas como
parmetro al constructor. Esto es necesario, ya que las cmaras generalmente permiten
En el destructor se libera el dispositivo de captura
trabajar con varias resoluciones.
creado por OpenCV (lnea 12 ).

Listado 31.29: VideoManager.cpp


1 #include "VideoManager.h"
2 VideoManager::VideoManager(int device, int w, int h,
3 Ogre::SceneManager* sm){
4 _sceneManager = sm;
5 _capture = cvCreateCameraCapture(device);
6 cvSetCaptureProperty(_capture, CV_CAP_PROP_FRAME_WIDTH, w);
7 cvSetCaptureProperty(_capture, CV_CAP_PROP_FRAME_HEIGHT, h);
8 createBackground (w, h); _frameIpl = NULL; _frameMat = NULL;
9 }
10
11 VideoManager::~VideoManager(){
12 cvReleaseCapture(&_capture); delete _frameIpl; delete _frameMat;
13 }
14 // ================================================================
15 // createBackground: Crea el plano sobre el que dibuja el video
16 void VideoManager::createBackground(int cols, int rows){
17 Ogre::TexturePtr texture=Ogre::TextureManager::getSingleton().
18 createManual("BackgroundTex", // Nombre de la textura
19 Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
20 Ogre::TEX_TYPE_2D, // Tipo de la textura
21 cols, rows, 0, // Filas, columas y Numero de Mipmaps
22 Ogre::PF_BYTE_BGRA,
23 Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY_DISCARDABLE);
24
31.23. Integracin con OpenCV y Ogre [1075]

25 Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().


26 create("Background",
27 Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
28 mat->getTechnique(0)->getPass(0)->createTextureUnitState();
29 mat->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false);
30 mat->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false);
31 mat->getTechnique(0)->getPass(0)->setLightingEnabled(false);
32 mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->
33 setTextureName("BackgroundTex");
34
35 // Creamos un rectangulo que cubra toda la pantalla
36 Ogre::Rectangle2D* rect = new Ogre::Rectangle2D(true);
37 rect->setCorners(-1.0, 1.0, 1.0, -1.0);
38 rect->setMaterial("Background");
39
40 // Dibujamos el background antes que nada
41 rect->setRenderQueueGroup(Ogre::RENDER_QUEUE_BACKGROUND);
42
43 Ogre::SceneNode* node = _sceneManager->getRootSceneNode()->
44 createChildSceneNode("BackgroundNode");
45 node->attachObject(rect);
46 }
47 // ================================================================
48 // UpdateFrame: Actualiza los punteros de frame Ipl y frame Mat
49 void VideoManager::UpdateFrame(){
50 _frameIpl = cvQueryFrame(_capture);
51 _frameMat = new cv::Mat(_frameIpl);
52 }
53 // = IplImage* getCurrentFrameIpl =================================
54 IplImage* VideoManager::getCurrentFrameIpl(){ return _frameIpl; }
55 // = IplImage* getCurrentFrameMat =================================
56 cv::Mat* VideoManager::getCurrentFrameMat(){ return _frameMat; }
57 // ================================================================
58 // DrawCurrentFrame: Despliega el ultimo frame actualizado
59 void VideoManager::DrawCurrentFrame(){
60 if(_frameMat->rows==0) return;
61 Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().
62 getByName("BackgroundTex",
63 Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
64 Ogre::HardwarePixelBufferSharedPtr pBuffer = tex->getBuffer();
65
66 pBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
67 const Ogre::PixelBox& pixelBox = pBuffer->getCurrentLock();
68
69 Ogre::uint8* pDest = static_cast<Ogre::uint8*>(pixelBox.data);
70 for(int j=0;j<_frameMat->rows;j++) {
71 for(int i=0;i<_frameMat->cols;i++) {
72 int idx = ((j) * pixelBox.rowPitch + i )*4;
73 pDest[idx] = _frameMat->data[(j*_frameMat->cols+i)*3];
74 pDest[idx+1] = _frameMat->data[(j*_frameMat->cols+i)*3+1];
75 pDest[idx+2] = _frameMat->data[(j*_frameMat->cols+i)*3+2];
76 pDest[idx+3] = 255;
77 }
78 }
79 pBuffer->unlock();
80 Ogre::Rectangle2D* rect = static_cast<Ogre::Rectangle2D*>
81 (_sceneManager->getSceneNode("BackgroundNode")->
82 getAttachedObject(0));
83 }

El mtodo createBackground se encarga de crear un plano sobre el que se actuali-


zar el vdeo obtenido de la cmara. Adems de las propiedades
relativas a desactivar
el uso del depthbuffer o la iluminacin (lneas 29-31 ), es importante indicar que el
buffer asociado a ese material va a ser actualizado muy
frecuentemente, mediante la
constante de uso del buffer denida en la lnea 23 ). Posteriormente crearemos un
C31
[1076] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS



rectngulo 2D (coordenadas paramtricas en la lnea 37 ), que aadiremos al grupo
41). Finalmente se aade el nodo, con el plano y la
que se dibujar primero (lnea
textura, a la escena (lneas 43-45 ) empleando el puntero al SceneManager indicado
en el constructor.
La actualizacin de la textura asociada al plano se realiza en el mtodo DrawCu-
rrentFrame, que se encarga de acceder a nivel de pxel y copiar el valor de la imagen
por la webcam. Para ello se obtiene un puntero compartido al pixel
obtenida buffer (l-
nea 64 ), y obtiene el uso de la memoriacon exclusin mutua (lnea 67 ). Esa regin
ser posteriormente liberada en la lnea 79 . La actualizacin
de la regin se realiza
recorriendo el frame en los bucles de las lneas 70-77 , y especicando el color de
pxel en valores RGBA (formato de orden de componentes congurado en lnea
cada
22 ).
El frame obtenido ser utilizado igualmente por ARToolKit para detectar las
marcas y proporcionar la transformacin relativa. Igualmente se ha denido una clase
para abstraer del uso de ARToolKit a la aplicacin de Ogre. El archivo de cabecera del
ARTKDetector se muestra a continuacin.
Listado 31.30: ARTKDetector.h
1 #include <AR/ar.h>
2 #include <AR/gsub.h>
3 #include <AR/param.h>
4 #include <Ogre.h>
5 #include <iostream>
6 #include "cv.h"
7
8 class ARTKDetector {
9 private:
10 ARMarkerInfo *_markerInfo;
11 int _markerNum;
12 int _thres;
13 int _pattId;
14 int _width; int _height;
15 double _pattTrans[3][4];
16 bool _detected;
17
18 int readConfiguration();
19 int readPatterns();
20 void Gl2Mat(double *gl, Ogre::Matrix4 &mat);
21
22 public:
23 ARTKDetector(int w, int h, int thres);
24 ~ARTKDetector();
25 bool detectMark(cv::Mat* frame);
26 void getPosRot(Ogre::Vector3 &pos, Ogre::Vector3 &look,
27 Ogre::Vector3 &up);
28 };

En este sencillo ejemplo nicamente se han creado dos clases para la deteccin de Compltame!
la marca detectMark, y para obtener los vectores de posicionamiento de la cmara en
Sera conveniente aadir nuevos
funcin de la ltima marca detectada (getPosRot). mtodos a la clase ARTKDetector,
que utilice una clase que encapsule
Listado 31.31: ARTKDetector.cpp los marcadores con su ancho, alto,
1 #include "ARTKDetector.h" identicador de patrn, etc...
2
3 ARTKDetector::ARTKDetector(int w, int h, int thres){
4 _markerNum=0; _markerInfo=NULL; _thres = thres;
5 _width = w; _height = h; _detected = false;
6 readConfiguration();
7 readPatterns();
8 }
9 ARTKDetector::~ARTKDetector(){ argCleanup(); }
10 // ================================================================
31.23. Integracin con OpenCV y Ogre [1077]

11 // readPatterns: Carga la definicion de patrones


12 int ARTKDetector::readPatterns(){
13 if((_pattId=arLoadPatt("data/simple.patt")) < 0) return -1;
14 return 0;
15 }
16 // ================================================================
17 // readConfiguration: Carga archivos de configuracion de camara...
18 int ARTKDetector::readConfiguration(){
19 ARParam wparam, cparam;
20 // Cargamos los parametros intrinsecos de la camara
21 if(arParamLoad("data/camera_para.dat",1, &wparam) < 0) return -1;
22
23 arParamChangeSize(&wparam, _width, _height, &cparam);
24 arInitCparam(&cparam); // Inicializamos la camara con param"
25 return 0;
26 }
27 // ================================================================
28 // detectMark (FIXME): Ojo solo soporta una marca de tamano fijo!
29 bool ARTKDetector::detectMark(cv::Mat* frame) {
30 int j, k;
31 double p_width = 120.0; // Ancho de marca... FIJO!
32 double p_center[2] = {0.0, 0.0}; // Centro de marca.. FIJO!
33
34 _detected = false;
35 if(frame->rows==0) return _detected;
36 if(arDetectMarker(frame->data, _thres,
37 &_markerInfo, &_markerNum) < 0){
38 return _detected;
39 }
40 for(j=0, k=-1; j < _markerNum; j++) {
41 if(_pattId == _markerInfo[j].id) {
42 if (k == -1) k = j;
43 else if(_markerInfo[k].cf < _markerInfo[j].cf) k = j;
44 }
45 }
46 if(k != -1) { // Si ha detectado el patron en algun sitio...
47 arGetTransMat(&_markerInfo[k], p_center, p_width, _pattTrans);
48 _detected = true;
49 }
50 return _detected;
51 }
52 // ================================================================
53 // Gl2Mat: Utilidad para convertir entre matriz OpenGL y Matrix4
54 void ARTKDetector::Gl2Mat(double *gl, Ogre::Matrix4 &mat){
55 for(int i=0;i<4;i++) for(int j=0;j<4;j++) mat[i][j]=gl[i*4+j];
56 }
57 // ================================================================
58 // getPosRot: Obtiene posicion y rotacion para la camara
59 void ARTKDetector::getPosRot(Ogre::Vector3 &pos,
60 Ogre::Vector3 &look, Ogre::Vector3 &up){
61 if (!_detected) return;
62
63 double glAuxd[16]; Ogre::Matrix4 m;
64 argConvGlpara(_pattTrans,glAuxd);
65 Gl2Mat(glAuxd, m); // Convertir a Matrix4 de Ogre
66
67 m[0][1]*=-1; m[1][1]*=-1; m[2][1]*=-1; m[3][1]*=-1;
68 m = m.inverse();
69 m = m.concatenate(Ogre::Matrix4(
70 Ogre::Quaternion(Ogre::Degree(90), Ogre::Vector3::UNIT_X)));
71 pos = Ogre::Vector3 (m[3][0], m[3][1], m[3][2]);
72 look = Ogre::Vector3 (m[2][0]+m[3][0], m[2][1]+m[3][1],
73 m[2][2]+m[3][2]);
74 up = Ogre::Vector3 (m[1][0], m[1][1], m[1][2]);
75 }

Esta clase est preparada para trabajar con una nica marca (cargada en el mtodo
privado readPatterns), consus variables
asociadas denidas como miembros de la cla-
se ARTKDetector (lneas 11-16 ). El siguiente listado implementa el ARTKDetector.
C31
[1078] CAPTULO 31. INTERFACES DE USUARIO AVANZADAS

una
El mtodo getPosRot es el nico que vamos a comentar (el resto son traduccin
directa de la funcionalidad estudiada anteriormente). En la lnea 65 se utiliza un
mtodo de la clase para transformar la matriz de OpenGL a una Matrix4 de Ogre.
Ambas matrices son matrices columna!, por lo que habr que tener cuidado a la hora
de trabajar con ellas (el campo de traslacin viene denido en la la inferior, y no en

la columna de la derecha como es el convenio habitual).
Las operaciones de la lnea 67 sirven para invertir el eje Y de toda la matriz,
para cambiar el criterio del sistema de ejes de mano izquierda a mano derecha (Ogre).
Invertimos la matriz y aplicamos una rotacin de 90 grados en X para tener el mismo

sistema con el que hemos trabajado en sesiones anteriores del curso.
Finalmente calculamos los vectores de pos, look y up (lneas 71-74 ) que
utilizaremos en el bucle de dibujado de Ogre.
El uso de estas clases de utilidad es directa desde el FrameListener. Ser necesario

crear dos variables miembro del VideoManager y ARTKDetector (lneas 7-8 ). En
el mtodo
frameStarted bastar con llamar a la actualizacin del frame (lneas
15-16
). La deteccin de la marca se realiza pasndole el puntero de tipo Mat del
VideoManager al mtodo detectMark del Detector.

Listado 31.32: MyFrameListener.cpp


1 #include "MyFrameListener.h"
2
3 MyFrameListener::MyFrameListener(Ogre::RenderWindow* win,
4 Ogre::Camera* cam, Ogre::SceneNode *node,
5 Ogre::OverlayManager *om, Ogre::SceneManager *sm) {
6 // Omitido el resto del codigo...
7 _videoManager = new VideoManager(1, 640, 480, _sceneManager);
8 _arDetector = new ARTKDetector(640, 480, 100);
9 }
10
11 bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
12 // Omitido el resto del codigo...
13 Ogre::Vector3 pos; Ogre::Vector3 look; Ogre::Vector3 up;
14
15 _videoManager->UpdateFrame();
16 _videoManager->DrawCurrentFrame();
17 if (_arDetector->detectMark(_videoManager->getCurrentFrameMat()))
{
18 _arDetector->getPosRot(pos, look, up);
19 _camera->setPosition(pos);
20 _camera->lookAt(look);
21 _camera->setFixedYawAxis(true, up);
22 }
23 }

31.24. Consideraciones Finales


Como no poda ser de otra forma, se han quedado muchas cosas en el tintero
relativas al uso de ARToolKit, como el uso de patrones multimarca, o el trabajo
exhaustivo con cambios entre sistemas de coordenadas. Se recomienda como ejercicio
ampliar la funcionalidad de la clase ARTKDetector para que soporte el uso del
histrico de percepciones y la carga de mltiples marcas.
Anexos
Introduccin a OgreFramework
Anexo A
Sergio Fernndez Durn

C
ada vez que creamos un nuevo proyecto con Ogre, existe un grupo de elementos
que necesitamos instanciar para crear un primer prototipo. Este conjunto de
clases y objetos se repiten con frecuencia para todos nuestros proyectos. En
este captulo estudiaremos los fundamentos de OgreFramework, que ofrece una serie
de facilidades muy interesantes.

A.1. Introduccin
OgreFramework fu creado por Philip Allgaier en 2009 y ofrece al programador
una serie de herramientas para la gestin de Ogre como:

Un sistema de Estado de Juego.


Una interfaz grca de usuario.
Diferentes modos de entrada.
Carga de escenas.
Manipulacin manual de materiales.

El uso de este framework ayuda a separar la entidad del motor de renderizado


de nuestro juego pudiendo combinarlo con nuevos mdulos como: el motor de fsicas
o un middleware de comunicaciones. Estos factores pueden ayudar a gestionar mejor
la coordinacin entre varios motores, como la gestin del tiempo entre el motor de
fsicas y el motor de renderizado.

1081
[1082] ANEXO A. INTRODUCCIN A OGREFRAMEWORK

Existen dos ramas del framework para Ogre, Basic y Advanced. La diferencia
entre los dos es la cantidad de recursos que manejan. Basic OgreFramework contiene
los recursos mnimos necesarios para crear una instancia del motor Ogre y que
muestre una escena como ejemplo. Y Advanced OgreFramework incluye un conjunto
completo de herramientas y clases para una gestin avanzada de nuestra aplicacin.
OgreFramework est desarrollado en C++ sobre Ogre y es multiplataforma, sin
embargo, es necesario realizar algunas modicaciones en el framework para que pueda
funcionar en OSX. Una de las mayores ventajas de utilizar esta tecnologa es la de
poder automatizar la carga inicial del motor y poder congurar Ogre con llamadas
simples a OgreFramework.

A.2. Basic OgreFramework


Una de las mejores formas de iniciarse con Ogre es mediante esta tecnologa. Con
esta herramienta no es necesario conocer la arquitectura del motor para poder crear un
primer ejecutable y ayuda a conocer cuales son sus funciones bsicas. Este paquete de
herramientas incluye:

Inicializacin de Ogre.
Bucle de renderizado personalizable.
Escena bsica.
Gestin de teclado bsica.
Crear capturas de pantallas (Screenshots).
Informacin bsica de la escena (FPS, contador batch. . . ).

Para conseguir la ltima versin de Basic OgreFramework clonaremos el proyecto


con mercurial desde la siguiente direccin:

hg clone https://bitbucket.org/spacegaier/basicogreframework

Cuando descargamos el proyecto, podemos crear un ejecutable de OgreFramework


directamente con los cheros descargados. Una vez hemos compilado y ejecutado el
cdigo de ejemplo, nos encontraremos con la pantalla mostrada en la Figura A.1.
Su estructura es muy sencilla y explicaremos con ms profundidad el funciona-
miento del framework en la siguiente seccin.

A.2.1. Arquitectura
La rama Basic de OgreFramework contiene en exclusiva la instanciacin del motor
de renderizado. Se basa en el patrn Singleton. Cada vez que queramos hacer uso de
alguna llamada al motor ser necesario recuperar el puntero al Singleton del objeto. Figura A.1: Resultado del primer
Dentro de la clase OgreFramework nos encontramos con el arranque de los siguientes ejemplo con Ogre Framework.
gestores:

OgreRoot, Camera, RenderWindow, ViewPort, SceneManager, Log, Ti-


mer, InputManager, etc.
A.2. Basic OgreFramework [1083]

Todos estos gestores de la clase OgreFramework sern explicados con ms detalle


en la siguiente seccin, donde hablaremos de la rama Advanced. Para poder crear un
ejemplo basado en Basic OgreFramework es necesario:

Llamar a las funciones de inicio, conguracin y ejecucin del motor.


Un puntero a una instancia de OgreFramework.
Una entidad y un nodo escena.
Una variable booleana que gestione el bucle principal del juego.

Ahora veremos los pasos necesarios para rellenar de contenido nuestra escena.
Para ello crearemos nodos y escenas igual que las creamos en Ogre pero llamando al
puntero de OgreFramework, como se muestra en el siguiente listado.

Listado A.1: Creacin de escena.


1 void ExampleApp::createScene()
2 {
3 OgreFramework::getSingletonPtr()->
4 m_pSceneMgr->setSkyBox(true, "Examples/Sky");
5 OgreFramework::getSingletonPtr()->
6 m_pSceneMgr->createLight("Light")->setPosition(75,75,75);
7
8 m_pSinbadEntity = OgreFramework::getSingletonPtr()->
9 m_pSceneMgr->createEntity("SinbadEntity", "Sinbad.mesh");
10 m_pSinbadNode = OgreFramework::getSingletonPtr()->
11 m_pSceneMgr->getRootSceneNode()->createChildSceneNode("
SinbadNode");
12 m_pSinbadNode->attachObject(m_pSinbadEntity);
13 }

Como podemos observar, para crear una entidad y un nodo, es necesario acceder
al gestor de escena mediante el puntero Singleton a OgreFramework. Se realizar
la misma accin para crear las luces de la escena y el Skybox. Esta restriccin
nos ayudar a mantener separado el motor Ogre de nuestro modelo y permitir la
integracin con otros mdulos de forma ms homognea para el desarrollador.
Realmente, cuando accedamos a uno de los gestores de OgreFramework como el
SceneManager, utilizaremos las mismas primitivas que utilizabamos en Ogre. Por lo
tanto, podremos usar la documentacin estndar de Ogre.
Para gestionar nuestro bucle del juego necesitaremos la variable booleana m_bShutdown
la cual nos permitir cerrar la aplicacin en cualquier momento. Si se produce un error
inesperado, tambin saldr del bucle principal.
En cada iteracin del bucle:

Se capturan las rdenes introducidas a travs de teclado y ratn para mostrar


nuevos eventos en la siguiente iteracin.
Se guarda el tiempo transcurrido desde el inicio.
Se actualiza el motor Ogre con el tiempo del ltimo frame generado.
Se renderiza un frame.
Se calcula el tiempo transcurrido desde el ltimo frame generado.
[1084] ANEXO A. INTRODUCCIN A OGREFRAMEWORK

A.3. Advanced OgreFramework


Para poder realizar un proyecto ms complejo en Ogre, este framework nos sirve
como ncleo. Este paquete de herramientas incluye lo siguiente:

Inicializacin de OGRE.
Carga de recursos bsicos.
Gestin de dispositivos de entrada basado en OIS.
Personalizacin del bucle de renderizado.
Jerarqua basada en estados.
Cargador de escenas mediante XML con DotSceneLoader.
Interfaz grca de usuario basada en SdkTrays.
Manipulacin de materiales en cdigo.

Para conseguir la ltima versin de Advanced OgreFramework clonaremos el


proyecto con mercurial desde la siguiente direccin:

hg clone https://bitbucket.org/spacegaier/advancedogreframework

Una vez hayamos descargado el proyecto, podremos generar un ejecutable sin


realizar ninguna modicacin. Esta rama de OgreFramework nos proporciona tres
estados distintos en la aplicacin: Menu, Pausa y Juego. Algo bsico para cualquier
prototipo.
Tambin nos proporciona un gestor de interfaz basado en SdkTrays (el mismo que
utiliza Ogre para sus ejemplos) con el que podemos gestionar los estados del juego.
La intefaz incluye por defecto un conjunto de paneles debug que permiten conocer
los FPS, el nombre del estado actual, la posicin de la cmara, panel de ayuda, etc. . .
Podremos agregar y editar Overlays a los estados Men para personalizar nuestra
interfaz. Cada vez que entremos en un estado, se generarn los widgets que aparecen
en pantalla y se crean los elementos de la interfaz especcos para ese estado. Cuando
salimos o pausamos ese estado para entrar en uno nuevo, los elementos de la interfaz
se destruyen y desaparecen de la escena. Podremos modicar los Widgets y elementos
de la interfaz editando sus propiedades y materiales.
Dedicaremos un captulo para explicar detalladamente cmo se gestiona la interfaz
mediante SdkTrays y cmo podemos crear nuevos elementos. Tambin explicaremos
cmo personalizar el diseo de nuestra interfaz. Sin embargo, SdkTrays est diseado Figura A.2: Ejemplo bsico de
para crear una interfaz fcil y sencilla, si queremos realizar una interfaz ms compleja Widgets.
es mejor realizarlo con otra alternativa.
En este ejemplo, todos los recursos de la escena han sido cargados desde un XML
con la herramienta DotSceneLoader. Este chero contiene las propiedades de cada
nodo y se carga de manera dinmica en nuestra escena.

A.3.1. Sistema de Estados


Cada estado creado en nuestra aplicacin incluye las siguientes acciones: entrar,
pausar, resumir y salir. Para que todos los estados mantengan esta condicin se
utiliza herencia de la clase AppState. La clase que gestiona todos los estados es Figura A.4: SdkTrays facilita la
AppStateManager cuya tarea es manejarlos y conectarlos. Para hacer esto, el manager construccin de interfaces sencillas.
A.3. Advanced OgreFramework [1085]

Figura A.3: Algunos de los widgets soportados en el men del ejemplo.

tiene una pila de estados activos y siempre ejecuta el estado de la cima de la pila. En
cualquier momento, se puede conectar otro estado de la pila, que ser usado hasta que
sea eliminado de la misma. En este caso, el manager resume el estado que dej en
pausa al introducir el anterior estado eliminado. En el momento en que la aplicacin
no contiene ningn estado activo, el manager cierra la aplicacin.

A.3.2. Arquitectura
La arquitectura de Advanced OgreFramework parece compleja pero, realmente, es
bastante fcil de entender. Esta herramienta gestiona la aplicacin de Ogre como un
conjunto de estados, que se van introduciendo en una pila. Esta gestin de estados es
la que comnmente hemos utilizado para los proyectos de Ogre.

OgreFramework

En la clase OgreFramework podemos observar que contiene todos los elementos


bsicos para iniciar una estancia de Ogre. OgreFramework contiene los siguientes
gestores:

Root, RenderWindow, ViewPort, Timer, Log, InputManager, SdkTrays-


Manager.

Si queremos aadir ms gestores como el gestor de sonido (SDL, OpenAL, etc. . . )


debemos iniciarlos en esta clase.
[1086] ANEXO A. INTRODUCCIN A OGREFRAMEWORK

El sistema se compone de una nica instancia de Ogre y es accesible desde los


estados de la aplicacin. Para iniciar Ogre utilizaremos la funcin initOgre(), que
crea los gestores necesarios para su funcionamiento como:

Un gestor de registros LogManager.


Crear el Root.
Crea el RenderWindow y Viewport.
Inicia los gestores de eventos de entrada mediante OIS.
Carga los recursos e inicia el Timer.
Establece el SDKTrayManager.

Para actualizar cada frame del motor Ogre, la clase AppStateManager llamar la
funcin updateOgre() para actualizar directamente Ogre. Este mtodo estar vaco
para que cada estado se encargue de actualizar el motor de Ogre, pero es necesario
ubicarlo aqu como tarea central del motor.

DotSceneLoader

Esta herramienta se usa para cargar las escenas en Ogre a partir de un chero XML.
Estos cheros XML son procesados mediante RapidXML; DotSceneLoader crea las
estructuras necesarias para ser cargadas en el SceneManager de Ogre. Si deseamos
aadir fsicas a un nodo, tendremos que aadir aqu su implementacin.

Listado A.2: Carga de escena DotScene.


1 DotSceneLoader* pDotSceneLoader = new DotSceneLoader();
2 pDotSceneLoader->parseDotScene("CubeScene.xml", "General",
3 m_pSceneMgr, m_pSceneMgr->getRootSceneNode());
4 Ogre::Entity* cube = m_pSceneMgr->getEntity("Cube");

Podemos ver que para crear una escena es necesario introducir 4 argumentos: el
chero, el nombre del nodo, el SceneManager que se est gestionando en este estado
y el nodo donde queremos introducirlo. Es muy interesante poder cargar las escenas
desde un chero XML por que podremos crearlas y destruirlas de manera dinmica,
permitiendo una mejor gestin de la memoria y del nivel del juego.
Normalmente, se pueden agrupar las escenas de un nivel por bloques e ir cargando
cada uno cuando sea necesario. Si, por ejemplo, nos encontramos con un juego
tipo endless-running, es interesante ir destruyendo las escenas que quedan detrs
del personaje ya que no vam a ser accesibles; tambin es interesante para ir auto-
generando la escena de forma aleatoria hacia adelante.
Cada vez que carguemos una escena, podremos recuperar una entidad como se
describe en el ejemplo; por un nombre bien conocido. Este nombre estar asignado en
el chero XML y podremos recuperar el nodo desde el propio juego. De esta manera
podremos cambiar las propiedades o materiales de dicha entidad al vuelo como, por
ejemplo, aadir un cuerpo rgido al motor de fsicas y conectarlo a la entidad.
A.3. Advanced OgreFramework [1087]

AppState

En este archivo se denen dos clases. La primera clase ser la que herede el
AppStateManager que gestiona los estados, pero est denido en esta clase por
cuestiones de diseo. Esta contiene los mtodos necesarios para la gestin de los
estados, estos mtodos son abstractos y estn denidos en la clase AppStateManager.
La segunda clase ser AppState la cul ser la clase de la que hereden todos los
estados que se implementen en el juego. Esta clase contiene una serie de mtodos
para mantener la consistencia entre estados y poder tratar cada uno como estado
independiente. En l se incluyen los mtodos: enter, exit, pause, resume y update.
Estos mtodos nos permitirn sobre todo mantener la interfaz organizada en cada
estado como, por ejemplo, el track de msica que se est escuchando en cada estado.
Cuando creamos un estado nuevo y lo agregamos a la pila de estados.

AppStateManager

Esta clase ser la encargada de gestionar todos los estados. Tendr los mecanismos
para pausar, recuperar y cambiar un estado en cualquier instante del juego. Hereda
directamente del AppStateListener e implementa sus mtodos abstractos. Contiene
dos vectores:

Un vector para todos los estados existentes.


Un vector para los estados activos.

Para cada estado existirn una serie de valores como su nombre, informacin y su
estado. Cada vez que se cree un estado, se llamar al mtodo manageAppState() para
que introduzca dicha informacin al estado y lo inserte en la pila de estados existentes.
En esta clase se encuentra el bucle principal del juego, dentro del mtodo start().
Dentro de este mtodo podemos destacar el clculo del tiempo de un frame a otro.
Aqu podremos realizar las modicaciones necesarias si necesitamos coordinar el
motor de renderizado con el motor de fsicas. Para poder arreglar el problema en
el desfase de tiempo entre ambos motores se ajusta un valor delta que discretiza el
tiempo entre un frame y otro. Esta tcnica nos ayudar a manejar correctamente el
tiempo en ambos motores para que se comporte de manera natural.

Listado A.3: Bucle principal del juego.


1 double t = 0.0;
2 const double dt = 0.01;
3 double currentTime = tiempo_actual_en_segundos();
4 double accumulator = 0.0;
5 while ( !quit ) {
6 double newTime = time(); // Tiempo actual en segundos
7 double frameTime = newTime - currentTime; // Tiempo de un frame
8 if ( frameTime > 0.25 )
9 frameTime = 0.25; // Tiempo maximo de espera entre frames
10 currentTime = newTime;
11 accumulator += frameTime;
12
13 while ( accumulator >= dt ) {
14 updateOgre(dt);
15 accumulator -= dt;
16 }
17
18 const double alpha = accumulator / dt;
19 renderOneFrame();
20 }
[1088] ANEXO A. INTRODUCCIN A OGREFRAMEWORK

MenuState

La clase MenuState ser una de las implementaciones de la clase AppState.Por


herencia se asegura que todos los estados tienen los mismos mtodos comunes
y funciones como enter(), exit(), pause(), resume() y update(). Estos mtodos se
ejecutarn automticamente por el AppStateManager. Esto es, si el estado actual
fue pausado, ejecutar el mtodo resume y seguir la ejecucin del bucle principal.
Igual para el resto de estados. En todos los estados se escribir la macro DECLARE-
_APPSTATE_CLASS(MenuState) que insertar el nombre del estado para poder ser
recuperado posteriormente.
Dentro de la funcin enter() de MenuState se crea un SceneManager, se crea la
cmara, se construye la interfaz grca de usuario y se llama al creador de escena. En
este caso el creador de escena no tendr nada puesto que ser un estado en el que se
base en la interfaz grca creada por SdkTrays y no en Ogre.
En el mtodo exit() estarn todos los destructores de escena, interfaz y cmara.
De esta forma aseguramos la integridad entre estados. Los manejadores de entrada
(teclado/ratn) tambin estarn presentes en todos los estados y servirn para capturar
los eventos que se realicen en la interfaz.
La funcin update() actualiza los elementos de la interfaz como, por ejemplo, si el
ratn est encima de un botn cambia la imagen; si, por otro lado, se pulsa el botn,
la imagen cambia de nuevo. Adems este mtodo capturar si se ha enviado la orden
de salir del sistema para nalizar la aplicacin.
Y por ltimo, el mtodo buttonHit() es un callback que se accionar cuando se
pulse un botn. Dentro de este mtodo podremos identicar que botn se ha accionado
y realizar una accin consecuente con ello.

GameState

Toda la lgica del juego est dentro del estado GameState. Aqu es donde
enlazaremos todos nuestros elementos de nuestro juego, como por ejemplo: el
motor de fsicas, el controlador de nuestro personaje principal, mdulos de inteligencia
articial, etc. En el caso de ejemplo de OgreFramework, incluye una escena mnima
que carga una escena con DotSceneLoader y rellena de contenido la pantalla.
Para crear los elementos necesarios de la interfaz se invoca a builGUI() para in-
sertar todos los elementos necesarios del estado mediante el gestor SdkTraysManager
que explicaremos en la siguiente seccin.
Para capturar los eventos de entrada por teclado, se utilizarn los mtodos
keyPressed() y keyReleased() para realizar los eventos correspondientes a si se pulsa
una tecla o se ha dejado de pulsar respectivamente. En nuestro caso, tendremos una
serie de condiciones para cada tecla que se pulse, las teclas son capturadas mediante
las macros denidas en la librera OIS.

A.4. SdkTrays
Tanto los ejemplos que vienen con el SDK de Ogre (SampleBrowser), como
OgreFramework utilizan SdkTrays. Este gestor de la interfaz se construy con el
propsito de ser sencillo, crear interfaces de una manera rpida y sin tanta complejidad
como CEGUI. El sistema de SdkTrays est basado en Ogre::Overlay por lo que nos
garantiza su portabilidad a todas las plataformas.
A.4. SdkTrays [1089]

A.4.1. Introduccin
El sistema de interfaz de SdkTrays se organiza en una serie de paneles (trays), que
se localizan en nueve de posiciones de nuestra pantalla. Cada introducimos un nuevo
Ogre::Overlay en nuestra escena, debemos calcular las coordenadas (x,y) del frame
actual, aadiendo complejidad a la hora de disear una interfaz. Cuando creamos un
widget nuevo con SdkTrays, debemos pasarle una posicin de las nueve posibles y
el tamao. Si ya existe una un widget en esa regin, el nuevo widget se colocar
justo debajo, manteniendo las proporciones de la pantalla y redimiensionando el panel
actual donde se encuentra dicho widget.

A.4.2. Requisitos
SdkTrays necesita la biblioteca OIS para la captura de datos de entrada mediante
ratn o teclado. Una vez que se han incluido estas dependencias al proyecto
simplemente es necesario incluir SdkTrays.h en cada clase. Y tambin es necesario
incluir el chero SdkTrays.zip a nuestro directorio de recursos, normalmente en
/media/packs.

A.4.3. SdkTrayManager
Para utilizar SdkTrays, es necesario crear un SdkTrayManager. Esta es la clase a
travs de la cual se van a crear y administrar todos los widgets, manipular el cursor,
cambiar la imagen de fondo, ajustar las propiedades de los paneles, mostrar dilogos,
mostrar / ocultar la barra de carga, etc. El SdkTrayManager requiere SdkTrays.zip,
por lo que slo se puede crear despus de cargar ese recurso. Es recomendable
asegurarse de que est utilizando el espacio de nombres OgreBites para poder incluir
las macros de localizacin. Estas macros permiten crear un widget en las esquinas, en
la parte central de la pantalla y en la parte central de los bordes.

A.4.4. Widgets
Existen 10 tipos de widgets bsicos. Cada widget es slo un ejemplo de
una plantilla de OverlayElement. Cada vez que se crea un widget es necesario
introducir las medidas en pixeles. Cada widget est identicado con un nombre
y se gestionar su creacin y destruccin mediante SdkTrayManager. Algunos de los
widgets predenidos que podemos crear son los siguientes:

Button, TextBox, SelectMenu, Label, Separator, Slider, ParamsPanel, Check-


Box, DecorWidget, ProgressBar, FrameStats, Loading Bar, Question Dia-
log.

A.4.5. SdkTrayListener
Esta clase contiene los controladores para todos los diferentes eventos que pueden
disparar los widgets. La clase SdkTrayManager es, en s misma, un SdkTrayListener
porque responde a los eventos de los widgets. Algunos widgets dan la opcin de no
disparar un evento cuando cambia su estado. Esto es til para inicializar o restablecer
un widget, en cuyo caso no debe haber una respuesta de cualquier tipo.
[1090] ANEXO A. INTRODUCCIN A OGREFRAMEWORK

A.4.6. Skins
SdkTrays no est orientado a una personalizacin profunda de los widgets como
en CEGUI. Pero eso no signica que no podamos cambiar su apariencia del modelo
estndar. Los recursos de las imgenes que utilizan los widgets se encuentran en el
chero SdkTrays.zip.
Si descomprimimos los recursos y modicamos el contenido, podemos persona-
lizar la apariencia de los widgets. Las imgenes que se muestran tienden a ser muy
pequeas con formas muy concretas. Este tipo de imgenes se utiliza para reducir el
espacio necesario para cada widget y que sea reutilizable por recursos. Si queremos
modicar la apariencia de un widget es recomendable seguir las mismas formas y
cambiar slo los colores o tambin podemos optar por cambiar la apariencia a co-
lores slidos/planos. Las imgenes del paquete SdkTrays.zip son PNG, por lo que
podemos crear transparencias si lo deseamos o sombreados.
La personalizacin es limitada, pero con diseo y creatividad se pueden conseguir
apariencias bastante diferentes de la original de OgreFramework.

A.4.7. OgreBites
Podemos observar como se distribuyen los widgets por regiones en la pantalla.
Estas posiciones vienen dadas por el espacio de nombres OgreBites, la cual incluye
una serie de macros que instancia los widgets en la regin deseada. Observamos
adems que si por ejemplo agregamos seis widgets en la parte inferior derecha, los
widgets se agrupan en un columna tipo pila. Este orden es el mismo que nosotros
agregaremos en las lineas de cdigo y las macros para instanciar los widgets son las
siguientes:

TL_TOPLEFT, TL_TOPCENTER, TL_TOPRIGHT, TL_LEFT, TL_CENTER,


TL_RIGHT, TL_BOTTOMLEFT, TL_BOTTOMCENTER, TL_BOTTOMRIGHT.

A.5. btOgre
btOgre es un conector ligero entre BulletOgre. Este conector no proporciona un
RigidBody ni CollisionShape directamente. En lugar de eso, se accede directamente
al motor correspondiente. La nica accin que realiza btOgre es la de conectar los
btRigidBodies con SceneNodes. Tambin puede convertir las mallas (mesh) de Ogre a
guras convexas en Bullet, proporcionando un mecanismo de debug de Bullet como
observamos en la imagen y herramientas de conversin de un vector de Bullet a un
quaternio de Ogre (ver Figura A.5).
En este ejemplo (listado A.4)comprobamos como btOgre conecta el Nodo de Ogre
con la forma convexa de Bullet. Cada vez que se aplique un cambio en el mundo fsico
de Bullet se vern sus consecuencias en el Nodo de Ogre. Esto permite la integracin
de los dos motores y el acceso independiente a las propiedades del objeto en ambos
mundos.
Cada vez que actualicemos el bucle principal del juego ser necesario hacer una
llamada a stepSimulation con el tiempo calculado entre frame y frame.

Listado A.5: Conguracin de la escena.


1 // Creamos el objeto debug para Bullet
A.5. btOgre [1091]

Figura A.5: Modo Debug con simulacin fsica en btOgre.

2 BtOgre::DebugDrawer m_pDebugDrawer =
3 new BtOgre::DebugDrawer(OgreFramework::getSingletonPtr()->
4 m_pSceneMgr->getRootSceneNode(), m_pDynamicsWorld);
5 m_pDynamicsWorld->setDebugDrawer(m_pDebugDrawer);
6
7 // Actualizamos el motor Bullet
8 m_pDynamicsWorld->stepSimulation(timeSinceLastFrame);
9 m_pDebugDrawer->step();
[1092] ANEXO A. INTRODUCCIN A OGREFRAMEWORK

Listado A.4: Conguracin de Bullet.


1 //Creamos una Entidad y un Nodo en Ogre.
2 m_pSphereEntity = OgreFramework::getSingletonPtr()->
3 m_pSceneMgr->createEntity("SphereEntity", "Sphere.mesh");
4 m_pSphereNode = OgreFramework::getSingletonPtr()->
5 m_pSceneMgr->getRootSceneNode()->createChildSceneNode("SphereNode
");
6 m_pSphereNode->attachObject(m_pSphereEntity);
7
8 //Creamos la forma de la malla en Bullet.
9 BtOgre::StaticMeshToShapeConverter converter(m_pSphereEntity);
10 m_pSphereShape = converter.createSphere();
11
12 // Insertamos la masa al cuerpo rigido.
13 btScalar mass = 5;
14 btVector3 inertia;
15 m_pSphereShape->calculateLocalInertia(mass, inertia);
16
17 // Creamos un estado de BtOgre (conectamos Ogre y Bullet).
18 BtOgre::RigidBodyState *state = new BtOgre::RigidBodyState(
m_pSphereNode);
19
20 // Creamos el Body.
21 BtOgre::btRigidBody m_pRigidBody =
22 new btRigidBody(mass, state, m_pSphereShape, inertia);
23 // Mundo fisico de Bullet (btDynamicsWorld).
24 m_pDynamicsWorld->addRigidBody(m_pRigidBody);

A.6. Referencias
http://www.ogre3d.org/tikiwiki/Basic+Ogre+Framework
http://www.ogre3d.org/tikiwiki/Advanced+Ogre+Framework
http://bitbucket.org/spacegaier/basicogreframework
http://bitbucket.org/spacegaier/advancedogreframework
http://gafferongames.com/game-physics/fix-your-timestep/
http://ogrees.wikispaces.com/Tutorial+Framework+Avanzado+
de+Ogre
http://www.ogre3d.org/tikiwiki/SdkTrays
http://www.ogre3d.org/tikiwiki/OgreBites
http://github.com/nikki93/btogre
Vault Defense al detalle
Anexo B
David Frutos Talavera

E
n este anexo se explicarn ciertos detalles del juego Vault Defense. Detalles de
su GUI (Graphical User Interface), interfaz que usa CEGUI y con el cual, se
han creado unos botones propios, una pantalla de carga y un efecto de cmara
manchada de sangre, tambin se explicar al detalle el efecto de parpadeo de las luces,
usado para darles un aspecto de luz de antorcha, y por ltimo los enemigos, unos
agentes de estado que implementan ciertos comportamientos.
Pero antes de explicar dichos detalles del juego, vamos a explicar cmo congurar
Visual C++ 2010 Express 1 para poder compilar nuestros juegos con OGRE + CEGUI
para la plataforma Windows.

B.1. Conguracin en Visual 2010 Express


Este apartado es un "tutorial"paso a paso de cmo congurar bien un proyecto de
Visual C++ para poder compilar nuestros juegos. Hay cosas que se dan por entendidas
y otras que, aunque puedan parecer muy sencillas, se explicarn para no dejar ningn
cabo suelto. Antes de nada lo primero es bajarse la versin correcta de CEGUI y
OGRE, en el momento de escribir este apartado la versin de OGRE era la 1.8.1 y la
0.7.9 de CEGUI.
1 http://www.microsoft.com/visualstudio/eng/downloads

1093
[1094] ANEXO B. VAULT DEFENSE AL DETALLE

En la seccin de Downloads de la Web de CEGUI 2 encontraremos la versin


0.7.9, de dicho apartado necesitamos descargar el cdigo para Windows y las
dependencias para MSVC++ 2010. Los descomprimimos donde queramos, eso s, las
dependencias para MSVC++ se tienen que colocar dentro de la carpeta de CEGUI-
0.7.9.
Para OGRE solo necesitaremos descargar el SDK de OGRE3 . En concreto, esta
versin .OGRE 1.8.1 SDK for Visual C++ .Net 2010 (32-bit)". Como ya ocurra con
CEGUI este archivo se puede descomprimir donde queramos.
Despus de esto se pueden congurar ciertos path en Windows, incluso buscar
algn template para Visual C++, pero es mejor aprender cmo se congura bien un
proyecto, y qu mejor manera de aprender que compilar las bibliotecas de CEGUI.

B.1.1. Compilando CEGUI en Windows


Para este paso iremos a la carpeta CEGUI-0.7.9/projects/premake. En dicha

carpeta nos toca preparar el archivo .bat que vamos a ejecutar. Antes de nada,
abriremos el archivo cong.lua y buscaremos por la linea 119 esta seccin:

-----------

-- Renderers

-- this controls which renderer modules are built

En este apartado del archivo podemos congurar qu proyectos queremos crear


para Visual C++. Para ello, pondremos a true la variable OGRE_RENDERER .
Con esto, el archivo estar bien congurado para nuestras necesidades, y aunque se
puede congurar mejor para que ponga las rutas bien desde el principio es mejor
aprender dnde se congura el proyecto en el programa. Ahora ejecutaremos el
archivo build_vs2008.bat , aunque es un proyecto de 2008 se podr cargar con Visual
C++ 2010, se generar un archivo CEGUI.sln, dicho archivo ser abierto con el
Visual C++. cuando lo abramos, aceptaremos la conversin del proyecto sin tocar
las opciones y ya podremos pasar a congurar las rutas.
Con el proyecto ya cargado y con la conversin ya realizada podemos pasar
a congurar las rutas. Lo primero es saber que Visual C++ mantiene 2 tipos de
conguraciones: una para modo debug y otra para modo Release, se pueden congurar
muchas ms, pero no ser necesario. Comprobamos si estn bien las rutas, que tienen
que estarlo en el proyecto CEGUIBase, pulsaremos botn derecho/propiedades sobre
el proyecto, en el explorador de soluciones y nos saldr una ventana de conguracin
(ver gura B.1), o con el proyecto seleccionado Alt + Enter. Observamos que esta
activo el modo Release, tambin har falta comprobar las rutas para el modo Debug.
Esto es lo que veremos:

1. Apartado C/C++ / general (Figura B.2). En dicho apartado se conguran los in-
cludes del proyecto, podemos observar que en CEGUIBase se aaden ../.
./../cegui/include; y, por otra parte, ../../../dependencies/
incluye; por eso es necesario colocar la carpeta dependencias en la carpeta
de CEGUI.
2. Vinculador / General (Figura B.3). Aqu es donde diremos dnde estn nuestras
bibliotecas (las carpetas Lib).
2 http://www.cegui.org.uk/wiki/index.php/Downloads
3 http://www.ogre3d.org/download/sdk
B.1. Conguracin en Visual 2010 Express [1095]

Figura B.1: Men del proyecto.

3. Vinculador / Entrada (Figura B.4). En este apartado se colocan los nombre de las
bibliotecas adicionales, es decir, las bibliotecas que estn en las rutas aadidas
en general y que vayamos a usar.

Ahora lo siguiente es comprobar que CEGUIOgreRenderer est bien congura-


do repasando dichos apartados. Por defecto el cong.lua tiene la ruta de Ogre pero
mal puesta, tendremos que congurar todos los apartados. Al nal quedar algo as,
suponiendo que lo tenis instalado en la raz.
Con las rutas bien conguradas pasamos a compilar toda la solucin, esto creara
una serie de archivos en la carpeta CEGUI-0.7.9/lib y CEGUI-0.7.9/bin, algunos de
estos archivos seran necesarios para poder compilar nuestros juegos.

B.1.2. Congurando OGRE en Windows


Explicado lo anterior pasamos a congurar Ogre en Windows.
[1096] ANEXO B. VAULT DEFENSE AL DETALLE

Figura B.2: Conguracin de los directorios de inclusin. CEGUI.

Conociendo ya todos los apartados de la conguracin del proyecto, solo ser


necesario enlazar bien todo, en includes aadiremos los includes de CEGUI y dems
cosas que necesitemos (SDL, Xerces, etc...) y en el vinculador aadiremos las
bibliotecas necesarias (ver guras B.5, B.6 y B.7). Podemos ver la conguracin
necesaria para compilar el juego Vault Defense.
Una vez congurado el proyecto de OGRE pasamos a compilarlo. Recordad que
aqu tambin tendremos una conguracin release y una debug, es necesario saber
cual estamos realizando para saber que .dlls necesitaremos para la ejecucin del juego.
Tambin decir que necesitaremos compilar CEGUI para dicha versin.
Una vez compilado todo buscaremos la carpeta donde se ha generado el archivo
ejecutable y aadiremos los archivos necesarios. Los principales son ogre.cfg, plu-
gins.cfg y resources.cfg. El nico que hace falta editar, si estamos usando uno de
GNU/Linux, es el plugins.cfg. En la variable PluginFolder pondremos la ruta de la
carpeta que contendra los .dlls de los plugins de OGRE, en nuestor caso la hemos lla-
mado plugins que ser la carpeta que tendremos que crear en el directorio del juego.
Adems comprobaremos que estamos cargando los plugins correctos; si hemos com-
pilado todo en modo debug tenemos que cargar los plugins correctos aadiendo _d al
nombre.
Un ejemplo de archivo para un juego compilado en modo debug sera ste.

# Directorio donde estn los plugins


PluginFolder=plugins

Plugin=RenderSystem_GL_d
B.1. Conguracin en Visual 2010 Express [1097]

Figura B.3: Conguracin de las bibliotecas adicionales. CEGUI.

Figura B.4: Conguracin de las dependencias adicionales. CEGUI.


[1098] ANEXO B. VAULT DEFENSE AL DETALLE

Figura B.5: Conguracin de los directorios de inclusin. OGRE

Figura B.6: Conguracin de las bibliotecas adicionales. OGRE

Plugin=Plugin_ParticleFX_d

Ya con estos 3 archivos aadidos solo falta crear una carpeta plugins y aadir los
.dlls correspondientes dentro de ella.
Con los plugins colocados pasamos a aadir los .dlls que faltan. Estos varan segn
las bibliotecas usadas pero por lo general har falta OgreMain y OIS. Acordaros de
copiar los dlls correspondientes segn vuestra compilacin, si estis en modo debug
usad los que terminan en _d.
B.1. Conguracin en Visual 2010 Express [1099]

Figura B.7: Conguracin de las dependencias adicionales. OGRE

Explicado esto ya podemos pasar a los detalles de Vault Defense.


[1100] ANEXO B. VAULT DEFENSE AL DETALLE

B.2. GUI en Vault Defense


En Vault Defense se han creado unos botones propios, una pantalla de carga y un
efecto de salpicadura en cmara, todo esto realizado con CEGUI.

B.2.1. Botones propios


Aunque CEGUI tiene un botn ya denido, no es un botn muy bonito, es el
tipico botn que se puede usar para un formulario, pero no el botn que se usaria en
un menu inicial de un juego, para estos casos lo mejor es crearse un botn propio. Para
esta solucion es necesario crear archivos scheme, imageset y lookNFeel.
Crearemos un archivo .imageset propio, creando una imagen compuesta con todos
los botones necesarios.
Ahora la parte complicada, el LookNFeel, para este chero podemos adaptar
un chero ya existente, en los botones del Vault Defense se ha usado el archivo de
TaharezLook.looknfeel.

Listado B.1: Fichero TaharezLook.looknfeel


1 <!-- .................... -->
2 <WidgetLook name="VaultDefense/BotonSinglePlayer">
3 <!-- .................... -->
4 <ImagerySection name="normal">
5 <ImageryComponent>
6 <Area>
7 <Dim type="LeftEdge"><AbsoluteDim value="0" /></Dim>
8 <Dim type="TopEdge"><AbsoluteDim value="0" /></Dim>
9 <Dim type="Width"><UnifiedDim scale="1.0" type="Width" />
10 </Dim>
11 <Dim type="Height"><UnifiedDim scale="1.0" type="Height" />
12 </Dim>
13 </Area>
14 <Image imageset="VaultDefense" image="BotonIzq" />
15 <VertFormat type="Stretched" />
16 </ImageryComponent>
17
18 <ImageryComponent>
19 <Area>
20 <Dim type="LeftEdge"><AbsoluteDim value="0" /></Dim>
21 <Dim type="TopEdge"><AbsoluteDim value="0" /></Dim>
22 <Dim type="Width"><UnifiedDim scale="1" type="Width"/>
23 </Dim>
24 <Dim type="Height"><UnifiedDim scale="1" type="Height"/>
25 </Dim>
26 </Area>
27 <Image imageset="VaultDefense" image="BotonDer" />
28 <VertFormat type="Stretched" />
29 <HorzFormat type="RightAligned" />
30 </ImageryComponent>
31
32 <ImageryComponent>
33 <Area>
34 <Dim type="LeftEdge">
35 <ImageDim imageset="VaultDefense" image="BotonIzq"
36 dimension="Width"/>
37 </Dim>
38 <Dim type="TopEdge"><AbsoluteDim value="0" /></Dim>
39 <Dim type="RightEdge">
40 <UnifiedDim scale="1" type="Width">
41 <DimOperator op="Subtract">
42 <ImageDim imageset="VaultDefense"
43 image="BotonDer" dimension="Width"/>
B.2. GUI en Vault Defense [1101]

44 </DimOperator>
45 </UnifiedDim>
46 </Dim>
47 <Dim type="Height"><UnifiedDim scale="1"
48 type="Height" /></Dim>
49 </Area>
50 <Image imageset="VaultDefense"
51 image="BotonSinglePlayerNormal" />
52 <VertFormat type="Stretched" />
53 <HorzFormat type="Stretched" />
54 </ImageryComponent>
55 </ImagerySection>
56
57 <ImagerySection name="hover">
58 <!-- .................... -->
59 </ImagerySection>
60
61 <ImagerySection name="pushed">
62 <!-- .................... -->
63 </ImagerySection>
64
65 <ImagerySection name="disabled">
66 <!-- .................... -->
67 </ImagerySection>
68 <StateImagery name="Normal">
69 <Layer>
70 <Section section="normal" />
71 </Layer>
72 </StateImagery>
73 <StateImagery name="Hover">
74 <Layer>
75 <Section section="hover" />
76 </Layer>
77 </StateImagery>
78 <StateImagery name="Pushed">
79 <Layer>
80 <Section section="pushed" />
81 </Layer>
82 </StateImagery>
83 <StateImagery name="PushedOff">
84 <Layer>
85 <Section section="hover" />
86 </Layer>
87 </StateImagery>
88 <StateImagery name="Disabled">
89 <Layer>
90 <Section section="disabled">
91 <Colours topLeft="FF7F7F7F" topRight="FF7F7F7F"
92 bottomLeft="FF7F7F7F" bottomRight="FF7F7F7F" />
93 </Section>
94 </Layer>
95 </StateImagery>
96 </WidgetLook>

Lo importante de estos archivos es:

1. WidgetLook...: Se dene el nombre del lookNFeel, este identicador se usar


en el scheme.
2. ImagerySection... : Segn el TargetType y el Renderer del archivo scheme estas
secciones tienen un identicador en caso de los botones son:
a) normal: Botn normal, sin nada que lo altere.
b) hover: Botn con el ratn encima.
c) pushed: Botn pulsado.
d) disable: Botn apagado.
[1102] ANEXO B. VAULT DEFENSE AL DETALLE

Dentro de ImagerySection podemos encontrar:

1. ImageryComponent...: Crea un componente a dibujar.


2. Area...: Dene un rea, denida por <Dim>
3. Image...: Dene una imagen que ir dentro del ImageryComponent

stas son las marcas ms usadas. Con estas marcas podemos crear unos botones
propios que acten como nosotros queremos. En el caso del juego Vault Defense los
botones estn creados por 3 zonas, una zona central donde se muestra la imagen del
botn, y 2 zonas, una a cada lado, que se usara en caso de que el botn este en estado
hover o pushed. La imagen BotonDer, BotonIzq es una imagen transparente que solo
se usa para rellenar el hueco, en los estados hover y pushed se pone una imagen distinta
dndole as un efecto curioso a los botones.
El codigo de hover, pushed y disable es similar al de normal, solo que cambia
las imagenes que carga. En hover en los bloques laterales, los que en normal son
BotonDer y BotonIzq cambian por otra imagen, en el caso del Vault Defense por
BotonDerHover y BotonIzqHover, imagenes que muestran un pico y una ballesta, que
apareceran a los lados del botn seleccionado.
Y lo ltimo es el archivo scheme:

Listado B.2: Fichero TaharezLook.scheme


1 <GUIScheme Name="VaultDefense">
2 <Imageset Filename="VaultDefense.imageset" />
3 <LookNFeel Filename="VaultDefense.looknfeel" />
4 <WindowRendererSet Filename="CEGUIFalagardWRBase" />
5 <FalagardMapping WindowType="VaultDefense/BotonSinglePlayer"
6 TargetType="CEGUI/PushButton" Renderer="Falagard/Button"
7 LookNFeel="VaultDefense/BotonSinglePlayer" />
8 <FalagardMapping WindowType="VaultDefense/BotonInstrucciones"
9 TargetType="CEGUI/PushButton" Renderer="Falagard/Button"
10 LookNFeel="VaultDefense/BotonInstrucciones" />
11 <FalagardMapping WindowType="VaultDefense/BotonCreditos"
12 TargetType="CEGUI/PushButton" Renderer="Falagard/Button"
13 LookNFeel="VaultDefense/BotonCreditos" />
14 <FalagardMapping WindowType="VaultDefense/BotonRecords"
15 TargetType="CEGUI/PushButton" Renderer="Falagard/Button"
16 LookNFeel="VaultDefense/BotonRecords" />
17 <FalagardMapping WindowType="VaultDefense/BotonSalir"
18 TargetType="CEGUI/PushButton" Renderer="Falagard/Button"
19 LookNFeel="VaultDefense/BotonSalir" />
20 <FalagardMapping WindowType="VaultDefense/StaticImage"
21 TargetType="DefaultWindow" Renderer="Falagard/
StaticImage"
22 LookNFeel="VaultDefense/StaticImage" />
23 </GUIScheme>

Denimos que imageset vamos a usar, que looknfeel usamos, y luego pasamos a
crear los botones con:

Listado B.3: Creacin de botones.


1 <FalagardMapping WindowType="VaultDefense/BotonCreditos"
2 TargetType="CEGUI/PushButton" Renderer="Falagard/Button"
3 LookNFeel="VaultDefense/BotonCreditos"/>
B.2. GUI en Vault Defense [1103]

En este cdigo podemos ver la creacin de unos botones muy simples, se ha cogido
como base un archivo scheme ya creado y se ha modicado. En WindowType ponemos
el identicador del window para poder usarlo en nuestros layouts y en LookNFeel
ponemos nuestro lookNFeel modicado.
En el cdigo es necesario cargar nuestros propios archivos:

Listado B.4: Carga de archivos.


1 CEGUI::SchemeManager::getSingleton().create
2 ("VaultDefense.scheme");
3
4 CEGUI::ImagesetManager::getSingleton().create
5 ("VaultDefense.imageset");

B.2.2. Pantalla de Carga


Para crear las pantallas de carga en Ogre es necesario conocer la funcin
renderOneFrame()4 de Ogre. Esta funcin obliga a Ogre a renderizar un solo frame;
llamaremos a dicha funcin dentro de la funcin enter del estado donde se quiera
aadir el cargador. Lo ms normal es que sea al comienzo del juego, en la funcin
enter del estado juego.

Listado B.5: Funcin enter() (clase PlayState).


1 void PlayState::enter () {
2
3 _root = Ogre::Root::getSingletonPtr();
4 _sceneMgr = _root->getSceneManager("SceneManager");
5
6 CEGUI::System::getSingleton().setDefaultMouseCursor
7 ("VaultDefense", "BotonDer");
8 CEGUI::Window *guiRoot =
9 CEGUI::WindowManager::getSingleton().loadWindowLayout
10 ("carga.layout");
11 CEGUI::System::getSingleton().setGUISheet(guiRoot);
12 CEGUI::DefaultWindow* staticText = static_cast<CEGUI::
13 DefaultWindow*>(CEGUI::WindowManager::getSingleton().
14 getWindow("carga/texto"));
15 staticText->setText("Generando mundo aleatorio 1-5");
16
17 CEGUI::ProgressBar* progressBar =
18 static_cast<CEGUI::ProgressBar*>
19 (CEGUI::WindowManager::getSingleton().
20 getWindow("carga/barra"));
21 _Renderizando=false;
22 _root->renderOneFrame();
23
24 /* ... */
25 }

La penltima linea, el _renderizando=false, se usa para no ejecutar el cdigo de


la funcin frameStarted(...), tened en cuenta que al ejecutar RenderOneFrame se
estara llamando a frameStarted y es posible que algunas de las acciones que realizamos
dentro de dicha funcin no se puedan realizar.
4 http://www.ogre3d.org/docs/api/html/classOgre_1_1Root.html
[1104] ANEXO B. VAULT DEFENSE AL DETALLE

Este mtodo de carga no calcula el tiempo real de lo que tardan las operaciones,
es una carga por pasos. Se denen una serie de cargas, y segun se van cargando se
va pasando de paso. En resumen, se dividen los calculos en grupos y se realiza un
RenderOneFrame por cada calculo realizado, cuando se realiza el calculo se ejecuta
este cdigo.

Listado B.6: Renderizando frame a frame.


1 progressBar->step();
2 staticText->setText("Calculando caminos 2-5");
3 _root->renderOneFrame();

El step de la barra est denido en el layout y su valor es 1/no grupos:

Listado B.7: GUI Layout.


1 <GUILayout >
2 <Window Type="DefaultWindow" Name="carga" >
3 <Property Name="UnifiedAreaRect"
4 Value="{{0.0025,0},{0,0},{1.0025,0},{1,0}}" />
5 <Window Type="VaultDefense/StaticImage"
6 Name="carga/Fondo" >
7 <Property Name="FrameEnabled" Value="False" />
8 <Property Name="UnifiedAreaRect"
9 Value="{{0,0},{0,0},{1,0},{1,0}}" />
10 <Property Name="BackgroundEnabled" Value="False" />
11 <Property Name="Image"
12 Value="set:VaultDefenseFondo image:Fondo" />
13 <Window Type="TaharezLook/ProgressBar"
14 Name="carga/barra" >
15 <Property Name="StepSize" Value="0.20" />
16 <Property Name="CurrentProgress" Value="0.0" />
17 <Property Name="UnifiedAreaRect"
18 Value="{{0.0975004,0},{0.753333,0},
19 {0.869998,0},{0.803333,0}}" />
20 </Window>
21 <Window Type="TaharezLook/StaticText"
22 Name="carga/texto" >
23 <Property Name="Font" Value="VaultDefense-15" />
24 <Property Name="UnifiedAreaRect"
25 Value="{{0.13875,0},{0.708333,0},
26 {0.828753,0},{0.75,0}}" />
27 <Property Name="BackgroundEnabled" Value="False" />
28 <Property Name="FrameEnabled" Value="False" />
29 </Window>
30 </Window>
31 </Window>
32 </GUILayout>

Esto se puede cambiar, se puede crear una barra propia y hacerle un escalado.
Tambin se puede poner una imagen en el centro e ir cambindola. Existen muchas
maneras, pero sta es la ms sencilla.

B.2.3. Manchas en la cmara


El mtodo creado para crear manchas de sangre en la cmara es muy sencillo. Lo
primero es aadir las imagenes al layout del juego, como una imagen normal,

Listado B.8: Imgenes en el Layout.


1 <Window Type="VaultDefense/StaticImage"
B.2. GUI en Vault Defense [1105]

2 Name="UI/Fondo/Sangre10"><Property Name="Image"
3 Value="set:VaultDefenseSangre2 image:Sangre"/>
4 <Property Name="UnifiedAreaRect"
5 Value="{{0.0,0},{0.0,0},{1,0},{1,0}}"/>
6 <Property Name="BackgroundEnabled" Value="False"/>
7 <Property Name="Alpha" Value="0"/>
8 <Property Name="FrameEnabled" Value="False"/>
9 </Window>

Despus se crea una estructura de datos para almacenar el nombre de la imagen,


su variable alpha y si est activa o no.

Listado B.9: Estructura sangreCEGUI.


1 typedef struct {
2 String nombre;
3 double alpha;
4 bool activo;
5 } sangreCEGUI;

Cuando estemos creando la interfaz pasamos a poner el alpha de todas las


imgenes a 0.

Listado B.10: Estableciendo el valor de alpha a 0


1 for (int i = 1; i < 11; i++) {
2 std::stringstream auxNombre;
3 auxNombre << "UI/Fondo/Sangre" << i;
4 sangreCEGUI aux;
5 aux.nombre=auxNombre.str();
6 aux.alpha=0.0;
7 aux.activo=false;
8 _sangrado.push_back(aux);
9 }

Luego en alguna parte del juego, en el caso de Vault Defense cuando el personaje
recibe un impacto, activamos una de las sangres de manera aleatoria, mostrndola y
poniendo su alpha a 1, durante el enterFrame() vamos decrementando el alpha.

Listado B.11: Activando la sangre.


1 for(int i = 0; i < _sangrado.size(); i++) {
2
3 if(_sangrado[i].activo) {
4 CEGUI::DefaultWindow* staticImage =
5 static_cast<CEGUI::DefaultWindow*>
6 (CEGUI::WindowManager::getSingleton().
7 getWindow(_sangrado[i].nombre));
8
9 _sangrado[i].alpha-=0.5*deltaT;
10
11 std::stringstream auxS;
12 auxS << _sangrado[i].alpha;
13 staticImage->setProperty("Alpha",auxS.str());
14
15 if(_sangrado[i].alpha<=0.0) {
16 _sangrado[i].activo=false;
17 }
18
19 }
20
21 }
[1106] ANEXO B. VAULT DEFENSE AL DETALLE

Esto creara un efecto de salpicon de sangre, creando una mancha en la pantalla y


haciendo que se desvanezca gradualmente.

B.3. Luces en Vault Defense: efecto fuego


El efecto de luz de fuego hace uso de la clase Controller y ControllerValue
de Ogre, que nos facilita el poder variar los valores de color e intensidad de la luz,
simulando la llama del fuego. Se ha aadido tambin una pequea variacin en el
movimiento del punto de luz para simular el movimiento de las sombras producido
por el fuego.
Para dicho efecto se uso el cdigo que se muestra en el siguiente listado.

Listado B.12: Activando la sangre.


1 Ogre::Light* fuego = _sceneMgr->createLight("Fuego");
2 fuego->setType(Ogre::Light::LT_POINT);
3 fuego->setDiffuseColour(0.8,0.5,0.0);
4
5 Ogre::SceneNode* nodeFuego =
6 _sceneMgr->createSceneNode("nodeFuego");
7 nodeFuego->attachObject(fuego);
8 nodeFuego->setPosition(314,2,290);
9
10 ControllerValueRealPtr RedLightFlasher =
11 ControllerValueRealPtr(new LightBlinker
12 (nodeFuego,fuego, ColourValue(0.8,0.5,0.0),
13 ColourValue(0.6,0.4,0.0), 0.975));
14 ControllerFunctionRealPtr RedLightControllerFunc =
15 ControllerFunctionRealPtr(new LightFlasherControllerFunction
16 (Ogre::WFT\_SINE, 3.50, 0.0));
17 _RedLightController = _ControllerManager->createController
18 (_ControllerManager->getFrameTimeSource(),
19 ControllerValueRealPtr(RedLightFlasher),
20 ControllerFunctionRealPtr(RedLightControllerFunc));
21
22 RedLightFlasher.freeMethod();
23 RedLightControllerFunc.freeMethod();

ControllerValueRealPtr es un controlador de variables a actualizar, basicamente


lo que recibe es un objeto que hereda de ControllerValue<T>, dicho objeto tiene que
tener un mtodo setValue y un getValue.
En la instruccin siguiente tenemos la funcin que va a calcular la variable,
LightFlasherControllerFuntion. Esto devuelve un valor entre 0 y 1 que se usar en
el setValue de la lnea anterior.
En la lnea siguientes lo que se hace es congurar bien el controlador para que
obtenga el tiempo entre frames y que sepa que controladores usar y que objetos
modicar.
Es un poco complejo de explicar y menos en un par de lneas, pero para mas
informacion se puede visitar la web ocial de Ogre5 .
5 http://www.ogre3d.org/tikiwiki/tiki-index.php?page=LightsCameraAction
B.4. Enemigos en Vault Defense [1107]

B.4. Enemigos en Vault Defense


Bueno la explicacin de los enemigos en s es muy extensa, todo esta entrelazado.
Esta explicacin ir detallando el cmo segn se vaya necesitando.
Inicialmente los enemigos estn creados como un agente de estados ya explicado
en el curso. La transicin entre dichos estados se realiza dependiendo de la proximidad
al jugador y al tesoro.
Dichos estados se muestran visualmente gracias a un icono posicionado encima
de los enemigos.

Listado B.13: Gestin de la IA.


1 _ia = sceneMgr->createBillboardSet(billAux.str(),1);
2 _ia->setBillboardType(Ogre::BBT_POINT);
3 _ia->setMaterialName("IATesoro");
4 _ia->setDefaultDimensions(1.,1);
5 _ia->createBillboard(Ogre::Vector3(0,0,0));
6 Ogre::SceneNode* iaNode =
7 _modelo->createChildSceneNode(billNodeAux.str());
8 iaNode->setPosition(0,6.0,0);
9 iaNode->attachObject(_ia);

Cuando el estado cambia se realiza adems una invocacin a la funcin setMate-


rialName(), que cambia la imagen a mostrar.
Lo ms importante de los enemigos es su forma de guiarse por el escenario; esto
se puede explicar respondiendo a una serie de preguntas.

Cmo se mueven los enemigos?

Bueno, para el movimiento de los enemigos se us un pathnder, un algoritmo


A-star para ser ms concreto. Dicho algoritmo devuelve un camino a los enemigos
para que recorran los pasillos del juego sin problemas, dicho camino es devuelto en
puntos y los enemigos van de punto a punto, encarando al punto y trasladndose en su
direccin.

Cmo se evitan las paredes, trampas y columnas ?

En la creacin del escenario se creo un array tridimensional para almacenar cierta


informacin (trampas y bloques). Dicho array se relleno usando unos archivos xml
generados mediante un script de Blender; en dicho array viene la posicion de los
bloques que forman el escenario. Gracias a esto y cogiendo lo que viene a ser el suelo
de array se puede rellenar un mapa del suelo que se le pasa al pathnder. De este modo,
las trampas se guardan en el mapa y se puede calcular nuevas rutas dependiendo de la
inteligencia de los enemigos.

Como evitan chocarse los enemigos?

Los enemigos llevan a sus pies unos bloques pequeos ocultos. Dichos bloques se
usan para calcular colisiones entre cajas AABB.]

Listado B.14: Evasin de colisiones.


1 Vector3 trans(0,0,0);
[1108] CAPTULO B. VAULT DEFENSE AL DETALLE

2 for (i =_ rMn; i < _ID; i++) {


3 std::stringstream saux, saux2;
4
5 if(_ID!=i) {
6 saux << "nodeAABBEnemigo" << i;
7 saux2 << "nodeAABBEnemigo" << _ID;
8 Ogre::SceneManager *sceneMgr =
9 Ogre::Root::getSingletonPtr()->
10 getSceneManager("SceneManager");
11 Ogre::SceneNode *nodeAux = sceneMgr->
12 getSceneNode(saux.str());
13 Ogre::SceneNode *nodeAct = sceneMgr->
14 getSceneNode(saux2.str());
15 AxisAlignedBox aab = nodeAct->_getWorldAABB().
16 intersection(nodeAux->_getWorldAABB());
17
18 if(!aab.isNull()) {
19 Vector3 diff = nodeAux->getPosition() -
20 _modelo->getPosition();
21 Vector3 dir = diff.normalisedCopy();
22 Vector3 pen = aab.getMaximum() - aab.getMinimum();
23 trans = dir *
24 Math::Abs(pen.normalisedCopy().dotProduct(dir)) *
25 pen.length();
26 }
27
28 }
29
30 }
31
32 _modelo->translate(-trans);


La parte importante son las lneas 15-16 . Dicha funcin comprueba la interseccin
de 2 nodos y devuelve una AxixAlignedBox, si es distinto de Null la caja invisible esta
chocando con otra caja entonces ambas cajas se repelen.
Bibliografa

[1] www.boost.org.
[2] www.cegui.org.uk.
[3] www.swig.org.
[4] ISO/IEC 9241. ISO/IEC 9241-11: Ergonomic requirements for ofce work with
visual display terminals (VDTs) Part 11: Guidance on usability. ISO, 1998.
[5] Advanced Micro Devices, Inc., Disponible en lnea en http://support.
amd.com/us/Processor_TechDocs/31116.pdf. BIOS and Kernel
Developers Guide (BKDG) For AMD Family 10h Processors, Apr. 2010.
[6] T. Akenine-Moller, E. Haines, and N. Hoffman. Real-Time Rendering (3rd
Edition). AK Peters, 2008.
[7] E. Akenine-Mller, T. Haines and N. Hoffman. Real-Time Rendering. AK
Peters, Ltd., 2008.
[8] T. Akenine-Mller, E. Haines, and N. Hoffman. Real-Time Rendering. AK
Peters, 3rd edition, 2008.
[9] Andrei Alexandrescu. Modern C++ Design: Generic Programming and
Design Patterns Applied. Addison-Wesley Professional, 2001.
[10] Grenville Armitage, Mark Claypool, and Philip Branch. Networking and Online
Games. Wiley, 2006.
[11] K. Beck. Extreme Programming Explained: Embrace Change. Addison- Wesley
Professional. Addison-Wesley Professional, 1999.

1109
[1110] CAPTULO 31. BIBLIOGRAFA

[12] E Bethke. Game Development and Production. Wordware Publishing, 2003.


[13] Carlos Ble. Diseo gil con TDD. 2009.
[14] D.M. Bourg and G. Seemann. AI for game developers. OReilly Media, 2004.
[15] Gary Bradski and Adrian Kaehler. Learning OpenCV. OReilly Media Inc.,
2008.
[16] M. Buckland. Programming Game AI by Example. Wordware Game
Developers Library, 2004.
[17] D. Bulka and D. Mayhew. Efcient C++, Performance Programming Techni-
ques. Addison-Wesley, 1999.
[18] Linux Kernel Community. Git Tutorial. 2013.
[19] James O. Coplien. Curiously recurring template patterns. C++ Report,
February 1995.
[20] T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein. Introduction to
algorithms. MIT Press, third edition edition, 2009.
[21] T.H. Cormen, C.E. Leiserson, R.L. Rivest, and C. Stein. Introduction to
Algorithms, Third Edition. 2009.
[22] Valve Corporation. http://source.valvesoftware.com/.
[23] Chris Dallaire. Binary triangle trees for terrain tile index buffer generation.
2006.
[24] D.S.C. Dalmau. Core techniques and algorithms in game programming. New
Riders Pub, 2004.
[25] W.H. De Boer. Fast terrain rendering using geometrical mipmapping. Unpublis-
hed paper, available at http://www. ipcode. com/articles/article geomipmaps.
pdf, 2000.
[26] X. Dcoret, F. Durand, F.X. Sillion, and J. Dorsey. Billboard clouds for extreme
model simplication. In ACM Transactions on Graphics (TOG), volume 22,
pages 689696. ACM, 2003.
[27] M.J. Dickheiser. C++ for Game Programmers (2nd Edition). Charles River
Media, 2007.
[28] A. Dix. Human computer interaction. Prentice Hall, 1993.
[29] M. Duchaineau, M. Wolinsky, D.E. Sigeti, M.C. Miller, C. Aldrich, and M.B.
Mineev-Weinstein. Roaming terrain: real-time optimally adapting meshes. In
Visualization97., Proceedings, pages 8188. IEEE, 1997.
[30] D.H. Eberly. 3D Game Engine Architecture. Morgan Kauffmann, 2005.
[31] C. Ericson. Real-time collision detection, volume 1. Morgan Kaufmann, 2005.
[32] G. Farin and D. Hansford. Practical Linear Algebra: A Geometry Toolbox.
CRC Press, 2013.
[33] Randima Fernando. GPU Gems: Programming Techniques, Tips and Tricks for
Real-Time Graphics. Pearson Higher Education, 2004.
. [1111]

[34] Randima Fernando and Mark J. Kilgard. The Cg Tutorial: The Denitive Guide
to Programmable Real-Time Graphics. Addison-Wesley Longman Publishing
Co., Inc., Boston, MA, USA, 2003.
[35] K. Flood. Game unied process (gup).
[36] S. Franklin and Graesser A. Is it an agent, or just a program?: A taxonomy
for autonomous agents. In Proceedings of the Third International Workshop
on Agent Theories, Architectures, and Languages, pages 2135, London, UK,
1997. Springer-Verlag.
[37] E. Gamma. Design Patterns: Elements of Reusable Object-Oriented Software.
Addison-Wesley Professional, 1995.
[38] E. Gamma, R. Helm, R. Johnson, and J. Vlissided. Design Patterns. Addison
Wesley, 2008.
[39] Joseph C. Giarratano and Gary Riley. Expert Systems. PWS Publishing Co.,
Boston, MA, USA, 3rd edition, 1998.
[40] J. L. Gonzlez. Jugabilidad: Caracterizacin de la Experiencia del Jugador en
Videojuegos. PhD thesis, Universidad de Granada, 2010.
[41] T. Granollers. MPIu+a. Una metodologa que integra la ingeniera del
software, la interaccin persona-ordenador y la accesibilidad en el contexto
de equipos de desarrollo multidisciplinares. PhD thesis, Universitat de Lleida,
2004.
[42] J Gregory. Game Engine Architecture. AK Peters, 2009.
[43] Khronos Group. COLLADA Format Specication.
http://www.khronos.org/collada/, 2012.
[44] H. Hoppe. Efcient implementation of progressive meshes. Computers &
Graphics, 22(1):2736, 1998.
[45] IberOgre. Formato OGRE 3D. http://osl2.uca.es/iberogre, 2012.
[46] R. Ierusalimschy. Programming in LUA (2nd Edition). Lua.org, 2006.
[47] InniteCode. Quadtree Demo with source code.
http://www.innitecode.com/?view_post=23, 2002.
[48] Intel Corporation, Disponible en lnea en http://www.intel.com/
content/dam/doc/manual/. Intel 64 and IA-32 Architectures Softwa-
re Developers Manual. Volume 3B: System Programming Guide, Part 2, Mar.
2012.
[49] ISO/IEC. Working Draft, Standard for Programming Language C++. Docu-
ment number N3242=11-0012., Feb. 2011.
[50] D. Johnson and J. Wiles. Effective affective user interface design in games.
Ergonomics, 46(13-14):13321345, 2003.
[51] N.M. Josuttis. The C++ standard library: a tutorial and handbook. C++
programming languages. Addison-Wesley, 1999.
[52] G. Junker. Pro OGRE 3D Programming. Apress, 2006.
[53] G. Junker. Pro Ogre 3D Programming. Apress, 2006.
[1112] CAPTULO 31. BIBLIOGRAFA

[54] C Keith. Agile game development tutorial. In Game Developers Conference,


2007.
[55] C Keith. Agile Game Development with Scrum. Addison-Wesley Professional,
2010.
[56] F. Kerger. Ogre 3D 1.7 Beginners Guide. Packt Publishing, 2010.
[57] Donald E. Knuth. Structured Programming with go to Statements. ACM
Computing Surveys, 6(4), Dec. 1974.
[58] V. Kurkova. Kolmogorovs theorem and multilayer neural networks. Neural
Networks, 5(3):501506, 1992.
[59] D. Luebke and C. Georges. Portals and mirrors: Simple, fast evaluation of
potentially visible sets. In Proceedings of the 1995 symposium on Interactive
3D graphics, pages 105ff. ACM, 1995.
[60] Robert C. Martin. Clean Code: A Handbook of Agile Software Craftmanship.
Prentice Hall, 2009.
[61] T. Masters. Practical neural network recipes in C++. Morgan Kaufmann,
1993.
[62] M. McShaffry. Game coding complete. "Paraglyph Publishing, 2003.
[63] S.D. Meyers. Effective STL: 50 specic ways to improve your use of the
standard template library. Addison-Wesley professional computing series.
Addison-Wesley, 2001.
[64] I. Millington and J. Funge. Articial Intelligence for Games. Morgan
Kaufmann, 2009.
[65] David R. Musser and Alexander A. Stepanov. Generic programming. In
Symbolic and Algebraic Computation: International symposium ISSAC 1988,
pages 1325, 1988.
[66] J. Nielsen. Usability Engineering. AP Professional, 1993.
[67] NNG. User Experience - Our Denition. Nielsen Norman Group.
[68] Assembla NocturnaIR. El Formato Collada.
http://www.assembla.com/wiki/show/reubencorp/ El_formato_COLLADA,
2012.
[69] ATI NVidia. Arb occlusion query, 2001.
[70] Manuel Palomo-Duarte, Juan Manuel Dodero, and Antonio Garca-
Domnguez. Betting system for formative code review in educational com-
petitions. Expert Systems with Applications, 41(5):22222230, 2014.
[71] David A. Patterson and John L. Hennessy. Computer Organization and Design,
Revised Fourth Edition. Morgan Kaufmann, 4th edition edition, 2012.
[72] E. Pipho. Focus on 3D models, volume 1. Course Technology, 2002.
[73] William T. Reeves. Particle systems - a technique for modelling a class of fuzzy
objects. ACM Transactions on Graphics, 2:91108, 1983.
[74] C.W. Reynolds. Steering behaviors for autonomous characters. In Game
Developers Conference, volume 1999, pages 763782, 1999.
. [1113]

[75] Gary Riley. CLIPS home site. http://clipsrules.sourceforge.net, 2013.


[76] R.J. Rost and J.M. Kessenich. OpenGL Shading Language. Addison-Wesley
Professional, 2006.
[77] R. Rouse III. Game Design: Theory and Practice. Wordware Publishing, 2001.
[78] W.W. Royce. Managing the development of large software systems. In
Proceedings of IEEE WESCON, 1970.
[79] S.J. Russell and Norvig. Articial Intelligence: A Modern Approach. Pearson
Education, 1998.
[80] E. Salen, K.; Zimmerman. Rules of Play: Game Design Fundamentals. The
MIT Press, 2003.
[81] H. Schildt. C++ from the Ground Up (3rd Edition). McGraw-Hill Osborne,
2003.
[82] D. Schmidt. Acceptor-connector: an object creational pattern for connecting
and initializing communication services, 1997.
[83] B. Schwab and Inc ebrary. AI game engine programming. Charles River Media,
2004.
[84] P. Shirley and S. Marschner. Fundamentals of Computer Graphics. AK Peters,
3rd edition, 2009.
[85] B. Shneiderman. Universal usability. Communications of the ACM, pages 84
91, 2000.
[86] D. Shreiner. OpenGL Programming Guide: The Ofcial Guide to Learning
OpenGL, Versions 3.0 and 3.1 (7th Edition). Addison-Wesley, 2009.
[87] B. Sierra Araujo. Aprendizaje automtico: conceptos bsicos y avanzados:
aspectos prcticos utilizando el software Weka. Pearson Prentice Hall, Madrid,
2006.
[88] D. Sikora. Incremental game development.
[89] A. Silberschatz, P.B. Galvin, and G. Gagne. Fundamentos de Sistemas
Operativos. McGraw Hill, 2006.
[90] Jouni Smed and Harri Hakonen. Algorithms and Networking for Computer
Games. Wiley, 2006.
[91] R.M. Stallman and GCC Developer Community. Using GCC: The GNU
Compiler Collection Reference Manual. GNU Press, 2003.
[92] R.M. Stallman, R. Pesch, and S. Shebs. Debugging with GDB: The GNU
Source-Level Debugger. GNU Press, 2002.
[93] B. Stroustrup. The C++ programming language. Addison-Wesley, 2000.
[94] B. Stroustrup. El lenguaje de programacion C++. Pearson Education, 2001.
[95] I. Takeuchi, H.; Nonaka. Scrum: The new product development game, 1986.
[96] K. Tanaka. An introduction to fuzzy logic for practical applications. Springer
Verlag, 1997.
[1114] CAPTULO 31. BIBLIOGRAFA

[97] S.J. Teller and C.H. Squin. Visibility preprocessing for interactive walkth-
roughs. In ACM SIGGRAPH Computer Graphics, volume 25, pages 6170.
ACM, 1991.
[98] T. Theoharis, G. Papaioannou, and N. Platis. Graphics and Visualization:
Principles & Algorithms. AK Peters, 2008.
[99] Jos Toms Tocino, Pablo Recio, and Manuel Palomo-Duarte. Gades Siege.
http://code.google.com/p/gsiege, 2012.
[100] T. Ulrich. Rendering massive terrains using chunked level of detail control.
SIGGRAPH Course Notes, 3(5), 2002.
[101] Wang and Niniane. Let there be clouds! Game Developer Magazine, 11:3439,
2004.
[102] G. Weiss. Multiagent Systems: a Modern Approach to Distributed Articial
Intelligence. The MIT Press, 1999.
[103] Wikipedia. Stratego wikipedia, the free encyclopedia, 2014. [Online;
accessed 30-May-2014].
[104] M. Wooldridge and N.R. Jennings. Intelligent agents: Theory and practice. The
Knowledge Engineering Review, 10(2):115152, 1995.
[105] L.A. Zadeh. Fuzzy sets. Information and control, 8(3):338353, 1965.
[106] H. Zhang, D. Manocha, T. Hudson, and K.E. Hoff III. Visibility culling
using hierarchical occlusion maps. In Proceedings of the 24th annual confe-
rence on Computer graphics and interactive techniques, pages 7788. ACM
Press/Addison-Wesley Publishing Co., 1997.
Este manual fue maquetado en
una mquina GNU/Linux en
Junio de 2014

También podría gustarte