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:

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.