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

4.14.1 Basic Collision Detection

Collision detection in Crystal Space is one of the more complicated issues. In this section I give a quick description of all the classes and interfaces in Crystal Space and what you should do to use them.

Loading the ‘iCollideSystem’

The basis of the collision detection system is the ‘iCollideSystem’. This is an interface which is implemented by some collision detection plugin. At this moment we have an implementation using the OPCODE collision detection system.

The easiest way to load a collision detection system is to add a line to your call to RequestPlugins:

 
if (!csInitializer::RequestPlugins (object_reg,
    CS_REQUEST_VFS,
    ...,
    CS_REQUEST_PLUGIN("crystalspace.collisiondetection.opcode",
		iCollideSystem),
    ...,
    CS_REQUEST_END))
{
  ...
}
csRef<iCollideSystem> cd_sys = csQueryRegistry<iCollideSystem> (object_reg);

Instead of using the above you can also load the collision detection plugin later with the following code:

 
csRef<iPluginManager> plugmgr =
  csQueryRegistry<iPluginManager> (object_reg);
csRef<iConfigManager> config =
  csQueryRegistry<iConfigManager> (object_reg);
const char* p = config->GetStr ("MyGame.Settings.CollDetPlugin",
  "crystalspace.collisiondetection.opcode");
csRef<iCollideSystem> cd_sys =
  csLoadPlugin<iCollideSystem> (plugmgr, p);
if (!cd_sys)
{
  csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
    "crystalspace.application.myapp",
    "No Collision Detection plugin found!");
  return false;
}

In addition to that you might also want to register the collision detection system to the object registry so that other modules can find it (RequestPlugins does this automatically):

 
object_reg->Register (cd_sys, "iCollideSystem");

This is a very general example. It will first get the preferred collision detection plugin from the configuration file. If the configuration file does not specify it then it will use ‘crystalspace.collisiondetection.opcode’ which is the one we have at the moment. If you do not want to let the users choose another plugin then you can also hard-code the string. The ‘cd_sys’ should be stored somewhere central (i.e. your application class).

Initializing Geometry from the Mesh: The Easy Way

Before you can use the collision detection system you have to make instances of ‘iCollider’. Only the collide system can do that. To create an ‘iCollider’ you have to give an instance of ‘iTriangleMesh’. Several meshes in Crystal Space implement ‘iTriangleMesh’. If you have special geometry on your own you can make your own classes to implement ‘iTriangleMesh’. The easiest way to initialize a collider for a model is to use this code:

 
iMeshWrapper* mesh = ...;
csColliderWrapper* wrapper = csColliderHelper::InitializeCollisionWrapper (
	cd_sys, mesh);

This code will create a collider (iCollider) for the geometry in the mesh. In addition to that it will also create an instance of csColliderWrapper (see ‘cstool/collider.h’). This is a class that helps to keep track of your collider. The csColliderWrapper will be attached to the mesh. So if you later have a pointer to the mesh you can get the pointer to the collider wrapper by doing:

 
iMeshWrapper* mesh = ...;
csColliderWrapper* wrapper = csColliderWrapper::GetColliderWrapper (
	mesh->QueryObject ());

The nice thing about InitializeCollisionWrapper is that it takes care of hierarchical meshes and knows how to take advantage of the polygon mesh information in the factory (possibly sharing colliders between mesh object instances that use the same factory).

Initializing Geometry from the Mesh: The Hard Way

The most primitive code to initialize a collider for a mesh is here:

 
csRef<iCollider> MyGame::InitCollider (iMeshWrapper* mesh)
{
  iObjectModel* objmodel = mesh->GetMeshObject ()->GetObjectModel ();
  iTriangleMesh* trimesh;
  if (objmodel->IsTriangleDataSet (cdsys->GetTriangleDataID ()))
    trimesh = objmodel->GetTriangleData (cdsys->GetTriangleDataID ());
  else
    trimesh = objmodel->GetTriangleData (cdsys->GetBaseDataID ());
  if (trimesh)
  {
    csRef<iCollider> cd = cd_sys->CreateCollider (trimesh);
    return cd;
  }
  else
  {
    return 0;
  }
}

This code can be useful if you have a simple case and you can easily store the collider somewhere in your game logic. In most cases you should probably use InitializeCollisionWrapper.

Initializing Geometry from the Mesh: The Intermediate Way

Another way to initialize a collider uses csColliderWrapper but without csColliderHelper:

 
bool MyGame::InitCollider (iMeshWrapper* mesh)
{
  iObjectModel* objmodel = mesh->GetMeshObject ()->GetObjectModel ();
  iTriangleMesh* trimesh;
  if (objmodel->IsTriangleDataSet (cdsys->GetTriangleDataID ()))
    trimesh = objmodel->GetTriangleData (cdsys->GetTriangleDataID ());
  else
    trimesh = objmodel->GetTriangleData (cdsys->GetBaseDataID ());
  if (trimesh)
  {
    csColliderWrapper* wrapper = new csColliderWrapper(
    	mesh->QueryObject(), cd_sys, trimesh);
    wrapper->DecRef ();
    return true;
  }
  else
  {
    return false;
  }
}

This example creates a new instance of ‘csColliderWrapper’ which is automatically stored with the ‘iObject’ that belongs with the given mesh. So there is no need to store it otherwise. Later on you can retrieve the collider for some mesh by using csColliderWrapper::GetColliderWrapper() again. This is more basic then using InitializeCollisionWrapper() as it doesn't take care of hierarchical meshes and also doesn't use the factory geometry if present.

Initializing Geometry from a Box

The collision detection system in Crystal Space cannot currently calculate correct collisions with objects that animate (like sprites and cal3d sprites (see section Sprite3D Mesh Object, see section SpriteCal3D Mesh Object). In case of cal3d sprites it is simply not supported and for normal sprites it will take only the first animation frame of the default action. So most of the times it is better to use a box for collision detection instead of the actual model. The easiest way to do that is with this code:

 
iMeshWrapper* mesh = ...;
csBox3 box (-.1, 0, -.1, .1, 1.7, .1);
csTriangleMeshBox* pmbox = new csTriangleMeshBox (box);
csColliderWrapper* wrapper = new csColliderWrapper (
	mesh->QueryObject (), cs_sys, pmbox);
pmbox->DecRef ();
wrapper->DecRef ();

We have to express the box in local object space for the mesh. In the example above we assume that the origin of the mesh is at the bottom center. So we create a box that is .2 units wide and deep and 1.7 units high.

The fact that the collider wrapper was created from a box instead of the usual mesh geometry makes no difference for the collision detection system.

Initializing Geometry for the Entire Level

The easiest way to initialize colliders for the entire level at once is to use the csColliderHelper::InitializeCollisionWrappers() function immediatelly after loading a map:

 
if (!loader->LoadMapFile ("world"))
{
  ...
}
csColliderHelper::InitializeCollisionWrappers (cd_sys, engine);

This will call csColliderHelper::InitializeCollisionWrapper() for every loaded mesh. You can also restrict the above code to one region.

The Player Collider

Depending on the game your player might have a representation of geometry or not. If it doesn't you will have to make your own version of ‘iTriangleMesh’ to create a collider for the player (possibly using csTriangleMeshBox as explained above). Even if your player has geometry (i.e. a 3D sprite) it is sometimes still preferable to create your own special geometry for the player. The reason is gravity. When you would just use one collider for the player you can have problems moving around because the player would not be able to jump over even the tiniest elevation in height. Sometimes the edge between adjacent polygons can even cause the player to collide with that other polygon due to numerical inprecision. To solve this problem it is best to make one collider that is used for gravity only and another collider that is used to test if you can move around. The gravity collider will be used only to test if the player can go downwards or upwards. To avoid not being able to go over small height elevations, the player collider should float slightly above the ground.

The best way to make the gravity collider is to make your own implementation of ‘iTriangleMesh’ using csTriangleMeshBox. This is very efficient. To keep the returned collider I recommend storing them somewhere in the player class or else use csColliderWrapper as explained above.

Doing Collision Detection

When everything is set up it is time to do collision detection. To test for collisions you use the Collide() function in ‘iCollideSystem’ or Collide() in csColliderWrapper. This will test the collisions between two colliders. The result of this will be true or false and in addition the collide system will keep a list of all triangle pairs for the hits. Those triangle pairs can be used to decide what to do on collision (i.e. slide on a wall for example).

Because collision detection works on two objects at a time it is a good idea to have some system on top of the collision detection system that detects when it is useful to do collision detection. You can use a bounding sphere for that. Also you should only do collision detection if the object moves.

Note: Do not forget to call ResetCollisionPairs() before doing collision detection! Otherwise the internal table of collision pairs will grow forever.

Gravity and Sliding Along Walls

Doing collision detection right is a hard problem. To help with this there is a csColliderActor class that you can use. It handles gravity and sliding with walls. To use it you must make sure that all world geometry has a collider (using csColliderHelper::InitializeCollisionWrappers() for example).

At initialization time you use the following code to create the collider actor:

 
csColliderActor collider_actor;
iMeshWrapper* mesh = ...;
...
collider_actor.SetCollideSystem (cdsys);
collider_actor.SetEngine (engine);
collider_actor.InitializeColliders (mesh,
  	csVector3 (0.2, 0.1, 0.2),
	csVector3 (0.2, 0.3, 0.2),
	csVector3 (0, -.2, 0));

Then when you want to move the model (for example this is typically done in SetupFrame()) you can do this:

 
csTicks elapsed_time = vc->GetElapsedTicks ();
collider_actor.Move (float (elapsed_time) / 1000.0f, 1.0f, obj_move,
	angularVelocity);

‘obj_move’ is a relative movement vector in object space. For example, to move the object forward you can use csVector3(0,0,1). ‘angularVelocity’ is a rotation vector for the model. Typically this is something like csVector3(0,1,0) or the 0 vector if you don't want to rotate.

Note! Even if the user didn't press any key it is important to keep calling the Move() function every frame. The reason for that is so that gravity can work. You should just call it with a 0 vector for movement and rotation.

Note that csColliderActor can also work with a camera instead of a mesh.

‘csColliderActor’ has a function to change gravity.

Limitation of OPCODE

The current OPCODE collision detection implementation has one important limitation. It assumes that the transform from object to world space will not change the size of the object; i.e. you cannot scale the object using the object to world transformation (which is kept in the ‘iMovable’) and expect collision detection to be ok. The only way around this limitation is to use HardTransform() to transform the object space vertices itself. But this can of course not be used dynamically as you would have to recalculate the collider every time the object changes.

Header Files

The header files useful for this section are:

 
#include <iutil/object.h>
#include <iutil/plugin.h>
#include <ivaria/collider.h>
#include <igeom/trimesh.h>
#include <iengine/mesh.h>
#include <cstool/collider.h>

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

This document was generated using texi2html 1.76.