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 năm trước cách đây
mục cha
commit
c9bc4cbae3

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

@@ -160,8 +160,10 @@ void Navigation::CreateScene()
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     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()

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

@@ -174,7 +174,7 @@ void CrowdNavigation::CreateScene()
     CreateMovingBarrels(navMesh);
 
     // 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
     // we want it to be unaffected by scene load / save
@@ -248,12 +248,15 @@ void CrowdNavigation::SubscribeToEvents()
 
     // Subscribe HandleCrowdAgentReposition() function for controlling the animation
     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>();
-    SharedPtr<Node> jackNode(scene_->CreateChild("Jack"));
+    SharedPtr<Node> jackNode(jackGroup->CreateChild("Jack"));
     jackNode->SetPosition(pos);
     AnimatedModel* modelObject = jackNode->CreateComponent<AnimatedModel>();
     modelObject->SetModel(cache->GetResource<Model>("Models/Jack.mdl"));
@@ -327,6 +330,7 @@ void CrowdNavigation::CreateMovingBarrels(DynamicNavigationMesh* navMesh)
         CrowdAgent* agent = clone->CreateComponent<CrowdAgent>();
         agent->SetRadius(clone->GetScale().x_ * 0.5f);
         agent->SetHeight(size);
+        agent->SetNavigationQuality(NAVIGATIONQUALITY_LOW);
     }
     barrel->Remove();
 }
@@ -340,12 +344,13 @@ void CrowdNavigation::SetPathPoint(bool spawning)
     {
         DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
         Vector3 pathPos = navMesh->FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
+        Node* jackGroup = scene_->GetChild("Jacks");
         if (spawning)
             // Spawn a jack at the target position
-            SpawnJack(pathPos);
+            SpawnJack(pathPos, jackGroup);
         else
             // 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);
     }
 }
+
+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.
     void AddOrRemoveObject();
     /// Create a "Jack" object at position.
-    void SpawnJack(const Vector3& pos);
+    void SpawnJack(const Vector3& pos, Node* jackGroup);
     /// Create a mushroom object at position.
     void CreateMushroom(const Vector3& pos);
     /// Create an off-mesh connection for each box to make it climbable.
@@ -160,6 +160,8 @@ private:
     void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData);
     /// Handle crowd agent reposition.
     void HandleCrowdAgentReposition(StringHash eventType, VariantMap& eventData);
+    /// Handle crowd agent formation.
+    void HandleCrowdAgentFormation(StringHash eventType, VariantMap& eventData);
 
     /// Flag for drawing debug geometry.
     bool drawDebug_;

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

@@ -201,7 +201,7 @@ struct dtCrowdAgentDebugInfo
 };
 
 // Urho3D: Add update callback support
-/// Type for the updat callback.
+/// Type for the update callback.
 typedef void (*dtUpdateCallback)(dtCrowdAgent* ag, float dt);
 
 /// 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
 {
     NAVIGATIONQUALITY_LOW = 0,
-    NAVIGATIONQUALITY_MEDIUM = 1,
-    NAVIGATIONQUALITY_HIGH = 2
+    NAVIGATIONQUALITY_MEDIUM,
+    NAVIGATIONQUALITY_HIGH
 };
 
 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);
 
     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;
     float GetMaxAgentRadius() const;
     NavigationMesh* GetNavigationMesh() const;
@@ -22,3 +29,13 @@ class CrowdManager : public Component
     tolua_property__get_set float maxAgentRadius;
     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;
 };
 
-
 class NavigationMesh : public Component
 {
     void SetTileSize(int size);
@@ -39,12 +38,9 @@ class NavigationMesh : public Component
     void SetDrawNavAreas(bool enable);
 
     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);
-
     Vector3 GetRandomPoint();
-
     Vector3 GetRandomPointInCircle(const Vector3& center, 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);
@@ -104,4 +100,4 @@ const PODVector<Vector3>& NavigationMeshFindPath(NavigationMesh* navMesh, const
     navMesh->FindPath(dest, start, end, extents);
     return dest;
 }
-$}
+$}

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

@@ -123,10 +123,13 @@ void CrowdAgent::RegisterObject(Context* context)
 
 void CrowdAgent::ApplyAttributes()
 {
+    // Values from Editor, saved-file, or network must be checked before applying
     maxAccel_ = Max(0.f, maxAccel_);
     maxSpeed_ = Max(0.f, maxSpeed_);
     radius_ = Max(0.f, radius_);
     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();
 
@@ -303,13 +306,15 @@ int CrowdAgent::AddAgentToCrowd(bool force)
         // Agent created, but initial state is invalid and needs to be addressed
         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);
 
             // Reevaluate states as handling of event may have resulted in changes
@@ -344,7 +349,11 @@ void CrowdAgent::SetTargetPosition(const Vector3& position)
         if (!IsInCrowd())
             AddAgentToCrowd();
         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;
 
-            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);
 
             if (updateNodePosition_)
@@ -553,25 +564,27 @@ void CrowdAgent::OnCrowdUpdate(dtCrowdAgent* ag, float dt)
         CrowdAgentState newAgentState = GetAgentState();
         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);
 
             // Send a failure event if either state is a failed status
             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);
             }
 

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

@@ -80,6 +80,7 @@ void CrowdManager::RegisterObject(Context* context)
 
 void CrowdManager::ApplyAttributes()
 {
+    // Values from Editor, saved-file, or network must be checked before applying
     maxAgents_ = Max(1, maxAgents_);
     maxAgentRadius_ = Max(0.f, maxAgentRadius_);
 
@@ -157,13 +158,21 @@ void CrowdManager::SetCrowdTarget(const Vector3& position, Node* node)
     Vector3 moveTarget(position);
     for (unsigned i = 0; i < agents.Size(); ++i)
     {
-        if (agents[i]->GetMaxAccel() > 0.f)
-        {
-            agents[i]->SetTargetPosition(moveTarget);
-            // FIXME: Should reimplement this using event callback, i.e. it should be application-specific to decide what is the desired crowd formation when they reach the target
-            if (navigationMesh_)
-                moveTarget = navigationMesh_->FindNearestPoint(position + Vector3(Random(-4.5f, 4.5f), 0.0f, Random(-4.5f, 4.5f)), Vector3(1.0f, 1.0f, 1.0f));
-        }
+        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
     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)
@@ -187,10 +193,7 @@ void CrowdManager::ResetCrowdTarget(Node* node)
 
     PODVector<CrowdAgent*> agents = GetAgents(node, true);
     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)
@@ -254,12 +257,58 @@ PODVector<CrowdAgent*> CrowdManager::GetAgents(Node* node, bool inCrowdFilter) c
     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)
 {
-    if (!navigationMesh_ || !navigationMesh_->navMesh_)
+    if (!navigationMesh_ || !navigationMesh_->InitializeQuery())
         return false;
-    if (!navigationMesh_->navMeshQuery_)
-        navigationMesh_->InitializeQuery();
 
     if (crowd_)
         dtFreeCrowd(crowd_);
@@ -310,7 +359,7 @@ bool CrowdManager::CreateCrowd(bool readdCrowdAgents)
         for (unsigned i = 0; i < agents.Size(); ++i)
         {
             // 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);
                 break;
@@ -331,17 +380,7 @@ int CrowdManager::AddAgent(CrowdAgent* agent, const Vector3& pos)
         agent->radius_ = navigationMesh_->GetAgentRadius();
     if (agent->height_ == 0.f)
         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)
@@ -354,39 +393,6 @@ void CrowdManager::RemoveAgent(CrowdAgent* agent)
     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)
 {
     if (!crowd_ || !navigationMesh_)
@@ -413,9 +419,7 @@ void CrowdManager::HandleNavMeshFullRebuild(StringHash eventType, VariantMap& ev
     using namespace NavigationMeshRebuilt;
 
     // The mesh being rebuilt may not have existed before
-    NavigationMesh* navMesh = static_cast<NavigationMesh*>(eventData[P_MESH].GetPtr());
-    if (!navigationMesh_ || !crowd_)
-        SetNavigationMesh(navMesh);
+    SetNavigationMesh(static_cast<NavigationMesh*>(eventData[P_MESH].GetPtr()));
 }
 
 void DetourCrowdManager::OnSceneSet(Scene* scene)
@@ -425,9 +429,7 @@ void DetourCrowdManager::OnSceneSet(Scene* scene)
     if (scene)
     {
         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)
         {
             SubscribeToEvent(mesh, E_NAVIGATION_MESH_REBUILT, HANDLER(CrowdManager, HandleNavMeshFullRebuild));

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

@@ -24,6 +24,12 @@
 
 #include "../Scene/Component.h"
 
+#ifdef DT_POLYREF64
+typedef uint64_t dtPolyRef;
+#else
+typedef unsigned int dtPolyRef;
+#endif
+
 class dtCrowd;
 struct dtCrowdAgent;
 
@@ -69,6 +75,22 @@ public:
     /// Set the cost of an area-type for the specified navigation filter type.
     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.
     unsigned GetMaxAgents() const { return maxAgents_; }
     /// 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.
     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:
     /// 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);
@@ -89,11 +108,6 @@ protected:
     /// Removes the detour crowd 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:
     /// Update the crowd simulation.
     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
 }
 
+/// 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.
 EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
 {
@@ -54,7 +64,7 @@ EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
     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)
 {
     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;
 }
 
-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())
         return point;
@@ -487,14 +487,13 @@ Vector3 NavigationMesh::FindNearestPoint(const Vector3& point, const Vector3& ex
     Vector3 nearestPoint;
 
     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())
         return end;
@@ -505,8 +504,9 @@ Vector3 NavigationMesh::MoveAlongSurface(const Vector3& start, const Vector3& en
     Vector3 localStart = inverse * start;
     Vector3 localEnd = inverse * end;
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
         return end;
 
@@ -514,12 +514,12 @@ Vector3 NavigationMesh::MoveAlongSurface(const Vector3& start, const Vector3& en
     int visitedCount = 0;
     maxVisited = Max(maxVisited, 0);
     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);
     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);
 
@@ -535,10 +535,11 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
     Vector3 localStart = inverse * start;
     Vector3 localEnd = inverse * end;
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
     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)
         return;
@@ -546,7 +547,7 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
     int numPolys = 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);
     if (!numPolys)
         return;
@@ -565,7 +566,7 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
         dest.Push(transform * pathData_->pathPoints_[i]);
 }
 
-Vector3 NavigationMesh::GetRandomPoint()
+Vector3 NavigationMesh::GetRandomPoint(const dtQueryFilter* filter, dtPolyRef* randomRef)
 {
     if (!InitializeQuery())
         return Vector3::ZERO;
@@ -573,13 +574,16 @@ Vector3 NavigationMesh::GetRandomPoint()
     dtPolyRef polyRef;
     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;
 }
 
-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())
         return center;
 
@@ -587,21 +591,29 @@ Vector3 NavigationMesh::GetRandomPointInCircle(const Vector3& center, float radi
     Matrix3x4 inverse = transform.Inverse();
     Vector3 localCenter = inverse * center;
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localCenter.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localCenter.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
         return center;
 
     dtPolyRef polyRef;
+    if (!randomRef)
+        randomRef = &polyRef;
     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;
 }
 
-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())
         return radius;
 
@@ -609,21 +621,29 @@ float NavigationMesh::GetDistanceToWall(const Vector3& point, float radius, cons
     Matrix3x4 inverse = transform.Inverse();
     Vector3 localPoint = inverse * point;
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localPoint.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localPoint.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
         return 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;
 }
 
-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())
         return end;
 
@@ -633,16 +653,19 @@ Vector3 NavigationMesh::Raycast(const Vector3& start, const Vector3& end, const
     Vector3 localStart = inverse * start;
     Vector3 localEnd = inverse * end;
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
         return end;
 
-    Vector3 localHitNormal;
+    Vector3 normal;
+    if (!hitNormal)
+        hitNormal = &normal;
     float t;
     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)
         t = 1.0f;
 

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

@@ -28,6 +28,12 @@
 #include "../Container/HashSet.h"
 #include "../Math/Matrix3x4.h"
 
+#ifdef DT_POLYREF64
+typedef uint64_t dtPolyRef;
+#else
+typedef unsigned int dtPolyRef;
+#endif
+
 class dtNavMesh;
 class dtNavMeshQuery;
 class dtQueryFilter;
@@ -71,6 +77,7 @@ struct NavigationGeometryInfo
 class URHO3D_API NavigationMesh : public Component
 {
     OBJECT(NavigationMesh);
+
     friend class CrowdManager;
 
 public:
@@ -118,20 +125,20 @@ public:
     virtual bool Build();
     /// Rebuild part of the navigation mesh contained by the world-space bounding box. Return true if successful.
     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.
-    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.
-    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.
-    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.
-    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.
-    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.
-    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.
     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, "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 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 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);
@@ -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 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", "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", "void set_maxAgents(int)", asMETHOD(CrowdManager, SetMaxAgents), 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")
     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
 
 function CreateUI()

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

@@ -117,7 +117,7 @@ function CreateScene()
     CreateMovingBarrels(navMesh)
 
     -- 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
     -- 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
     SubscribeToEvent("CrowdAgentReposition", "HandleCrowdAgentReposition")
+
+    -- Subscribe HandleCrowdAgentFormation() function for positioning agent into a formation
+    SubscribeToEvent("CrowdAgentFormation", "HandleCrowdAgentFormation")
 end
 
-function SpawnJack(pos)
-    local jackNode = scene_:CreateChild("Jack")
+function SpawnJack(pos, jackGroup)
+    local jackNode = jackGroup:CreateChild("Jack")
     jackNode.position = pos
     local modelObject = jackNode:CreateComponent("AnimatedModel")
     modelObject.model = cache:GetResource("Model", "Models/Jack.mdl")
@@ -246,6 +249,7 @@ function CreateMovingBarrels(navMesh)
         local agent = clone:CreateComponent("CrowdAgent")
         agent.radius = clone.scale.x * 0.5
         agent.height = size
+        agent.navigationQuality = NAVIGATIONQUALITY_LOW
     end
     barrel:Remove()
 end
@@ -256,13 +260,13 @@ function SetPathPoint(spawning)
     if hitDrawable then
         local navMesh = scene_:GetComponent("DynamicNavigationMesh")
         local pathPos = navMesh:FindNearestPoint(hitPos, Vector3.ONE)
-
+        local jackGroup = scene_:GetChild("Jacks")
         if spawning then
             -- Spawn a jack at the target position
-            SpawnJack(pathPos)
+            SpawnJack(pathPos, jackGroup)
         else
             -- Set crowd agents target position
-            scene_:GetComponent("CrowdManager"):SetCrowdTarget(pathPos)
+            scene_:GetComponent("CrowdManager"):SetCrowdTarget(pathPos, jackGroup)
         end
     end
 end
@@ -426,6 +430,19 @@ function HandleCrowdAgentReposition(eventType, eventData)
     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
 function GetScreenJoystickPatchString()
     return

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

@@ -114,8 +114,10 @@ void CreateScene()
     Camera@ camera = cameraNode.CreateComponent("Camera");
     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()

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

@@ -119,7 +119,7 @@ void CreateScene()
     CreateMovingBarrels(navMesh);
 
     // 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
     // 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
     SubscribeToEvent("CrowdAgentReposition", "HandleCrowdAgentReposition");
+
+    // Subscribe HandleCrowdAgentFormation() function for positioning agent into a formation
+    SubscribeToEvent("CrowdAgentFormation", "HandleCrowdAgentFormation");
 }
 
 void CreateMushroom(const Vector3& pos)
@@ -204,9 +207,9 @@ void CreateMushroom(const Vector3& pos)
     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;
     AnimatedModel@ modelObject = jackNode.CreateComponent("AnimatedModel");
     modelObject.model = cache.GetResource("Model", "Models/Jack.mdl");
@@ -260,6 +263,7 @@ void CreateMovingBarrels(DynamicNavigationMesh@ navMesh)
         CrowdAgent@ agent = clone.CreateComponent("CrowdAgent");
         agent.radius = clone.scale.x * 0.5f;
         agent.height = size;
+        agent.navigationQuality = NAVIGATIONQUALITY_LOW;
     }
     barrel.Remove();
 }
@@ -273,12 +277,13 @@ void SetPathPoint(bool spawning)
     {
         DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
         Vector3 pathPos = navMesh.FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
+        Node@ jackGroup = scene_.GetChild("Jacks");
         if (spawning)
             // Spawn a jack at the target position
-            SpawnJack(pathPos);
+            SpawnJack(pathPos, jackGroup);
         else
             // 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)
 {
     const String WALKING_ANI = "Models/Jack_Walk.ani";