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:

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:

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:

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).

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:

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:

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

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:

‘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.