diff --git a/DetourCrowd/Include/DetourCrowd.h b/DetourCrowd/Include/DetourCrowd.h index 96c9c44..52c5fd7 100644 --- a/DetourCrowd/Include/DetourCrowd.h +++ b/DetourCrowd/Include/DetourCrowd.h @@ -26,66 +26,121 @@ #include "DetourProximityGrid.h" #include "DetourPathQueue.h" - +/// The maximum number of neighbors that a crowd agent can take into account +/// for steering decisions. +/// @ingroup crowd static const int DT_CROWDAGENT_MAX_NEIGHBOURS = 6; + +/// The maximum number of corners a crowd agent will look ahead in the path. +/// This value is used for sizing the crowd agent corner buffers. +/// Due to the behavior of the crowd manager, the actual number of useful +/// corners will be one less than this number. +/// @ingroup crowd static const int DT_CROWDAGENT_MAX_CORNERS = 4; + +/// The maximum number of crowd avoidance configurations supported by the +/// crowd manager. +/// @ingroup crowd +/// @see dtObstacleAvoidanceParams, dtCrowd::setObstacleAvoidanceParams(), dtCrowd::getObstacleAvoidanceParams(), +/// dtCrowdAgentParams::obstacleAvoidanceType static const int DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS = 8; + +/// Provides neighbor data for agents managed by the crowd. +/// @ingroup crowd +/// @see dtCrowdAgent::neis, dtCrowd struct dtCrowdNeighbour { - int idx; - float dist; + int idx; ///< The index of the neighbor in the crowd. + float dist; ///< The distance between the current agent and the neighbor. }; +/// The type of navigation mesh polygon the agent is currently traversing. +/// @ingroup crowd enum CrowdAgentState { - DT_CROWDAGENT_STATE_INVALID, - DT_CROWDAGENT_STATE_WALKING, - DT_CROWDAGENT_STATE_OFFMESH, + DT_CROWDAGENT_STATE_INVALID, ///< The agent is not in a valid state. + DT_CROWDAGENT_STATE_WALKING, ///< The agent is traversing a normal navigation mesh polygon. + DT_CROWDAGENT_STATE_OFFMESH, ///< The agent is traversing an off-mesh connection. }; +/// Configuration parameters for a crowd agent. +/// @ingroup crowd struct dtCrowdAgentParams { - float radius; - float height; - float maxAcceleration; - float maxSpeed; + float radius; ///< Agent radius. [Limit: >= 0] + float height; ///< Agent height. [Limit: > 0] + float maxAcceleration; ///< Maximum allowed acceleration. [Limit: >= 0] + float maxSpeed; ///< Maximum allowed speed. [Limit: >= 0] + + /// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0] float collisionQueryRange; - float pathOptimizationRange; + + float pathOptimizationRange; ///< The path visibility optimization range. [Limit: > 0] + + /// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0] float separationWeight; + + /// Flags that impact steering behavior. (See: #UpdateFlags) unsigned char updateFlags; - unsigned char obstacleAvoidanceType; + + /// The index of the avoidance configuration to use for the agent. + /// [Limits: 0 <= value <= #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS] + unsigned char obstacleAvoidanceType; + + /// User defined data attached to the agent. void* userData; }; +/// Represents an agent managed by a #dtCrowd object. +/// @ingroup crowd struct dtCrowdAgent { + /// 1 if the agent is active, or 0 if the agent is in an unused slot in the agent pool. unsigned char active; + + /// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState) unsigned char state; + /// The path corridor the agent is using. dtPathCorridor corridor; + + /// The local boundary data for the agent. dtLocalBoundary boundary; float t; float var; + /// The last time the agent's path corridor was optimized. float topologyOptTime; + /// The known neighbors of the agent. dtCrowdNeighbour neis[DT_CROWDAGENT_MAX_NEIGHBOURS]; + + /// The number of neighbors. int nneis; + /// The desired speed. float desiredSpeed; - float npos[3]; + float npos[3]; ///< The current agent position. [(x, y, z)] float disp[3]; - float dvel[3]; + float dvel[3]; ///< The desired velocity of the agent. [(x, y, z)] float nvel[3]; - float vel[3]; + float vel[3]; ///< The actual velocity of the agent. [(x, y, z)] + /// The agent's configuration parameters. dtCrowdAgentParams params; + /// The local path corridor corners for the agent. (Staight path.) [(x, y, z) * #ncorners] float cornerVerts[DT_CROWDAGENT_MAX_CORNERS*3]; + + /// The local path corridor corner flags. (See: #dtStraightPathFlags) [(flags) * #ncorners] unsigned char cornerFlags[DT_CROWDAGENT_MAX_CORNERS]; + + /// The reference id of the polygon being entered at the corner. [(polyRef) * #ncorners] dtPolyRef cornerPolys[DT_CROWDAGENT_MAX_CORNERS]; + + /// The number of corners. int ncorners; }; @@ -97,13 +152,16 @@ struct dtCrowdAgentAnimation float t, tmax; }; +/// Crowd agent update flags. +/// @ingroup crowd +/// @see dtCrowdAgentParams::updateFlags enum UpdateFlags { DT_CROWD_ANTICIPATE_TURNS = 1, DT_CROWD_OBSTACLE_AVOIDANCE = 2, DT_CROWD_SEPARATION = 4, - DT_CROWD_OPTIMIZE_VIS = 8, - DT_CROWD_OPTIMIZE_TOPO = 16, + DT_CROWD_OPTIMIZE_VIS = 8, ///< Use #dtPathCorridor::optimizePathVisibility() to optimize the agent path. + DT_CROWD_OPTIMIZE_TOPO = 16, ///< Use dtPathCorridor::optimizePathTopology() to optimize the agent path. }; struct dtCrowdAgentDebugInfo @@ -113,6 +171,8 @@ struct dtCrowdAgentDebugInfo dtObstacleAvoidanceDebugData* vod; }; +/// Provides local steering behaviors for a group of agents. +/// @ingroup crowd class dtCrowd { int m_maxAgents; @@ -182,37 +242,201 @@ public: dtCrowd(); ~dtCrowd(); + /// Initializes the crowd. + /// @param[in] maxAgents The maximum number of agents the crowd can manage. [Limit: >= 1] + /// @param[in] maxAgentRadius The maximum radius of any agent that will be added to the crowd. [Limit: > 0] + /// @param[in] nav The navigation mesh to use for planning. + /// @return True if the initialization succeeded. bool init(const int maxAgents, const float maxAgentRadius, dtNavMesh* nav); + /// Sets the shared avoidance configuration for the specified index. + /// @param[in] idx The index. [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS] + /// @param[in] params The new configuration. void setObstacleAvoidanceParams(const int idx, const dtObstacleAvoidanceParams* params); + + /// Gets the shared avoidance configuration for the specified index. + /// @param[in] idx The index of the configuration to retreive. + /// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS] + /// @return The requested configuration. const dtObstacleAvoidanceParams* getObstacleAvoidanceParams(const int idx) const; + /// Gets the specified agent from the pool. + /// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()] + /// @return The requested agent. const dtCrowdAgent* getAgent(const int idx); + + /// The maximum number of agents that can be managed by the object. + /// @return The maximum number of agents. const int getAgentCount() const; + /// Adds a new agent to the crowd. + /// @param[in] pos The requested position of the agent. [(x, y, z)] + /// @param[in] params The configutation of the agent. + /// @return The index of the agent in the agent pool. Or -1 if the agent could not be added. int addAgent(const float* pos, const dtCrowdAgentParams* params); + + /// Updates the specified agent's configuration. + /// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()] + /// @param[in] params The new agent configuration. void updateAgentParameters(const int idx, const dtCrowdAgentParams* params); + + /// Removes the agent from the crowd. + /// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()] void removeAgent(const int idx); + /// Submits a new move request for the specified agent. + /// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()] + /// @param[in] ref The position's polygon reference. + /// @param[in] pos The position within the polygon. [(x, y, z)] + /// @return True if the request was successfully submitted. bool requestMoveTarget(const int idx, dtPolyRef ref, const float* pos); + + /// Sumbits a request to adjust the target position of the specified agent. + /// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()] + /// @param[in] ref The position's polygon reference. + /// @param[in] pos The position within the polygon. [(x, y, z)] + /// @return True if the request was successfully submitted. bool adjustMoveTarget(const int idx, dtPolyRef ref, const float* pos); + /// Gets the active agents int the agent pool. + /// @param[out] agents An array of agent pointers. [(#dtCrowdAgent *) * maxAgents] + /// @param[in] maxAgents The size of the crowd agent array. + /// @return The number of agents returned in @p agents. int getActiveAgents(dtCrowdAgent** agents, const int maxAgents); + + /// Updates the steering and positions of all agents. + /// @param[in] dt The time, in seconds, to update the simulation. [Limit: > 0] + /// @param[out] debug A debug object to load with debug information. [Opt] void update(const float dt, dtCrowdAgentDebugInfo* debug); + /// Gets the filter used by the crowd. + /// @return The filter used by the crowd. const dtQueryFilter* getFilter() const { return &m_filter; } + + /// Gets the filter used by the crowd. + /// @return The filter used by the crowd. dtQueryFilter* getEditableFilter() { return &m_filter; } + + /// Gets the search extents [(x, y, z)] used by the crowd for query operations. + /// @return The search extents used by the crowd. [(x, y, z)] const float* getQueryExtents() const { return m_ext; } + /// Gets the velocity sample count. + /// @return The velocity sample count. inline int getVelocitySampleCount() const { return m_velocitySampleCount; } + /// Gets the crowd's proximity grid. + /// @return The crowd's proximity grid. const dtProximityGrid* getGrid() const { return m_grid; } + + /// Gets the crowd's path request queue. + /// @return The crowd's path request queue. const dtPathQueue* getPathQueue() const { return &m_pathq; } + + /// Gets the query object used by the crowd. const dtNavMeshQuery* getNavMeshQuery() const { return m_navquery; } }; +/// Allocates a crowd object using the Detour allocator. +/// @return A crowd object that is ready for initialization, or null on failure. +/// @ingroup crowd dtCrowd* dtAllocCrowd(); + +/// Frees the specified crowd object using the Detour allocator. +/// @param[in] ptr A crowd object allocated using #dtAllocCrowd +/// @ingroup crowd void dtFreeCrowd(dtCrowd* ptr); #endif // DETOURCROWD_H + +/////////////////////////////////////////////////////////////////////////// + +// This section contains detailed documentation for members that don't have +// a source file. It reduces clutter in the main section of the header. + +/** + +@defgroup crowd Crowd + +Members in this module implement local steering and dynamic avoidance features. + +The crowd is the big beast of the navigation features. It not only handles a +lot of the path management for you, but also local steering and dynamic +avoidance between members of the crowd. I.e. It can keep your agents from +running into each other. + +Main class: #dtCrowd + +The #dtNavMeshQuery and #dtPathCorridor classes provide perfectly good, easy +to use path planning features. But in the end they only give you points that +your navigation client should be moving toward. When it comes to deciding things +like agent velocity and steering to avoid other agents, that is up to you to +implement. Unless, of course, you decide to use #dtCrowd. + +Basically, you add an agent to the crowd, providing various configuration +settings such as maximum speed and acceleration. You also provide a local +target to more toward. The crowd manager then provides, with every update, the +new agent position and velocity for the frame. The movement will be +constrained to the navigation mesh, and steering will be applied to ensure +agents managed by the crowd do not collide with each other. + +This is very powerful feature set. But it comes with limitations. + +The biggest limitation is that you must give control of the agent's position +completely over to the crowd manager. You can update things like maximum speed +and acceleration. But in order for the crowd manager to do its thing, it can't +allow you to constantly be giving it overrides to position and velocity. So +you give up direct control of the agent's movement. It belongs to the crowd. + +The second biggest limitation revolves around the fact that the crowd manager +deals with local planning. So the agent's target should never be more than +256 polygons aways from its current position. If it is, you risk +your agent failing to reach its target. So you may still need to do long +distance planning and provide the crowd manager with intermediate targets. + +Other significant limitations: + +- All agents using the crowd manager will use the same #dtQueryFilter. +- Crowd management is relatively expensive. The maximum agents under crowd + management at any one time is between 20 and 30. A good place to start + is a maximum of 25 agents for 0.5ms per frame. + +@note This is a summary list of members. Use the index or search +feature to find minor members. + +@struct dtCrowdAgentParams +@see dtCrowdAgent, dtCrowd::addAgent(), dtCrowd::updateAgentParameters() + +@var dtCrowdAgentParams::obstacleAvoidanceType +@par + +#dtCrowd permits agents to use different avoidance configurations. This value +is the index of the #dtObstacleAvoidanceParams within the crowd. + +@see dtObstacleAvoidanceParams, dtCrowd::setObstacleAvoidanceParams(), + dtCrowd::getObstacleAvoidanceParams() + +@var dtCrowdAgentParams::collisionQueryRange +@par + +Collision elements include other agents and navigation mesh boundaries. + +This value is often based on the agent radius and/or maximum speed. E.g. radius * 8 + +@var dtCrowdAgentParams::pathOptimizationRange +@par + +Only applicalbe if #updateFlags includes the #DT_CROWD_OPTIMIZE_VIS flag. + +This value is often based on the agent radius. E.g. radius * 30 + +@see dtPathCorridor::optimizePathVisibility() + +@var dtCrowdAgentParams::separationWeight +@par + +A higher value will result in agents trying to stay farther away from each other at +the cost of more difficult steering in tight spaces. + +*/ \ No newline at end of file diff --git a/DetourCrowd/Include/DetourPathCorridor.h b/DetourCrowd/Include/DetourPathCorridor.h index 32bac6a..6a3d338 100644 --- a/DetourCrowd/Include/DetourPathCorridor.h +++ b/DetourCrowd/Include/DetourPathCorridor.h @@ -21,7 +21,8 @@ #include "DetourNavMeshQuery.h" -/// Represents a dynamic polygon corridor used to plan agent movement +/// Represents a dynamic polygon corridor used to plan agent movement. +/// @ingroup crowd, detour class dtPathCorridor { float m_pos[3]; diff --git a/DetourCrowd/Source/DetourCrowd.cpp b/DetourCrowd/Source/DetourCrowd.cpp index 84e6a54..ca984dc 100644 --- a/DetourCrowd/Source/DetourCrowd.cpp +++ b/DetourCrowd/Source/DetourCrowd.cpp @@ -218,7 +218,43 @@ static int getNeighbours(const float* pos, const float height, const float range return n; } +/** +@class dtCrowd +@par +This is the core class of the @ref crowd module. See the @ref crowd documentation for a summary +of the crowd features. + +A common method for setting up the crowd is as follows: + +-# Allocate the crowd using #dtAllocCrowd. +-# Initialize the crowd using #init(). +-# Set the avoidance configurations using #setObstacleAvoidanceParams(). +-# Add agents using #addAgent() and make an initial movement request using #requestMoveTarget(). + +A common process for managing the crowd is as follows: + +-# Call #update() to allow the crowd to manage its agents. +-# Retrieve agent information using #getActiveAgents(). +-# Make movement requests using #requestMoveTarget() and #adjustMoveTarget(). +-# Repeat every frame. + +Some agent configuration settings can be updated using #updateAgentParameters(). But the crowd owns the +agent position. So it is not possible to update an active agent's position. If agent position +must be fed back into the crowd, the agent must be removed and re-added. + +Notes: + +- Path related information is available for newly added agents only after an #update() has been + performed. +- Agent objects are kept in a pool and re-used. So it is important when using agent objects to check the value of + #dtCrowdAgent::active to determine if the agent is actually in use or not. +- This class is meant to provide 'local' movement. There is a limit of 256 polygons in the path corridor. + So it is not meant to provide automatic pathfinding services over long distances. + +@see dtAllocCrowd(), dtFreeCrowd(), init(), dtCrowdAgent + +*/ dtCrowd::dtCrowd() : m_maxAgents(0), @@ -273,6 +309,9 @@ void dtCrowd::purge() m_navquery = 0; } +/// @par +/// +/// May be called more than once to purge and re-initialize the crowd. bool dtCrowd::init(const int maxAgents, const float maxAgentRadius, dtNavMesh* nav) { purge(); @@ -378,6 +417,9 @@ const int dtCrowd::getAgentCount() const return m_maxAgents; } +/// @par +/// +/// Agents in the pool may not be in use. Check #dtCrowdAgent.active before using the returned object. const dtCrowdAgent* dtCrowd::getAgent(const int idx) { return &m_agents[idx]; @@ -390,6 +432,9 @@ void dtCrowd::updateAgentParameters(const int idx, const dtCrowdAgentParams* par memcpy(&m_agents[idx].params, params, sizeof(dtCrowdAgentParams)); } +/// @par +/// +/// The agent's position will be constrained to the surface of the navigation mesh. int dtCrowd::addAgent(const float* pos, const dtCrowdAgentParams* params) { // Find empty slot. @@ -439,6 +484,10 @@ int dtCrowd::addAgent(const float* pos, const dtCrowdAgentParams* params) return idx; } +/// @par +/// +/// The agent is deactivated and will no longer be processed. Its #dtCrowdAgent object +/// is not removed from the pool. It is marked as inactive so that it is available for reuse. void dtCrowd::removeAgent(const int idx) { if (idx >= 0 && idx < m_maxAgents) @@ -505,7 +554,15 @@ bool dtCrowd::requestMoveTargetReplan(const int idx, dtPolyRef ref, const float* return true; } - +/// @par +/// +/// This method is used when a new target is set. Use #adjustMoveTarget() when +/// only small local adjustments are needed. (Such as happens when following a +/// moving target.) +/// +/// The position will be constrained to the surface of the navigation mesh. +/// +/// The request will be processed during the next #update(). bool dtCrowd::requestMoveTarget(const int idx, dtPolyRef ref, const float* pos) { if (idx < 0 || idx > m_maxAgents) @@ -545,7 +602,15 @@ bool dtCrowd::requestMoveTarget(const int idx, dtPolyRef ref, const float* pos) return true; } - +/// @par +/// +/// This method is used when to make small local adjustments to the current +/// target. (Such as happens when following a moving target.) Use +/// #requestMoveTarget() when a new target is needed. +/// +/// The position will be constrained to the surface of the navigation mesh. +/// +/// The request will be processed during the next #update(). bool dtCrowd::adjustMoveTarget(const int idx, dtPolyRef ref, const float* pos) { if (idx < 0 || idx > m_maxAgents) diff --git a/DetourCrowd/Source/DetourPathCorridor.cpp b/DetourCrowd/Source/DetourPathCorridor.cpp index ae3c444..30ea97b 100644 --- a/DetourCrowd/Source/DetourPathCorridor.cpp +++ b/DetourCrowd/Source/DetourPathCorridor.cpp @@ -293,8 +293,11 @@ int dtPathCorridor::findCorners(float* cornerVerts, unsigned char* cornerFlags, @par Inaccurate locomotion or dynamic obstacle avoidance can force the argent position significantly outside the -original corridor. Over time this can result in the formation of a non-optimal corridor. This function uses an -efficient local visibility search to try to re-optimize the corridor between the current position and @p next. +original corridor. Over time this can result in the formation of a non-optimal corridor. Non-optimal paths can +also form near the corners of tiles. + +This function uses an efficient local visibility search to try to optimize the corridor +between the current position and @p next. The corridor will change only if @p next is visible from the current position and moving directly toward the point is better than following the existing path.