Browse Source

Add crowd update callback support. Remove redundant vectors.

Yao Wei Tjong 姚伟忠 10 years ago
parent
commit
549b6fd8be

+ 8 - 7
Source/Samples/39_CrowdNavigation/CrowdNavigation.cpp

@@ -37,7 +37,7 @@
 #include <Urho3D/Math/MathDefs.h>
 #include <Urho3D/Graphics/Model.h>
 #include <Urho3D/Navigation/CrowdAgent.h>
-#include <Urho3D/Navigation/DetourCrowdManager.h>
+#include <Urho3D/Navigation/CrowdManager.h>
 #include <Urho3D/Navigation/DynamicNavigationMesh.h>
 #include <Urho3D/Navigation/Navigable.h>
 #include <Urho3D/Navigation/NavigationEvents.h>
@@ -167,8 +167,8 @@ void CrowdNavigation::CreateScene()
     for (unsigned i = 0; i < 100; ++i)
         CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
 
-    // Create a DetourCrowdManager component to the scene root
-    scene_->CreateComponent<DetourCrowdManager>();
+    // Create a CrowdManager component to the scene root
+    scene_->CreateComponent<CrowdManager>();
 
     // Create some movable barrels. We create them as crowd agents, as for moving entities it is less expensive and more convenient than using obstacles
     CreateMovingBarrels(navMesh);
@@ -345,7 +345,7 @@ void CrowdNavigation::SetPathPoint(bool spawning)
             SpawnJack(pathPos);
         else
             // Set crowd agents target position
-            scene_->GetComponent<DetourCrowdManager>()->SetCrowdTarget(pathPos);
+            scene_->GetComponent<CrowdManager>()->SetCrowdTarget(pathPos);
     }
 }
 
@@ -485,7 +485,7 @@ void CrowdNavigation::HandlePostRenderUpdate(StringHash eventType, VariantMap& e
         // Visualize navigation mesh, obstacles and off-mesh connections
         scene_->GetComponent<DynamicNavigationMesh>()->DrawDebugGeometry(true);
         // Visualize agents' path and position to reach
-        scene_->GetComponent<DetourCrowdManager>()->DrawDebugGeometry(true);
+        scene_->GetComponent<CrowdManager>()->DrawDebugGeometry(true);
     }
 }
 
@@ -515,6 +515,7 @@ void CrowdNavigation::HandleCrowdAgentReposition(StringHash eventType, VariantMa
     Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
     CrowdAgent* agent = static_cast<CrowdAgent*>(eventData[P_CROWD_AGENT].GetPtr());
     Vector3 velocity = eventData[P_VELOCITY].GetVector3();
+    float timeStep = eventData[P_TIMESTEP].GetFloat();
 
     // Only Jack agent has animation controller
     AnimationController* animCtrl = node->GetComponent<AnimationController>();
@@ -524,8 +525,8 @@ void CrowdNavigation::HandleCrowdAgentReposition(StringHash eventType, VariantMa
         if (animCtrl->IsPlaying(WALKING_ANI))
         {
             float speedRatio = speed / agent->GetMaxSpeed();
-            // Face the direction of its velocity but moderate the turning speed based on the speed ratio as we do not have timeStep here
-            node->SetRotation(node->GetRotation().Slerp(Quaternion(Vector3::FORWARD, velocity), 0.1f * speedRatio));
+            // Face the direction of its velocity but moderate the turning speed based on the speed ratio and timeStep
+            node->SetRotation(node->GetRotation().Slerp(Quaternion(Vector3::FORWARD, velocity), 10.0f * timeStep * speedRatio));
             // Throttle the animation speed based on agent speed ratio (ratio = 1 is full throttle)
             animCtrl->SetSpeed(WALKING_ANI, speedRatio);
         }

+ 17 - 3
Source/ThirdParty/DetourCrowd/include/DetourCrowd.h

@@ -16,6 +16,8 @@
 // 3. This notice may not be removed or altered from any source distribution.
 //
 
+// Modified by Yao Wei Tjong for Urho3D
+
 #ifndef DETOURCROWD_H
 #define DETOURCROWD_H
 
@@ -198,10 +200,15 @@ struct dtCrowdAgentDebugInfo
 	dtObstacleAvoidanceDebugData* vod;
 };
 
+// Urho3D: Add update callback support
+/// Type for the updat callback.
+typedef void (*dtUpdateCallback)(dtCrowdAgent* ag, float dt);
+
 /// Provides local steering behaviors for a group of agents. 
 /// @ingroup crowd
 class dtCrowd
 {
+	dtUpdateCallback m_updateCallback;
 	int m_maxAgents;
 	dtCrowdAgent* m_agents;
 	dtCrowdAgent** m_activeAgents;
@@ -236,17 +243,19 @@ class dtCrowd
 	bool requestMoveTargetReplan(const int idx, dtPolyRef ref, const float* pos);
 
 	void purge();
-	
+
 public:
 	dtCrowd();
 	~dtCrowd();
 	
+	// Urho3D: Add update callback support
 	/// 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.
+	///  @param[in]		cb				The update callback.
 	/// @return True if the initialization succeeded.
-	bool init(const int maxAgents, const float maxAgentRadius, dtNavMesh* nav);
+	bool init(const int maxAgents, const float maxAgentRadius, dtNavMesh* nav, dtUpdateCallback cb = 0);
 	
 	/// Sets the shared avoidance configuration for the specified index.
 	///  @param[in]		idx		The index. [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
@@ -272,6 +281,11 @@ public:
 	/// The maximum number of agents that can be managed by the object.
 	/// @return The maximum number of agents.
 	int getAgentCount() const;
+
+	// Urho3D: Add missing getter
+	/// The maximum radius of any agent that will be added to the crowd.
+	/// @return The maximum radius of any agent.
+	float getMaxAgentRadius() const { return m_maxAgentRadius; }
 	
 	/// Adds a new agent to the crowd.
 	///  @param[in]		pos		The requested position of the agent. [(x, y, z)]
@@ -447,4 +461,4 @@ This value is often based on the agent radius. E.g. radius * 30
 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.
 
-*/
+*/

+ 10 - 3
Source/ThirdParty/DetourCrowd/source/DetourCrowd.cpp

@@ -16,7 +16,7 @@
 // 3. This notice may not be removed or altered from any source distribution.
 //
 
-// Modified by Lasse Oorni for Urho3D
+// Modified by Lasse Oorni and Yao Wei Tjong for Urho3D
 
 #define _USE_MATH_DEFINES
 #include <string.h>
@@ -332,6 +332,8 @@ Notes:
 */
 
 dtCrowd::dtCrowd() :
+	// Urho3D: Add update callback support
+	m_updateCallback(0),
 	m_maxAgents(0),
 	m_agents(0),
 	m_activeAgents(0),
@@ -378,13 +380,15 @@ void dtCrowd::purge()
 	m_navquery = 0;
 }
 
+// Urho3D: Add update callback support
 /// @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)
+bool dtCrowd::init(const int maxAgents, const float maxAgentRadius, dtNavMesh* nav, dtUpdateCallback cb)
 {
 	purge();
-	
+
+	m_updateCallback = cb;
 	m_maxAgents = maxAgents;
 	m_maxAgentRadius = maxAgentRadius;
 
@@ -1409,6 +1413,9 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug)
 			ag->partial = false;
 		}
 
+		// Urho3D: Add update callback support
+		if (m_updateCallback)
+			(*m_updateCallback)(ag, dt);
 	}
 	
 	// Update agents using off-mesh connection.

+ 6 - 5
Source/Urho3D/LuaScript/pkgs/Navigation/CrowdAgent.pkg

@@ -41,12 +41,14 @@ enum NavigationPushiness
 
 class CrowdAgent : public Component
 {
+    void DrawDebugGeometry(bool depthTest);
+
     void SetTargetPosition(const Vector3& position);
     void SetTargetVelocity(const Vector3& velocity);
     void ResetTarget();
     void SetUpdateNodePosition(bool unodepos);
-    void SetMaxAccel(float val);
-    void SetMaxSpeed(float val);
+    void SetMaxAccel(float maxAccel);
+    void SetMaxSpeed(float maxSpeed);
     void SetRadius(float radius);
     void SetHeight(float height);
     void SetFilterType(unsigned filterType);
@@ -63,8 +65,8 @@ class CrowdAgent : public Component
     CrowdAgentState GetAgentState() const;
     CrowdAgentTargetState GetTargetState() const;
     bool GetUpdateNodePosition() const;
-    float GetMaxSpeed() const;
     float GetMaxAccel() const;
+    float GetMaxSpeed() const;
     float GetRadius() const;
     float GetHeight() const;
     unsigned GetFilterType() const;
@@ -74,13 +76,12 @@ class CrowdAgent : public Component
     bool HasRequestedTarget() const;
     bool HasArrived() const;
     bool IsInCrowd() const;
-    void DrawDebugGeometry(bool depthTest);
 
     tolua_property__get_set Vector3 targetPosition;
     tolua_property__get_set Vector3 targetVelocity;
     tolua_property__get_set bool updateNodePosition;
-    tolua_property__get_set float maxSpeed;
     tolua_property__get_set float maxAccel;
+    tolua_property__get_set float maxSpeed;
     tolua_property__get_set float radius;
     tolua_property__get_set float height;
     tolua_property__get_set unsigned filterType;

+ 24 - 0
Source/Urho3D/LuaScript/pkgs/Navigation/CrowdManager.pkg

@@ -0,0 +1,24 @@
+$#include "Navigation/CrowdManager.h"
+
+class CrowdManager : public Component
+{
+    void DrawDebugGeometry(bool depthTest);
+
+    void SetCrowdTarget(const Vector3& position, Node* node = 0);
+    void SetCrowdVelocity(const Vector3& velocity, Node* node = 0);
+    void ResetCrowdTarget(Node* node = 0);
+    void SetMaxAgents(unsigned agentCt);
+    void SetMaxAgentRadius(float maxAgentRadius);
+    void SetNavigationMesh(NavigationMesh *navMesh);
+    void SetAreaCost(unsigned filterID, unsigned areaID, float cost);
+
+    PODVector<CrowdAgent*> GetAgents(Node* node = 0, bool inCrowdFilter = true) const;
+    unsigned GetMaxAgents() const;
+    float GetMaxAgentRadius() const;
+    NavigationMesh* GetNavigationMesh() const;
+    float GetAreaCost(unsigned filterID, unsigned areaID) const;
+
+    tolua_property__get_set int maxAgents;
+    tolua_property__get_set float maxAgentRadius;
+    tolua_property__get_set NavigationMesh* navigationMesh;
+};

+ 0 - 24
Source/Urho3D/LuaScript/pkgs/Navigation/DetourCrowdManager.pkg

@@ -1,24 +0,0 @@
-$#include "Navigation/DetourCrowdManager.h"
-
-class DetourCrowdManager : public Component
-{
-    bool CreateCrowd();
-    void DrawDebugGeometry(bool depthTest);
-
-    void SetNavigationMesh(NavigationMesh *navMesh);
-    void SetAreaCost(unsigned filterID, unsigned areaID, float cost);
-    void SetMaxAgents(unsigned agentCt);
-    void SetCrowdTarget(const Vector3& position, int startId = 0, int endId = M_MAX_INT);
-    void ResetCrowdTarget(int startId = 0, int endId = M_MAX_INT);
-    void SetCrowdVelocity(const Vector3& velocity, int startId = 0, int endId = M_MAX_INT);
-
-    NavigationMesh* GetNavigationMesh() const;
-    unsigned GetMaxAgents() const;
-    float GetAreaCost(unsigned filterID, unsigned areaID) const;
-    unsigned GetAgentCount() const;
-    const PODVector<CrowdAgent*>& GetActiveAgents() const;
-
-    tolua_property__get_set NavigationMesh* navigationMesh;
-    tolua_property__get_set int maxAgents;
-    tolua_readonly tolua_property__get_set unsigned agentCount;
-};

+ 5 - 6
Source/Urho3D/LuaScript/pkgs/NavigationLuaAPI.pkg

@@ -1,12 +1,11 @@
-$pfile "Navigation/Navigable.pkg"
-$pfile "Navigation/NavigationMesh.pkg"
+$pfile "Navigation/CrowdAgent.pkg"
+$pfile "Navigation/CrowdManager.pkg"
 $pfile "Navigation/DynamicNavigationMesh.pkg"
-$pfile "Navigation/OffMeshConnection.pkg"
 $pfile "Navigation/NavArea.pkg"
+$pfile "Navigation/Navigable.pkg"
+$pfile "Navigation/NavigationMesh.pkg"
 $pfile "Navigation/Obstacle.pkg"
-$pfile "Navigation/DetourCrowdManager.pkg"
-$pfile "Navigation/CrowdAgent.pkg"
-
+$pfile "Navigation/OffMeshConnection.pkg"
 
 $using namespace Urho3D;
 $#pragma warning(disable:4800)

+ 30 - 20
Source/Urho3D/Navigation/CrowdAgent.cpp

@@ -25,8 +25,8 @@
 #include "../Scene/Component.h"
 #include "../Core/Context.h"
 #include "../Navigation/CrowdAgent.h"
+#include "../Navigation/CrowdManager.h"
 #include "../Graphics/DebugRenderer.h"
-#include "../Navigation/DetourCrowdManager.h"
 #include "../IO/Log.h"
 #include "../IO/MemoryBuffer.h"
 #include "../Navigation/NavigationEvents.h"
@@ -123,13 +123,18 @@ void CrowdAgent::RegisterObject(Context* context)
 
 void CrowdAgent::ApplyAttributes()
 {
+    maxAccel_ = Max(0.f, maxAccel_);
+    maxSpeed_ = Max(0.f, maxSpeed_);
+    radius_ = Max(0.f, radius_);
+    height_ = Max(0.f, height_);
+
     UpdateParameters();
 
     // Set or reset target after we have attributes applied to the agent's parameters.
     CrowdAgentRequestedTarget requestedTargetType = requestedTargetType_;
     if (CA_REQUESTEDTARGET_NONE != requestedTargetType_)
     {
-        requestedTargetType_ = CA_REQUESTEDTARGET_NONE;     // Assign any dummy value so that the value check in the setter method passes
+        requestedTargetType_ = CA_REQUESTEDTARGET_NONE;     // Assign a dummy value such that the value check in the setter method passes
         if (requestedTargetType == CA_REQUESTEDTARGET_POSITION)
             SetTargetPosition(targetPosition_);
         else
@@ -277,22 +282,20 @@ void CrowdAgent::UpdateParameters(unsigned scope)
     }
 }
 
-void CrowdAgent::AddAgentToCrowd()
+int CrowdAgent::AddAgentToCrowd(bool force)
 {
     if (!node_ || !crowdManager_ || !crowdManager_->crowd_)
-        return;
+        return -1;
 
-    if (!IsInCrowd())
+    if (force || !IsInCrowd())
     {
         PROFILE(AddAgentToCrowd);
 
         agentCrowdId_ = crowdManager_->AddAgent(this, node_->GetPosition());
         if (agentCrowdId_ == -1)
-        {
-            LOGERROR("AddAgentToCrowd: Could not add agent to crowd");
-            return;
-        }
-        UpdateParameters();
+            return -1;
+
+        ApplyAttributes();
 
         previousAgentState_ = GetAgentState();
         previousTargetState_ = GetTargetState();
@@ -317,6 +320,8 @@ void CrowdAgent::AddAgentToCrowd()
         // Save the initial position to prevent CrowdAgentReposition event being triggered unnecessarily
         previousPosition_ = GetPosition();
     }
+
+    return agentCrowdId_;
 }
 
 void CrowdAgent::RemoveAgentFromCrowd()
@@ -377,21 +382,21 @@ void CrowdAgent::SetUpdateNodePosition(bool unodepos)
     }
 }
 
-void CrowdAgent::SetMaxSpeed(float speed)
+void CrowdAgent::SetMaxAccel(float maxAccel)
 {
-    if (speed != maxSpeed_)
+    if (maxAccel != maxAccel_ && maxAccel >= 0.f)
     {
-        maxSpeed_ = speed;
+        maxAccel_ = maxAccel;
         UpdateParameters(SCOPE_BASE_PARAMS);
         MarkNetworkUpdate();
     }
 }
 
-void CrowdAgent::SetMaxAccel(float accel)
+void CrowdAgent::SetMaxSpeed(float maxSpeed)
 {
-    if (accel != maxAccel_)
+    if (maxSpeed != maxSpeed_ && maxSpeed >= 0.f)
     {
-        maxAccel_ = accel;
+        maxSpeed_ = maxSpeed;
         UpdateParameters(SCOPE_BASE_PARAMS);
         MarkNetworkUpdate();
     }
@@ -399,17 +404,17 @@ void CrowdAgent::SetMaxAccel(float accel)
 
 void CrowdAgent::SetRadius(float radius)
 {
-    if (radius != radius_)
+    if (radius != radius_ && radius > 0.f)
     {
         radius_ = radius;
-        UpdateParameters(SCOPE_BASE_PARAMS);
+        UpdateParameters(SCOPE_BASE_PARAMS | SCOPE_NAVIGATION_PUSHINESS_PARAMS);
         MarkNetworkUpdate();
     }
 }
 
 void CrowdAgent::SetHeight(float height)
 {
-    if (height != height_)
+    if (height != height_ && height > 0.f)
     {
         height_ = height;
         UpdateParameters(SCOPE_BASE_PARAMS);
@@ -513,10 +518,14 @@ bool CrowdAgent::IsInCrowd() const
     return crowdManager_ && agentCrowdId_ != -1;
 }
 
-void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newVel)
+void CrowdAgent::OnCrowdUpdate(dtCrowdAgent* ag, float dt)
 {
+    assert (ag);
     if (node_)
     {
+        Vector3 newPos(ag->npos);
+        Vector3 newVel(ag->vel);
+
         // Notify parent node of the reposition
         if (newPos != previousPosition_)
         {
@@ -528,6 +537,7 @@ void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& ne
             map[CrowdAgentReposition::P_POSITION] = newPos;
             map[CrowdAgentReposition::P_VELOCITY] = newVel;
             map[CrowdAgentReposition::P_ARRIVED] = HasArrived();
+            map[CrowdAgentReposition::P_TIMESTEP] = dt;
             SendEvent(E_CROWD_AGENT_REPOSITION, map);
 
             if (updateNodePosition_)

+ 17 - 15
Source/Urho3D/Navigation/CrowdAgent.h

@@ -23,7 +23,7 @@
 #pragma once
 
 #include "../Scene/Component.h"
-#include "../Navigation/DetourCrowdManager.h"
+#include "../Navigation/CrowdManager.h"
 
 namespace Urho3D
 {
@@ -67,11 +67,13 @@ enum NavigationPushiness
     NAVIGATIONPUSHINESS_HIGH
 };
 
-/// Crowd agent component, requires a DetourCrowdManager in the scene. When not set explicitly, agent's radius and height are defaulted to navigation mesh's agent radius and height, respectively.
+/// Crowd agent component, requires a CrowdManager component in the scene. When not set explicitly, agent's radius and height are defaulted to navigation mesh's agent radius and height, respectively.
 class URHO3D_API CrowdAgent : public Component
 {
     OBJECT(CrowdAgent);
-    friend class DetourCrowdManager;
+
+    friend class CrowdManager;
+    friend void CrowdAgentUpdateCallback(dtCrowdAgent* ag, float dt);
 
 public:
     /// Construct.
@@ -99,13 +101,13 @@ public:
     /// Update the node position. When set to false, the node position should be updated by other means (e.g. using Physics) in response to the E_CROWD_AGENT_REPOSITION event.
     void SetUpdateNodePosition(bool unodepos);
     /// Set the agent's max acceleration.
-    void SetMaxAccel(float val);
+    void SetMaxAccel(float maxAccel);
     /// Set the agent's max velocity.
-    void SetMaxSpeed(float val);
+    void SetMaxSpeed(float maxSpeed);
     /// Set the agent's radius.
-    void SetRadius(float val);
+    void SetRadius(float radius);
     /// Set the agent's height.
-    void SetHeight(float val);
+    void SetHeight(float height);
     /// Set the agent's filter type.
     void SetFilterType(unsigned filterType);
     /// Set the agent's obstacle avoidance type.
@@ -135,10 +137,10 @@ public:
     bool GetUpdateNodePosition() const { return updateNodePosition_; }
     /// Return the agent id.
     int GetAgentCrowdId() const { return agentCrowdId_; }
-    /// Get the agent's max velocity.
-    float GetMaxSpeed() const { return maxSpeed_; }
     /// Get the agent's max acceleration.
     float GetMaxAccel() const { return maxAccel_; }
+    /// Get the agent's max velocity.
+    float GetMaxSpeed() const { return maxSpeed_; }
     /// Get the agent's radius.
     float GetRadius() const { return radius_; }
     /// Get the agent's height.
@@ -164,8 +166,8 @@ public:
     void SetAgentDataAttr(const PODVector<unsigned char>& value);
 
 protected:
-    /// Update the nodes position if updateNodePosition is set. Is called in DetourCrowdManager::Update().
-    virtual void OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newVel);
+    /// Handle crowd agent being updated. It is called by CrowdManager::Update() via callback.
+    virtual void OnCrowdUpdate(dtCrowdAgent* ag, float dt);
     /// Handle node being assigned.
     virtual void OnNodeSet(Node* node);
     /// Handle node being assigned.
@@ -178,12 +180,12 @@ private:
     /// Update Detour crowd agent parameter.
     void UpdateParameters(unsigned scope = M_MAX_UNSIGNED);
     /// Add agent into crowd.
-    void AddAgentToCrowd();
+    int AddAgentToCrowd(bool force = false);
     /// Remove agent from crowd.
     void RemoveAgentFromCrowd();
-    /// Detour crowd manager.
-    WeakPtr<DetourCrowdManager> crowdManager_;
-    /// DetourCrowd reference to this agent.
+    /// Crowd manager.
+    WeakPtr<CrowdManager> crowdManager_;
+    /// Crowd manager reference to this agent.
     int agentCrowdId_;
     /// Reference to poly closest to requested target position.
     unsigned targetRef_;

+ 175 - 180
Source/Urho3D/Navigation/DetourCrowdManager.cpp → Source/Urho3D/Navigation/CrowdManager.cpp

@@ -25,8 +25,8 @@
 #include "../Scene/Component.h"
 #include "../Core/Context.h"
 #include "../Navigation/CrowdAgent.h"
+#include "../Navigation/CrowdManager.h"
 #include "../Graphics/DebugRenderer.h"
-#include "../Navigation/DetourCrowdManager.h"
 #include "../Navigation/DynamicNavigationMesh.h"
 #include "../IO/Log.h"
 #include "../Navigation/NavigationEvents.h"
@@ -48,129 +48,48 @@ namespace Urho3D
 extern const char* NAVIGATION_CATEGORY;
 
 static const unsigned DEFAULT_MAX_AGENTS = 512;
+static const float DEFAULT_MAX_AGENT_RADIUS = 0.f;
 
-DetourCrowdManager::DetourCrowdManager(Context* context) :
+void CrowdAgentUpdateCallback(dtCrowdAgent* ag, float dt)
+{
+    static_cast<CrowdAgent*>(ag->params.userData)->OnCrowdUpdate(ag, dt);
+}
+
+CrowdManager::CrowdManager(Context* context) :
     Component(context),
-    maxAgents_(DEFAULT_MAX_AGENTS),
     crowd_(0),
-    navigationMesh_(0),
-    agentDebug_(0)
+    maxAgents_(DEFAULT_MAX_AGENTS),
+    maxAgentRadius_(DEFAULT_MAX_AGENT_RADIUS)
 {
-    agentBuffer_.Resize(maxAgents_);
 }
 
-DetourCrowdManager::~DetourCrowdManager()
+CrowdManager::~CrowdManager()
 {
     dtFreeCrowd(crowd_);
     crowd_ = 0;
-    delete agentDebug_;
-    agentDebug_ = 0;
-}
-
-void DetourCrowdManager::RegisterObject(Context* context)
-{
-    context->RegisterFactory<DetourCrowdManager>(NAVIGATION_CATEGORY);
-
-    ACCESSOR_ATTRIBUTE("Max Agents", GetMaxAgents, SetMaxAgents, unsigned, DEFAULT_MAX_AGENTS, AM_DEFAULT);
-}
-
-void DetourCrowdManager::SetNavigationMesh(NavigationMesh* navMesh)
-{
-    navigationMesh_ = navMesh;
-    if (navigationMesh_ && !navigationMesh_->navMeshQuery_)
-        navigationMesh_->InitializeQuery();
-    CreateCrowd();
-    MarkNetworkUpdate();
-}
-
-void DetourCrowdManager::SetAreaCost(unsigned filterID, unsigned areaID, float weight)
-{
-    dtQueryFilter* filter = crowd_->getEditableFilter(filterID);
-    if (filter)
-        filter->setAreaCost((int)areaID, weight);
-}
-
-void DetourCrowdManager::SetMaxAgents(unsigned agentCt)
-{
-    maxAgents_ = agentCt;
-    if (crowd_ && crowd_->getAgentCount() > 0)
-        LOGERROR("DetourCrowdManager contains active agents, their state will be lost");
-    agentBuffer_.Resize(maxAgents_);
-    CreateCrowd();
-    if (crowd_)
-    {
-        PODVector<CrowdAgent*> agents = agents_;
-        // Reset the existing values in the agent
-        for (unsigned i = 0; i < agents.Size(); ++i)
-            agents[i]->agentCrowdId_ = -1;
-        // Add the agents back in
-        for (unsigned i = 0; i < agents.Size() && i < maxAgents_; ++i)
-            agents[i]->AddAgentToCrowd();
-        if (agents.Size() > maxAgents_)
-            LOGERROR("DetourCrowdManager: resize left " + String(agents.Size() - maxAgents_) + " agents orphaned");
-    }
-    MarkNetworkUpdate();
 }
 
-void DetourCrowdManager::SetCrowdTarget(const Vector3& position, int startId, int endId)
+void CrowdManager::RegisterObject(Context* context)
 {
-    startId = Max(0, startId);
-    endId = Clamp(endId, startId, agents_.Size() - 1);
-    Vector3 moveTarget(position);
-    for (int i = startId; i <= endId; ++i)
-    {
-        // Skip agent that does not have acceleration
-        if (agents_[i]->GetMaxAccel() > 0.f)
-        {
-            agents_[i]->SetTargetPosition(moveTarget);
-            // FIXME: Should reimplement this using event callback, i.e. it should be application-specific to decide what is the desired crowd formation when they reach the target
-            if (navigationMesh_)
-                moveTarget = navigationMesh_->FindNearestPoint(position + Vector3(Random(-4.5f, 4.5f), 0.0f, Random(-4.5f, 4.5f)), Vector3(1.0f, 1.0f, 1.0f));
-        }
-    }
-}
-
-void DetourCrowdManager::SetCrowdVelocity(const Vector3& velocity, int startId, int endId)
-{
-    startId = Max(0, startId);
-    endId = Clamp(endId, startId, agents_.Size() - 1);
-    for (int i = startId; i <= endId; ++i)
-    {
-        if (agents_[i]->GetMaxAccel() > 0.f)
-            agents_[i]->SetTargetVelocity(velocity);
-    }
-}
+    context->RegisterFactory<CrowdManager>(NAVIGATION_CATEGORY);
 
-void DetourCrowdManager::ResetCrowdTarget(int startId, int endId)
-{
-    startId = Max(0, startId);
-    endId = Clamp(endId, startId, agents_.Size() - 1);
-    for (int i = startId; i <= endId; ++i)
-    {
-        if (agents_[i]->GetMaxAccel() > 0.f)
-            agents_[i]->ResetTarget();
-    }
+    ATTRIBUTE("Max Agents", unsigned, maxAgents_, DEFAULT_MAX_AGENTS, AM_DEFAULT);
+    ATTRIBUTE("Max Agent Radius", float, maxAgentRadius_, DEFAULT_MAX_AGENT_RADIUS, AM_DEFAULT);
+    //TODO: add attribute for navmesh
 }
 
-float DetourCrowdManager::GetAreaCost(unsigned filterID, unsigned areaID) const
+void CrowdManager::ApplyAttributes()
 {
-    if (crowd_ && navigationMesh_)
-    {
-        const dtQueryFilter* filter = crowd_->getFilter((int)filterID);
-        if (filter)
-            return filter->getAreaCost((int)areaID);
-    }
-    return 0.0f;
-}
+    maxAgents_ = Max(1, maxAgents_);
+    maxAgentRadius_ = Max(0.f, maxAgentRadius_);
 
-unsigned DetourCrowdManager::GetAgentCount() const
-{
-    return crowd_ ? crowd_->getAgentCount() : 0;
+    if (crowd_ && (crowd_->getAgentCount() != maxAgents_ || crowd_->getMaxAgentRadius() != (maxAgentRadius_ > 0.f ? maxAgentRadius_ : navigationMesh_->GetAgentRadius())))
+        CreateCrowd(true);
 }
 
-void DetourCrowdManager::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
+void CrowdManager::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
-    if (debug && navigationMesh_.NotNull() && crowd_)
+    if (debug && crowd_)
     {
         // Current position-to-target line
         for (int i = 0; i < crowd_->getAgentCount(); i++)
@@ -211,7 +130,7 @@ void DetourCrowdManager::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
     }
 }
 
-void DetourCrowdManager::DrawDebugGeometry(bool depthTest)
+void CrowdManager::DrawDebugGeometry(bool depthTest)
 {
     Scene* scene = GetScene();
     if (scene)
@@ -222,19 +141,132 @@ void DetourCrowdManager::DrawDebugGeometry(bool depthTest)
     }
 }
 
-bool DetourCrowdManager::CreateCrowd()
+void CrowdManager::SetAreaCost(unsigned filterID, unsigned areaID, float cost)
+{
+    dtQueryFilter* filter = crowd_->getEditableFilter(filterID);
+    if (filter)
+        filter->setAreaCost((int)areaID, cost);
+}
+
+void CrowdManager::SetCrowdTarget(const Vector3& position, Node* node)
+{
+    if (!crowd_)
+        return;
+
+    PODVector<CrowdAgent*> agents = GetAgents(node, false);     // Get all crowd agent components
+    Vector3 moveTarget(position);
+    for (unsigned i = 0; i < agents.Size(); ++i)
+    {
+        if (agents[i]->GetMaxAccel() > 0.f)
+        {
+            agents[i]->SetTargetPosition(moveTarget);
+            // FIXME: Should reimplement this using event callback, i.e. it should be application-specific to decide what is the desired crowd formation when they reach the target
+            if (navigationMesh_)
+                moveTarget = navigationMesh_->FindNearestPoint(position + Vector3(Random(-4.5f, 4.5f), 0.0f, Random(-4.5f, 4.5f)), Vector3(1.0f, 1.0f, 1.0f));
+        }
+    }
+}
+
+void CrowdManager::SetCrowdVelocity(const Vector3& velocity, Node* node)
+{
+    if (!crowd_)
+        return;
+
+    PODVector<CrowdAgent*> agents = GetAgents(node, true);      // Get only crowd agent components already in the crowd
+    for (unsigned i = 0; i < agents.Size(); ++i)
+    {
+        if (agents[i]->GetMaxAccel() > 0.f)
+            agents[i]->SetTargetVelocity(velocity);
+    }
+}
+
+void CrowdManager::ResetCrowdTarget(Node* node)
+{
+    if (!crowd_)
+        return;
+
+    PODVector<CrowdAgent*> agents = GetAgents(node, true);
+    for (unsigned i = 0; i < agents.Size(); ++i)
+    {
+        if (agents[i]->GetMaxAccel() > 0.f)
+            agents[i]->ResetTarget();
+    }
+}
+
+void CrowdManager::SetMaxAgents(unsigned maxAgents)
+{
+    if (maxAgents != maxAgents_ && maxAgents > 0)
+    {
+        maxAgents_ = maxAgents;
+        CreateCrowd(true);
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdManager::SetMaxAgentRadius(float maxAgentRadius)
+{
+    if (maxAgentRadius != maxAgentRadius_ && maxAgentRadius > 0.f)
+    {
+        maxAgentRadius_ = maxAgentRadius;
+        CreateCrowd(true);
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdManager::SetNavigationMesh(NavigationMesh* navMesh)
+{
+    if (navMesh != navigationMesh_ && navMesh)
+    {
+        navigationMesh_ = navMesh;
+        CreateCrowd(true);
+        MarkNetworkUpdate();
+    }
+}
+
+float CrowdManager::GetAreaCost(unsigned filterID, unsigned areaID) const
+{
+    if (crowd_ && navigationMesh_)
+    {
+        const dtQueryFilter* filter = crowd_->getFilter((int)filterID);
+        if (filter)
+            return filter->getAreaCost((int)areaID);
+    }
+    return 0.0f;
+}
+
+PODVector<CrowdAgent*> CrowdManager::GetAgents(Node* node, bool inCrowdFilter) const
+{
+    if (!node)
+        node = GetScene();
+    PODVector<CrowdAgent*> agents;
+    node->GetComponents<CrowdAgent>(agents, true);
+    if (inCrowdFilter)
+    {
+        PODVector<CrowdAgent*>::Iterator i = agents.Begin();
+        while (i != agents.End())
+        {
+            if ((*i)->IsInCrowd())
+                ++i;
+            else
+                i = agents.Erase(i);
+        }
+    }
+    return agents;
+}
+
+bool CrowdManager::CreateCrowd(bool readdCrowdAgents)
 {
     if (!navigationMesh_ || !navigationMesh_->navMesh_)
         return false;
+    if (!navigationMesh_->navMeshQuery_)
+        navigationMesh_->InitializeQuery();
 
     if (crowd_)
         dtFreeCrowd(crowd_);
     crowd_ = dtAllocCrowd();
-    if (!agentDebug_)
-        agentDebug_ = new dtCrowdAgentDebugInfo();
 
     // Initialize the crowd
-    if (!crowd_->init(maxAgents_, navigationMesh_->GetAgentRadius(), navigationMesh_->navMesh_))
+    if (!crowd_->init(maxAgents_, maxAgentRadius_ > 0.f ? maxAgentRadius_ : navigationMesh_->GetAgentRadius(), navigationMesh_->navMesh_, CrowdAgentUpdateCallback))
     {
         LOGERROR("Could not initialize DetourCrowd");
         return false;
@@ -272,62 +304,57 @@ bool DetourCrowdManager::CreateCrowd()
     params.adaptiveDepth = 3;
     crowd_->setObstacleAvoidanceParams(3, &params);
 
+    if (readdCrowdAgents)
+    {
+        PODVector<CrowdAgent*> agents = GetAgents();
+        for (unsigned i = 0; i < agents.Size(); ++i)
+        {
+            // Keep adding until the crowd cannot take it anymore
+            if (agents[i]->AddAgentToCrowd(readdCrowdAgents) == -1)
+            {
+                LOGWARNINGF("CrowdManager: %d crowd agents orphaned", agents.Size() - i);
+                break;
+            }
+        }
+    }
+
     return true;
 }
 
-int DetourCrowdManager::AddAgent(CrowdAgent* agent, const Vector3& pos)
+int CrowdManager::AddAgent(CrowdAgent* agent, const Vector3& pos)
 {
-    if (!crowd_ || navigationMesh_.Expired())
+    if (!crowd_ || !navigationMesh_ || !agent)
         return -1;
     dtCrowdAgentParams params;
     params.userData = agent;
-    if (agent->radius_ <= 0.0f)
+    if (agent->radius_ == 0.f)
         agent->radius_ = navigationMesh_->GetAgentRadius();
-    params.radius = agent->radius_;
-    if (agent->height_ <= 0.0f)
+    if (agent->height_ == 0.f)
         agent->height_ = navigationMesh_->GetAgentHeight();
-    params.height = agent->height_;
-    params.queryFilterType = (unsigned char)agent->filterType_;
-    params.maxAcceleration = agent->maxAccel_;
-    params.maxSpeed = agent->maxSpeed_;
-    // TODO: should be initialized according to agent's pushiness
-    params.separationWeight = 2.0f;
-    params.collisionQueryRange = params.radius * 8.0f;
-    params.pathOptimizationRange = params.radius * 30.0f;
-    params.updateFlags = DT_CROWD_ANTICIPATE_TURNS
-        | DT_CROWD_OPTIMIZE_VIS
-        | DT_CROWD_OPTIMIZE_TOPO
-        | DT_CROWD_OBSTACLE_AVOIDANCE;
-    params.obstacleAvoidanceType = 3;
-    dtPolyRef polyRef;
+
     float nearestPos[3];
     rcVcopy(nearestPos, &pos.x_);
     dtStatus status = navigationMesh_->navMeshQuery_->findNearestPoly(
         pos.Data(),
         crowd_->getQueryExtents(),
         crowd_->getFilter(agent->filterType_),
-        &polyRef,
+        0,
         nearestPos);
 
-    const int agentID = crowd_->addAgent(nearestPos, &params);
-    if (agentID != -1)
-        agents_.Push(agent);
-    return agentID;
+    return crowd_->addAgent(nearestPos, &params);
 }
 
-void DetourCrowdManager::RemoveAgent(CrowdAgent* agent)
+void CrowdManager::RemoveAgent(CrowdAgent* agent)
 {
-    if (!crowd_)
+    if (!crowd_ || !agent)
         return;
-    // Clear user data
     dtCrowdAgent* agt = crowd_->getEditableAgent(agent->GetAgentCrowdId());
     if (agt)
         agt->params.userData = 0;
     crowd_->removeAgent(agent->GetAgentCrowdId());
-    agents_.Remove(agent);
 }
 
-bool DetourCrowdManager::SetAgentTarget(CrowdAgent* agent, const Vector3& target)
+bool CrowdManager::SetAgentTarget(CrowdAgent* agent, const Vector3& target)
 {
     if (!crowd_ || !navigationMesh_ || !agent)
         return false;
@@ -345,7 +372,7 @@ bool DetourCrowdManager::SetAgentTarget(CrowdAgent* agent, const Vector3& target
         crowd_->getAgent(agent->GetAgentCrowdId())->targetState != DT_CROWDAGENT_TARGET_FAILED;
 }
 
-Vector3 DetourCrowdManager::GetClosestWalkablePosition(const Vector3& pos) const
+Vector3 CrowdManager::GetClosestWalkablePosition(const Vector3& pos) const
 {
     if (!crowd_ || !navigationMesh_)
         return Vector3::ZERO;
@@ -360,39 +387,20 @@ Vector3 DetourCrowdManager::GetClosestWalkablePosition(const Vector3& pos) const
     return Vector3(closest);
 }
 
-void DetourCrowdManager::Update(float delta)
+void CrowdManager::Update(float delta)
 {
-    if (!crowd_)
+    if (!crowd_ || !navigationMesh_)
         return;
-
     PROFILE(UpdateCrowd);
-
-    crowd_->update(delta, agentDebug_);
-
-    memset(&agentBuffer_[0], 0, maxAgents_ * sizeof(dtCrowdAgent*));
-    const int count = crowd_->getActiveAgents(&agentBuffer_[0], maxAgents_);
-
-    {
-        PROFILE(ApplyCrowdUpdates);
-        for (int i = 0; i < count; i++)
-        {
-            dtCrowdAgent* agent = agentBuffer_[i];
-            if (agent)
-            {
-                CrowdAgent* crowdAgent = static_cast<CrowdAgent*>(agent->params.userData);
-                if (crowdAgent)
-                    crowdAgent->OnCrowdAgentReposition(Vector3(agent->npos), Vector3(agent->vel));
-            }
-        }
-    }
+    crowd_->update(delta, 0);
 }
 
-const dtCrowdAgent* DetourCrowdManager::GetCrowdAgent(int agent)
+const dtCrowdAgent* CrowdManager::GetCrowdAgent(int agent)
 {
     return crowd_ ? crowd_->getAgent(agent) : 0;
 }
 
-void DetourCrowdManager::HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData)
+void CrowdManager::HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData)
 {
     using namespace SceneSubsystemUpdate;
 
@@ -400,26 +408,14 @@ void DetourCrowdManager::HandleSceneSubsystemUpdate(StringHash eventType, Varian
         Update(eventData[P_TIMESTEP].GetFloat());
 }
 
-void DetourCrowdManager::HandleNavMeshFullRebuild(StringHash eventType, VariantMap& eventData)
+void CrowdManager::HandleNavMeshFullRebuild(StringHash eventType, VariantMap& eventData)
 {
     using namespace NavigationMeshRebuilt;
 
     // The mesh being rebuilt may not have existed before
     NavigationMesh* navMesh = static_cast<NavigationMesh*>(eventData[P_MESH].GetPtr());
     if (!navigationMesh_ || !crowd_)
-    {
         SetNavigationMesh(navMesh);
-
-        // Scan for existing agents that are potentially important
-        PODVector<Node*> agents;
-        GetScene()->GetChildrenWithComponent<CrowdAgent>(agents, true);
-        for (unsigned i = 0; i < agents.Size(); ++i)
-        {
-            CrowdAgent* agent = agents[i]->GetComponent<CrowdAgent>();
-            if (agent && agent->IsEnabledEffective())
-                agent->AddAgentToCrowd();
-        }
-    }
 }
 
 void DetourCrowdManager::OnSceneSet(Scene* scene)
@@ -428,17 +424,17 @@ void DetourCrowdManager::OnSceneSet(Scene* scene)
     // to the scene's NavigationMesh
     if (scene)
     {
-        SubscribeToEvent(scene, E_SCENESUBSYSTEMUPDATE, HANDLER(DetourCrowdManager, HandleSceneSubsystemUpdate));
+        SubscribeToEvent(scene, E_SCENESUBSYSTEMUPDATE, HANDLER(CrowdManager, HandleSceneSubsystemUpdate));
         NavigationMesh* mesh = GetScene()->GetComponent<NavigationMesh>();
         if (!mesh)
             mesh = GetScene()->GetComponent<DynamicNavigationMesh>();
         if (mesh)
         {
-            SubscribeToEvent(mesh, E_NAVIGATION_MESH_REBUILT, HANDLER(DetourCrowdManager, HandleNavMeshFullRebuild));
+            SubscribeToEvent(mesh, E_NAVIGATION_MESH_REBUILT, HANDLER(CrowdManager, HandleNavMeshFullRebuild));
             SetNavigationMesh(mesh);
         }
         else
-            LOGERROR("DetourCrowdManager requires an existing navigation mesh");
+            LOGERROR("CrowdManager requires an existing navigation mesh");
     }
     else
     {
@@ -446,7 +442,6 @@ void DetourCrowdManager::OnSceneSet(Scene* scene)
         UnsubscribeFromEvent(E_NAVIGATION_MESH_REBUILT);
         navigationMesh_.Reset();
     }
-
 }
 
 }

+ 35 - 35
Source/Urho3D/Navigation/DetourCrowdManager.h → Source/Urho3D/Navigation/CrowdManager.h

@@ -26,7 +26,6 @@
 
 class dtCrowd;
 struct dtCrowdAgent;
-struct dtCrowdAgentDebugInfo;
 
 namespace Urho3D
 {
@@ -34,52 +33,57 @@ namespace Urho3D
 class CrowdAgent;
 class NavigationMesh;
 
-/// Detour crowd manager scene component. Should be added only to the root scene node.
-class URHO3D_API DetourCrowdManager : public Component
+/// Crowd manager scene component. Should be added only to the root scene node.
+class URHO3D_API CrowdManager : public Component
 {
-    OBJECT(DetourCrowdManager);
+    OBJECT(CrowdManager);
     friend class CrowdAgent;
 
 public:
     /// Construct.
-    DetourCrowdManager(Context* context);
+    CrowdManager(Context* context);
     /// Destruct.
-    virtual ~DetourCrowdManager();
+    virtual ~CrowdManager();
     /// Register object factory.
     static void RegisterObject(Context* context);
+    /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
+    virtual void ApplyAttributes();
 
+    /// Draw the agents' pathing debug data.
+    virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
+    /// Add debug geometry to the debug renderer.
+    void DrawDebugGeometry(bool depthTest);
+
+    /// Set the crowd target position. The target position is set to all crowd agents found in the specified node, excluding crowd agent which does not have acceleration. Defaulted to scene node.
+    void SetCrowdTarget(const Vector3& position, Node* node = 0);
+    /// Set the crowd move velocity. The move velocity is applied to all crowd agents found in the specified node, excluding crowd agent which does not have acceleration. Defaulted to scene node.
+    void SetCrowdVelocity(const Vector3& velocity, Node* node = 0);
+    /// Reset any crowd target for all crowd agents found in the specified node, excluding crowd agent which does not have acceleration. Defaulted to scene node.
+    void ResetCrowdTarget(Node* node = 0);
+    /// Set the maximum number of agents.
+    void SetMaxAgents(unsigned maxAgents);
+    /// Set the maximum radius of any agent.
+    void SetMaxAgentRadius(float maxAgentRadius);
     /// Assigns the navigation mesh for the crowd.
     void SetNavigationMesh(NavigationMesh* navMesh);
     /// Set the cost of an area-type for the specified navigation filter type.
-    void SetAreaCost(unsigned filterTypeID, unsigned areaID, float weight);
-    /// Set the maximum number of agents.
-    void SetMaxAgents(unsigned agentCt);
-    /// Set the crowd target position. The target position is set to all crowd agents within the id range, excluding crowd agent which does not have acceleration.
-    void SetCrowdTarget(const Vector3& position, int startId = 0, int endId = M_MAX_INT);
-    /// Set the crowd move velocity. The move velocity is applied to all crowd agents within the id range, excluding crowd agent which does not have acceleration.
-    void SetCrowdVelocity(const Vector3& velocity, int startId = 0, int endId = M_MAX_INT);
-    /// Reset any crowd target for all crowd agents within the id range, excluding crowd agent which does not have acceleration.
-    void ResetCrowdTarget(int startId = 0, int endId = M_MAX_INT);
+    void SetAreaCost(unsigned filterTypeID, unsigned areaID, float cost);
 
+    /// Get the maximum number of agents.
+    unsigned GetMaxAgents() const { return maxAgents_; }
+    /// Get the maximum radius of any agent.
+    float GetMaxAgentRadius() const { return maxAgentRadius_; }
     /// Get the Navigation mesh assigned to the crowd.
     NavigationMesh* GetNavigationMesh() const { return navigationMesh_; }
     /// Get the cost of an area-type for the specified navigation filter type.
     float GetAreaCost(unsigned filterTypeID, unsigned areaID) const;
-    /// Get the maximum number of agents.
-    unsigned GetMaxAgents() const { return maxAgents_; }
-    /// Get the current number of active agents.
-    unsigned GetAgentCount() const;
 
-    /// Draw the agents' pathing debug data.
-    virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
-    /// Add debug geometry to the debug renderer.
-    void DrawDebugGeometry(bool depthTest);
-    /// Get the currently included agents.
-    const PODVector<CrowdAgent*>& GetActiveAgents() const { return agents_; }
-    /// Create detour crowd component for the specified navigation mesh.
-    bool CreateCrowd();
+    /// Get all the crowd agent components in the specified node hierarchy. If the node is not specified then use scene node. When inCrowdFilter is set to true then only get agents that are in the crowd.
+    PODVector<CrowdAgent*> GetAgents(Node* node = 0, bool inCrowdFilter = true) const;
 
 protected:
+    /// Create internal Detour crowd object for the specified navigation mesh. If readdCrowdAgents is true then attempt to re-add existing agents in the previous crowd back to the newly created crowd.
+    bool CreateCrowd(bool readdCrowdAgents = false);
     /// Create and adds an detour crowd agent, Agent's radius and height is set through the navigation mesh. Return -1 on error, agent ID on success.
     int AddAgent(CrowdAgent* agent, const Vector3& pos);
     /// Removes the detour crowd agent.
@@ -106,18 +110,14 @@ private:
     /// Handle full rebuilds of the navigation mesh.
     void HandleNavMeshFullRebuild(StringHash eventType, VariantMap& eventData);
 
-    /// Internal crowd component.
+    /// Internal Detour crowd object.
     dtCrowd* crowd_;
     /// NavigationMesh for which the crowd was created.
     WeakPtr<NavigationMesh> navigationMesh_;
-    /// Max agents for the crowd.
+    /// The maximum number of agents the crowd can manage.
     unsigned maxAgents_;
-    /// Internal debug information.
-    dtCrowdAgentDebugInfo* agentDebug_;
-    /// Container for fetching agents from DetourCrowd during update.
-    PODVector<dtCrowdAgent*> agentBuffer_;
-    /// Container for fetching agents from DetourCrowd during update.
-    PODVector<CrowdAgent*> agents_;
+    /// The maximum radius of any agent that will be added to the crowd.
+    float maxAgentRadius_;
 };
 
 }

+ 3 - 3
Source/Urho3D/Navigation/DynamicNavigationMesh.cpp

@@ -369,7 +369,7 @@ bool DynamicNavigationMesh::Build()
         }
 
         // For a full build it's necessary to update the nav mesh
-        // not doing so will cause dependent components to crash, like DetourCrowdManager
+        // not doing so will cause dependent components to crash, like CrowdManager
         tileCache_->update(0, navMesh_);
 
         LOGDEBUG("Built navigation mesh with " + String(numTiles) + " tiles");
@@ -901,7 +901,7 @@ void DynamicNavigationMesh::AddObstacle(Obstacle* obstacle, bool silent)
         rcVcopy(pos, &obsPos.x_);
         dtObstacleRef refHolder;
 
-        // Because dtTileCache doesn't process obstacle requests while updating tiles 
+        // Because dtTileCache doesn't process obstacle requests while updating tiles
         // it's necessary update until sufficient request space is available
         while (tileCache_->isObstacleQueueFull())
             tileCache_->update(1, navMesh_);
@@ -941,7 +941,7 @@ void DynamicNavigationMesh::RemoveObstacle(Obstacle* obstacle, bool silent)
 {
     if (tileCache_ && obstacle->obstacleId_ > 0)
     {
-        // Because dtTileCache doesn't process obstacle requests while updating tiles 
+        // Because dtTileCache doesn't process obstacle requests while updating tiles
         // it's necessary update until sufficient request space is available
         while (tileCache_->isObstacleQueueFull())
             tileCache_->update(1, navMesh_);

+ 1 - 0
Source/Urho3D/Navigation/NavigationEvents.h

@@ -51,6 +51,7 @@ EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
     PARAM(P_POSITION, Position); // Vector3
     PARAM(P_VELOCITY, Velocity); // Vector3
     PARAM(P_ARRIVED, Arrived); // bool
+    PARAM(P_TIMESTEP, TimeStep); // float
 }
 
 /// Crowd agent's internal state has become invalidated.

+ 2 - 2
Source/Urho3D/Navigation/NavigationMesh.cpp

@@ -53,7 +53,7 @@
 #include <Recast/Recast.h>
 
 #include "../Navigation/CrowdAgent.h"
-#include "../Navigation/DetourCrowdManager.h"
+#include "../Navigation/CrowdManager.h"
 
 #include "../DebugNew.h"
 
@@ -1366,7 +1366,7 @@ void RegisterNavigationLibrary(Context* context)
     NavigationMesh::RegisterObject(context);
     OffMeshConnection::RegisterObject(context);
     CrowdAgent::RegisterObject(context);
-    DetourCrowdManager::RegisterObject(context);
+    CrowdManager::RegisterObject(context);
     DynamicNavigationMesh::RegisterObject(context);
     Obstacle::RegisterObject(context);
     NavArea::RegisterObject(context);

+ 1 - 1
Source/Urho3D/Navigation/NavigationMesh.h

@@ -71,7 +71,7 @@ struct NavigationGeometryInfo
 class URHO3D_API NavigationMesh : public Component
 {
     OBJECT(NavigationMesh);
-    friend class DetourCrowdManager;
+    friend class CrowdManager;
 
 public:
     /// Construct.

+ 25 - 24
Source/Urho3D/Script/NavigationAPI.cpp

@@ -26,12 +26,12 @@
 
 #include "../Navigation/Navigable.h"
 #include "../Navigation/CrowdAgent.h"
-#include "../Navigation/NavigationMesh.h"
+#include "../Navigation/CrowdManager.h"
 #include "../Navigation/DynamicNavigationMesh.h"
-#include "../Navigation/OffMeshConnection.h"
 #include "../Navigation/NavArea.h"
+#include "../Navigation/NavigationMesh.h"
 #include "../Navigation/Obstacle.h"
-#include "../Script/APITemplates.h"
+#include "../Navigation/OffMeshConnection.h"
 
 namespace Urho3D
 {
@@ -57,9 +57,9 @@ static CScriptArray* DynamicNavigationMeshFindPath(const Vector3& start, const V
     return VectorToArray<Vector3>(dest, "Array<Vector3>");
 }
 
-static CScriptArray* DetourCrowdManagerGetActiveAgents(DetourCrowdManager* crowd)
+static CScriptArray* CrowdManagerGetAgents(Node* node, bool inCrowdFilter, CrowdManager* crowd)
 {
-    const PODVector<CrowdAgent*>& agents = crowd->GetActiveAgents();
+    PODVector<CrowdAgent*> agents = crowd->GetAgents(node, inCrowdFilter);
     return VectorToHandleArray<CrowdAgent>(agents, "Array<CrowdAgent@>");
 }
 
@@ -175,21 +175,22 @@ void RegisterNavArea(asIScriptEngine* engine)
     engine->RegisterObjectMethod("NavArea", "BoundingBox get_worldBoundingBox() const", asMETHOD(NavArea, GetWorldBoundingBox), asCALL_THISCALL);
 }
 
-void RegisterDetourCrowdManager(asIScriptEngine* engine)
+void RegisterCrowdManager(asIScriptEngine* engine)
 {
-    RegisterComponent<DetourCrowdManager>(engine, "DetourCrowdManager");
-    engine->RegisterObjectMethod("DetourCrowdManager", "void CreateCrowd()", asMETHOD(DetourCrowdManager, CreateCrowd), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DetourCrowdManager", "void DrawDebugGeometry(bool)", asMETHODPR(DetourCrowdManager, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DetourCrowdManager", "void set_navMesh(NavigationMesh@+)", asMETHOD(DetourCrowdManager, SetNavigationMesh), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DetourCrowdManager", "NavigationMesh@+ get_navMesh() const", asMETHOD(DetourCrowdManager, GetNavigationMesh), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DetourCrowdManager", "int get_maxAgents() const", asMETHOD(DetourCrowdManager, GetMaxAgents), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DetourCrowdManager", "void set_maxAgents(int)", asMETHOD(DetourCrowdManager, SetMaxAgents), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DetourCrowdManager", "Array<CrowdAgent@>@ GetActiveAgents()", asFUNCTION(DetourCrowdManagerGetActiveAgents), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("DetourCrowdManager", "void SetAreaCost(uint, uint, float)", asMETHOD(DetourCrowdManager, SetAreaCost), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DetourCrowdManager", "float GetAreaCost(uint, uint)", asMETHOD(DetourCrowdManager, GetAreaCost), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DetourCrowdManager", "void SetCrowdTarget(const Vector3&in, int startId = 0, int endId = M_MAX_INT)", asMETHOD(DetourCrowdManager, SetCrowdTarget), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DetourCrowdManager", "void SetCrowdVelocity(const Vector3&in, int startId = 0, int endId = M_MAX_INT)", asMETHOD(DetourCrowdManager, SetCrowdVelocity), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DetourCrowdManager", "void ResetCrowdTarget(int startId = 0, int endId = M_MAX_INT)", asMETHOD(DetourCrowdManager, ResetCrowdTarget), asCALL_THISCALL);
+    RegisterComponent<CrowdManager>(engine, "CrowdManager");
+    engine->RegisterObjectMethod("CrowdManager", "void DrawDebugGeometry(bool)", asMETHODPR(CrowdManager, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "void SetCrowdTarget(const Vector3&in, Node@+ node = null)", asMETHOD(CrowdManager, SetCrowdTarget), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "void SetCrowdVelocity(const Vector3&in, Node@+ node = null)", asMETHOD(CrowdManager, SetCrowdVelocity), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "void ResetCrowdTarget(Node@+ node = null)", asMETHOD(CrowdManager, ResetCrowdTarget), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "Array<CrowdAgent@>@ GetAgents(Node@+ node = null, bool inCrowdFilter = true)", asFUNCTION(CrowdManagerGetAgents), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("CrowdManager", "int get_maxAgents() const", asMETHOD(CrowdManager, GetMaxAgents), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "void set_maxAgents(int)", asMETHOD(CrowdManager, SetMaxAgents), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "float get_maxAgentRadius() const", asMETHOD(CrowdManager, GetMaxAgentRadius), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "void set_maxAgentRadius(float)", asMETHOD(CrowdManager, SetMaxAgentRadius), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "void set_navMesh(NavigationMesh@+)", asMETHOD(CrowdManager, SetNavigationMesh), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "NavigationMesh@+ get_navMesh() const", asMETHOD(CrowdManager, GetNavigationMesh), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "void SetAreaCost(uint, uint, float)", asMETHOD(CrowdManager, SetAreaCost), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "float GetAreaCost(uint, uint)", asMETHOD(CrowdManager, GetAreaCost), asCALL_THISCALL);
 }
 
 void RegisterCrowdAgent(asIScriptEngine* engine)
@@ -261,14 +262,14 @@ void RegisterCrowdAgent(asIScriptEngine* engine)
 
 void RegisterNavigationAPI(asIScriptEngine* engine)
 {
-    RegisterNavigable(engine);
     RegisterNavigationMesh(engine);
-    RegisterDynamicNavigationMesh(engine);
-    RegisterOffMeshConnection(engine);
     RegisterCrowdAgent(engine);
-    RegisterDetourCrowdManager(engine);
-    RegisterObstacle(engine);
+    RegisterCrowdManager(engine);
+    RegisterDynamicNavigationMesh(engine);
     RegisterNavArea(engine);
+    RegisterNavigable(engine);
+    RegisterObstacle(engine);
+    RegisterOffMeshConnection(engine);
 }
 
 }

+ 7 - 6
bin/Data/LuaScripts/39_CrowdNavigation.lua

@@ -110,8 +110,8 @@ function CreateScene()
         CreateMushroom(Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0))
     end
 
-    -- Create a DetourCrowdManager component to the scene root (mandatory for crowd agents)
-    scene_:CreateComponent("DetourCrowdManager")
+    -- Create a CrowdManager component to the scene root (mandatory for crowd agents)
+    scene_:CreateComponent("CrowdManager")
 
     -- Create some movable barrels. We create them as crowd agents, as for moving entities it is less expensive and more convenient than using obstacles
     CreateMovingBarrels(navMesh)
@@ -262,7 +262,7 @@ function SetPathPoint(spawning)
             SpawnJack(pathPos)
         else
             -- Set crowd agents target position
-            scene_:GetComponent("DetourCrowdManager"):SetCrowdTarget(pathPos)
+            scene_:GetComponent("CrowdManager"):SetCrowdTarget(pathPos)
         end
     end
 end
@@ -380,7 +380,7 @@ function HandlePostRenderUpdate(eventType, eventData)
         -- Visualize navigation mesh, obstacles and off-mesh connections
         scene_:GetComponent("DynamicNavigationMesh"):DrawDebugGeometry(true)
         -- Visualize agents' path and position to reach
-        scene_:GetComponent("DetourCrowdManager"):DrawDebugGeometry(true)
+        scene_:GetComponent("CrowdManager"):DrawDebugGeometry(true)
     end
 end
 
@@ -403,6 +403,7 @@ function HandleCrowdAgentReposition(eventType, eventData)
     local node = eventData["Node"]:GetPtr("Node")
     local agent = eventData["CrowdAgent"]:GetPtr("CrowdAgent")
     local velocity = eventData["Velocity"]:GetVector3()
+    local timeStep = eventData["TimeStep"]:GetFloat()
 
     -- Only Jack agent has animation controller
     local animCtrl = node:GetComponent("AnimationController")
@@ -410,8 +411,8 @@ function HandleCrowdAgentReposition(eventType, eventData)
         local speed = velocity:Length()
         if animCtrl:IsPlaying(WALKING_ANI) then
             local speedRatio = speed / agent.maxSpeed
-            -- Face the direction of its velocity but moderate the turning speed based on the speed ratio as we do not have timeStep here
-            node.rotation = node.rotation:Slerp(Quaternion(Vector3.FORWARD, velocity), 0.1 * speedRatio)
+            -- Face the direction of its velocity but moderate the turning speed based on the speed ratio and timeStep
+            node.rotation = node.rotation:Slerp(Quaternion(Vector3.FORWARD, velocity), 10.0 * timeStep * speedRatio)
             -- Throttle the animation speed based on agent speed ratio (ratio = 1 is full throttle)
             animCtrl:SetSpeed(WALKING_ANI, speedRatio)
         else

+ 7 - 6
bin/Data/Scripts/39_CrowdNavigation.as

@@ -112,8 +112,8 @@ void CreateScene()
     for (uint i = 0; i < 100; ++i)
         CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
 
-    // Create a DetourCrowdManager component to the scene root (mandatory for crowd agents)
-    scene_.CreateComponent("DetourCrowdManager");
+    // Create a CrowdManager component to the scene root (mandatory for crowd agents)
+    scene_.CreateComponent("CrowdManager");
 
     // Create some movable barrels. We create them as crowd agents, as for moving entities it is less expensive and more convenient than using obstacles
     CreateMovingBarrels(navMesh);
@@ -278,7 +278,7 @@ void SetPathPoint(bool spawning)
             SpawnJack(pathPos);
         else
             // Set crowd agents target position
-            cast<DetourCrowdManager>(scene_.GetComponent("DetourCrowdManager")).SetCrowdTarget(pathPos);
+            cast<CrowdManager>(scene_.GetComponent("CrowdManager")).SetCrowdTarget(pathPos);
     }
 }
 
@@ -410,7 +410,7 @@ void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
         // Visualize navigation mesh, obstacles and off-mesh connections
         cast<DynamicNavigationMesh>(scene_.GetComponent("DynamicNavigationMesh")).DrawDebugGeometry(true);
         // Visualize agents' path and position to reach
-        cast<DetourCrowdManager>(scene_.GetComponent("DetourCrowdManager")).DrawDebugGeometry(true);
+        cast<CrowdManager>(scene_.GetComponent("CrowdManager")).DrawDebugGeometry(true);
     }
 }
 
@@ -437,6 +437,7 @@ void HandleCrowdAgentReposition(StringHash eventType, VariantMap& eventData)
     Node@ node = eventData["Node"].GetPtr();
     CrowdAgent@ agent = eventData["CrowdAgent"].GetPtr();
     Vector3 velocity = eventData["Velocity"].GetVector3();
+    float timeStep = eventData["TimeStep"].GetFloat();
 
     // Only Jack agent has animation controller
     AnimationController@ animCtrl = node.GetComponent("AnimationController");
@@ -446,8 +447,8 @@ void HandleCrowdAgentReposition(StringHash eventType, VariantMap& eventData)
         if (animCtrl.IsPlaying(WALKING_ANI))
         {
             float speedRatio = speed / agent.maxSpeed;
-            // Face the direction of its velocity but moderate the turning speed based on the speed ratio as we do not have timeStep here
-            node.rotation = node.rotation.Slerp(Quaternion(FORWARD, velocity), 0.1f * speedRatio);
+            // Face the direction of its velocity but moderate the turning speed based on the speed ratio and timeStep
+            node.rotation = node.rotation.Slerp(Quaternion(FORWARD, velocity), 10.f * timeStep * speedRatio);
             // Throttle the animation speed based on agent speed ratio (ratio = 1 is full throttle)
             animCtrl.SetSpeed(WALKING_ANI, speedRatio);
         }

+ 1 - 5
bin/Data/UI/EditorIcons.xml

@@ -207,7 +207,7 @@
         <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
         <attribute name="Image Rect" value="240 0 254 14" />
     </element>
-    <element type="DetourCrowdManager">
+    <element type="CrowdManager">
         <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
         <attribute name="Image Rect" value="128 16 142 30" />
     </element>
@@ -219,10 +219,6 @@
         <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
         <attribute name="Image Rect" value="160 16 174 30" />
     </element>
-    <element type="NavigationMesh">
-        <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
-        <attribute name="Image Rect" value="64 16 78 30" />
-    </element>
     <element type="Text3D">
         <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
         <attribute name="Image Rect" value="80 16 94 30" />