Using the Collision System

Gameware Navigation includes a lightweight, low-complexity system for conducting high-performance collision tests (raycasts) between two points in space, against the level geometry.

For example, you could use this system to:

Often, these tests are carried out by a physics subsystem. However, for many games (most often MMOs), network communication lags, technical complexity, and/or runtime performance overhead may make an integration of a full-fledged physics system problematic or prohibitive. If this is the case for your project, but you still need to conduct these kinds of collision tests against the geometry in your game levels, you may be able to take advantage of the Gameware Navigation collision system.

Static objects only

The built-in collision system relies on pre-generated data that is separate from the NavData for your terrain. Therefore, it can detect collisions only with static elements of the terrain that you provide during your data generation process. It cannot take into account dynamic objects that you add at runtime, even if you integrate those objects into the NavData for your terrain.

Pre-requisites

In order to generate collision data using the C++ NavData generation API. If you have not already integrated the NavData generation API into your game editor, level design tools or build process, see Integration Phase 6: Using the NavData Generation API.

Step 1. Generating collision data

In your NavData generation code, you need to activate collision data generation for each desired sector in your terrain.

To activate collision data generation for a sector:

When you generate NavData for the sector, its collision data is also generated automatically from the triangles that you pass in to the NavData generation system for that sector. The data for each sector is written to disk in a .ColData file, in the same location and with the same file name as the .NavData file generated for that sector.

For example:

// Create a sector...
Kaim::GeneratorSectorConfig sectorConfig(Kaim::KyGuid::GetDefaultGuid(), generationName);
Kaim::GeneratorSector* sector = env.m_generatorInputOutput.AddSector(sectorConfig);
...
// Activate collision data generation
sector->SetColDataBuildMode(Kaim::GenFlags::SECTOR_COLDATA_BUILD_ENABLED);
...
// Generate NavData and collision data
KyResult result = m_generator->Generate(m_generatorInputOutput);

Option: Using heightfields for better performance

Instead of providing the NavData generation system with the individual triangles that make up each sector of your terrain, you can provide it with heightfields and triangulated meshes.

A heightfield is an optimized way of expressing the altitude of a simple single-story terrain. Instead of containing all of the triangles that make up the terrain mesh, it consists of a single origin point, plus a set of altitude values sampled in a regular grid around the origin. Because the data is much more compact and less complex than a full triangulated mesh, it offers lower runtime memory usage and much greater performance in collision tests.

The negative side of using heightfields is that they are typically also less accurate to local variations in height that occur within the spacing of the samples in the grid. This can cause the NavMesh produced from the heightfields, and the collision tests that are conducted against them, to be less accurate. To offset this lack of accuracy, you can also provide triangulated meshes for the more complex areas of the terrain, where closer fidelity to the smaller features of the terrain is needed.

To get the best compromise between efficiency and accuracy, use heightfields to represent wide-open areas such as fields, and triangulated meshes to represent areas where you need greater precision, such as city blocks. For example:

To provide heightfields and indexed meshes for a sector, you must update your implementation of GeneratorInputProducer::Produce() in the GeneratorInputProducer that you use in your NavData generation pipeline to pass the terrain geometry to the NavData generation system.

To provide heightfields for a sector:

  • For each heightfield that you want to provide for a sector, your GeneratorInputProducer should create an instance of the Kaim::Heightfield class, set it up with the data for the current sector, and pass a pointer in a call to ClientInputConsumer::ConsumeHeightField().

To provide triangulated meshes for a sector:

  • For each mesh that you want to provide for a sector, your GeneratorInputProducer should create an instance of the Kaim::IndexedMesh class, set it up with the data for the current sector, and pass a pointer in a call to ClientInputConsumer::ConsumeIndexedMesh().

Note that when you use this approach, NavData is generated on your heightfields and triangulated meshes. It is therefore unnecessary to provide individual triangles directly to the ClientInputConsumer.

Step 2. Setting up the runtime World

Each World maintains an instance of the CollisionWorld class in its World::m_collisionWorld class member. This class is responsible for aggregating the collision data you load into memory, and for managing the collision tests that you conduct against that data. It does this by calling the methods of an instance of a class that implements ICollisionInterface, which you need to provide after you initialize the World.

Gameware Navigation includes an implementation of ICollisionInterface that relies on the open-source Bullet physics and collision testing system. For details, see www.bulletphysics.org.

To use the default ICollisionInterface implementation:

  1. Add all of the header and source files in the integration\gwnavruntimeglue\bulletcollisioninterface directory to your project, and configure your compiler to look for include files in that directory.
  2. In your runtime initialization code, create a new instance of the CollisionInterface class.
  3. After you create your World, set up its World::m_collisionWorld to use your interface object by passing it in a call to the CollisionWorld::SetCollisionInterface() method.

For example:

m_navigationWorld = *KY_NEW Kaim::World(databaseCount);
Kaim::Ptr<Kaim::ICollisionInterface> visInterface = *KY_NEW CollisionInterface(m_navigationWorld);
m_navigationWorld->m_collisionWorld->SetCollisionInterface(visInterface);

Step 3. Loading collision data at runtime

When you need to conduct collision tests against the collision data you have created for your terrain, you need to load your collision data into memory and add it to the collision world. Typically you would do this for each sector of the terrain at the same time as you stream the NavData and other kinds of data that are needed for that sector in and out of memory. The process is very similar to streaming NavData in and out of a Database.

To load collision data into a collision world at runtime:

  1. Create a new CollisionData object.
  2. Load the collision data into the object. You can use exactly the same approaches you use to fill a NavData object with pre-generated NavData:

    Both methods are equivalent; you can use whichever fits better with your asset pipeline.

  3. Retrieve the CollisionWorld object maintained in World::m_collisionWorld, call its CollisionWorld::AddCollisionData() method, and pass a pointer to the CollisionData object.

For example:

Kaim::Ptr<Kaim::CollisionData> colData = *KY_NEW Kaim::CollisionData;
if (Kaim::Result::Fail(colData->Load(GetAbsoluteInputFileName(fileName).c_str(), &m_fileOpener)))
		return KY_NULL;

GetWorld()->m_collisionWorld->AddCollisionData(colData);

To remove collision data from the collision world:

For example:

GetWorld()->m_collisionWorld->RemoveCollisionData(colData);

Step 4. Conducting collision queries

To run a collision test against the collision data you have loaded into the World, set up and launch an instance of the CollisionRayCastQuery class.

For example, the following code tests for a collision along the straight-line path between point A and point B:

Kaim::CollisionRayCastQuery query;
query.BindToWorld(m_navigationWorld);
query.Initialize(pointA, pointB);
query.PerformQuery();

if (query.GetResult() == Kaim::RayHit)
    ... // A collision was found
if (query.GetResult() == Kaim::RayDidNotHit)
    ... // No collision was detected

You can also take advantage of the mechanisms provided by the query system to run your CollisionRayCastQuery asynchronously in a queue maintained by the World, or in a custom queue that you create and set up with a maximum time budget per frame. If your game may need to request several collision tests in the same frame, these approaches can help avoid CPU peaks.

For more information, see Using the Query System.