Browse Source

Add filter and obstacle avoidance type attribute to CrowdManager class.
Enhance Editor to have functionality to edit the CrowdManager's filter and obstacle avoidance type attributes.

Yao Wei Tjong 姚伟忠 10 years ago
parent
commit
27cc9c4277

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

@@ -168,7 +168,14 @@ void CrowdNavigation::CreateScene()
         CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
 
     // Create a CrowdManager component to the scene root
-    scene_->CreateComponent<CrowdManager>();
+    CrowdManager* crowdManager = scene_->CreateComponent<CrowdManager>();
+    CrowdObstacleAvoidanceParams params = crowdManager->GetObstacleAvoidanceParams(0);
+    // Set the params to "High (66)" setting
+    params.velBias = 0.5f;
+    params.adaptiveDivs = 7;
+    params.adaptiveRings = 3;
+    params.adaptiveDepth = 3;
+    crowdManager->SetObstacleAvoidanceParams(0, params);
 
     // 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);

+ 4 - 2
Source/ThirdParty/Detour/include/DetourNavMeshQuery.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 DETOURNAVMESHQUERY_H
 #define DETOURNAVMESHQUERY_H
 
@@ -86,12 +88,12 @@ public:
 	/// Returns the traversal cost of the area.
 	///  @param[in]		i		The id of the area.
 	/// @returns The traversal cost of the area.
-	inline float getAreaCost(const int i) const { return m_areaCost[i]; }
+	inline float getAreaCost(const int i) const { return (i >= 0 && i < DT_MAX_AREAS) ? m_areaCost[i] : 1.f; }  // Urho3D: Out of bound check
 
 	/// Sets the traversal cost of the area.
 	///  @param[in]		i		The id of the area.
 	///  @param[in]		cost	The new cost of traversing the area.
-	inline void setAreaCost(const int i, const float cost) { m_areaCost[i] = cost; } 
+	inline void setAreaCost(const int i, const float cost) { if (i >= 0 && i < DT_MAX_AREAS) m_areaCost[i] = cost; }  // Urho3D: Out of bound check
 
 	/// Returns the include flags for the filter.
 	/// Any polygons that include one or more of these flags will be

+ 11 - 2
Source/Urho3D/LuaScript/pkgs/Navigation/CrowdManager.pkg

@@ -10,7 +10,10 @@ class CrowdManager : public Component
     void SetMaxAgents(unsigned agentCt);
     void SetMaxAgentRadius(float maxAgentRadius);
     void SetNavigationMesh(NavigationMesh *navMesh);
-    void SetAreaCost(unsigned filterID, unsigned areaID, float cost);
+    void SetIncludeFlags(unsigned filterType, unsigned short flags);
+    void SetExcludeFlags(unsigned filterType, unsigned short flags);
+    void SetAreaCost(unsigned filterType, unsigned areaID, float cost);
+    void SetObstacleAvoidanceParams(unsigned obstacleAvoidanceType, const CrowdObstacleAvoidanceParams& params);
 
     PODVector<CrowdAgent*> GetAgents(Node* node = 0, bool inCrowdFilter = true) const;
     Vector3 FindNearestPoint(const Vector3& point, int filterType);
@@ -23,7 +26,13 @@ class CrowdManager : public Component
     unsigned GetMaxAgents() const;
     float GetMaxAgentRadius() const;
     NavigationMesh* GetNavigationMesh() const;
-    float GetAreaCost(unsigned filterID, unsigned areaID) const;
+    unsigned GetNumFilterTypes() const;
+    unsigned GetNumAreas(unsigned filterType) const;
+    unsigned short GetIncludeFlags(unsigned filterType) const;
+    unsigned short GetExcludeFlags(unsigned filterType) const;
+    float GetAreaCost(unsigned filterType, unsigned areaID) const;
+    unsigned GetNumObstacleAvoidanceTypes() const;
+    const CrowdObstacleAvoidanceParams& GetObstacleAvoidanceParams(unsigned obstacleAvoidanceType) const;
 
     tolua_property__get_set int maxAgents;
     tolua_property__get_set float maxAgentRadius;

+ 1 - 1
Source/Urho3D/Navigation/CrowdAgent.cpp

@@ -608,7 +608,7 @@ void CrowdAgent::OnMarkedDirty(Node* node)
 
 const dtCrowdAgent* CrowdAgent::GetDetourCrowdAgent() const
 {
-    return IsInCrowd() ? crowdManager_->GetCrowdAgent(agentCrowdId_) : 0;
+    return IsInCrowd() ? crowdManager_->GetDetourCrowdAgent(agentCrowdId_) : 0;
 }
 
 }

+ 262 - 72
Source/Urho3D/Navigation/CrowdManager.cpp

@@ -60,8 +60,14 @@ CrowdManager::CrowdManager(Context* context) :
     crowd_(0),
     maxAgents_(DEFAULT_MAX_AGENTS),
     maxAgentRadius_(DEFAULT_MAX_AGENT_RADIUS),
-    navigationMeshId_(0)
+    navigationMeshId_(0),
+    numFilterTypes_(0),
+    numObstacleAvoidanceTypes_(0)
 {
+    // The actual buffer is allocated inside dtCrowd, we only track the number of "slots" being configured explicitly
+    numAreas_.Reserve(DT_CROWD_MAX_QUERY_FILTER_TYPE);
+    for (unsigned i = 0; i < DT_CROWD_MAX_QUERY_FILTER_TYPE; ++i)
+        numAreas_.Push(0);
 }
 
 CrowdManager::~CrowdManager()
@@ -77,7 +83,8 @@ void CrowdManager::RegisterObject(Context* context)
     ATTRIBUTE("Max Agents", unsigned, maxAgents_, DEFAULT_MAX_AGENTS, AM_DEFAULT);
     ATTRIBUTE("Max Agent Radius", float, maxAgentRadius_, DEFAULT_MAX_AGENT_RADIUS, AM_DEFAULT);
     ATTRIBUTE("Navigation Mesh", unsigned, navigationMeshId_, 0, AM_DEFAULT | AM_COMPONENTID);
-    // TODO: add attributes for crowd filter type and avoidance parameters configuration
+    MIXED_ACCESSOR_ATTRIBUTE("Filter Types", GetFilterTypesAttr, SetFilterTypesAttr, VariantVector, Variant::emptyVariantVector, AM_DEFAULT);
+    MIXED_ACCESSOR_ATTRIBUTE("Obstacle Avoidance Types", GetObstacleAvoidanceTypesAttr, SetObstacleAvoidanceTypesAttr, VariantVector, Variant::emptyVariantVector, AM_DEFAULT);
 }
 
 void CrowdManager::ApplyAttributes()
@@ -86,17 +93,21 @@ void CrowdManager::ApplyAttributes()
     maxAgents_ = Max(1, maxAgents_);
     maxAgentRadius_ = Max(0.f, maxAgentRadius_);
 
+    bool navMeshChange = false;
     Scene* scene = GetScene();
     if (scene && navigationMeshId_)
     {
         NavigationMesh* navMesh = dynamic_cast<NavigationMesh*>(scene->GetComponent(navigationMeshId_));
         if (navMesh)
+        {
+            navMeshChange = navMesh != navigationMesh_;
             navigationMesh_ = navMesh;
+        }
     }
-    navigationMeshId_ = navigationMesh_ ? navigationMesh_->GetID() : 0;
+    navigationMeshId_ = navigationMesh_ ? navigationMesh_->GetID() : 0;     // In case of receiving an invalid component id, revert it back to the existing navmesh component id (if any)
 
     // If the Detour crowd initialization parameters have changed then recreate it
-    if (crowd_ && (crowd_->getAgentCount() != maxAgents_ || crowd_->getMaxAgentRadius() != (maxAgentRadius_ > 0.f ? maxAgentRadius_ : navigationMesh_->GetAgentRadius())))
+    if (crowd_ && (navMeshChange || crowd_->getAgentCount() != maxAgents_ || crowd_->getMaxAgentRadius() != (maxAgentRadius_ > 0.f ? maxAgentRadius_ : navigationMesh_->GetAgentRadius())))
         CreateCrowd();
 }
 
@@ -154,13 +165,6 @@ void CrowdManager::DrawDebugGeometry(bool depthTest)
     }
 }
 
-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_)
@@ -240,35 +244,114 @@ void CrowdManager::SetNavigationMesh(NavigationMesh* navMesh)
     }
 }
 
-float CrowdManager::GetAreaCost(unsigned filterID, unsigned areaID) const
+void CrowdManager::SetFilterTypesAttr(const VariantVector& value)
 {
-    if (crowd_ && navigationMesh_)
+    if (!crowd_)
+        return;
+
+    unsigned index = 0;
+    unsigned filterType = 0;
+    numFilterTypes_ = index < value.Size() ? Min(value[index++].GetUInt(), DT_CROWD_MAX_QUERY_FILTER_TYPE) : 0;
+
+    while (filterType < numFilterTypes_)
     {
-        const dtQueryFilter* filter = crowd_->getFilter((int)filterID);
-        if (filter)
-            return filter->getAreaCost((int)areaID);
+        if (index + 3 <= value.Size())
+        {
+            dtQueryFilter* filter = crowd_->getEditableFilter(filterType);
+            assert(filter);
+            filter->setIncludeFlags((unsigned short)value[index++].GetUInt());
+            filter->setExcludeFlags((unsigned short)value[index++].GetUInt());
+            unsigned prevNumAreas = numAreas_[filterType];
+            numAreas_[filterType] = Min(value[index++].GetUInt(), DT_MAX_AREAS);
+
+            // Must loop thru based on previous number of areas, the new area cost (if any) can only be set in the next attribute get/set iteration
+            if (index + prevNumAreas <= value.Size())
+            {
+                for (int i = 0; i < prevNumAreas; ++i)
+                    filter->setAreaCost(i, value[index++].GetFloat());
+            }
+        }
+        ++filterType;
     }
-    return 0.0f;
 }
 
-PODVector<CrowdAgent*> CrowdManager::GetAgents(Node* node, bool inCrowdFilter) const
+void CrowdManager::SetIncludeFlags(unsigned filterType, unsigned short flags)
 {
-    if (!node)
-        node = GetScene();
-    PODVector<CrowdAgent*> agents;
-    node->GetComponents<CrowdAgent>(agents, true);
-    if (inCrowdFilter)
+    dtQueryFilter* filter = const_cast<dtQueryFilter*>(GetDetourQueryFilter(filterType));
+    if (filter)
     {
-        PODVector<CrowdAgent*>::Iterator i = agents.Begin();
-        while (i != agents.End())
+        filter->setIncludeFlags(flags);
+        if (numFilterTypes_ < filterType + 1)
+            numFilterTypes_ = filterType + 1;
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdManager::SetExcludeFlags(unsigned filterType, unsigned short flags)
+{
+    dtQueryFilter* filter = const_cast<dtQueryFilter*>(GetDetourQueryFilter(filterType));
+    if (filter)
+    {
+        filter->setExcludeFlags(flags);
+        if (numFilterTypes_ < filterType + 1)
+            numFilterTypes_ = filterType + 1;
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdManager::SetAreaCost(unsigned filterType, unsigned areaID, float cost)
+{
+    dtQueryFilter* filter = const_cast<dtQueryFilter*>(GetDetourQueryFilter(filterType));
+    if (filter && areaID < DT_MAX_AREAS)
+    {
+        filter->setAreaCost((int)areaID, cost);
+        if (numFilterTypes_ < filterType + 1)
+            numFilterTypes_ = filterType + 1;
+        if (numAreas_[filterType] < areaID + 1)
+            numAreas_[filterType] = areaID + 1;
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdManager::SetObstacleAvoidanceTypesAttr(const VariantVector& value)
+{
+    if (!crowd_)
+        return;
+
+    unsigned index = 0;
+    unsigned obstacleAvoidanceType = 0;
+    numObstacleAvoidanceTypes_ = index < value.Size() ? Min(value[index++].GetUInt(), DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS) : 0;
+
+    while (obstacleAvoidanceType < numObstacleAvoidanceTypes_)
+    {
+        if (index + 10 <= value.Size())
         {
-            if ((*i)->IsInCrowd())
-                ++i;
-            else
-                i = agents.Erase(i);
+            dtObstacleAvoidanceParams params;
+            params.velBias = value[index++].GetFloat();
+            params.weightDesVel = value[index++].GetFloat();
+            params.weightCurVel = value[index++].GetFloat();
+            params.weightSide = value[index++].GetFloat();
+            params.weightToi = value[index++].GetFloat();
+            params.horizTime = value[index++].GetFloat();
+            params.gridSize = (unsigned char)value[index++].GetUInt();
+            params.adaptiveDivs = (unsigned char)value[index++].GetUInt();
+            params.adaptiveRings = (unsigned char)value[index++].GetUInt();
+            params.adaptiveDepth = (unsigned char)value[index++].GetUInt();
+            crowd_->setObstacleAvoidanceParams(obstacleAvoidanceType, &params);
         }
+        ++obstacleAvoidanceType;
+    }
+}
+
+void CrowdManager::SetObstacleAvoidanceParams(unsigned obstacleAvoidanceType, const CrowdObstacleAvoidanceParams& params)
+{
+    if (crowd_ && obstacleAvoidanceType < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
+    {
+        crowd_->setObstacleAvoidanceParams(obstacleAvoidanceType, (dtObstacleAvoidanceParams*)&params);
+        if (numObstacleAvoidanceTypes_ < obstacleAvoidanceType + 1)
+            numObstacleAvoidanceTypes_ = obstacleAvoidanceType + 1;
+        MarkNetworkUpdate();
     }
-    return agents;
 }
 
 Vector3 CrowdManager::FindNearestPoint(const Vector3& point, int filterType, dtPolyRef* nearestRef)
@@ -319,13 +402,136 @@ Vector3 CrowdManager::Raycast(const Vector3& start, const Vector3& end, int filt
     return crowd_ && navigationMesh_ ? navigationMesh_->Raycast(start, end, crowd_->getQueryExtents(), crowd_->getFilter(filterType), hitNormal) : end;
 }
 
-bool CrowdManager::CreateCrowd(bool readdCrowdAgents)
+unsigned CrowdManager::GetNumAreas(unsigned filterType) const
+{
+    return filterType < numFilterTypes_ ? numAreas_[filterType] : 0;
+}
+
+VariantVector CrowdManager::GetFilterTypesAttr() const
+{
+    VariantVector ret;
+    if (crowd_)
+    {
+        unsigned totalNumAreas = 0;
+        for (unsigned i = 0; i < numFilterTypes_; ++i)
+            totalNumAreas += numAreas_[i];
+
+        ret.Reserve(numFilterTypes_ * 3 + totalNumAreas + 1);
+        ret.Push(numFilterTypes_);
+
+        for (unsigned i = 0; i < numFilterTypes_; ++i)
+        {
+            const dtQueryFilter* filter = crowd_->getFilter(i);
+            assert(filter);
+            ret.Push(filter->getIncludeFlags());
+            ret.Push(filter->getExcludeFlags());
+            ret.Push(numAreas_[i]);
+
+            for (unsigned j = 0; j < numAreas_[i]; ++j)
+                ret.Push(filter->getAreaCost(j));
+        }
+    }
+    else
+        ret.Push(0);
+
+    return ret;
+}
+
+unsigned short CrowdManager::GetIncludeFlags(unsigned filterType) const
+{
+    if (filterType >= numFilterTypes_)
+        LOGWARNINGF("Filter type %d is not configured yet, returning the default include flags initialized by dtCrowd", filterType);
+    const dtQueryFilter* filter = GetDetourQueryFilter(filterType);
+    return filter ? filter->getIncludeFlags() : 0xffff;
+}
+
+unsigned short CrowdManager::GetExcludeFlags(unsigned filterType) const
+{
+    if (filterType >= numFilterTypes_)
+        LOGWARNINGF("Filter type %d is not configured yet, returning the default exclude flags initialized by dtCrowd", filterType);
+    const dtQueryFilter* filter = GetDetourQueryFilter(filterType);
+    return filter ? filter->getExcludeFlags() : 0;
+}
+
+float CrowdManager::GetAreaCost(unsigned filterType, unsigned areaID) const
+{
+    if (filterType >= numFilterTypes_ || areaID >= numAreas_[filterType])
+        LOGWARNINGF("Filter type %d and/or area id %d are not configured yet, returning the default area cost initialized by dtCrowd", filterType, areaID);
+    const dtQueryFilter* filter = GetDetourQueryFilter(filterType);
+    return filter ? filter->getAreaCost((int)areaID) : 1.f;
+}
+
+VariantVector CrowdManager::GetObstacleAvoidanceTypesAttr() const
+{
+    VariantVector ret;
+    if (crowd_)
+    {
+        ret.Reserve(numObstacleAvoidanceTypes_ * 10 + 1);
+        ret.Push(numObstacleAvoidanceTypes_);
+
+        for (unsigned i = 0; i < numObstacleAvoidanceTypes_; ++i)
+        {
+            const dtObstacleAvoidanceParams* params = crowd_->getObstacleAvoidanceParams(i);
+            assert(params);
+            ret.Push(params->velBias);
+            ret.Push(params->weightDesVel);
+            ret.Push(params->weightCurVel);
+            ret.Push(params->weightSide);
+            ret.Push(params->weightToi);
+            ret.Push(params->horizTime);
+            ret.Push(params->gridSize);
+            ret.Push(params->adaptiveDivs);
+            ret.Push(params->adaptiveRings);
+            ret.Push(params->adaptiveDepth);
+        }
+    }
+    else
+        ret.Push(0);
+
+    return ret;
+}
+
+const CrowdObstacleAvoidanceParams& CrowdManager::GetObstacleAvoidanceParams(unsigned obstacleAvoidanceType) const
+{
+    static const CrowdObstacleAvoidanceParams EMPTY_PARAMS = CrowdObstacleAvoidanceParams();
+    const dtObstacleAvoidanceParams* params = crowd_ ? crowd_->getObstacleAvoidanceParams(obstacleAvoidanceType) : 0;
+    return params ? *(const CrowdObstacleAvoidanceParams*)params : EMPTY_PARAMS;
+}
+
+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()
 {
     if (!navigationMesh_ || !navigationMesh_->InitializeQuery())
         return false;
 
-    if (crowd_)
+    // Preserve the existing crowd configuration before recreating it
+    VariantVector filterTypeConfiguration, obstacleAvoidanceTypeConfiguration;
+    bool recreate = crowd_ != 0;
+    if (recreate)
+    {
+        filterTypeConfiguration = GetFilterTypesAttr();
+        obstacleAvoidanceTypeConfiguration = GetObstacleAvoidanceTypesAttr();
         dtFreeCrowd(crowd_);
+    }
     crowd_ = dtAllocCrowd();
 
     // Initialize the crowd
@@ -335,39 +541,14 @@ bool CrowdManager::CreateCrowd(bool readdCrowdAgents)
         return false;
     }
 
-    // Setup local avoidance params to different qualities.
-    dtObstacleAvoidanceParams params;
-    memcpy(&params, crowd_->getObstacleAvoidanceParams(0), sizeof(dtObstacleAvoidanceParams));
-
-    // Low (11)
-    params.velBias = 0.5f;
-    params.adaptiveDivs = 5;
-    params.adaptiveRings = 2;
-    params.adaptiveDepth = 1;
-    crowd_->setObstacleAvoidanceParams(0, &params);
-
-    // Medium (22)
-    params.velBias = 0.5f;
-    params.adaptiveDivs = 5;
-    params.adaptiveRings = 2;
-    params.adaptiveDepth = 2;
-    crowd_->setObstacleAvoidanceParams(1, &params);
-
-    // Good (45)
-    params.velBias = 0.5f;
-    params.adaptiveDivs = 7;
-    params.adaptiveRings = 2;
-    params.adaptiveDepth = 3;
-    crowd_->setObstacleAvoidanceParams(2, &params);
-
-    // High (66)
-    params.velBias = 0.5f;
-    params.adaptiveDivs = 7;
-    params.adaptiveRings = 3;
-    params.adaptiveDepth = 3;
-    crowd_->setObstacleAvoidanceParams(3, &params);
-
-    if (readdCrowdAgents)
+    // Reconfigure the newly initialized crowd
+    if (recreate)
+    {
+        SetFilterTypesAttr(filterTypeConfiguration);
+        SetObstacleAvoidanceTypesAttr(obstacleAvoidanceTypeConfiguration);
+    }
+
+    if (recreate)
     {
         PODVector<CrowdAgent*> agents = GetAgents();
         for (unsigned i = 0; i < agents.Size(); ++i)
@@ -414,13 +595,14 @@ void DetourCrowdManager::OnSceneSet(Scene* scene)
     if (scene)
     {
         SubscribeToEvent(scene, E_SCENESUBSYSTEMUPDATE, HANDLER(CrowdManager, HandleSceneSubsystemUpdate));
+        // TODO: Do not assume navmesh component always attach to scene node
         NavigationMesh* mesh = GetScene()->GetDerivedComponent<NavigationMesh>();
         if (mesh)
         {
             SubscribeToEvent(mesh, E_NAVIGATION_MESH_REBUILT, HANDLER(CrowdManager, HandleNavMeshFullRebuild));
             navigationMesh_ = navMesh;
             navigationMeshId_ = navMesh->GetID();
-            CreateCrowd(false);
+            CreateCrowd();
         }
         else
             LOGERROR("CrowdManager requires an existing navigation mesh");
@@ -435,23 +617,31 @@ void DetourCrowdManager::OnSceneSet(Scene* scene)
 
 void CrowdManager::Update(float delta)
 {
-    if (!crowd_ || !navigationMesh_)
-        return;
+    assert(crowd_ && navigationMesh_);
     PROFILE(UpdateCrowd);
     crowd_->update(delta, 0);
 }
 
-const dtCrowdAgent* CrowdManager::GetCrowdAgent(int agent)
+const dtCrowdAgent* CrowdManager::GetDetourCrowdAgent(int agent) const
 {
     return crowd_ ? crowd_->getAgent(agent) : 0;
 }
 
+const dtQueryFilter* CrowdManager::GetDetourQueryFilter(unsigned filterType) const
+{
+    return crowd_ ? crowd_->getFilter(filterType) : 0;
+}
+
 void CrowdManager::HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData)
 {
-    using namespace SceneSubsystemUpdate;
+    // Perform update tick as long as the crowd is initialized and navmesh has not expired
+    if (crowd_ && navigationMesh_)
+    {
+        using namespace SceneSubsystemUpdate;
 
-    if (IsEnabledEffective())
-        Update(eventData[P_TIMESTEP].GetFloat());
+        if (IsEnabledEffective())
+            Update(eventData[P_TIMESTEP].GetFloat());
+    }
 }
 
 void CrowdManager::HandleNavMeshFullRebuild(StringHash eventType, VariantMap& eventData)

+ 57 - 7
Source/Urho3D/Navigation/CrowdManager.h

@@ -31,6 +31,7 @@ typedef unsigned int dtPolyRef;
 #endif
 
 class dtCrowd;
+class dtQueryFilter;
 struct dtCrowdAgent;
 
 namespace Urho3D
@@ -39,6 +40,21 @@ namespace Urho3D
 class CrowdAgent;
 class NavigationMesh;
 
+/// Parameter structure for obstacle avoidance params (copied from DetourObstacleAvoidance.h in order to hide Detour header from Urho3D library users).
+struct CrowdObstacleAvoidanceParams
+{
+    float velBias;
+    float weightDesVel;
+    float weightCurVel;
+    float weightSide;
+    float weightToi;
+    float horizTime;
+    unsigned char gridSize;         ///< grid
+    unsigned char adaptiveDivs;     ///< adaptive
+    unsigned char adaptiveRings;    ///< adaptive
+    unsigned char adaptiveDepth;    ///< adaptive
+};
+
 /// Crowd manager scene component. Should be added only to the root scene node.
 class URHO3D_API CrowdManager : public Component
 {
@@ -73,8 +89,18 @@ public:
     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 cost);
+    /// Set all the filter types configured in the crowd based on the corresponding attribute.
+    void SetFilterTypesAttr(const VariantVector& value);
+    /// Set the include flags for the specified navigation filter type.
+    void SetIncludeFlags(unsigned filterType, unsigned short flags);
+    /// Set the exclude flags for the specified navigation filter type.
+    void SetExcludeFlags(unsigned filterType, unsigned short flags);
+    /// Set the cost of an area for the specified navigation filter type.
+    void SetAreaCost(unsigned filterType, unsigned areaID, float cost);
+    /// Set all the obstacle avoidance types configured in the crowd based on the corresponding attribute.
+    void SetObstacleAvoidanceTypesAttr(const VariantVector& value);
+    /// Set the params for the specified obstacle avoidance type.
+    void SetObstacleAvoidanceParams(unsigned obstacleAvoidanceType, const CrowdObstacleAvoidanceParams& params);
 
     /// 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;
@@ -98,12 +124,28 @@ public:
     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 number of configured filter types.
+    unsigned GetNumFilterTypes() const { return numFilterTypes_; }
+    /// Get the number of configured area in the specified filter type.
+    unsigned GetNumAreas(unsigned filterType) const;
+    /// Return all the filter types configured in the crowd as attribute.
+    VariantVector GetFilterTypesAttr() const;
+    /// Get the include flags for the specified navigation filter type.
+    unsigned short GetIncludeFlags(unsigned filterType) const;
+    /// Get the exclude flags for the specified navigation filter type.
+    unsigned short GetExcludeFlags(unsigned filterType) const;
+    /// Get the cost of an area for the specified navigation filter type.
+    float GetAreaCost(unsigned filterType, unsigned areaID) const;
+    /// Get the number of configured obstacle avoidance types.
+    unsigned GetNumObstacleAvoidanceTypes() const { return numObstacleAvoidanceTypes_; }
+    /// Return all the obstacle avoidance types configured in the crowd as attribute.
+    VariantVector GetObstacleAvoidanceTypesAttr() const;
+    /// Get the params for the specified obstacle avoidance type.
+    const CrowdObstacleAvoidanceParams& GetObstacleAvoidanceParams(unsigned obstacleAvoidanceType) 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 = true);
+    /// Create and initialized internal Detour crowd object. When it is a recreate, it preserves the configuration and attempts to re-add existing agents in the previous crowd back to the newly created crowd.
+    bool CreateCrowd();
     /// 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.
@@ -115,7 +157,9 @@ protected:
     /// Update the crowd simulation.
     void Update(float delta);
     /// Get the detour crowd agent.
-    const dtCrowdAgent* GetCrowdAgent(int agent);
+    const dtCrowdAgent* GetDetourCrowdAgent(int agent) const;
+    /// Get the detour query filter.
+    const dtQueryFilter* GetDetourQueryFilter(unsigned filterType) const;
     /// Get the internal detour crowd component.
     dtCrowd* GetCrowd() const { return crowd_; }
 
@@ -135,6 +179,12 @@ private:
     float maxAgentRadius_;
     /// The NavigationMesh component Id for pending crowd creation.
     unsigned navigationMeshId_;
+    /// Number of filter types configured in the crowd. Limit to DT_CROWD_MAX_QUERY_FILTER_TYPE.
+    unsigned numFilterTypes_;
+    /// Number of configured area in each filter type. Limit to DT_MAX_AREAS.
+    PODVector<unsigned> numAreas_;
+    /// Number of obstacle avoidance types configured in the crowd. Limit to DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS.
+    unsigned numObstacleAvoidanceTypes_;
 };
 
 }

+ 23 - 2
Source/Urho3D/Script/NavigationAPI.cpp

@@ -177,11 +177,27 @@ void RegisterNavArea(asIScriptEngine* engine)
 
 void RegisterCrowdManager(asIScriptEngine* engine)
 {
+    engine->RegisterObjectType("CrowdObstacleAvoidanceParams", sizeof(CrowdObstacleAvoidanceParams), asOBJ_VALUE | asOBJ_POD);
+    engine->RegisterObjectProperty("CrowdObstacleAvoidanceParams", "float velBias", offsetof(CrowdObstacleAvoidanceParams, velBias));
+    engine->RegisterObjectProperty("CrowdObstacleAvoidanceParams", "float weightDesVel", offsetof(CrowdObstacleAvoidanceParams, weightDesVel));
+    engine->RegisterObjectProperty("CrowdObstacleAvoidanceParams", "float weightCurVel", offsetof(CrowdObstacleAvoidanceParams, weightCurVel));
+    engine->RegisterObjectProperty("CrowdObstacleAvoidanceParams", "float weightSide", offsetof(CrowdObstacleAvoidanceParams, weightSide));
+    engine->RegisterObjectProperty("CrowdObstacleAvoidanceParams", "float weightToi", offsetof(CrowdObstacleAvoidanceParams, weightToi));
+    engine->RegisterObjectProperty("CrowdObstacleAvoidanceParams", "float horizTime", offsetof(CrowdObstacleAvoidanceParams, horizTime));
+    engine->RegisterObjectProperty("CrowdObstacleAvoidanceParams", "uint8 gridSize", offsetof(CrowdObstacleAvoidanceParams, gridSize));
+    engine->RegisterObjectProperty("CrowdObstacleAvoidanceParams", "uint8 adaptiveDivs", offsetof(CrowdObstacleAvoidanceParams, adaptiveDivs));
+    engine->RegisterObjectProperty("CrowdObstacleAvoidanceParams", "uint8 adaptiveRings", offsetof(CrowdObstacleAvoidanceParams, adaptiveRings));
+    engine->RegisterObjectProperty("CrowdObstacleAvoidanceParams", "uint8 adaptiveDepth", offsetof(CrowdObstacleAvoidanceParams, adaptiveDepth));
+
     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", "void SetIncludeFlags(uint, uint16)", asMETHOD(CrowdManager, SetIncludeFlags), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "void SetExcludeFlags(uint, uint16)", asMETHOD(CrowdManager, SetExcludeFlags), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "void SetAreaCost(uint, uint, float)", asMETHOD(CrowdManager, SetAreaCost), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "void SetObstacleAvoidanceParams(uint, const CrowdObstacleAvoidanceParams&in)", asMETHOD(CrowdManager, SetObstacleAvoidanceParams), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdManager", "Array<CrowdAgent@>@ GetAgents(Node@+ node = null, bool inCrowdFilter = true)", asFUNCTION(CrowdManagerGetAgents), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("CrowdManager", "Vector3 FindNearestPoint(const Vector3&in, int)", asMETHOD(CrowdManager, FindNearestPoint), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdManager", "Vector3 MoveAlongSurface(const Vector3&in, const Vector3&in, int, uint maxVisited = 3)", asMETHOD(CrowdManager, MoveAlongSurface), asCALL_THISCALL);
@@ -189,14 +205,19 @@ void RegisterCrowdManager(asIScriptEngine* engine)
     engine->RegisterObjectMethod("CrowdManager", "Vector3 GetRandomPointInCircle(const Vector3&in, float, int)", asMETHOD(CrowdManager, GetRandomPointInCircle), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdManager", "float GetDistanceToWall(const Vector3&in, float, int)", asMETHOD(CrowdManager, GetDistanceToWall), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdManager", "Vector3 Raycast(const Vector3&in, const Vector3&in, int)", asMETHOD(CrowdManager, Raycast), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "uint16 GetIncludeFlags(uint)", asMETHOD(CrowdManager, GetIncludeFlags), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "uint16 GetExcludeFlags(uint)", asMETHOD(CrowdManager, GetExcludeFlags), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "float GetAreaCost(uint, uint)", asMETHOD(CrowdManager, GetAreaCost), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "const CrowdObstacleAvoidanceParams& GetObstacleAvoidanceParams(uint)", asMETHOD(CrowdManager, GetObstacleAvoidanceParams), asCALL_THISCALL);
     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);
+    engine->RegisterObjectMethod("CrowdManager", "uint get_numFilterTypes() const", asMETHOD(CrowdManager, GetNumFilterTypes), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "uint get_numAreas(uint) const", asMETHOD(CrowdManager, GetNumAreas), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdManager", "uint get_numObstacleAvoidanceTypes() const", asMETHOD(CrowdManager, GetNumObstacleAvoidanceTypes), asCALL_THISCALL);
 }
 
 void RegisterCrowdAgent(asIScriptEngine* engine)

+ 8 - 1
bin/Data/LuaScripts/39_CrowdNavigation.lua

@@ -111,7 +111,14 @@ function CreateScene()
     end
 
     -- Create a CrowdManager component to the scene root (mandatory for crowd agents)
-    scene_:CreateComponent("CrowdManager")
+    local crowdManager = scene_:CreateComponent("CrowdManager")
+    local params = crowdManager:GetObstacleAvoidanceParams(0)
+    -- Set the params to "High (66)" setting
+    params.velBias = 0.5
+    params.adaptiveDivs = 7
+    params.adaptiveRings = 3
+    params.adaptiveDepth = 3
+    crowdManager:SetObstacleAvoidanceParams(0, params)
 
     -- 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)

+ 8 - 1
bin/Data/Scripts/39_CrowdNavigation.as

@@ -113,7 +113,14 @@ void CreateScene()
         CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
 
     // Create a CrowdManager component to the scene root (mandatory for crowd agents)
-    scene_.CreateComponent("CrowdManager");
+    CrowdManager@ crowdManager = scene_.CreateComponent("CrowdManager");
+    CrowdObstacleAvoidanceParams params = crowdManager.GetObstacleAvoidanceParams(0);
+    // Set the params to "High (66)" setting
+    params.velBias = 0.5f;
+    params.adaptiveDivs = 7;
+    params.adaptiveRings = 3;
+    params.adaptiveDepth = 3;
+    crowdManager.SetObstacleAvoidanceParams(0, params);
 
     // 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);

+ 94 - 12
bin/Data/Scripts/Editor/AttributeEditor.as

@@ -288,7 +288,7 @@ UIElement@ CreateNumAttributeEditor(ListView@ list, Array<Serializable@>@ serial
     {
         LineEdit@ attrEdit = CreateAttributeLineEdit(parent, serializables, index, subIndex);
         attrEdit.vars["Coordinate"] = i;
-        
+
         CreateDragSlider(attrEdit);
 
         SubscribeToEvent(attrEdit, "TextChanged", "EditAttribute");
@@ -422,6 +422,10 @@ Button@ CreateResourcePickerButton(UIElement@ container, Array<Serializable@>@ s
     return button;
 }
 
+// Use internally for nested variant vector
+uint nestedSubIndex;
+Array<Variant>@ nestedVector;
+
 UIElement@ CreateAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, const AttributeInfo&in info, uint index, uint subIndex, bool suppressedSeparatedLabel = false)
 {
     UIElement@ parent;
@@ -451,22 +455,61 @@ UIElement@ CreateAttributeEditor(ListView@ list, Array<Serializable@>@ serializa
     }
     else if (type == VAR_VARIANTVECTOR)
     {
-        VectorStruct@ vectorStruct = GetVectorStruct(serializables, index);
+        uint nameIndex = 0;
+        uint repeat = M_MAX_UNSIGNED;
+
+        VectorStruct@ vectorStruct;
+        Array<Variant>@ vector;
+        bool emptyNestedVector = false;
+        if (info.name.Contains('>'))
+        {
+            @vector = @nestedVector;
+            vectorStruct = GetNestedVectorStruct(serializables, info.name);
+            repeat = vector[subIndex].GetUInt();    // Nested VariantVector must have a predefined repeat count at the start of the vector
+            emptyNestedVector = repeat == 0;
+        }
+        else
+        {
+            @vector = serializables[0].attributes[index].GetVariantVector();
+            vectorStruct = GetVectorStruct(serializables, index);
+            subIndex = 0;
+        }
         if (vectorStruct is null)
             return null;
-        uint nameIndex = 0;
 
-        Array<Variant>@ vector = serializables[0].attributes[index].GetVariantVector();
-        for (uint i = 0; i < vector.length; ++i)
+        for (uint i = subIndex; i < vector.length; ++i)
         {
             // The individual variant in the vector is not an attribute of the serializable, the structure is reused for convenience
             AttributeInfo vectorInfo;
-            vectorInfo.name = vectorStruct.variableNames[nameIndex];
-            vectorInfo.type = vector[i].type;
+            vectorInfo.name = vectorStruct.variableNames[nameIndex++];
+            bool nested = vectorInfo.name.Contains('>');
+            if (nested)
+            {
+                vectorInfo.type = VAR_VARIANTVECTOR;
+                @nestedVector = @vector;
+            }
+            else
+                vectorInfo.type = vector[i].type;
             CreateAttributeEditor(list, serializables, vectorInfo, index, i);
-            ++nameIndex;
+            if (nested)
+            {
+                i = nestedSubIndex;
+                @nestedVector = null;
+            }
+            if (emptyNestedVector)
+            {
+                nestedSubIndex = i;
+                break;
+            }
             if (nameIndex >= vectorStruct.variableNames.length)
+            {
+                if (--repeat == 0)
+                {
+                    nestedSubIndex = i;
+                    break;
+                }
                 nameIndex = vectorStruct.restartIndex;
+            }
         }
     }
     else if (type == VAR_VARIANTMAP)
@@ -990,7 +1033,7 @@ void EditAttribute(StringHash eventType, VariantMap& eventData)
     StoreAttributeEditor(parent, serializables, index, subIndex, coordinate);
     for (uint i = 0; i < serializables.length; ++i)
         serializables[i].ApplyAttributes();
-    
+
     if (!dragEditAttribute)
     {
         // Do the editor post logic after attribute has been modified.
@@ -1321,7 +1364,7 @@ String GetResourceNameFromFullName(const String&in resourceName)
             continue;
         return resourceName.Substring(resourceDirs[i].length);
     }
-    
+
     return ""; // Not found
 }
 
@@ -1379,7 +1422,7 @@ void TestResource(StringHash eventType, VariantMap& eventData)
     LineEdit@ attrEdit = button.parent.children[0];
 
     StringHash resourceType(attrEdit.vars[TYPE_VAR].GetUInt());
-    
+
     // For now only Animations can be tested
     StringHash animType("Animation");
     if (resourceType == animType)
@@ -1494,6 +1537,35 @@ void InitVectorStructs()
         "   NodeID"
     };
     vectorStructs.Push(VectorStruct("SplinePath", "Control Points", splinePathInstanceVariables, 1));
+
+    Array<String> crowdManagerFilterTypeVariables = {
+        "Filter Type Count",
+        "   Include Flags",
+        "   Exclude Flags",
+        "   >AreaCost"
+    };
+    vectorStructs.Push(VectorStruct("CrowdManager", "Filter Types", crowdManagerFilterTypeVariables, 1));
+
+    Array<String> crowdManagerAreaCostVariables = {
+        "   Area Count",
+        "      Cost"
+    };
+    vectorStructs.Push(VectorStruct("CrowdManager", "   >AreaCost", crowdManagerAreaCostVariables, 1));
+
+    Array<String> crowdManagerObstacleAvoidanceTypeVariables = {
+        "Obstacle Avoid. Type Count",
+        "   Velocity Bias",
+        "   Desired Velocity Weight",
+        "   Current Velocity Weight",
+        "   Side Bias Weight",
+        "   Time of Impact Weight",
+        "   Time Horizon",
+        "   Grid Size",
+        "   Adaptive Divs",
+        "   Adaptive Rings",
+        "   Adaptive Depth"
+    };
+    vectorStructs.Push(VectorStruct("CrowdManager", "Obstacle Avoidance Types", crowdManagerObstacleAvoidanceTypeVariables, 1));
 }
 
 VectorStruct@ GetVectorStruct(Array<Serializable@>@ serializables, uint index)
@@ -1507,6 +1579,16 @@ VectorStruct@ GetVectorStruct(Array<Serializable@>@ serializables, uint index)
     return null;
 }
 
+VectorStruct@ GetNestedVectorStruct(Array<Serializable@>@ serializables, const String&in name)
+{
+    for (uint i = 0; i < vectorStructs.length; ++i)
+    {
+        if (vectorStructs[i].componentTypeName == serializables[0].typeName && vectorStructs[i].attributeName == name)
+            return vectorStructs[i];
+    }
+    return null;
+}
+
 int GetAttributeIndex(Serializable@ serializable, const String&in attrName)
 {
     for (uint i = 0; i < serializable.numAttributes; ++i)
@@ -1514,6 +1596,6 @@ int GetAttributeIndex(Serializable@ serializable, const String&in attrName)
         if (serializable.attributeInfos[i].name.Compare(attrName, false) == 0)
             return i;
     }
-    
+
     return -1;
 }