[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ] [ Search: ]

6.3 The Engine Internals Explained

Written by Jorrit Tyberghein, jorrit.tyberghein@gmail.com. Last updated 30 April 2004.

This section explains, in some detail, how the engine works internally. This information is mainly for people who are interested in learning how the Crystal Space engine works internally and for people who want to contribute to the engine proper. The sources for the 3D engine can be found in ‘CS/plugins/engine/3d’.

Engine Data Structures

The following sections explain the various data structures that are used in the engine. Many of these data structures are subclasses of ‘csObject’, which means that you can use them in your game to attach other game specific objects.

Engine Data Structures: csEngine

At the core of everything there is the ‘csEngine’ class which implements the publicly viewable ‘iEngine’ interface. The engine class implements the following interfaces:

The engine class maintains the following lists of different types of objects or operations:

In addition, the engine also maintains the following important variables:

Engine Data Structures: csSector

A sector is one of the most important data structures in the engine. It is a container for other objects (meshes and lights). The sector class implements the following interfaces:

The sector maintains the following lists:

In addition, the sector also maintains the following important variables:

Engine Data Structures: csMeshFactoryWrapper

A ‘csMeshFactoryWrapper’ is a wrapper class which the engine uses to keep track of iMeshObjectFactory instances (these represent the real mesh object factories), and also stores some engine-specific information. The mesh factory wrapper class implements the following interfaces:

A mesh factory wrapper maintains the following important variables:

Engine Data Structures: csMeshWrapper

A ‘csMeshWrapper’ is a wrapper class for the engine which allows it to keep track of an ‘iMeshObject’ instance (real mesh objects), and also stores some engine-specific information. The mesh wrapper class implements the following interfaces:

A mesh object wrapper maintains the following important variables:

Engine Data Structures: csCollection

A collection is a group of iObjects. It utilizes ‘csObject’ to hold all objects that are in the collection. The collection class implements the following interfaces:

Engine Data Structures: csTextureWrapper

A texture wrapper is an engine object which keeps track of the real textures from the renderers (‘iTextureHandle’). The texture wrapper class implements the following interfaces:

A texture wrapper maintains the following important variables:

Engine Data Structures: csMaterialWrapper

A material wrapper is an engine object which keeps track of the real materials from the renderers (‘iMaterialHandle’). The material wrapper class implements the following interfaces:

A material wrapper maintains the following important variables:

Engine Data Structures: csMovable

The movable class is used by the ‘csMeshWrapper’ class to keep track of the mesh's current location. The movable class implements the following interfaces:

A movable maintains the following important variables:

Engine Data Structures: csLight

The light class is the representation of a light. A light lives in a sector. The light class implements the following interfaces:

A light maintains the following important variables:

Engine Data Structures: csPortalContainer

This is the representation of a portal container. It is the only mesh object that is implemented directly in the engine itself. A portal container is a collection of portals (i.e. every polygon in the portal container is a portal to another sector). The portal container class implements the following interfaces:

A portal container maintains the following important variables:

Engine Data Structures: csRenderView

This is a temporary data structure that is used during rendering. It will be discussed in more detail in the section about drawing a frame. Here is a brief summary of the class. The renderview class implements the following interfaces:

The renderview class maintains the following important variables:

Drawing a Frame

When rendering a frame the base entry point is iEngine::Draw(). However, most applications do not call this function directly. Most applications will invoke iView::Draw() instead.

iEngine::Draw() accepts two arguments: the camera to use, and the 2D clipping area. When this function is called then engine will perform the following steps in the order shown:

iRenderLoop::Draw() is called for every sector in the recursion. It will first be invoked by csEngine::Draw(), and it will also be called again whenever portal traversal occurs (see below). The following operations are performed by this function in the order shown:

What actually occurs during invocation of the rendersteps depends upon how the renderloop is set up, and upon how many rendersteps there are. What follows is an explanation of the default case of a single render step that uses the old-renderer compatibility shader type (this is the renderloop that is created by iEngine::CreateDefaultRenderLoop()). This render step is designed to be compatible with old renderer style maps that use lightmaps without no special shaders. It uses ‘CS_ZBUF_MESH’ for this render step which means that the z-buf mode of the mesh itself will be used. It also disables Z-offset since there will only be one step. Z-offset is used to make sure that, when using multi-texturing, the polygons end up at exactly the same Z-distance (i.e. no z fighting occurs). The first step is always rendered using disabled z-offset. Subsequent steps should enable it to make sure they will render nicely on the previously rendered base. This render step also enables portal traversal.

iRenderStep::Perform() resides in the generic render step plugin which is located in the source tree at ‘CS/plugins/engine/renderloop/stdsteps’. This function performs the following tasks in the order shown:

Drawing a Frame: Getting the Visible Meshes

One very important function that is called from within iRenderStep::Perform() is iSector::GetVisibleMeshes(). This function is responsible for finding all meshes that are visible. To calculate this the sector uses the visibility culler that is in every sector. At this moment we have two possible implementations for that visibility culler: Frustvis and Dynavis. Frustvis performs frustum culling alone. This means that every object that is in the view frustum is considered visible. Dynavis does frustum culling and also attempts to find out which objects are visible and which are not by using an occlusion buffer. There is another chapter explaining the differences a bit more and also how Dynavis works internally. The description here is only a short synopsis. GetVisibleMeshes() performs the following actions in the order shown:

iVisibilityCuller::VisTest() internally uses a KD-tree to traverse the objects in the sector and, for all visible objects, it invokes the ‘iVisibilityCullerListener’ callback that was provided as a parameter of the VisTest() call. More detail about the actual implementation of VisTest() for both Frustvis and Dynavis will be presented in later sections.

The callback's ObjectVisible() is a function that resides in the sector (the culler itself is implemented in another plugin). ObjectVisible() will mark the given object as visible by adding all ‘csRenderMesh’ instances as returned by the visible ‘iMeshObject’ (through the iMeshObject::GetRenderMeshes() invocation) to the list of visible meshes which is being built. In addition, it will also do something special if the mesh is a hierarchical mesh that uses static LOD. In such a case, it will calculate which of the children should be visible for the given distance to the camera (and other potential factors), and will mark that child as visible too.

It is important to note that there is an important difference between normal hierarchical meshes and static LOD meshes with regards to the visibility culler. Both are implemented using hierarchical meshes but, in case of a normal hierarchical mesh, all children are registered with the visibility culler as separate meshes. This means that every mesh in a hierarchical mesh is considered a separate entity for the visibility culler, and is also tested for visibility on its own (so a child of a hierarchical mesh can be visible even if the parent is not visible). For a static LOD mesh the situation is completely different. In that case, the children are not registered with the visibility culler but, instead, the mesh object will mark the correct child as visible when it, itself, is visible. This means that you must always make sure that, with static LOD, the parent is a mesh that is at least as big as the biggest child. Usually, a ‘nullmesh’ with an appropriate box is used for this purpose.

Drawing a Frame: Traversing Portals

While performing the renderstep, it will try traverse portals if that renderstep has portal traversal enabled. This will proceed to iPortalContainer::Draw(), which will perform the following actions in the order shown:

@@@TODO: explain Frustvis::VisTest()
@@@TODO: explain Dynavis::VisTest()
@@@TODO: explain the operations of one mesh object in the above context


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

This document was generated using texi2html 1.76.