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.
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.
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.
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.
// 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);
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:
To provide triangulated meshes for a sector:
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.
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:
m_navigationWorld = *KY_NEW Kaim::World(databaseCount); Kaim::Ptr<Kaim::ICollisionInterface> visInterface = *KY_NEW CollisionInterface(m_navigationWorld); m_navigationWorld->m_collisionWorld->SetCollisionInterface(visInterface);
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:
Both methods are equivalent; you can use whichever fits better with your asset pipeline.
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:
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.