Browse Source

Add new event for crowd agent formation.
Add convenient methods for querying point, path, raycast, etc using crowd's extent and filter type configuration.

Yao Wei Tjong 姚伟忠 10 years ago
parent
commit
c9bc4cbae3

+ 4 - 2
Source/Samples/15_Navigation/Navigation.cpp

@@ -160,8 +160,10 @@ void Navigation::CreateScene()
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     camera->SetFarClip(300.0f);
     camera->SetFarClip(300.0f);
     
     
-    // Set an initial position for the camera scene node above the plane
-    cameraNode_->SetPosition(Vector3(0.0f, 5.0f, 0.0f));
+    // Set an initial position for the camera scene node above the plane and looking down
+    cameraNode_->SetPosition(Vector3(0.0f, 50.0f, 0.0f));
+    pitch_ = 80.0f;
+    cameraNode_->SetRotation(Quaternion(pitch_, yaw_, 0.0f));
 }
 }
 
 
 void Navigation::CreateUI()
 void Navigation::CreateUI()

+ 27 - 5
Source/Samples/39_CrowdNavigation/CrowdNavigation.cpp

@@ -174,7 +174,7 @@ void CrowdNavigation::CreateScene()
     CreateMovingBarrels(navMesh);
     CreateMovingBarrels(navMesh);
 
 
     // Create Jack node as crowd agent
     // Create Jack node as crowd agent
-    SpawnJack(Vector3(-5.0f, 0.0f, 20.0f));
+    SpawnJack(Vector3(-5.0f, 0.0f, 20.0f), scene_->CreateChild("Jacks"));
 
 
     // Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside the scene, because
     // Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside the scene, because
     // we want it to be unaffected by scene load / save
     // we want it to be unaffected by scene load / save
@@ -248,12 +248,15 @@ void CrowdNavigation::SubscribeToEvents()
 
 
     // Subscribe HandleCrowdAgentReposition() function for controlling the animation
     // Subscribe HandleCrowdAgentReposition() function for controlling the animation
     SubscribeToEvent(E_CROWD_AGENT_REPOSITION, HANDLER(CrowdNavigation, HandleCrowdAgentReposition));
     SubscribeToEvent(E_CROWD_AGENT_REPOSITION, HANDLER(CrowdNavigation, HandleCrowdAgentReposition));
+
+    // Subscribe HandleCrowdAgentFormation() function for positioning agent into a formation
+    SubscribeToEvent(E_CROWD_AGENT_FORMATION, HANDLER(CrowdNavigation, HandleCrowdAgentFormation));
 }
 }
 
 
-void CrowdNavigation::SpawnJack(const Vector3& pos)
+void CrowdNavigation::SpawnJack(const Vector3& pos, Node* jackGroup)
 {
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     ResourceCache* cache = GetSubsystem<ResourceCache>();
-    SharedPtr<Node> jackNode(scene_->CreateChild("Jack"));
+    SharedPtr<Node> jackNode(jackGroup->CreateChild("Jack"));
     jackNode->SetPosition(pos);
     jackNode->SetPosition(pos);
     AnimatedModel* modelObject = jackNode->CreateComponent<AnimatedModel>();
     AnimatedModel* modelObject = jackNode->CreateComponent<AnimatedModel>();
     modelObject->SetModel(cache->GetResource<Model>("Models/Jack.mdl"));
     modelObject->SetModel(cache->GetResource<Model>("Models/Jack.mdl"));
@@ -327,6 +330,7 @@ void CrowdNavigation::CreateMovingBarrels(DynamicNavigationMesh* navMesh)
         CrowdAgent* agent = clone->CreateComponent<CrowdAgent>();
         CrowdAgent* agent = clone->CreateComponent<CrowdAgent>();
         agent->SetRadius(clone->GetScale().x_ * 0.5f);
         agent->SetRadius(clone->GetScale().x_ * 0.5f);
         agent->SetHeight(size);
         agent->SetHeight(size);
+        agent->SetNavigationQuality(NAVIGATIONQUALITY_LOW);
     }
     }
     barrel->Remove();
     barrel->Remove();
 }
 }
@@ -340,12 +344,13 @@ void CrowdNavigation::SetPathPoint(bool spawning)
     {
     {
         DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
         DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
         Vector3 pathPos = navMesh->FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
         Vector3 pathPos = navMesh->FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
+        Node* jackGroup = scene_->GetChild("Jacks");
         if (spawning)
         if (spawning)
             // Spawn a jack at the target position
             // Spawn a jack at the target position
-            SpawnJack(pathPos);
+            SpawnJack(pathPos, jackGroup);
         else
         else
             // Set crowd agents target position
             // Set crowd agents target position
-            scene_->GetComponent<CrowdManager>()->SetCrowdTarget(pathPos);
+            scene_->GetComponent<CrowdManager>()->SetCrowdTarget(pathPos, jackGroup);
     }
     }
 }
 }
 
 
@@ -538,3 +543,20 @@ void CrowdNavigation::HandleCrowdAgentReposition(StringHash eventType, VariantMa
             animCtrl->Stop(WALKING_ANI, 0.8f);
             animCtrl->Stop(WALKING_ANI, 0.8f);
     }
     }
 }
 }
+
+void CrowdNavigation::HandleCrowdAgentFormation(StringHash eventType, VariantMap& eventData)
+{
+    using namespace CrowdAgentFormation;
+
+    unsigned index = eventData[P_INDEX].GetUInt();
+    unsigned size = eventData[P_SIZE].GetUInt();
+    Vector3 position = eventData[P_POSITION].GetVector3();
+
+    // The first agent will always move to the exact position, all other agents will select a random point nearby
+    if (index)
+    {
+        CrowdManager* crowdManager = static_cast<CrowdManager*>(GetEventSender());
+        CrowdAgent* agent = static_cast<CrowdAgent*>(eventData[P_CROWD_AGENT].GetPtr());
+        eventData[P_POSITION] = crowdManager->GetRandomPointInCircle(position, agent->GetRadius(), agent->GetFilterType());
+    }
+}

+ 3 - 1
Source/Samples/39_CrowdNavigation/CrowdNavigation.h

@@ -143,7 +143,7 @@ private:
     /// Add new obstacle or remove existing obstacle/agent.
     /// Add new obstacle or remove existing obstacle/agent.
     void AddOrRemoveObject();
     void AddOrRemoveObject();
     /// Create a "Jack" object at position.
     /// Create a "Jack" object at position.
-    void SpawnJack(const Vector3& pos);
+    void SpawnJack(const Vector3& pos, Node* jackGroup);
     /// Create a mushroom object at position.
     /// Create a mushroom object at position.
     void CreateMushroom(const Vector3& pos);
     void CreateMushroom(const Vector3& pos);
     /// Create an off-mesh connection for each box to make it climbable.
     /// Create an off-mesh connection for each box to make it climbable.
@@ -160,6 +160,8 @@ private:
     void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData);
     void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData);
     /// Handle crowd agent reposition.
     /// Handle crowd agent reposition.
     void HandleCrowdAgentReposition(StringHash eventType, VariantMap& eventData);
     void HandleCrowdAgentReposition(StringHash eventType, VariantMap& eventData);
+    /// Handle crowd agent formation.
+    void HandleCrowdAgentFormation(StringHash eventType, VariantMap& eventData);
 
 
     /// Flag for drawing debug geometry.
     /// Flag for drawing debug geometry.
     bool drawDebug_;
     bool drawDebug_;

+ 1 - 1
Source/ThirdParty/DetourCrowd/include/DetourCrowd.h

@@ -201,7 +201,7 @@ struct dtCrowdAgentDebugInfo
 };
 };
 
 
 // Urho3D: Add update callback support
 // Urho3D: Add update callback support
-/// Type for the updat callback.
+/// Type for the update callback.
 typedef void (*dtUpdateCallback)(dtCrowdAgent* ag, float dt);
 typedef void (*dtUpdateCallback)(dtCrowdAgent* ag, float dt);
 
 
 /// Provides local steering behaviors for a group of agents. 
 /// Provides local steering behaviors for a group of agents. 

+ 2 - 2
Source/Urho3D/LuaScript/pkgs/Navigation/CrowdAgent.pkg

@@ -28,8 +28,8 @@ enum CrowdAgentState
 enum NavigationQuality
 enum NavigationQuality
 {
 {
     NAVIGATIONQUALITY_LOW = 0,
     NAVIGATIONQUALITY_LOW = 0,
-    NAVIGATIONQUALITY_MEDIUM = 1,
-    NAVIGATIONQUALITY_HIGH = 2
+    NAVIGATIONQUALITY_MEDIUM,
+    NAVIGATIONQUALITY_HIGH
 };
 };
 
 
 enum NavigationPushiness
 enum NavigationPushiness

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

@@ -13,6 +13,13 @@ class CrowdManager : public Component
     void SetAreaCost(unsigned filterID, unsigned areaID, float cost);
     void SetAreaCost(unsigned filterID, unsigned areaID, float cost);
 
 
     PODVector<CrowdAgent*> GetAgents(Node* node = 0, bool inCrowdFilter = true) const;
     PODVector<CrowdAgent*> GetAgents(Node* node = 0, bool inCrowdFilter = true) const;
+    Vector3 FindNearestPoint(const Vector3& point, int filterType);
+    Vector3 MoveAlongSurface(const Vector3& start, const Vector3& end, int filterType, int maxVisited = 3);
+    tolua_outside const PODVector<Vector3>& CrowdManagerFindPath @ FindPath(const Vector3& start, const Vector3& end, int filterType);
+    Vector3 GetRandomPoint(int filterType);
+    Vector3 GetRandomPointInCircle(const Vector3& center, float radius, int filterType);
+    float GetDistanceToWall(const Vector3& point, float radius, int filterType, Vector3* hitPos = 0, Vector3* hitNormal = 0);
+    Vector3 Raycast(const Vector3& start, const Vector3& end, int filterType, Vector3* hitNormal = 0);
     unsigned GetMaxAgents() const;
     unsigned GetMaxAgents() const;
     float GetMaxAgentRadius() const;
     float GetMaxAgentRadius() const;
     NavigationMesh* GetNavigationMesh() const;
     NavigationMesh* GetNavigationMesh() const;
@@ -22,3 +29,13 @@ class CrowdManager : public Component
     tolua_property__get_set float maxAgentRadius;
     tolua_property__get_set float maxAgentRadius;
     tolua_property__get_set NavigationMesh* navigationMesh;
     tolua_property__get_set NavigationMesh* navigationMesh;
 };
 };
+
+${
+const PODVector<Vector3>& CrowdManagerFindPath(CrowdManager* crowdManager, const Vector3& start, const Vector3& end, int filterType)
+{
+    static PODVector<Vector3> dest;
+    dest.Clear();
+    crowdManager->FindPath(dest, start, end, filterType);
+    return dest;
+}
+$}

+ 2 - 6
Source/Urho3D/LuaScript/pkgs/Navigation/NavigationMesh.pkg

@@ -14,7 +14,6 @@ struct NavigationGeometryInfo
     BoundingBox boundingBox_ @ boundingBox;
     BoundingBox boundingBox_ @ boundingBox;
 };
 };
 
 
-
 class NavigationMesh : public Component
 class NavigationMesh : public Component
 {
 {
     void SetTileSize(int size);
     void SetTileSize(int size);
@@ -39,12 +38,9 @@ class NavigationMesh : public Component
     void SetDrawNavAreas(bool enable);
     void SetDrawNavAreas(bool enable);
 
 
     Vector3 FindNearestPoint(const Vector3& point, const Vector3& extents = Vector3::ONE);
     Vector3 FindNearestPoint(const Vector3& point, const Vector3& extents = Vector3::ONE);
-    Vector3 MoveAlongSurface(const Vector3& start, const Vector3& end, const Vector3& extents=Vector3::ONE, int maxVisited=3);
-    // void FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE);
+    Vector3 MoveAlongSurface(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE, int maxVisited = 3);
     tolua_outside const PODVector<Vector3>& NavigationMeshFindPath @ FindPath(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE);
     tolua_outside const PODVector<Vector3>& NavigationMeshFindPath @ FindPath(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE);
-
     Vector3 GetRandomPoint();
     Vector3 GetRandomPoint();
-
     Vector3 GetRandomPointInCircle(const Vector3& center, float radius, const Vector3& extents = Vector3::ONE);
     Vector3 GetRandomPointInCircle(const Vector3& center, float radius, const Vector3& extents = Vector3::ONE);
     float GetDistanceToWall(const Vector3& point, float radius, const Vector3& extents = Vector3::ONE);
     float GetDistanceToWall(const Vector3& point, float radius, const Vector3& extents = Vector3::ONE);
     Vector3 Raycast(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE);
     Vector3 Raycast(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE);
@@ -104,4 +100,4 @@ const PODVector<Vector3>& NavigationMeshFindPath(NavigationMesh* navMesh, const
     navMesh->FindPath(dest, start, end, extents);
     navMesh->FindPath(dest, start, end, extents);
     return dest;
     return dest;
 }
 }
-$}
+$}

+ 42 - 29
Source/Urho3D/Navigation/CrowdAgent.cpp

@@ -123,10 +123,13 @@ void CrowdAgent::RegisterObject(Context* context)
 
 
 void CrowdAgent::ApplyAttributes()
 void CrowdAgent::ApplyAttributes()
 {
 {
+    // Values from Editor, saved-file, or network must be checked before applying
     maxAccel_ = Max(0.f, maxAccel_);
     maxAccel_ = Max(0.f, maxAccel_);
     maxSpeed_ = Max(0.f, maxSpeed_);
     maxSpeed_ = Max(0.f, maxSpeed_);
     radius_ = Max(0.f, radius_);
     radius_ = Max(0.f, radius_);
     height_ = Max(0.f, height_);
     height_ = Max(0.f, height_);
+    filterType_ = Min(filterType_, DT_CROWD_MAX_QUERY_FILTER_TYPE - 1);
+    obstacleAvoidanceType_ = Min(obstacleAvoidanceType_, DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS - 1);
 
 
     UpdateParameters();
     UpdateParameters();
 
 
@@ -303,13 +306,15 @@ int CrowdAgent::AddAgentToCrowd(bool force)
         // Agent created, but initial state is invalid and needs to be addressed
         // Agent created, but initial state is invalid and needs to be addressed
         if (previousAgentState_ == CA_STATE_INVALID)
         if (previousAgentState_ == CA_STATE_INVALID)
         {
         {
-            VariantMap& map = GetContext()->GetEventDataMap();
-            map[CrowdAgentFailure::P_NODE] = GetNode();
-            map[CrowdAgentFailure::P_CROWD_AGENT] = this;
-            map[CrowdAgentFailure::P_CROWD_TARGET_STATE] = previousTargetState_;
-            map[CrowdAgentFailure::P_CROWD_AGENT_STATE] = previousAgentState_;
-            map[CrowdAgentFailure::P_POSITION] = GetPosition();
-            map[CrowdAgentFailure::P_VELOCITY] = GetActualVelocity();
+            using namespace CrowdAgentFailure;
+
+            VariantMap& map = GetEventDataMap();
+            map[P_NODE] = GetNode();
+            map[P_CROWD_AGENT] = this;
+            map[P_CROWD_TARGET_STATE] = previousTargetState_;
+            map[P_CROWD_AGENT_STATE] = previousAgentState_;
+            map[P_POSITION] = GetPosition();
+            map[P_VELOCITY] = GetActualVelocity();
             SendEvent(E_CROWD_AGENT_FAILURE, map);
             SendEvent(E_CROWD_AGENT_FAILURE, map);
 
 
             // Reevaluate states as handling of event may have resulted in changes
             // Reevaluate states as handling of event may have resulted in changes
@@ -344,7 +349,11 @@ void CrowdAgent::SetTargetPosition(const Vector3& position)
         if (!IsInCrowd())
         if (!IsInCrowd())
             AddAgentToCrowd();
             AddAgentToCrowd();
         if (IsInCrowd())   // Make sure the previous method call is successful
         if (IsInCrowd())   // Make sure the previous method call is successful
-            crowdManager_->SetAgentTarget(this, position);
+        {
+            dtPolyRef nearestRef;
+            Vector3 nearestPos = crowdManager_->FindNearestPoint(position, filterType_, &nearestRef);
+            crowdManager_->GetCrowd()->requestMoveTarget(agentCrowdId_, nearestRef, nearestPos.Data());
+        }
     }
     }
 }
 }
 
 
@@ -531,13 +540,15 @@ void CrowdAgent::OnCrowdUpdate(dtCrowdAgent* ag, float dt)
         {
         {
             previousPosition_ = newPos;
             previousPosition_ = newPos;
 
 
-            VariantMap& map = GetContext()->GetEventDataMap();
-            map[CrowdAgentReposition::P_NODE] = node_;
-            map[CrowdAgentReposition::P_CROWD_AGENT] = this;
-            map[CrowdAgentReposition::P_POSITION] = newPos;
-            map[CrowdAgentReposition::P_VELOCITY] = newVel;
-            map[CrowdAgentReposition::P_ARRIVED] = HasArrived();
-            map[CrowdAgentReposition::P_TIMESTEP] = dt;
+            using namespace CrowdAgentReposition;
+
+            VariantMap& map = GetEventDataMap();
+            map[P_NODE] = node_;
+            map[P_CROWD_AGENT] = this;
+            map[P_POSITION] = newPos;
+            map[P_VELOCITY] = newVel;
+            map[P_ARRIVED] = HasArrived();
+            map[P_TIMESTEP] = dt;
             SendEvent(E_CROWD_AGENT_REPOSITION, map);
             SendEvent(E_CROWD_AGENT_REPOSITION, map);
 
 
             if (updateNodePosition_)
             if (updateNodePosition_)
@@ -553,25 +564,27 @@ void CrowdAgent::OnCrowdUpdate(dtCrowdAgent* ag, float dt)
         CrowdAgentState newAgentState = GetAgentState();
         CrowdAgentState newAgentState = GetAgentState();
         if (newAgentState != previousAgentState_ || newTargetState != previousTargetState_)
         if (newAgentState != previousAgentState_ || newTargetState != previousTargetState_)
         {
         {
-            VariantMap& map = GetContext()->GetEventDataMap();
-            map[CrowdAgentStateChanged::P_NODE] = node_;
-            map[CrowdAgentStateChanged::P_CROWD_AGENT] = this;
-            map[CrowdAgentStateChanged::P_CROWD_TARGET_STATE] = newTargetState;
-            map[CrowdAgentStateChanged::P_CROWD_AGENT_STATE] = newAgentState;
-            map[CrowdAgentStateChanged::P_POSITION] = newPos;
-            map[CrowdAgentStateChanged::P_VELOCITY] = newVel;
+            using namespace CrowdAgentStateChanged;
+
+            VariantMap& map = GetEventDataMap();
+            map[P_NODE] = node_;
+            map[P_CROWD_AGENT] = this;
+            map[P_CROWD_TARGET_STATE] = newTargetState;
+            map[P_CROWD_AGENT_STATE] = newAgentState;
+            map[P_POSITION] = newPos;
+            map[P_VELOCITY] = newVel;
             SendEvent(E_CROWD_AGENT_STATE_CHANGED, map);
             SendEvent(E_CROWD_AGENT_STATE_CHANGED, map);
 
 
             // Send a failure event if either state is a failed status
             // Send a failure event if either state is a failed status
             if (newAgentState == CA_STATE_INVALID || newTargetState == CA_TARGET_FAILED)
             if (newAgentState == CA_STATE_INVALID || newTargetState == CA_TARGET_FAILED)
             {
             {
-                VariantMap& map = GetContext()->GetEventDataMap();
-                map[CrowdAgentFailure::P_NODE] = node_;
-                map[CrowdAgentFailure::P_CROWD_AGENT] = this;
-                map[CrowdAgentFailure::P_CROWD_TARGET_STATE] = newTargetState;
-                map[CrowdAgentFailure::P_CROWD_AGENT_STATE] = newAgentState;
-                map[CrowdAgentFailure::P_POSITION] = newPos;
-                map[CrowdAgentFailure::P_VELOCITY] = newVel;
+                VariantMap& map = GetEventDataMap();
+                map[P_NODE] = node_;
+                map[P_CROWD_AGENT] = this;
+                map[P_CROWD_TARGET_STATE] = newTargetState;
+                map[P_CROWD_AGENT_STATE] = newAgentState;
+                map[P_POSITION] = newPos;
+                map[P_VELOCITY] = newVel;
                 SendEvent(E_CROWD_AGENT_FAILURE, map);
                 SendEvent(E_CROWD_AGENT_FAILURE, map);
             }
             }
 
 

+ 71 - 69
Source/Urho3D/Navigation/CrowdManager.cpp

@@ -80,6 +80,7 @@ void CrowdManager::RegisterObject(Context* context)
 
 
 void CrowdManager::ApplyAttributes()
 void CrowdManager::ApplyAttributes()
 {
 {
+    // Values from Editor, saved-file, or network must be checked before applying
     maxAgents_ = Max(1, maxAgents_);
     maxAgents_ = Max(1, maxAgents_);
     maxAgentRadius_ = Max(0.f, maxAgentRadius_);
     maxAgentRadius_ = Max(0.f, maxAgentRadius_);
 
 
@@ -157,13 +158,21 @@ void CrowdManager::SetCrowdTarget(const Vector3& position, Node* node)
     Vector3 moveTarget(position);
     Vector3 moveTarget(position);
     for (unsigned i = 0; i < agents.Size(); ++i)
     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));
-        }
+        CrowdAgent* agent = agents[i];
+        // Give application a chance to determine the desired crowd formation when they reach the target position
+        using namespace CrowdAgentFormation;
+
+        VariantMap& map = GetEventDataMap();
+        map[P_NODE] = agent->GetNode();
+        map[P_CROWD_AGENT] = agent;
+        map[P_INDEX] = i;
+        map[P_SIZE] = agents.Size();
+        map[P_POSITION] = moveTarget;   // Expect the event handler will modify this position accordingly
+
+        SendEvent(E_CROWD_AGENT_FORMATION, map);
+
+        moveTarget = map[P_POSITION].GetVector3();
+        agent->SetTargetPosition(moveTarget);
     }
     }
 }
 }
 
 
@@ -174,10 +183,7 @@ void CrowdManager::SetCrowdVelocity(const Vector3& velocity, Node* node)
 
 
     PODVector<CrowdAgent*> agents = GetAgents(node, true);      // Get only crowd agent components already in the crowd
     PODVector<CrowdAgent*> agents = GetAgents(node, true);      // Get only crowd agent components already in the crowd
     for (unsigned i = 0; i < agents.Size(); ++i)
     for (unsigned i = 0; i < agents.Size(); ++i)
-    {
-        if (agents[i]->GetMaxAccel() > 0.f)
-            agents[i]->SetTargetVelocity(velocity);
-    }
+        agents[i]->SetTargetVelocity(velocity);
 }
 }
 
 
 void CrowdManager::ResetCrowdTarget(Node* node)
 void CrowdManager::ResetCrowdTarget(Node* node)
@@ -187,10 +193,7 @@ void CrowdManager::ResetCrowdTarget(Node* node)
 
 
     PODVector<CrowdAgent*> agents = GetAgents(node, true);
     PODVector<CrowdAgent*> agents = GetAgents(node, true);
     for (unsigned i = 0; i < agents.Size(); ++i)
     for (unsigned i = 0; i < agents.Size(); ++i)
-    {
-        if (agents[i]->GetMaxAccel() > 0.f)
-            agents[i]->ResetTarget();
-    }
+        agents[i]->ResetTarget();
 }
 }
 
 
 void CrowdManager::SetMaxAgents(unsigned maxAgents)
 void CrowdManager::SetMaxAgents(unsigned maxAgents)
@@ -254,12 +257,58 @@ PODVector<CrowdAgent*> CrowdManager::GetAgents(Node* node, bool inCrowdFilter) c
     return agents;
     return agents;
 }
 }
 
 
+Vector3 CrowdManager::FindNearestPoint(const Vector3& point, int filterType, dtPolyRef* nearestRef)
+{
+    if (nearestRef)
+        *nearestRef = 0;
+    return crowd_ && navigationMesh_ ? navigationMesh_->FindNearestPoint(point, crowd_->getQueryExtents(), crowd_->getFilter(filterType), nearestRef) : point;
+}
+
+Vector3 CrowdManager::MoveAlongSurface(const Vector3& start, const Vector3& end, int filterType, int maxVisited)
+{
+    return crowd_ && navigationMesh_ ? navigationMesh_->MoveAlongSurface(start, end, crowd_->getQueryExtents(), maxVisited, crowd_->getFilter(filterType)) : end;
+}
+
+void CrowdManager::FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, int filterType)
+{
+    if (crowd_ && navigationMesh_)
+        navigationMesh_->FindPath(dest, start, end, crowd_->getQueryExtents(), crowd_->getFilter(filterType));
+}
+
+Vector3 CrowdManager::GetRandomPoint(int filterType, dtPolyRef* randomRef)
+{
+    if (randomRef)
+        *randomRef = 0;
+    return crowd_ && navigationMesh_ ? navigationMesh_->GetRandomPoint(crowd_->getFilter(filterType), randomRef) : Vector3::ZERO;
+}
+
+Vector3 CrowdManager::GetRandomPointInCircle(const Vector3& center, float radius, int filterType, dtPolyRef* randomRef)
+{
+    if (randomRef)
+        *randomRef = 0;
+    return crowd_ && navigationMesh_ ? navigationMesh_->GetRandomPointInCircle(center, radius, crowd_->getQueryExtents(), crowd_->getFilter(filterType), randomRef) : center;
+}
+
+float CrowdManager::GetDistanceToWall(const Vector3& point, float radius, int filterType, Vector3* hitPos, Vector3* hitNormal)
+{
+    if (hitPos)
+        *hitPos = Vector3::ZERO;
+    if (hitNormal)
+        *hitNormal = Vector3::DOWN;
+    return crowd_ && navigationMesh_ ? navigationMesh_->GetDistanceToWall(point, radius, crowd_->getQueryExtents(), crowd_->getFilter(filterType), hitPos, hitNormal) : radius;
+}
+
+Vector3 CrowdManager::Raycast(const Vector3& start, const Vector3& end, int filterType, Vector3* hitNormal)
+{
+    if (hitNormal)
+        *hitNormal = Vector3::DOWN;
+    return crowd_ && navigationMesh_ ? navigationMesh_->Raycast(start, end, crowd_->getQueryExtents(), crowd_->getFilter(filterType), hitNormal) : end;
+}
+
 bool CrowdManager::CreateCrowd(bool readdCrowdAgents)
 bool CrowdManager::CreateCrowd(bool readdCrowdAgents)
 {
 {
-    if (!navigationMesh_ || !navigationMesh_->navMesh_)
+    if (!navigationMesh_ || !navigationMesh_->InitializeQuery())
         return false;
         return false;
-    if (!navigationMesh_->navMeshQuery_)
-        navigationMesh_->InitializeQuery();
 
 
     if (crowd_)
     if (crowd_)
         dtFreeCrowd(crowd_);
         dtFreeCrowd(crowd_);
@@ -310,7 +359,7 @@ bool CrowdManager::CreateCrowd(bool readdCrowdAgents)
         for (unsigned i = 0; i < agents.Size(); ++i)
         for (unsigned i = 0; i < agents.Size(); ++i)
         {
         {
             // Keep adding until the crowd cannot take it anymore
             // Keep adding until the crowd cannot take it anymore
-            if (agents[i]->AddAgentToCrowd(readdCrowdAgents) == -1)
+            if (agents[i]->AddAgentToCrowd(true) == -1)
             {
             {
                 LOGWARNINGF("CrowdManager: %d crowd agents orphaned", agents.Size() - i);
                 LOGWARNINGF("CrowdManager: %d crowd agents orphaned", agents.Size() - i);
                 break;
                 break;
@@ -331,17 +380,7 @@ int CrowdManager::AddAgent(CrowdAgent* agent, const Vector3& pos)
         agent->radius_ = navigationMesh_->GetAgentRadius();
         agent->radius_ = navigationMesh_->GetAgentRadius();
     if (agent->height_ == 0.f)
     if (agent->height_ == 0.f)
         agent->height_ = navigationMesh_->GetAgentHeight();
         agent->height_ = navigationMesh_->GetAgentHeight();
-
-    float nearestPos[3];
-    rcVcopy(nearestPos, &pos.x_);
-    dtStatus status = navigationMesh_->navMeshQuery_->findNearestPoly(
-        pos.Data(),
-        crowd_->getQueryExtents(),
-        crowd_->getFilter(agent->filterType_),
-        0,
-        nearestPos);
-
-    return crowd_->addAgent(nearestPos, &params);
+    return crowd_->addAgent(pos.Data(), &params);
 }
 }
 
 
 void CrowdManager::RemoveAgent(CrowdAgent* agent)
 void CrowdManager::RemoveAgent(CrowdAgent* agent)
@@ -354,39 +393,6 @@ void CrowdManager::RemoveAgent(CrowdAgent* agent)
     crowd_->removeAgent(agent->GetAgentCrowdId());
     crowd_->removeAgent(agent->GetAgentCrowdId());
 }
 }
 
 
-bool CrowdManager::SetAgentTarget(CrowdAgent* agent, const Vector3& target)
-{
-    if (!crowd_ || !navigationMesh_ || !agent)
-        return false;
-    int crowdId = agent->GetAgentCrowdId();
-    dtPolyRef polyRef;
-    float nearestPos[3];
-    dtStatus status = navigationMesh_->navMeshQuery_->findNearestPoly(
-        target.Data(),
-        crowd_->getQueryExtents(),
-        crowd_->getFilter(agent->filterType_),
-        &polyRef,
-        nearestPos);
-
-    return !dtStatusFailed(status) && crowd_->requestMoveTarget(crowdId, polyRef, nearestPos) &&
-        crowd_->getAgent(agent->GetAgentCrowdId())->targetState != DT_CROWDAGENT_TARGET_FAILED;
-}
-
-Vector3 CrowdManager::GetClosestWalkablePosition(const Vector3& pos) const
-{
-    if (!crowd_ || !navigationMesh_)
-        return Vector3::ZERO;
-    float closest[3];
-    dtQueryFilter filter;
-    dtStatus status = navigationMesh_->navMeshQuery_->findNearestPoly(
-        pos.Data(),
-        crowd_->getQueryExtents(),
-        &filter,
-        0,
-        closest);
-    return Vector3(closest);
-}
-
 void CrowdManager::Update(float delta)
 void CrowdManager::Update(float delta)
 {
 {
     if (!crowd_ || !navigationMesh_)
     if (!crowd_ || !navigationMesh_)
@@ -413,9 +419,7 @@ void CrowdManager::HandleNavMeshFullRebuild(StringHash eventType, VariantMap& ev
     using namespace NavigationMeshRebuilt;
     using namespace NavigationMeshRebuilt;
 
 
     // The mesh being rebuilt may not have existed before
     // The mesh being rebuilt may not have existed before
-    NavigationMesh* navMesh = static_cast<NavigationMesh*>(eventData[P_MESH].GetPtr());
-    if (!navigationMesh_ || !crowd_)
-        SetNavigationMesh(navMesh);
+    SetNavigationMesh(static_cast<NavigationMesh*>(eventData[P_MESH].GetPtr()));
 }
 }
 
 
 void DetourCrowdManager::OnSceneSet(Scene* scene)
 void DetourCrowdManager::OnSceneSet(Scene* scene)
@@ -425,9 +429,7 @@ void DetourCrowdManager::OnSceneSet(Scene* scene)
     if (scene)
     if (scene)
     {
     {
         SubscribeToEvent(scene, E_SCENESUBSYSTEMUPDATE, HANDLER(CrowdManager, HandleSceneSubsystemUpdate));
         SubscribeToEvent(scene, E_SCENESUBSYSTEMUPDATE, HANDLER(CrowdManager, HandleSceneSubsystemUpdate));
-        NavigationMesh* mesh = GetScene()->GetComponent<NavigationMesh>();
-        if (!mesh)
-            mesh = GetScene()->GetComponent<DynamicNavigationMesh>();
+        NavigationMesh* mesh = GetScene()->GetDerivedComponent<NavigationMesh>();
         if (mesh)
         if (mesh)
         {
         {
             SubscribeToEvent(mesh, E_NAVIGATION_MESH_REBUILT, HANDLER(CrowdManager, HandleNavMeshFullRebuild));
             SubscribeToEvent(mesh, E_NAVIGATION_MESH_REBUILT, HANDLER(CrowdManager, HandleNavMeshFullRebuild));

+ 22 - 8
Source/Urho3D/Navigation/CrowdManager.h

@@ -24,6 +24,12 @@
 
 
 #include "../Scene/Component.h"
 #include "../Scene/Component.h"
 
 
+#ifdef DT_POLYREF64
+typedef uint64_t dtPolyRef;
+#else
+typedef unsigned int dtPolyRef;
+#endif
+
 class dtCrowd;
 class dtCrowd;
 struct dtCrowdAgent;
 struct dtCrowdAgent;
 
 
@@ -69,6 +75,22 @@ public:
     /// Set the cost of an area-type for the specified navigation filter type.
     /// Set the cost of an area-type for the specified navigation filter type.
     void SetAreaCost(unsigned filterTypeID, unsigned areaID, float cost);
     void SetAreaCost(unsigned filterTypeID, unsigned areaID, float cost);
 
 
+    /// 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;
+    /// Find the nearest point on the navigation mesh to a given point using the crowd initialized query extent (based on maxAgentRadius) and the specified crowd filter type.
+    Vector3 FindNearestPoint(const Vector3& point, int filterType, dtPolyRef* nearestRef = 0);
+    /// Try to move along the surface from one point to another using the crowd initialized query extent (based on maxAgentRadius) and the specified crowd filter type.
+    Vector3 MoveAlongSurface(const Vector3& start, const Vector3& end, int filterType, int maxVisited = 3);
+    /// Find a path between world space points using the crowd initialized query extent (based on maxAgentRadius) and the specified crowd filter type. Return non-empty list of points if successful.
+    void FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, int filterType);
+    /// Return a random point on the navigation mesh using the crowd initialized query extent (based on maxAgentRadius) and the specified crowd filter type.
+    Vector3 GetRandomPoint(int filterType, dtPolyRef* randomRef = 0);
+    /// Return a random point on the navigation mesh within a circle using the crowd initialized query extent (based on maxAgentRadius) and the specified crowd filter type. The circle radius is only a guideline and in practice the returned point may be further away.
+    Vector3 GetRandomPointInCircle(const Vector3& center, float radius, int filterType, dtPolyRef* randomRef = 0);
+    /// Return distance to wall from a point using the crowd initialized query extent (based on maxAgentRadius) and the specified crowd filter type. Maximum search radius must be specified.
+    float GetDistanceToWall(const Vector3& point, float radius, int filterType, Vector3* hitPos = 0, Vector3* hitNormal = 0);
+    /// Perform a walkability raycast on the navigation mesh between start and end using the crowd initialized query extent (based on maxAgentRadius) and the specified crowd filter type. Return the point where a wall was hit, or the end point if no walls.
+    Vector3 Raycast(const Vector3& start, const Vector3& end, int filterType, Vector3* hitNormal = 0);
     /// Get the maximum number of agents.
     /// Get the maximum number of agents.
     unsigned GetMaxAgents() const { return maxAgents_; }
     unsigned GetMaxAgents() const { return maxAgents_; }
     /// Get the maximum radius of any agent.
     /// Get the maximum radius of any agent.
@@ -78,9 +100,6 @@ public:
     /// Get the cost of an area-type for the specified navigation filter type.
     /// Get the cost of an area-type for the specified navigation filter type.
     float GetAreaCost(unsigned filterTypeID, unsigned areaID) const;
     float GetAreaCost(unsigned filterTypeID, unsigned areaID) const;
 
 
-    /// 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:
 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.
     /// 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);
     bool CreateCrowd(bool readdCrowdAgents = false);
@@ -89,11 +108,6 @@ protected:
     /// Removes the detour crowd agent.
     /// Removes the detour crowd agent.
     void RemoveAgent(CrowdAgent* agent);
     void RemoveAgent(CrowdAgent* agent);
 
 
-    /// Set the move target for the specified agent.
-    bool SetAgentTarget(CrowdAgent* agent, const Vector3& target);
-    /// Get the closest walkable position.
-    Vector3 GetClosestWalkablePosition(const Vector3& pos) const;
-
 protected:
 protected:
     /// Update the crowd simulation.
     /// Update the crowd simulation.
     void Update(float delta);
     void Update(float delta);

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

@@ -43,6 +43,16 @@ EVENT(E_NAVIGATION_AREA_REBUILT, NavigationAreaRebuilt)
     PARAM(P_BOUNDSMAX, BoundsMax); // Vector3
     PARAM(P_BOUNDSMAX, BoundsMax); // Vector3
 }
 }
 
 
+/// Crowd agent formation.
+EVENT(E_CROWD_AGENT_FORMATION, CrowdAgentFormation)
+{
+    PARAM(P_NODE, Node); // Node pointer
+    PARAM(P_CROWD_AGENT, CrowdAgent); // CrowdAgent pointer
+    PARAM(P_INDEX, Index); // unsigned
+    PARAM(P_SIZE, Size); // unsigned
+    PARAM(P_POSITION, Position); // Vector3 [in/out]
+}
+
 /// Crowd agent has been repositioned.
 /// Crowd agent has been repositioned.
 EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
 EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
 {
 {
@@ -54,7 +64,7 @@ EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
     PARAM(P_TIMESTEP, TimeStep); // float
     PARAM(P_TIMESTEP, TimeStep); // float
 }
 }
 
 
-/// Crowd agent's internal state has become invalidated.
+/// Crowd agent's internal state has become invalidated. This is a special case of CrowdAgentStateChanged event.
 EVENT(E_CROWD_AGENT_FAILURE, CrowdAgentFailure)
 EVENT(E_CROWD_AGENT_FAILURE, CrowdAgentFailure)
 {
 {
     PARAM(P_NODE, Node); // Node pointer
     PARAM(P_NODE, Node); // Node pointer

+ 51 - 28
Source/Urho3D/Navigation/NavigationMesh.cpp

@@ -475,7 +475,7 @@ bool NavigationMesh::Build(const BoundingBox& boundingBox)
     return true;
     return true;
 }
 }
 
 
-Vector3 NavigationMesh::FindNearestPoint(const Vector3& point, const Vector3& extents)
+Vector3 NavigationMesh::FindNearestPoint(const Vector3& point, const Vector3& extents, const dtQueryFilter* filter, dtPolyRef* nearestRef)
 {
 {
     if(!InitializeQuery())
     if(!InitializeQuery())
         return point;
         return point;
@@ -487,14 +487,13 @@ Vector3 NavigationMesh::FindNearestPoint(const Vector3& point, const Vector3& ex
     Vector3 nearestPoint;
     Vector3 nearestPoint;
 
 
     dtPolyRef pointRef;
     dtPolyRef pointRef;
-    navMeshQuery_->findNearestPoly(&localPoint.x_, &extents.x_, queryFilter_, &pointRef, &nearestPoint.x_);
-    if (!pointRef)
-        return point;
-
-    return transform*nearestPoint;
+    if (!nearestRef)
+        nearestRef = &pointRef;
+    navMeshQuery_->findNearestPoly(&localPoint.x_, &extents.x_, filter ? filter : queryFilter_, nearestRef, &nearestPoint.x_);
+    return *nearestRef ? transform * nearestPoint : point;
 }
 }
 
 
-Vector3 NavigationMesh::MoveAlongSurface(const Vector3& start, const Vector3& end, const Vector3& extents, int maxVisited)
+Vector3 NavigationMesh::MoveAlongSurface(const Vector3& start, const Vector3& end, const Vector3& extents, int maxVisited, const dtQueryFilter* filter)
 {
 {
     if (!InitializeQuery())
     if (!InitializeQuery())
         return end;
         return end;
@@ -505,8 +504,9 @@ Vector3 NavigationMesh::MoveAlongSurface(const Vector3& start, const Vector3& en
     Vector3 localStart = inverse * start;
     Vector3 localStart = inverse * start;
     Vector3 localEnd = inverse * end;
     Vector3 localEnd = inverse * end;
 
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
     if (!startRef)
         return end;
         return end;
 
 
@@ -514,12 +514,12 @@ Vector3 NavigationMesh::MoveAlongSurface(const Vector3& start, const Vector3& en
     int visitedCount = 0;
     int visitedCount = 0;
     maxVisited = Max(maxVisited, 0);
     maxVisited = Max(maxVisited, 0);
     PODVector<dtPolyRef> visited(maxVisited);
     PODVector<dtPolyRef> visited(maxVisited);
-    navMeshQuery_->moveAlongSurface(startRef, &localStart.x_, &localEnd.x_, queryFilter_, &resultPos.x_, maxVisited ?
+    navMeshQuery_->moveAlongSurface(startRef, &localStart.x_, &localEnd.x_, queryFilter, &resultPos.x_, maxVisited ?
         &visited[0] : (dtPolyRef*)0, &visitedCount, maxVisited);
         &visited[0] : (dtPolyRef*)0, &visitedCount, maxVisited);
     return transform * resultPos;
     return transform * resultPos;
 }
 }
 
 
-void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, const Vector3& extents)
+void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, const Vector3& extents, const dtQueryFilter* filter)
 {
 {
     PROFILE(FindPath);
     PROFILE(FindPath);
 
 
@@ -535,10 +535,11 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
     Vector3 localStart = inverse * start;
     Vector3 localStart = inverse * start;
     Vector3 localEnd = inverse * end;
     Vector3 localEnd = inverse * end;
 
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
     dtPolyRef startRef;
     dtPolyRef endRef;
     dtPolyRef endRef;
-    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter_, &startRef, 0);
-    navMeshQuery_->findNearestPoly(&localEnd.x_, &extents.x_, queryFilter_, &endRef, 0);
+    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localEnd.x_, &extents.x_, queryFilter, &endRef, 0);
 
 
     if (!startRef || !endRef)
     if (!startRef || !endRef)
         return;
         return;
@@ -546,7 +547,7 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
     int numPolys = 0;
     int numPolys = 0;
     int numPathPoints = 0;
     int numPathPoints = 0;
 
 
-    navMeshQuery_->findPath(startRef, endRef, &localStart.x_, &localEnd.x_, queryFilter_, pathData_->polys_, &numPolys,
+    navMeshQuery_->findPath(startRef, endRef, &localStart.x_, &localEnd.x_, queryFilter, pathData_->polys_, &numPolys,
         MAX_POLYS);
         MAX_POLYS);
     if (!numPolys)
     if (!numPolys)
         return;
         return;
@@ -565,7 +566,7 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
         dest.Push(transform * pathData_->pathPoints_[i]);
         dest.Push(transform * pathData_->pathPoints_[i]);
 }
 }
 
 
-Vector3 NavigationMesh::GetRandomPoint()
+Vector3 NavigationMesh::GetRandomPoint(const dtQueryFilter* filter, dtPolyRef* randomRef)
 {
 {
     if (!InitializeQuery())
     if (!InitializeQuery())
         return Vector3::ZERO;
         return Vector3::ZERO;
@@ -573,13 +574,16 @@ Vector3 NavigationMesh::GetRandomPoint()
     dtPolyRef polyRef;
     dtPolyRef polyRef;
     Vector3 point(Vector3::ZERO);
     Vector3 point(Vector3::ZERO);
 
 
-    navMeshQuery_->findRandomPoint(queryFilter_, Random, &polyRef, &point.x_);
+    navMeshQuery_->findRandomPoint(filter ? filter : queryFilter_, Random, randomRef ? randomRef : &polyRef, &point.x_);
 
 
     return node_->GetWorldTransform() * point;
     return node_->GetWorldTransform() * point;
 }
 }
 
 
-Vector3 NavigationMesh::GetRandomPointInCircle(const Vector3& center, float radius, const Vector3& extents)
+Vector3 NavigationMesh::GetRandomPointInCircle(const Vector3& center, float radius, const Vector3& extents, const dtQueryFilter* filter, dtPolyRef* randomRef)
 {
 {
+    if (randomRef)
+        *randomRef = 0;
+
     if (!InitializeQuery())
     if (!InitializeQuery())
         return center;
         return center;
 
 
@@ -587,21 +591,29 @@ Vector3 NavigationMesh::GetRandomPointInCircle(const Vector3& center, float radi
     Matrix3x4 inverse = transform.Inverse();
     Matrix3x4 inverse = transform.Inverse();
     Vector3 localCenter = inverse * center;
     Vector3 localCenter = inverse * center;
 
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localCenter.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localCenter.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
     if (!startRef)
         return center;
         return center;
 
 
     dtPolyRef polyRef;
     dtPolyRef polyRef;
+    if (!randomRef)
+        randomRef = &polyRef;
     Vector3 point(localCenter);
     Vector3 point(localCenter);
 
 
-    navMeshQuery_->findRandomPointAroundCircle(startRef, &localCenter.x_, radius, queryFilter_, Random, &polyRef, &point.x_);
+    navMeshQuery_->findRandomPointAroundCircle(startRef, &localCenter.x_, radius, queryFilter, Random, randomRef, &point.x_);
 
 
     return transform * point;
     return transform * point;
 }
 }
 
 
-float NavigationMesh::GetDistanceToWall(const Vector3& point, float radius, const Vector3& extents)
+float NavigationMesh::GetDistanceToWall(const Vector3& point, float radius, const Vector3& extents, const dtQueryFilter* filter, Vector3* hitPos, Vector3* hitNormal)
 {
 {
+    if (hitPos)
+        *hitPos = Vector3::ZERO;
+    if (hitNormal)
+        *hitNormal = Vector3::DOWN;
+
     if (!InitializeQuery())
     if (!InitializeQuery())
         return radius;
         return radius;
 
 
@@ -609,21 +621,29 @@ float NavigationMesh::GetDistanceToWall(const Vector3& point, float radius, cons
     Matrix3x4 inverse = transform.Inverse();
     Matrix3x4 inverse = transform.Inverse();
     Vector3 localPoint = inverse * point;
     Vector3 localPoint = inverse * point;
 
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localPoint.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localPoint.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
     if (!startRef)
         return radius;
         return radius;
 
 
     float hitDist = radius;
     float hitDist = radius;
-    Vector3 hitPos;
-    Vector3 hitNormal;
-
-    navMeshQuery_->findDistanceToWall(startRef, &localPoint.x_, radius, queryFilter_, &hitDist, &hitPos.x_, &hitNormal.x_);
+    Vector3 pos;
+    if (!hitPos)
+        hitPos = &pos;
+    Vector3 normal;
+    if (!hitNormal)
+        hitNormal = &normal;
+
+    navMeshQuery_->findDistanceToWall(startRef, &localPoint.x_, radius, queryFilter, &hitDist, &hitPos->x_, &hitNormal->x_);
     return hitDist;
     return hitDist;
 }
 }
 
 
-Vector3 NavigationMesh::Raycast(const Vector3& start, const Vector3& end, const Vector3& extents)
+Vector3 NavigationMesh::Raycast(const Vector3& start, const Vector3& end, const Vector3& extents, const dtQueryFilter* filter, Vector3* hitNormal)
 {
 {
+    if (hitNormal)
+        *hitNormal = Vector3::DOWN;
+
     if (!InitializeQuery())
     if (!InitializeQuery())
         return end;
         return end;
 
 
@@ -633,16 +653,19 @@ Vector3 NavigationMesh::Raycast(const Vector3& start, const Vector3& end, const
     Vector3 localStart = inverse * start;
     Vector3 localStart = inverse * start;
     Vector3 localEnd = inverse * end;
     Vector3 localEnd = inverse * end;
 
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
     if (!startRef)
         return end;
         return end;
 
 
-    Vector3 localHitNormal;
+    Vector3 normal;
+    if (!hitNormal)
+        hitNormal = &normal;
     float t;
     float t;
     int numPolys;
     int numPolys;
 
 
-    navMeshQuery_->raycast(startRef, &localStart.x_, &localEnd.x_, queryFilter_, &t, &localHitNormal.x_, pathData_->polys_, &numPolys, MAX_POLYS);
+    navMeshQuery_->raycast(startRef, &localStart.x_, &localEnd.x_, queryFilter, &t, &hitNormal->x_, pathData_->polys_, &numPolys, MAX_POLYS);
     if (t == FLT_MAX)
     if (t == FLT_MAX)
         t = 1.0f;
         t = 1.0f;
 
 

+ 15 - 8
Source/Urho3D/Navigation/NavigationMesh.h

@@ -28,6 +28,12 @@
 #include "../Container/HashSet.h"
 #include "../Container/HashSet.h"
 #include "../Math/Matrix3x4.h"
 #include "../Math/Matrix3x4.h"
 
 
+#ifdef DT_POLYREF64
+typedef uint64_t dtPolyRef;
+#else
+typedef unsigned int dtPolyRef;
+#endif
+
 class dtNavMesh;
 class dtNavMesh;
 class dtNavMeshQuery;
 class dtNavMeshQuery;
 class dtQueryFilter;
 class dtQueryFilter;
@@ -71,6 +77,7 @@ struct NavigationGeometryInfo
 class URHO3D_API NavigationMesh : public Component
 class URHO3D_API NavigationMesh : public Component
 {
 {
     OBJECT(NavigationMesh);
     OBJECT(NavigationMesh);
+
     friend class CrowdManager;
     friend class CrowdManager;
 
 
 public:
 public:
@@ -118,20 +125,20 @@ public:
     virtual bool Build();
     virtual bool Build();
     /// Rebuild part of the navigation mesh contained by the world-space bounding box. Return true if successful.
     /// Rebuild part of the navigation mesh contained by the world-space bounding box. Return true if successful.
     virtual bool Build(const BoundingBox& boundingBox);
     virtual bool Build(const BoundingBox& boundingBox);
-    /// Find the nearest point on the navigation mesh to a given point. Extens specifies how far out from the specified point to check along each axis.
-    Vector3 FindNearestPoint(const Vector3& point, const Vector3& extents=Vector3::ONE);
+    /// Find the nearest point on the navigation mesh to a given point. Extents specifies how far out from the specified point to check along each axis.
+    Vector3 FindNearestPoint(const Vector3& point, const Vector3& extents = Vector3::ONE, const dtQueryFilter* filter = 0, dtPolyRef* nearestRef = 0);
     /// Try to move along the surface from one point to another.
     /// Try to move along the surface from one point to another.
-    Vector3 MoveAlongSurface(const Vector3& start, const Vector3& end, const Vector3& extents=Vector3::ONE, int maxVisited=3);
+    Vector3 MoveAlongSurface(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE, int maxVisited = 3, const dtQueryFilter* filter = 0);
     /// Find a path between world space points. Return non-empty list of points if successful. Extents specifies how far off the navigation mesh the points can be.
     /// Find a path between world space points. Return non-empty list of points if successful. Extents specifies how far off the navigation mesh the points can be.
-    void FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE);
+    void FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE, const dtQueryFilter* filter = 0);
     /// Return a random point on the navigation mesh.
     /// Return a random point on the navigation mesh.
-    Vector3 GetRandomPoint();
+    Vector3 GetRandomPoint(const dtQueryFilter* filter = 0, dtPolyRef* randomRef = 0);
     /// Return a random point on the navigation mesh within a circle. The circle radius is only a guideline and in practice the returned point may be further away.
     /// Return a random point on the navigation mesh within a circle. The circle radius is only a guideline and in practice the returned point may be further away.
-    Vector3 GetRandomPointInCircle(const Vector3& center, float radius, const Vector3& extents = Vector3::ONE);
+    Vector3 GetRandomPointInCircle(const Vector3& center, float radius, const Vector3& extents = Vector3::ONE, const dtQueryFilter* filter = 0, dtPolyRef* randomRef = 0);
     /// Return distance to wall from a point. Maximum search radius must be specified.
     /// Return distance to wall from a point. Maximum search radius must be specified.
-    float GetDistanceToWall(const Vector3& point, float radius, const Vector3& extents = Vector3::ONE);
+    float GetDistanceToWall(const Vector3& point, float radius, const Vector3& extents = Vector3::ONE, const dtQueryFilter* filter = 0, Vector3* hitPos = 0, Vector3* hitNormal = 0);
     /// Perform a walkability raycast on the navigation mesh between start and end and return the point where a wall was hit, or the end point if no walls.
     /// Perform a walkability raycast on the navigation mesh between start and end and return the point where a wall was hit, or the end point if no walls.
-    Vector3 Raycast(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE);
+    Vector3 Raycast(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE, const dtQueryFilter* filter = 0, Vector3* hitNormal = 0);
     /// Add debug geometry to the debug renderer.
     /// Add debug geometry to the debug renderer.
     void DrawDebugGeometry(bool depthTest);
     void DrawDebugGeometry(bool depthTest);
 
 

+ 7 - 1
Source/Urho3D/Script/NavigationAPI.cpp

@@ -70,7 +70,7 @@ template<class T> static void RegisterNavMeshBase(asIScriptEngine* engine, const
     engine->RegisterObjectMethod(name, "void SetAreaCost(uint, float)", asMETHOD(T, SetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "void SetAreaCost(uint, float)", asMETHOD(T, SetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "float GetAreaCost(uint) const", asMETHOD(T, GetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "float GetAreaCost(uint) const", asMETHOD(T, GetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "Vector3 FindNearestPoint(const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(T, FindNearestPoint), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "Vector3 FindNearestPoint(const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(T, FindNearestPoint), asCALL_THISCALL);
-    engine->RegisterObjectMethod(name, "Vector3 MoveAlongSurface(const Vector3&in, const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0), uint = 3)", asMETHOD(T, MoveAlongSurface), asCALL_THISCALL);
+    engine->RegisterObjectMethod(name, "Vector3 MoveAlongSurface(const Vector3&in, const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0), uint maxVisited = 3)", asMETHOD(T, MoveAlongSurface), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "Vector3 GetRandomPoint()", asMETHOD(T, GetRandomPoint), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "Vector3 GetRandomPoint()", asMETHOD(T, GetRandomPoint), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "Vector3 GetRandomPointInCircle(const Vector3&in, float, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(T, GetRandomPointInCircle), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "Vector3 GetRandomPointInCircle(const Vector3&in, float, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(T, GetRandomPointInCircle), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "float GetDistanceToWall(const Vector3&in, float, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(T, GetDistanceToWall), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "float GetDistanceToWall(const Vector3&in, float, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(T, GetDistanceToWall), asCALL_THISCALL);
@@ -183,6 +183,12 @@ void RegisterCrowdManager(asIScriptEngine* engine)
     engine->RegisterObjectMethod("CrowdManager", "void SetCrowdVelocity(const Vector3&in, Node@+ node = null)", asMETHOD(CrowdManager, SetCrowdVelocity), 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 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", "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);
+    engine->RegisterObjectMethod("CrowdManager", "Vector3 GetRandomPoint(int)", asMETHOD(CrowdManager, GetRandomPoint), asCALL_THISCALL);
+    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", "int get_maxAgents() const", asMETHOD(CrowdManager, GetMaxAgents), 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", "void set_maxAgents(int)", asMETHOD(CrowdManager, SetMaxAgents), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdManager", "float get_maxAgentRadius() const", asMETHOD(CrowdManager, GetMaxAgentRadius), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdManager", "float get_maxAgentRadius() const", asMETHOD(CrowdManager, GetMaxAgentRadius), asCALL_THISCALL);

+ 4 - 2
bin/Data/LuaScripts/15_Navigation.lua

@@ -111,8 +111,10 @@ function CreateScene()
     local camera = cameraNode:CreateComponent("Camera")
     local camera = cameraNode:CreateComponent("Camera")
     camera.farClip = 300.0
     camera.farClip = 300.0
 
 
-    -- Set an initial position for the camera scene node above the plane
-    cameraNode.position = Vector3(0.0, 5.0, 0.0)
+    -- Set an initial position for the camera scene node above the plane and looking down
+    cameraNode.position = Vector3(0.0, 50.0, 0.0)
+    pitch = 80.0
+    cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
 end
 end
 
 
 function CreateUI()
 function CreateUI()

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

@@ -117,7 +117,7 @@ function CreateScene()
     CreateMovingBarrels(navMesh)
     CreateMovingBarrels(navMesh)
 
 
     -- Create Jack node as crowd agent
     -- Create Jack node as crowd agent
-    SpawnJack(Vector3(-5, 0, 20))
+    SpawnJack(Vector3(-5, 0, 20), scene_:CreateChild("Jacks"))
 
 
     -- Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     -- Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     -- the scene, because we want it to be unaffected by scene load / save
     -- the scene, because we want it to be unaffected by scene load / save
@@ -179,10 +179,13 @@ function SubscribeToEvents()
 
 
     -- Subscribe HandleCrowdAgentReposition() function for controlling the animation
     -- Subscribe HandleCrowdAgentReposition() function for controlling the animation
     SubscribeToEvent("CrowdAgentReposition", "HandleCrowdAgentReposition")
     SubscribeToEvent("CrowdAgentReposition", "HandleCrowdAgentReposition")
+
+    -- Subscribe HandleCrowdAgentFormation() function for positioning agent into a formation
+    SubscribeToEvent("CrowdAgentFormation", "HandleCrowdAgentFormation")
 end
 end
 
 
-function SpawnJack(pos)
-    local jackNode = scene_:CreateChild("Jack")
+function SpawnJack(pos, jackGroup)
+    local jackNode = jackGroup:CreateChild("Jack")
     jackNode.position = pos
     jackNode.position = pos
     local modelObject = jackNode:CreateComponent("AnimatedModel")
     local modelObject = jackNode:CreateComponent("AnimatedModel")
     modelObject.model = cache:GetResource("Model", "Models/Jack.mdl")
     modelObject.model = cache:GetResource("Model", "Models/Jack.mdl")
@@ -246,6 +249,7 @@ function CreateMovingBarrels(navMesh)
         local agent = clone:CreateComponent("CrowdAgent")
         local agent = clone:CreateComponent("CrowdAgent")
         agent.radius = clone.scale.x * 0.5
         agent.radius = clone.scale.x * 0.5
         agent.height = size
         agent.height = size
+        agent.navigationQuality = NAVIGATIONQUALITY_LOW
     end
     end
     barrel:Remove()
     barrel:Remove()
 end
 end
@@ -256,13 +260,13 @@ function SetPathPoint(spawning)
     if hitDrawable then
     if hitDrawable then
         local navMesh = scene_:GetComponent("DynamicNavigationMesh")
         local navMesh = scene_:GetComponent("DynamicNavigationMesh")
         local pathPos = navMesh:FindNearestPoint(hitPos, Vector3.ONE)
         local pathPos = navMesh:FindNearestPoint(hitPos, Vector3.ONE)
-
+        local jackGroup = scene_:GetChild("Jacks")
         if spawning then
         if spawning then
             -- Spawn a jack at the target position
             -- Spawn a jack at the target position
-            SpawnJack(pathPos)
+            SpawnJack(pathPos, jackGroup)
         else
         else
             -- Set crowd agents target position
             -- Set crowd agents target position
-            scene_:GetComponent("CrowdManager"):SetCrowdTarget(pathPos)
+            scene_:GetComponent("CrowdManager"):SetCrowdTarget(pathPos, jackGroup)
         end
         end
     end
     end
 end
 end
@@ -426,6 +430,19 @@ function HandleCrowdAgentReposition(eventType, eventData)
     end
     end
 end
 end
 
 
+function HandleCrowdAgentFormation(eventType, eventData)
+    local index = eventData:GetUInt("Index")
+    local size = eventData:GetUInt("Size")
+    local position = eventData:GetVector3("Position")
+
+    -- The first agent will always move to the exact position, all other agents will select a random point nearby
+    if index > 0 then
+        local crowdManager = GetEventSender()
+        local agent = eventData:GetPtr("CrowdAgent", "CrowdAgent")
+        eventData:SetVector3("Position", crowdManager:GetRandomPointInCircle(position, agent.radius, agent.filterType))
+    end
+end
+
 -- Create XML patch instructions for screen joystick layout specific to this sample app
 -- Create XML patch instructions for screen joystick layout specific to this sample app
 function GetScreenJoystickPatchString()
 function GetScreenJoystickPatchString()
     return
     return

+ 4 - 2
bin/Data/Scripts/15_Navigation.as

@@ -114,8 +114,10 @@ void CreateScene()
     Camera@ camera = cameraNode.CreateComponent("Camera");
     Camera@ camera = cameraNode.CreateComponent("Camera");
     camera.farClip = 300.0f;
     camera.farClip = 300.0f;
 
 
-    // Set an initial position for the camera scene node above the plane
-    cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
+    // Set an initial position for the camera scene node above the plane and looking down
+    cameraNode.position = Vector3(0.0f, 50.0f, 0.0f);
+    pitch = 80.0f;
+    cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
 }
 }
 
 
 void CreateUI()
 void CreateUI()

+ 25 - 5
bin/Data/Scripts/39_CrowdNavigation.as

@@ -119,7 +119,7 @@ void CreateScene()
     CreateMovingBarrels(navMesh);
     CreateMovingBarrels(navMesh);
 
 
     // Create Jack node as crowd agent
     // Create Jack node as crowd agent
-    SpawnJack(Vector3(-5.0f, 0, 20.0f));
+    SpawnJack(Vector3(-5.0f, 0, 20.0f), scene_.CreateChild("Jacks"));
 
 
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     // the scene, because we want it to be unaffected by scene load / save
     // the scene, because we want it to be unaffected by scene load / save
@@ -185,6 +185,9 @@ void SubscribeToEvents()
 
 
     // Subscribe HandleCrowdAgentReposition() function for controlling the animation
     // Subscribe HandleCrowdAgentReposition() function for controlling the animation
     SubscribeToEvent("CrowdAgentReposition", "HandleCrowdAgentReposition");
     SubscribeToEvent("CrowdAgentReposition", "HandleCrowdAgentReposition");
+
+    // Subscribe HandleCrowdAgentFormation() function for positioning agent into a formation
+    SubscribeToEvent("CrowdAgentFormation", "HandleCrowdAgentFormation");
 }
 }
 
 
 void CreateMushroom(const Vector3& pos)
 void CreateMushroom(const Vector3& pos)
@@ -204,9 +207,9 @@ void CreateMushroom(const Vector3& pos)
     obstacle.height = mushroomNode.scale.y;
     obstacle.height = mushroomNode.scale.y;
 }
 }
 
 
-void SpawnJack(const Vector3& pos)
+void SpawnJack(const Vector3& pos, Node@ jackGroup)
 {
 {
-    Node@ jackNode = scene_.CreateChild("Jack");
+    Node@ jackNode = jackGroup.CreateChild("Jack");
     jackNode.position = pos;
     jackNode.position = pos;
     AnimatedModel@ modelObject = jackNode.CreateComponent("AnimatedModel");
     AnimatedModel@ modelObject = jackNode.CreateComponent("AnimatedModel");
     modelObject.model = cache.GetResource("Model", "Models/Jack.mdl");
     modelObject.model = cache.GetResource("Model", "Models/Jack.mdl");
@@ -260,6 +263,7 @@ void CreateMovingBarrels(DynamicNavigationMesh@ navMesh)
         CrowdAgent@ agent = clone.CreateComponent("CrowdAgent");
         CrowdAgent@ agent = clone.CreateComponent("CrowdAgent");
         agent.radius = clone.scale.x * 0.5f;
         agent.radius = clone.scale.x * 0.5f;
         agent.height = size;
         agent.height = size;
+        agent.navigationQuality = NAVIGATIONQUALITY_LOW;
     }
     }
     barrel.Remove();
     barrel.Remove();
 }
 }
@@ -273,12 +277,13 @@ void SetPathPoint(bool spawning)
     {
     {
         DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
         DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
         Vector3 pathPos = navMesh.FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
         Vector3 pathPos = navMesh.FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
+        Node@ jackGroup = scene_.GetChild("Jacks");
         if (spawning)
         if (spawning)
             // Spawn a jack at the target position
             // Spawn a jack at the target position
-            SpawnJack(pathPos);
+            SpawnJack(pathPos, jackGroup);
         else
         else
             // Set crowd agents target position
             // Set crowd agents target position
-            cast<CrowdManager>(scene_.GetComponent("CrowdManager")).SetCrowdTarget(pathPos);
+            cast<CrowdManager>(scene_.GetComponent("CrowdManager")).SetCrowdTarget(pathPos, jackGroup);
     }
     }
 }
 }
 
 
@@ -429,6 +434,21 @@ void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData)
     }
     }
 }
 }
 
 
+void HandleCrowdAgentFormation(StringHash eventType, VariantMap& eventData)
+{
+    uint index = eventData["Index"].GetUInt();
+    uint size = eventData["Size"].GetUInt();
+    Vector3 position = eventData["Position"].GetVector3();
+
+    // The first agent will always move to the exact position, all other agents will select a random point nearby
+    if (index > 0)
+    {
+        CrowdManager@ crowdManager =GetEventSender();
+        CrowdAgent@ agent = eventData["CrowdAgent"].GetPtr();
+        eventData["Position"] = crowdManager.GetRandomPointInCircle(position, agent.radius, agent.filterType);
+    }
+}
+
 void HandleCrowdAgentReposition(StringHash eventType, VariantMap& eventData)
 void HandleCrowdAgentReposition(StringHash eventType, VariantMap& eventData)
 {
 {
     const String WALKING_ANI = "Models/Jack_Walk.ani";
     const String WALKING_ANI = "Models/Jack_Walk.ani";