Crystal Space 3D: un motor libre para un mundo libre Estructura En este tutorial conocerás lo básico para poder empezar a realizar tus aplicaciones con Crystal Space. Lo primero, evitemos las confusiones. Leyendo esto no vas a ser un experto en el tema, simplemente obtendrás conocimientos esenciales que te ayudarán a no estar perdido. Si de verdad quieres aprender tendrás que practicar mucho por tu cuenta y echarle horas. Tampoco pretendas hacer grandes juegos, ten en cuenta que el motor gráfico tiene sus limitaciones. Esto es una explicacion paso a paso para empezar con tu aplicación en CS. Cuando bajas el paquete de CS contiene un directorio llamado “CS/include”. Este directorio contiene todos los archivos y librerías necesarias para nuestra aplicación. Por un lado, contiene las definiciones de las interfaces SCF. Por otro lado también tiene archivos en C++ para utilizar con CS. Cuando lo instalas, estos archivos están localizados en lugares fáciles de acceder cuando escribimos nuevas aplicaciones en CS. En sistemas operativos unix, suelen encontrarse en “/usr/local/include/crystalspace”. CS cuenta además con un manual y documentación. Están en formato HTML y los podrás encontrar en “/CS/docs/html”. El código fuente utilizado para este tutorial lo podrás encontrar en “CS/apps/tutorials”. A continuación se explica como hacer un archivo con la estructura inicial para empezar a trabajar. Es una buena costumbre poner las declaraciones y las funciones en los archivos cabecera y no en los fuentes. Algunas veces es imprescindible, aunque a para este ejemplo no es fundamental. Además, hemos usado una clase para encapsular la aplicación. El archivo que creamos se llama 'simple.h' y su aspecto es el siguiente. #ifndef __SIMPLE_H__ #define __SIMPLE_H__ #include <crystalspace.h> class Simple : public csApplicationFramework, public csBaseEventHandler { private: csRef<iEngine> engine; csRef<iLoader> loader; csRef<iGraphics3D> g3d; csRef<iKeyboardDriver> kbd; csRef<iVirtualClock> vc; void ProcessFrame (); void FinishFrame (); public: Simple (); ~Simple (); void OnExit (); bool OnInitialize (int argc, char* argv[]); bool Application (); CS_EVENTHANDLER_NAMES("application.simple1") CS_EVENTHANDLER_NIL_CONSTRAINTS }; #endif // __SIMPLE1_H__ En esta clase guardamos muchas referencias a objetos que vamos a necesitar más adelante. De esta manera evitamos hacerlo más adelante y perder tiempo. También tenemos un constructor para inicializar estas variables, un destructor para eliminar lo innecesario y una función para inicializarlo todo. Observa que hemos usado punteros (csRef<>) lo que hace mas fácil trabajar. Las macros para manejar eventos son usadas porque nuestro ejemplo necesita tener un control de eventos (csBaseEventHandler). Las dos macros indican el nombre del manejador de eventos y también el mecanismo a llevar a cabo. En este caso utilizamos CS_EVENTHANDLER_NIL_CONSTRAINTS, lo que significa que no nos importa el orden en que lleguen. En el archivo fuente 'simple.cpp' situamos lo siguiente: #include "simple.h" CS_IMPLEMENT_APPLICATION Simple::Simple () { SetApplicationName ("CrystalSpace.Simple1"); } Simple::~Simple () { } void Simple::ProcessFrame () { } void Simple::FinishFrame () { } bool Simple::OnInitialize(int argc, char* argv[]) { if (!csInitializer::RequestPlugins(GetObjectRegistry(), CS_REQUEST_VFS, CS_REQUEST_OPENGL3D, CS_REQUEST_ENGINE, CS_REQUEST_FONTSERVER, CS_REQUEST_IMAGELOADER, CS_REQUEST_LEVELLOADER, CS_REQUEST_REPORTER, CS_REQUEST_REPORTERLISTENER, CS_REQUEST_END)) return ReportError("Failed to initialize plugins!"); csBaseEventHandler::Initialize(GetObjectRegistry()); if (!RegisterQueue(GetObjectRegistry(), csevAllEvents(GetObjectRegistry()))) return ReportError("Failed to set up event handler!"); return true; } void Simple::OnExit() { } bool Simple::Application() { if (!OpenApplication(GetObjectRegistry())) return ReportError("Error opening system!"); g3d = csQueryRegistry<iGraphics3D> (GetObjectRegistry()); if (!g3d) return ReportError("Failed to locate 3D renderer!"); engine = csQueryRegistry<iEngine> (GetObjectRegistry()); if (!engine) return ReportError("Failed to locate 3D engine!"); vc = csQueryRegistry<iVirtualClock> (GetObjectRegistry()); if (!vc) return ReportError("Failed to locate Virtual Clock!"); kbd = csQueryRegistry<iKeyboardDriver> (GetObjectRegistry()); if (!kbd) return ReportError("Failed to locate Keyboard Driver!"); loader = csQueryRegistry<iLoader> (GetObjectRegistry()); if (!loader) return ReportError("Failed to locate Loader!"); Run(); return true; } /*­­­­­­­­­­­­­­­* * Main function *­­­­­­­­­­­­­­­*/ int main (int argc, char* argv[]) { return csApplicationRunner<Simple>::Run (argc, argv); } Esta aplicación es prácticamente la más sencilla y inútil. Aun así, hemos conseguido con esto abrir una ventana, controlar el tamaño de ésta los drivers de video mediante opciones por linea de comandos, poder pedir ayuda usando la linea de comandos (­help) y inicializar un montón de plugins (el motor, renderizado 3D, cargador de imágenes, cargador de mapas...). Antes de comenzar a hacer esta aplicación mas compleja hay que utilizar algunos macros. El CS_IMPLEMENT_APLICATION es esencial para toda aplicación de CS. Esta macro se asegura que el main() es correcto y funciona en cada plataforma. CsInitializer::RequestPlugins() usará el archivo de configuración (que no se emplea en este tutorial) y la linea de comandos para encontrar los plugins y cargarlos. Esto concluye la parte de inicialización. En Simple::Application() abrimos una ventana con una llamada a la función csInitializer::OpenApplication(). Esto envía un mensaje a todos los componentes que están usando la cola de eventos. Finalmente arrancamos el main() llamando a la función Run(). COMO CREAR NUESTRO MUNDO En este momento nuestra apliación consiste en una ventana en negro. Está claro que nuestras pretensiones son mayores, así que vamos a enseñarte a crear cosas en 3D. Vamos añadir un gestor de texturas, una habitación (llamada sector) y varias luces. Primero, añadimos un puntero a nuestro sector principal y a la función CreateRoom() archivo de cabecera Simple() utitlizado anteriormente. ... struct iSector; ... class Simple { private: ... iSector* room; float rotX, rotY; ... void CreateRoom (); ... Ahora añadiremos estos trozos de código al archivo 'simple.cpp'. bool Simple::Application () { ... // Primero desactica el cache para la iluminacion. Nuestro app es simple y // no le hara falta. engine­>SetLightingCacheMode (0); ... // Son usados para guardar la orientacion de la camara. rotY = rotX = 0; ... CreateRoom () ... } ... void Simple::CreateRoom () { // Carga la textura de la librería estandar. Esta localizada en // CS/data/standard.zip y montada como /lib/std using the Virtual // File System (VFS) plugin. if (!loader­>LoadTexture ("stone", "/lib/std/stone4.gif")) ReportError("Error loading 'stone4' texture!"); iMaterialWrapper* tm =engine­>GetMaterialList ()­>FindByName ("stone"); room = engine­>CreateSector ("room"); csRef<iMeshWrapper> walls ( engine­>CreateSectorWallsMesh (room, "walls")); iMeshObject* walls_object = walls­>GetMeshObject (); iMeshObjectFactory* walls_factory = walls_object­>GetFactory(); csRef<iThingFactoryState> walls_state = scfQueryInterface<iThingFactoryState> (walls_factory); walls_state­>AddInsideBox ( csVector3 (­5, 0, ­5), csVector3 (5, 20, 5)); walls_state­>SetPolygonMaterial (CS_POLYRANGE_LAST, tm); walls_state­>SetPolygonTextureMapping (CS_POLYRANGE_LAST, 3); csRef<iLight> light; iLightList* ll = room­>GetLights (); light = engine­>CreateLight (0, csVector3 (­3, 5, 0), 10,csColor (1, 0, 0)); ll­>Add (light); light = engine­>CreateLight (0, csVector3 (3, 5, 0), 10,csColor (0, 0, 1)); ll­>Add (light); light = engine­>CreateLight (0, csVector3 (0, 5, ­3), 10,csColor (0, 1, 0)); ll­>Add (light); engine­>Prepare (); } Vamos a crear una habitación con CreateSector (). Esta habitación primero se encontrará vacía. Una habitación en CS se representa por 'iSector' lo cual es basicamente un soporte para objetos geométricos. Los objetos en Crystal Space son representados por mesh objects (mira la sección del manual Mesh Object Plug­In System para más información). Hay muchos tipos. Cada uno representa varias formas de representar geometría. En este tutorial solo vamos a usar el objeto de tipo 'mesh'. Este tipo es muy útil para las paredes interioes de edificios o mapas. Ahora, vamos a crear las seis paredes de nuestra habitación. Primero, construiremos nuestro mesh object. Debido a que es una caso muy común, el motor de CS tiene una función (llamada CreateSectorWallsMesh()) que lo creará y lo añadirá al sector dado. Lo único que tendrás que hacer después es añadir polígonos al elemento 'mesh'. La geometría de un 'mesh' normalmente es guardada en un objeto 'factory', para lo que necesitamos la interfaz de este elemento. Para hacerlo, debemos obtener la interfaz 'iMeshObject'. Esta interfaz, devuelta por CreateSectorWallsMesh(), tiene un método GetMeshObject() con el cuál haremos esto. En el siguiente paso, tenemos que cosultar 'mesh factory'. Otra vez, el 'iMeshObject' obtenido anteriormente tiene un método el cuál devuelve una interfaz a 'mesh factory'. Y por último, para obtener la interfaz de control sobre los aspectos de 'mesh factory', necesitamos la interfaz llamada 'iThingFactoryState' de la interfaz de mesh factory. Usamos la función 'scfQueryInterface<>()' que forma parte SCF (mira la sección Shared Class Facility ( SCF). Esto combrueba que le 'mesh object' implementa 'iThingFactoryState' (que debería ser en este caso), y si es así, devolver un puntero a él. Ahora que tenemos el 'iThingFactoryState' podemos usarlo para crear polígonos. Hay varias funciones para crearlos, en este caso queremos crear una caja que sea vista desde dentro para simular nuestra habitación. Esta función es AddInsideBox(). SetPolygonMaterial() fija el material de los polígonos. El primer parámetro es un rango. Aquí usamos 'CS_POLYRANGE_LAST' para indicar que estamos interesados en poner el material de los últimos polígonos creados. Esta textura es llevada a los polígonos usando SetPolygonTextureMapping(). Existen muchas versiones de esta función. La que usamos en este tutorial es de las más simples pero también de las que ofrecen menos control. Finalmente creamos varias luces para asegurarnos de que somos capaces de ver las paredes. La interfaz 'iLight' representa una luz. En este caso creamos varias luces que ni se moverán ni cambiarán de intensidad. Creamos tres luces y las añadimos a la habitación con 'AddLight()'. Ten en cuenta que la iluminación en un sector es representada por 'iLightList' que conseguirás llamando a iSertor::GetLights(). Cuando creamos una luz hay que utilizar varios parámetros. En primer lugar tenemos el nombre de la luz. No se usa muy a menudo y puedes dejarlo como 0. El segundo parámetro es la localización de la luz en el mundo. Luego sigue el radio de la luz. Los polígonos que se encuentren fuera de su radio no se verán afectados. A continuación viene el color de la luz en formato RGB normal. El último parámetro se usa si quieres que la luz varíe de intensidad. La llamada a Prepare() prepara el motor para mostrar la escena. Creará las texturas y la iluminación. Solo después de esta llamada puedes motrar tu mundo. Bien, ahora ya tenemos creada una habitación con varias luces. Sin embargo si compilas y lo pruebas, verán que solo aparece una pantalla en negro. Esto es debido a que no has creado la cámara para ver tu mundo. Pasa a la siguiente parte del tutorial para saber que hacer.