Crystal Space: 4.2.1.3 Creating a "World"

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

#### 4.2.1.3 Creating a "World"

Now we have a very exciting application which opens a black window and waits for the `ESC` key to quit. We assume this is the application you always wanted to have? No? Ok then, let's create some 3D stuff.

We'll add a texture manager, a room (technically called a sector) and some lights. First, add a pointer to our main sector and a function `CreateRoom()` to the ``Simple'` class header file:

    ...
    struct iSector;
    ...
    class Simple
    {
    private:
      ...
      iSector* room;
      float rotX, rotY;
      ...
      void CreateRoom ();
      ...

Now add these chunks of code (texture manager, room, lights) to ``simple.cpp'`:

    bool Simple::Application ()
    {
      ...
      // First disable the lighting cache. Our app is simple enough
      // not to need this.
      engine->SetLightingCacheMode (0);
      ...
      // These are used store the current orientation of the camera.
      rotY = rotX = 0;
      ...
      CreateRoom ()
      ...
    }
    ...
    void Simple::CreateRoom ()
    {
      // Load the texture from the standard library.  This is located in
      // CS/data/standard.zip and mounted as /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 ();
    }

This extra code first loads a texture with `LoadTexture()`. The first parameter is the name of the texture as it will be known in the engine; and the second is the actual filename on the VFS volume (see section Virtual File System (VFS)). Note, if you don't have the ``stone4.gif'` texture you can use another one. The only requirement is that it must have sizes which are a power of 2 (e.g. 64x64); note that Crystal Space will scale them automatically if this requirement is not met but this can reduce quality. This function returns a ``iTextureWrapper'` which we don't use. Instead we use the ``iMaterialWrapper'` which is created automatically by `LoadTexture()`.

Then, we create our room with `CreateSector()`. This room will initially be empty. A room in Crystal Space is represented by ``iSector'` which is basically a container which can hold geometrical objects. Objects in Crystal Space are represented by mesh objects (see section Mesh Object Plug-In System). There are several types of mesh objects in Crystal Space. Every type of mesh object represents some different way to represent geometry. In this tutorial we are only going to use the "thing" mesh object type. This mesh object type is very useful for walls of indoor maps or buildings.

Now, we want to create the six walls of our room. First, we make our thing mesh object. Because this is a very common case there is a convenience function in the engine (called `CreateSectorWallsMesh()`) which will create a thing mesh and add it to the given sector. The only thing that has to be done after this is add polygons to that mesh. The geometry of a mesh is generally stored in its factory, thus we need to obtain an interface to the "thing" factory.

To do this we first need to obtain the interface to the ``iMeshObject'` interface. The ``iMeshWrapper'` interface, as returned by `CreateSectorWallsMesh()`, has a method `GetMeshObject()` which gives us just that.

In the next step, we need to query the mesh factory. Again, the previously obtained ``iMeshObject'` has a method which returns an interface to the mesh factory.

And lastly, to obtain the interface to control "thing"-specific aspects of the mesh factory, we query the interface called ``iThingFactoryState'` from the mesh factory interface. We use the function `scfQueryInterface<>()` which is part of SCF (see section Shared Class Facility (SCF)). This will see if the mesh factory actually implements ``iThingFactoryState'` (which should be the case here) and, if so, it will return a pointer to the implementation of ``iThingFactoryState'`. All mesh objects and factories implement some kind of state interface which is used to set up or query the state of that mesh object or factory. Note that all interfaces which you query using `scfQueryInterface<>()` must be assigned to a variable of type `csRef<>`. This ensures that the reference to the interface is released once it is no longer needed.

We now have the factory state (``iThingFactoryState'`) which we can now use to create polygons. There are various functions to create individual polygons, if you want that, but in this case we use a convenience function to create a box that can be seen from the inside. That will serve as the walls of our room. The `AddInsideBox()` function does this. It will create six polygons arranged so that they are visible from inside. (Note that in Crystal Space a polygon is visible if vertices are oriented clock-wise). The box coordinates given to `AddInsideBox()` are in object space (in contrast with world space and camera space).

The `SetPolygonMaterial()` function will set the material of the polygons. The first parameter is a range. We use ``CS_POLYRANGE_LAST'` here to indicate that we are interested in setting the materials of the last created polygons (i.e. all polygons created with `AddInsideBox()`).

The texture is mapped onto the polygon using `SetPolygonTextureMapping()`. There are several versions of this function. The one we use in this tutorial is one of the simplest but it offers the least control. In this particular case we take the first two vertices of each polygon that was created for the box and use that as the u-axis of the texture. The v-axis will be calculated perpendicular to the u-axis. The 3rd parameter indicates that the texture will be scaled so that one texture tile is exactly 3x3 world units in size.

Finally, we create some lights in our room to make sure that we actually are able to see the walls. The interface ``iLight'` represents a light. In this case we created some static lights which can not move and change intensity. We create three such lights and add them to the room with `AddLight()`. Note that the list of lights in a sector is represented by an object implementing ``iLightList'`. To get this list you call `iSector::GetLights()`.

When creating a light we use several parameters. First we have the name of the light. This is not used often and mostly you can set this to 0. The second parameter is the location of the light in the world. Then follows a radius. The light will not affect polygons which are outside the sphere described by the center of the light and the radius. The next parameter is the color of the light in RGB format (<1,1,1> means white and <0,0,0> means black). The last parameter indicates whether or not we want to have a pseudo-dynamic light. A pseudo-dynamic light still cannot move but it can change intensity. There are some performance costs associated with pseudo-dynamic lights so it is not enabled by default.

The call to `Prepare()` prepares the engine for rendering your scene. It will prepare all textures and create all lightmaps if needed. Only after this call can you start rendering your world, because lightmaps may have to be converted to a format more suitable for the chosen 3D renderer.

Ok, now we have created our room and properly initialized it. If you compile and run this application you would still see a black screen. Why? Because we have not created a camera through which you can view the room.

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ]

This document was generated using texi2html 1.76.