This page describes how to create, update and destroy BoxObstacles in your game.
For an introduction to the role of the BoxObstacles class in the obstacle management system, see Using Dynamic Obstacles and TagVolumes.
For background information on the NavTag system, see Tagging with Custom Data.
You can choose one of two possible rotation models for each BoxObstacle. The mode you should choose for each BoxObstacle you create depends on the expected rotations the corresponding object will display in your game, and affects the precision with which the dynamic avoidance system treats each obstacle.
Many objects such as vehicles or doors rotate mostly horizontally, keeping a relatively consistent local Up axis. In this case, the volume of the obstacle is sampled internally into several vertical cylinders. The dynamic avoidance system uses these cylinders to predict the movements of the obstacle in order to generate avoidance trajectories.
For example, the yaw-rotating box in the following image is represented in the dynamic avoidance system as five cylinders:
This typically produces more accurate trajectories for Bots that need to avoid the obstacle. You should use this approach as long as the local Up axis of your obstacle remains approximately aligned to the global Up axis. There is some tolerance to pitch and roll rotation, so you can use this representation even if (for example) your cars are moving on sloped terrain.
Many objects need to rotate freely in all three dimensions, such as rubble or concrete blocks after an explosion, or a pile of crates that can tumble and roll. In this case, the objects cannot be sub-sampled effectively into vertical cylinders, since the local Up axis of the object does not remain consistent. Instead, a single vertical cylinder is created to enclose the volume of the obstacle. The dynamic avoidance system uses this single cylinder to predict the movements of the obstacle in order to generate avoidance trajectories.
This is reasonably accurate for cubic objects. However, the approximation becomes less accurate with greater differences between the obstacle's length, width and height, such as the long box shown above. In these cases, the cylinder is very likely to over-estimate the actual extents of the box in at least one direction.
You need to make a class in your game manage the initialization and destruction of the BoxObstacle. You may already have a class in your game that manages the lifespan of the object that the BoxObstacle is intended to represent within the Gameware Navigation World. If you do, you can use that class. You may also have an analogous object in your physics system, from which you can retrieve the position and velocity of the object at each frame.
For example: [code from Tutorial_ObstacleIntegration.cpp]
class MyGameBoxObstacle { public: MyGameBoxObstacle(): m_navBoxObstacle(KY_NULL) {} void Initialize(Kaim::World* world, const Kaim::Vec3f& boxHalfExtents, const Kaim::Vec3f& position, const Kaim::Vec3f& linearVelocity, const Kaim::Vec3f& angularVelocity); void Destroy(); void Update(KyFloat32 simulationStepsInSeconds); RigidBodyPhysics m_rigidBodyPhysics; Kaim::Ptr<Kaim::BoxObstacle> m_navBoxObstacle; };
To initialize your BoxObstacle, you must provide an instance of the BoxObstacleInitConfig configuration class, which you set up with the data needed by the BoxObstacle. You must set up at a minimum:
For example: [code from Tutorial_ObstacleIntegration.cpp]
void MyGameBoxObstacle::Initialize(Kaim::World* world, const Kaim::Vec3f& boxHalfExtents, const Kaim::Vec3f& position, const Kaim::Vec3f& linearVelocity, const Kaim::Vec3f& angularVelocity) { // Initialize the object that represents this obstacle in the physics system. m_rigidBodyPhysics.Initialize(position, linearVelocity, angularVelocity); // Set up the BoxObstacleInitConfig. Kaim::BoxObstacleInitConfig boxObstacleInitConfig; boxObstacleInitConfig.m_world = world; boxObstacleInitConfig.m_localHalfExtents = boxHalfExtents; boxObstacleInitConfig.m_startPosition = m_rigidBodyPhysics.GetPosition(); boxObstacleInitConfig.m_rotationMode = Kaim::BoxObstacleRotation_Yaw; // This sets the NavTag as non-walkable: it will "cut a hole" in the NavMesh. boxObstacleInitConfig.m_navTag.SetAsExclusive(); // Create and initialize the BoxObstacle, and add it to its World m_navBoxObstacle = *KY_NEW Kaim::BoxObstacle; m_navBoxObstacle->Init(boxObstacleInitConfig); m_navBoxObstacle->AddToWorld(); ... }
Each time the object represented by the BoxObstacle changes its position, rotation or velocity in your game, you need to update your BoxObstacle to reflect the new state. Note that this includes both the linear velocity of the obstacle as it moves through space, and its angular velocity as it rotates. You can use the functions in BoxObstacle to reflect the new state.
Note that this does not modify the BoxObstacle immediately; the update is deferred until the next update of the World.
For example: [code from Tutorial_ObstacleIntegration.cpp]
void MyGameBoxObstacle::Update(KyFloat32 simulationStepsInSeconds) { ... // Update the object in the physics system. m_rigidBodyPhysics.Update(simulationStepsInSeconds); // Update the BoxObstacle. m_navBoxObstacle->SetTransform(m_rigidBodyPhysics.GetTransform()); m_navBoxObstacle->SetLinearVelocity(m_rigidBodyPhysics.GetLinearVelocity()); m_navBoxObstacle->SetAngularVelocity(m_rigidBodyPhysics.GetAngularVelocity()); }
If your obstacle stops moving and becomes stable, you will likely want to integrate it into the NavMesh so that the Bots in your game can take its presence into account when planning paths through the terrain, instead of only reacting to its presence during the dynamic avoidance phase of their path following.
When your obstacle becomes stationary, call its BoxObstacle::DoesTriggerTagVolume() method and pass true. During the next update of the World the obstacle will transparently spawn a TagVolume with the same position and dimensions, and with the NavTag that you set when you initialized the BoxObstacle. This TagVolume is then integrated into the NavMesh, tagging the area with custom data or tagging it as "exclusive" and removing it from the NavMesh. Once the integration has been completed and the NavMesh is updated, the obstacle's volume is automatically removed from all dynamic avoidance calculations, since its presence is now managed through the NavMesh.
If your obstacle starts moving again, call BoxObstacle::DoesTriggerTagVolume() and pass false in order to reverse the process.
For example: [code from Tutorial_ObstacleIntegration.cpp]
void MyGameBoxObstacle::Update(KyFloat32 simulationStepsInSeconds) { const bool isStopped = m_rigidBodyPhysics.IsSleeping(); m_navBoxObstacle->SetDoesTriggerTagVolume(isStopped); if (isStopped) return; ... }
To destroy the BoxObstacle:
For example: [code from Tutorial_ObstacleIntegration.cpp]
void MyGameBoxObstacle::Destroy() { m_navBoxObstacle->RemoveFromWorld(); m_navBoxObstacle = KY_NULL; }