[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Collision detection in CS is one of the more complicated issues. In this section I give a quick description of all the classes and interfaces in CS and what you should do to use them.
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 implementations of RAPID and OPCODE collision detection systems.
To load a collision detection system you can use the following code:
csRef<iPluginManager> plugmgr = CS_QUERY_REGISTRY (object_reg, iPluginManager); csRef<iConfigManager> config = CS_QUERY_REGISTRY (object_reg, iConfigManager); const char* p = config->GetStr ("MyGame.Settings.CollDetPlugin", "crystalspace.collisiondetection.rapid"); csRef<iCollideSystem> cd_sys = CS_LOAD_PLUGIN (plugmgr, p, iCollideSystem); if (!cd_sys) { csReport (object_reg, CS_REPORTER_SEVERITY_ERROR, "crystalspace.application.myapp", "No Collision Detection plugin found!"); return false; } |
This is a very general example. It will first get the preferred collision detection plugin from the config file. If the config file doesn't specify it then it will use 'crystalspace.collisiondetection.rapid' which is the only one we have at the moment. If you don't want to let the users choose another plugin then you can also hardcode the string. The cd_sys should be stored somewhere central (i.e. your application class).
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 `iPolygonMesh'. Several meshes in CS implement `iPolygonMesh'. If you have special geometry on your own you can make your own classes to implement `iPolygonMesh'. Here is some code on how to initialize the collider for a mesh:
csRef<iCollider> MyGame::InitCollider (iMeshWrapper* mesh) { iPolygonMesh* polmesh = mesh->GetMeshObject ()->GetObjectModel () ->GetPolygonMeshColldet (); if (polmesh) { csRef<iCollider> cd = cd_sys->CreateCollider (polmesh); return cd; } else { return 0; } } |
After that you need to store the returned collider somewhere so you can easily retrieve it later when you want to do the collision detection. Usually in a game you have entity classes which will have a pointer to the mesh. In that case you can easily store the collider in that entity class.
However, you can also use the `iObject' system to attach your collider
to the mesh itself. An easy way to do this is to use the
csColliderWrapper
class that you can find in the cstool
library. csColliderWrapper
is a subclass of csObject
which
means that you can attach this object to any other csObject
.
csColliderWrapper
additionally holds a pointer to an iCollider
.
This means that you can create a csColliderWrapper
to hold your
iCollider
for some mesh object and then you can attach
that csColliderWrapper
to the mesh object itself. To do this
use the following code instead of the InitCollider()
routine
above:
bool MyGame::InitCollider (iMeshWrapper* mesh) { iPolygonMesh* polmesh = mesh->GetMeshObject ()->GetObjectModel () ->GetPolygonMeshColldet (); if (polmesh) { new csColliderWrapper(mesh->QueryObject(), cd_sys, polmesh); 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 doing:
The function csColliderHelper::InitializeCollisionWrapper()
does this work for you and also takes care of a lot of finer details
that are ignored in the example above (like improved memory usage and
performance by sharing the colliders if a factory mesh is used). In addition
csColliderHelper::InitializeCollisionWrappers()
initializes all collider wrappers for all objects in the engine.
csColliderWrapper* colwrap = csColliderWrapper::GetColliderWrapper(mesh->QueryObject ()); iCollider* collider = colwrap->GetCollider(); |
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 `iPolygonMesh' to create a collider for the player. 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 `iPolygonMesh'. This is very efficient. To keep the returned collider I recommend storing them somewhere in the player class or else the main game class.
When everything is set up it is time to do collision detection. To test
for collisions you use the Collide
function in `iCollideSystem'.
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! Don't forget to call ResetCollisionPairs
before doing collision
detection! Otherwise the internal table of collision pairs will grow
forever.
For detecting collision between a mesh and the static world, it is better
to use the CollidePath
method of `iCollideSystem'. Its use
is similar to Collide
but it takes arrays of `iCollider''s
and `csReversibleTransform''s. It also takes a `csVector3'
(passed by reference). This is the translation vector of the movement
that the mesh wants to take (CollidePath
is called before the
movement occurs). The interface is detailed further in the public API docs.
The translation vector is modified to reflect the distance that the mesh
was able to move before it collided with the world. So you should move
the mesh that far, and subtract the new vector from the original vector,
to get the distance still to go. We will call this V
.
Use GetCollisionPairs
to get the triangle that was collided with.
It is described by the points a2, b2, c2
in the
`csCollisionPair' structure. Then use those points to create a
`csPlane3'. We will call this P
.
We want to give our vector a new direction so that it is on P
.
The cross-product (V % P.Normal()
) of the two will give a vector
which is on the plane, but perpendicular to the vector that we want.
Taking the cross-product again (now (V % P.Normal()) % P.Normal()
)
will give the vector we want.
Now we try to move the mesh along this new vector. Hopefully there will be no collision this time, but if there is we must do the whole thing over again. Example:
void Player::Move (csVector3 move) { csVector3 old (move); while (collsys->CollidePath (player_collider, player_transform, move, world_colliders, world_transforms) != 1) { player_transform->Translate (move); csCollisionPair *pair = collsys->GetCollisionPairs (); csPlane3 wall (pair->a2, pair->b2, pair->c2); csVector3 newmove (old - move); old = move; move = newmove; move %= wall.Normal(); move %= wall.Normal(); } player_transform->Translate (move); } |
This example is limited in that it will run into an infinite loop if the player walks into a corner, but it serves as an example.
The current RAPID 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
aroud 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.
The include files useful for this section are:
#include "iutil/object.h" #include "iutil/plugin.h" #include "ivaria/collider.h" #include "igeom/polymesh.h" #include "iengine/mesh.h" #include "cstool/collider.h" |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |