[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ] 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
  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");

  // First we make a primitive for our geometry.
  using namespace CS::Geometry;
  DensityTextureMapper mapper (0.3f);
  TesselatedBox box (csVector3 (-5, 0, -5), csVector3 (5, 20, 5));
  box.SetLevel (3);
  box.SetMapper (&mapper);
  box.SetFlags (Primitives::CS_PRIMBOX_INSIDE);

  // Now we make a factory and a mesh at once.
  csRef<iMeshWrapper> walls = GeneralMeshBuilder::CreateFactoryAndMesh (
      engine, room, "walls", "walls_factory", &box);

  csRef<iGeneralMeshState> mesh_state = scfQueryInterface<
    iGeneralMeshState> (walls->GetMeshObject ());
  mesh_state->SetShadowReceiving (true);
  walls->GetMeshObject ()->SetMaterialWrapper (tm);

  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 “genmesh” mesh object type. This mesh object type is very useful for walls of indoor maps or buildings. Most mesh objects don't contain any geometry. The geometry definition is actually contained in the mesh factory. So that's why we first create a factory for our walls.

Now, we want to create the six walls of our room. For this the best object to use is the GenMesh object (see section Genmesh Mesh Object). With genmeshes the geometry is represented in the factory. From that factory you can then create multiple meshes. In our case we only need one mesh for the walls (which will be a box visible from the inside). So we will create one factory and one mesh from that factory.

There is a ‘CS::Geometry’ namespace where there are several conveniance classes to help you build genmeshes. In our case we need a box that is visible from the inside. We will use the ‘TesselatedBox’ class for that purpose. We use a tesselated box (as opposed to a normal box) so that our lighting is more accurate. If you don't use lightmaps (like in this example) then genmeshes are only lit at vertices of the model and lighting for the rest of the surface is interpolated. So to get accurate lighting you need a model that has sufficient vertices. We use a tesselation level so that every face has 16 vertices (3 times 3 quads or eighteen triangles for every face). You can increase the value in the TesselatedBox::SetLevel() call even more to get even better lighting resolution but that will increase the number of triangles in the model too much and it is not needed in this simple example.

From this primitive we can now create both the factory and the mesh at once using the GeneralMeshBuilder::CreateFactoryAndMesh() method. In the ‘GeneralMeshBuilder’ class there are other methods that you can use to create only factories or meshes but in our case we use the simple method of creating both at once. Because we give it a pointer to our tesselated box it will immediatelly populate the factory with the triangles and vertices required for this box. The CreateFactoryAndMesh() method places the object at the origin in the sector (i.e. ‘0,0,0’). If you want to change that you have to use iMeshWrapper::GetMovable(). But in this example the origin is fine.

There are only two more things to be done before our object is good for us. First we call iGeneralMeshState::SetShadowReceiving(true). The ‘iGeneralMeshState’ interface is the interface containing genmesh specific functions for genmesh objects. ‘iGeneralFactoryState’ was the interface for genmesh specific functions for factories. To get access to these special interfaces we use the function scfQueryInterface<>() which is part of SCF (see section Shared Class Facility (SCF)). This will see if the mesh object actually implements ‘iGeneralMeshState’ (which should be the case here) and, if so, it will return a pointer to the implementation of ‘iGeneralMeshState’. 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 use SetShadowReceiving(true) so that our object will be lit by a more accurate per-vertex method. If we don't use this then the genmesh will be lit according to the light that it receives at the center of the model.

The last thing we have to do is to set a material to use for this mesh. You can set a material both on the factory (in which case all objects that are created from this factory will share it) or on the mesh. In this case we set it on the mesh by using SetMaterialWrapper().

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 ] [ >> ]         [Top] [Contents] [Index] [ ? ]

This document was generated using texi2html 1.76.