Browse Source

Minor code clean up and bug fixes for DetourCrowd implementation.
Avoid using multiple vectors to hold the same set of objects in the DetourCrowd sample.

Yao Wei Tjong 姚伟忠 10 years ago
parent
commit
6b72c4412a

+ 84 - 98
Source/Samples/39_CrowdNavigation/CrowdNavigation.cpp

@@ -57,6 +57,8 @@
 
 
 #include <Urho3D/DebugNew.h>
 #include <Urho3D/DebugNew.h>
 
 
+const String INSTRUCTION("instructionText");
+
 DEFINE_APPLICATION_MAIN(CrowdNavigation)
 DEFINE_APPLICATION_MAIN(CrowdNavigation)
 
 
 CrowdNavigation::CrowdNavigation(Context* context) :
 CrowdNavigation::CrowdNavigation(Context* context) :
@@ -121,10 +123,10 @@ void CrowdNavigation::CreateScene()
     light->SetShadowCascade(CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f));
     light->SetShadowCascade(CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f));
 
 
     // Create randomly sized boxes. If boxes are big enough, make them occluders
     // Create randomly sized boxes. If boxes are big enough, make them occluders
-    Vector< SharedPtr<Node> > boxes;
+    Node* boxGroup = scene_->CreateChild("Boxes");
     for (unsigned i = 0; i < 20; ++i)
     for (unsigned i = 0; i < 20; ++i)
     {
     {
-        Node* boxNode = scene_->CreateChild("Box");
+        Node* boxNode = boxGroup->CreateChild("Box");
         float size = 1.0f + Random(10.0f);
         float size = 1.0f + Random(10.0f);
         boxNode->SetPosition(Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f));
         boxNode->SetPosition(Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f));
         boxNode->SetScale(size);
         boxNode->SetScale(size);
@@ -133,46 +135,43 @@ void CrowdNavigation::CreateScene()
         boxObject->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
         boxObject->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
         boxObject->SetCastShadows(true);
         boxObject->SetCastShadows(true);
         if (size >= 3.0f)
         if (size >= 3.0f)
-        {
             boxObject->SetOccluder(true);
             boxObject->SetOccluder(true);
-            boxes.Push(SharedPtr<Node>(boxNode));
-        }
     }
     }
 
 
     // Create a DynamicNavigationMesh component to the scene root
     // Create a DynamicNavigationMesh component to the scene root
-    navMesh_ = scene_->CreateComponent<DynamicNavigationMesh>();
+    DynamicNavigationMesh* navMesh = scene_->CreateComponent<DynamicNavigationMesh>();
     // Enable drawing debug geometry for obstacles and off-mesh connections
     // Enable drawing debug geometry for obstacles and off-mesh connections
-    navMesh_->SetDrawObstacles(true);
-    navMesh_->SetDrawOffMeshConnections(true);
+    navMesh->SetDrawObstacles(true);
+    navMesh->SetDrawOffMeshConnections(true);
     // Set the agent height large enough to exclude the layers under boxes
     // Set the agent height large enough to exclude the layers under boxes
-    navMesh_->SetAgentHeight(10.0f);
+    navMesh->SetAgentHeight(10.0f);
     // Set nav mesh cell height to minimum (allows agents to be grounded)
     // Set nav mesh cell height to minimum (allows agents to be grounded)
-    navMesh_->SetCellHeight(0.05f);
+    navMesh->SetCellHeight(0.05f);
     // Create a Navigable component to the scene root. This tags all of the geometry in the scene as being part of the
     // Create a Navigable component to the scene root. This tags all of the geometry in the scene as being part of the
     // navigation mesh. By default this is recursive, but the recursion could be turned off from Navigable
     // navigation mesh. By default this is recursive, but the recursion could be turned off from Navigable
     scene_->CreateComponent<Navigable>();
     scene_->CreateComponent<Navigable>();
     // Add padding to the navigation mesh in Y-direction so that we can add objects on top of the tallest boxes
     // Add padding to the navigation mesh in Y-direction so that we can add objects on top of the tallest boxes
     // in the scene and still update the mesh correctly
     // in the scene and still update the mesh correctly
-    navMesh_->SetPadding(Vector3(0.0f, 10.0f, 0.0f));
+    navMesh->SetPadding(Vector3(0.0f, 10.0f, 0.0f));
     // Now build the navigation geometry. This will take some time. Note that the navigation mesh will prefer to use
     // Now build the navigation geometry. This will take some time. Note that the navigation mesh will prefer to use
     // physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
     // physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
     // it will use renderable geometry instead
     // it will use renderable geometry instead
-    navMesh_->Build();
+    navMesh->Build();
 
 
     // Create an off-mesh connection to each box to make them climbable (tiny boxes are skipped). A connection is built from 2 nodes.
     // Create an off-mesh connection to each box to make them climbable (tiny boxes are skipped). A connection is built from 2 nodes.
     // Note that OffMeshConnections must be added before building the navMesh, but as we are adding Obstacles next, tiles will be automatically rebuilt.
     // Note that OffMeshConnections must be added before building the navMesh, but as we are adding Obstacles next, tiles will be automatically rebuilt.
     // Creating connections post-build here allows us to use FindNearestPoint() to procedurally set accurate positions for the connection
     // Creating connections post-build here allows us to use FindNearestPoint() to procedurally set accurate positions for the connection
-    CreateBoxOffMeshConnections(boxes);
+    CreateBoxOffMeshConnections(navMesh, boxGroup);
 
 
     // Create some mushrooms as obstacles. Note that obstacles are non-walkable areas
     // Create some mushrooms as obstacles. Note that obstacles are non-walkable areas
     for (unsigned i = 0; i < 100; ++i)
     for (unsigned i = 0; i < 100; ++i)
         CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
         CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
 
 
     // Create a DetourCrowdManager component to the scene root
     // Create a DetourCrowdManager component to the scene root
-    crowdManager_ = scene_->CreateComponent<DetourCrowdManager>();
+    scene_->CreateComponent<DetourCrowdManager>();
 
 
     // Create some movable barrels. We create them as crowd agents, as for moving entities it is less expensive and more convenient than using obstacles
     // Create some movable barrels. We create them as crowd agents, as for moving entities it is less expensive and more convenient than using obstacles
-    CreateMovingBarrels();
+    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));
@@ -183,8 +182,10 @@ void CrowdNavigation::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 CrowdNavigation::CreateUI()
 void CrowdNavigation::CreateUI()
@@ -204,16 +205,17 @@ void CrowdNavigation::CreateUI()
     cursor->SetPosition(graphics->GetWidth() / 2, graphics->GetHeight() / 2);
     cursor->SetPosition(graphics->GetWidth() / 2, graphics->GetHeight() / 2);
 
 
     // Construct new Text object, set string to display and font to use
     // Construct new Text object, set string to display and font to use
-    Text* instructionText = ui->GetRoot()->CreateChild<Text>();
+    Text* instructionText = ui->GetRoot()->CreateChild<Text>(INSTRUCTION);
     instructionText->SetText(
     instructionText->SetText(
         "Use WASD keys to move, RMB to rotate view\n"
         "Use WASD keys to move, RMB to rotate view\n"
         "LMB to set destination, SHIFT+LMB to spawn a Jack\n"
         "LMB to set destination, SHIFT+LMB to spawn a Jack\n"
         "CTRL+LMB to teleport main agent\n"
         "CTRL+LMB to teleport main agent\n"
         "MMB to add obstacles or remove obstacles/agents\n"
         "MMB to add obstacles or remove obstacles/agents\n"
         "F5 to save scene, F7 to load\n"
         "F5 to save scene, F7 to load\n"
-        "Space to toggle debug geometry"
+        "Space to toggle debug geometry\n"
+        "F12 to toggle this instruction text"
     );
     );
-    instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 12);
+    instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
     // The text has multiple rows. Center them in relation to each other
     // The text has multiple rows. Center them in relation to each other
     instructionText->SetTextAlignment(HA_CENTER);
     instructionText->SetTextAlignment(HA_CENTER);
 
 
@@ -237,13 +239,15 @@ void CrowdNavigation::SubscribeToEvents()
     // Subscribe HandleUpdate() function for processing update events
     // Subscribe HandleUpdate() function for processing update events
     SubscribeToEvent(E_UPDATE, HANDLER(CrowdNavigation, HandleUpdate));
     SubscribeToEvent(E_UPDATE, HANDLER(CrowdNavigation, HandleUpdate));
 
 
-    // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
-    // debug geometry
+    // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request debug geometry
     SubscribeToEvent(E_POSTRENDERUPDATE, HANDLER(CrowdNavigation, HandlePostRenderUpdate));
     SubscribeToEvent(E_POSTRENDERUPDATE, HANDLER(CrowdNavigation, HandlePostRenderUpdate));
 
 
     // Subscribe HandleCrowdAgentFailure() function for resolving invalidation issues with agents, during which we
     // Subscribe HandleCrowdAgentFailure() function for resolving invalidation issues with agents, during which we
     // use a larger extents for finding a point on the navmesh to fix the agent's position
     // use a larger extents for finding a point on the navmesh to fix the agent's position
     SubscribeToEvent(E_CROWD_AGENT_FAILURE, HANDLER(CrowdNavigation, HandleCrowdAgentFailure));
     SubscribeToEvent(E_CROWD_AGENT_FAILURE, HANDLER(CrowdNavigation, HandleCrowdAgentFailure));
+
+    // Subscribe HandleCrowdAgentReposition() function for controlling the animation
+    SubscribeToEvent(E_CROWD_AGENT_REPOSITION, HANDLER(CrowdNavigation, HandleCrowdAgentReposition));
 }
 }
 
 
 void CrowdNavigation::SpawnJack(const Vector3& pos)
 void CrowdNavigation::SpawnJack(const Vector3& pos)
@@ -260,9 +264,8 @@ void CrowdNavigation::SpawnJack(const Vector3& pos)
     // Create a CrowdAgent component and set its height and realistic max speed/acceleration. Use default radius
     // Create a CrowdAgent component and set its height and realistic max speed/acceleration. Use default radius
     CrowdAgent* agent = jackNode->CreateComponent<CrowdAgent>();
     CrowdAgent* agent = jackNode->CreateComponent<CrowdAgent>();
     agent->SetHeight(2.0f);
     agent->SetHeight(2.0f);
-    agent->SetMaxSpeed(4.0f);
+    agent->SetMaxSpeed(3.0f);
     agent->SetMaxAccel(100.0f);
     agent->SetMaxAccel(100.0f);
-    agents_ = crowdManager_->GetActiveAgents(); // Update agents container
 }
 }
 
 
 void CrowdNavigation::CreateMushroom(const Vector3& pos)
 void CrowdNavigation::CreateMushroom(const Vector3& pos)
@@ -284,8 +287,9 @@ void CrowdNavigation::CreateMushroom(const Vector3& pos)
     obstacle->SetHeight(mushroomNode->GetScale().y_);
     obstacle->SetHeight(mushroomNode->GetScale().y_);
 }
 }
 
 
-void CrowdNavigation::CreateBoxOffMeshConnections(Vector< SharedPtr<Node> > boxes)
+void CrowdNavigation::CreateBoxOffMeshConnections(DynamicNavigationMesh* navMesh, Node* boxGroup)
 {
 {
+    const Vector<SharedPtr<Node> >& boxes = boxGroup->GetChildren();
     for (unsigned i=0; i < boxes.Size(); ++i)
     for (unsigned i=0; i < boxes.Size(); ++i)
     {
     {
         Node* box = boxes[i];
         Node* box = boxes[i];
@@ -294,9 +298,9 @@ void CrowdNavigation::CreateBoxOffMeshConnections(Vector< SharedPtr<Node> > boxe
 
 
         // Create 2 empty nodes for the start & end points of the connection. Note that order matters only when using one-way/unidirectional connection.
         // Create 2 empty nodes for the start & end points of the connection. Note that order matters only when using one-way/unidirectional connection.
         Node* connectionStart = box->CreateChild("ConnectionStart");
         Node* connectionStart = box->CreateChild("ConnectionStart");
-        connectionStart->SetWorldPosition(navMesh_->FindNearestPoint(boxPos + Vector3(boxHalfSize, -boxHalfSize, 0))); // Base of box
+        connectionStart->SetWorldPosition(navMesh->FindNearestPoint(boxPos + Vector3(boxHalfSize, -boxHalfSize, 0))); // Base of box
         Node* connectionEnd = connectionStart->CreateChild("ConnectionEnd");
         Node* connectionEnd = connectionStart->CreateChild("ConnectionEnd");
-        connectionEnd->SetWorldPosition(navMesh_->FindNearestPoint(boxPos + Vector3(boxHalfSize, boxHalfSize, 0))); // Top of box
+        connectionEnd->SetWorldPosition(navMesh->FindNearestPoint(boxPos + Vector3(boxHalfSize, boxHalfSize, 0))); // Top of box
 
 
         // Create the OffMeshConnection component to one node and link the other node
         // Create the OffMeshConnection component to one node and link the other node
         OffMeshConnection* connection = connectionStart->CreateComponent<OffMeshConnection>();
         OffMeshConnection* connection = connectionStart->CreateComponent<OffMeshConnection>();
@@ -304,7 +308,7 @@ void CrowdNavigation::CreateBoxOffMeshConnections(Vector< SharedPtr<Node> > boxe
     }
     }
 }
 }
 
 
-void CrowdNavigation::CreateMovingBarrels()
+void CrowdNavigation::CreateMovingBarrels(DynamicNavigationMesh* navMesh)
 {
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     Node* barrel = scene_->CreateChild("Barrel");
     Node* barrel = scene_->CreateChild("Barrel");
@@ -314,12 +318,12 @@ void CrowdNavigation::CreateMovingBarrels()
     model->SetMaterial(material);
     model->SetMaterial(material);
     material->SetTexture(TU_DIFFUSE, cache->GetResource<Texture2D>("Textures/TerrainDetail2.dds"));
     material->SetTexture(TU_DIFFUSE, cache->GetResource<Texture2D>("Textures/TerrainDetail2.dds"));
     model->SetCastShadows(true);
     model->SetCastShadows(true);
-    for (unsigned i = 0;  i < NUM_BARRELS; ++i)
+    for (unsigned i = 0;  i < 20; ++i)
     {
     {
         Node* clone = barrel->Clone();
         Node* clone = barrel->Clone();
         float size = 0.5f + Random(1.0f);
         float size = 0.5f + Random(1.0f);
         clone->SetScale(Vector3(size / 1.5f, size * 2.0f, size / 1.5f));
         clone->SetScale(Vector3(size / 1.5f, size * 2.0f, size / 1.5f));
-        clone->SetPosition(navMesh_->FindNearestPoint(Vector3(Random(80.0f) - 40.0, size * 0.5 , Random(80.0f) - 40.0)));
+        clone->SetPosition(navMesh->FindNearestPoint(Vector3(Random(80.0f) - 40.0, size * 0.5 , Random(80.0f) - 40.0)));
         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);
@@ -327,49 +331,21 @@ void CrowdNavigation::CreateMovingBarrels()
     barrel->Remove();
     barrel->Remove();
 }
 }
 
 
-void CrowdNavigation::SetPathPoint()
+void CrowdNavigation::SetPathPoint(bool spawning)
 {
 {
     Vector3 hitPos;
     Vector3 hitPos;
     Drawable* hitDrawable;
     Drawable* hitDrawable;
 
 
     if (Raycast(250.0f, hitPos, hitDrawable))
     if (Raycast(250.0f, hitPos, hitDrawable))
     {
     {
-        Vector3 pathPos = navMesh_->FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
-
-        if (GetSubsystem<Input>()->GetQualifierDown(QUAL_SHIFT))
+        DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
+        Vector3 pathPos = navMesh->FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
+        if (spawning)
             // Spawn a jack at the target position
             // Spawn a jack at the target position
             SpawnJack(pathPos);
             SpawnJack(pathPos);
-        else if (GetSubsystem<Input>()->GetQualifierDown(QUAL_CTRL) && agents_.Size() > NUM_BARRELS)
-        {
-            // Teleport
-            CrowdAgent* agent = agents_[NUM_BARRELS]; // Get first Jack agent
-            Node* node = agent->GetNode();
-            if (node->GetName() == "Barrel")
-                return;
-            node->LookAt(pathPos); // Face target
-            agent->SetMoveVelocity(Vector3(0.0f, 0.0f, 0.0f)); // Stop agent
-            node->SetPosition(pathPos);
-        }
         else
         else
-        {
-            // Set target position and init agents' move
-            for (unsigned i = NUM_BARRELS; i < agents_.Size(); ++i)
-            {
-                CrowdAgent* agent = agents_[i];
-                if (i == NUM_BARRELS)
-                {
-                    // The first Jack agent will always move to the exact position and is strong enough to push barrels and his siblings (no avoidance)
-                    agent->SetNavigationPushiness(PUSHINESS_HIGH);
-                    agent->SetMoveTarget(pathPos);
-                }
-                else
-                {
-                    // Other Jack agents will move to a random point nearby
-                    Vector3 targetPos = navMesh_->FindNearestPoint(pathPos + Vector3(Random(-4.5f, 4.5f), 0.0f, Random(-4.5f, 4.5f)), Vector3(1.0f, 1.0f, 1.0f));
-                    agent->SetMoveTarget(targetPos);
-                }
-            }
-        }
+            // Set crowd agents target position
+            scene_->GetComponent<DetourCrowdManager>()->SetCrowdTarget(pathPos);
     }
     }
 }
 }
 
 
@@ -387,10 +363,7 @@ void CrowdNavigation::AddOrRemoveObject()
         if (hitNode->GetName() == "Mushroom")
         if (hitNode->GetName() == "Mushroom")
             hitNode->Remove();
             hitNode->Remove();
         else if (hitNode->GetName() == "Jack")
         else if (hitNode->GetName() == "Jack")
-        {
             hitNode->Remove();
             hitNode->Remove();
-            agents_ = crowdManager_->GetActiveAgents(); // Update agents container
-        }
         else
         else
             CreateMushroom(hitPos);
             CreateMushroom(hitPos);
     }
     }
@@ -465,9 +438,9 @@ void CrowdNavigation::MoveCamera(float timeStep)
 
 
     // Set destination or spawn a new jack with left mouse button
     // Set destination or spawn a new jack with left mouse button
     if (input->GetMouseButtonPress(MOUSEB_LEFT))
     if (input->GetMouseButtonPress(MOUSEB_LEFT))
-        SetPathPoint();
+        SetPathPoint(input->GetQualifierDown(QUAL_SHIFT));
     // Add new obstacle or remove existing obstacle/agent with middle mouse button
     // Add new obstacle or remove existing obstacle/agent with middle mouse button
-    if (input->GetMouseButtonPress(MOUSEB_MIDDLE))
+    else if (input->GetMouseButtonPress(MOUSEB_MIDDLE))
         AddOrRemoveObject();
         AddOrRemoveObject();
 
 
     // Check for loading/saving the scene from/to the file Data/Scenes/CrowdNavigation.xml relative to the executable directory
     // Check for loading/saving the scene from/to the file Data/Scenes/CrowdNavigation.xml relative to the executable directory
@@ -476,20 +449,22 @@ void CrowdNavigation::MoveCamera(float timeStep)
         File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CrowdNavigation.xml", FILE_WRITE);
         File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CrowdNavigation.xml", FILE_WRITE);
         scene_->SaveXML(saveFile);
         scene_->SaveXML(saveFile);
     }
     }
-    if (input->GetKeyPress(KEY_F7))
+    else if (input->GetKeyPress(KEY_F7))
     {
     {
         File loadFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CrowdNavigation.xml", FILE_READ);
         File loadFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CrowdNavigation.xml", FILE_READ);
         scene_->LoadXML(loadFile);
         scene_->LoadXML(loadFile);
-
-        // After reload, reacquire navMesh, crowd manager & agents
-        navMesh_ = scene_->GetComponent<DynamicNavigationMesh>();
-        crowdManager_ = scene_->GetComponent<DetourCrowdManager>();
-        agents_ = crowdManager_->GetActiveAgents();
     }
     }
 
 
     // Toggle debug geometry with space
     // Toggle debug geometry with space
-    if (input->GetKeyPress(KEY_SPACE))
+    else if (input->GetKeyPress(KEY_SPACE))
         drawDebug_ = !drawDebug_;
         drawDebug_ = !drawDebug_;
+
+    // Toggle instruction text with F12
+    else if (input->GetKeyPress(KEY_F12))
+    {
+        UIElement* instruction = ui->GetRoot()->GetChild(INSTRUCTION);
+        instruction->SetVisible(!instruction->IsVisible());
+    }
 }
 }
 
 
 void CrowdNavigation::HandleUpdate(StringHash eventType, VariantMap& eventData)
 void CrowdNavigation::HandleUpdate(StringHash eventType, VariantMap& eventData)
@@ -501,24 +476,6 @@ void CrowdNavigation::HandleUpdate(StringHash eventType, VariantMap& eventData)
 
 
     // Move the camera, scale movement with time step
     // Move the camera, scale movement with time step
     MoveCamera(timeStep);
     MoveCamera(timeStep);
-
-    // Make the Jack CrowdAgents face the direction of their velocity and update animation
-    for (unsigned i = NUM_BARRELS; i < agents_.Size(); ++i)
-    {
-        CrowdAgent* agent = agents_[i];
-        Node* node = agent->GetNode();
-        AnimationController* animCtrl = node->GetComponent<AnimationController>();
-        Vector3 velocity = agent->GetActualVelocity();
-
-        if (velocity.Length() < 0.6)
-            animCtrl->Stop("Models/Jack_Walk.ani", 0.2);
-        else
-        {
-            node->SetWorldDirection(velocity);
-            animCtrl->PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.2);
-            animCtrl->SetSpeed("Models/Jack_Walk.ani", velocity.Length() * 0.3);
-        }
-    }
 }
 }
 
 
 void CrowdNavigation::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
 void CrowdNavigation::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
@@ -526,9 +483,9 @@ void CrowdNavigation::HandlePostRenderUpdate(StringHash eventType, VariantMap& e
     if (drawDebug_)
     if (drawDebug_)
     {
     {
         // Visualize navigation mesh, obstacles and off-mesh connections
         // Visualize navigation mesh, obstacles and off-mesh connections
-        navMesh_->DrawDebugGeometry(true);
+        scene_->GetComponent<DynamicNavigationMesh>()->DrawDebugGeometry(true);
         // Visualize agents' path and position to reach
         // Visualize agents' path and position to reach
-        crowdManager_->DrawDebugGeometry(true);
+        scene_->GetComponent<DetourCrowdManager>()->DrawDebugGeometry(true);
     }
     }
 }
 }
 
 
@@ -543,8 +500,37 @@ void CrowdNavigation::HandleCrowdAgentFailure(StringHash eventType, VariantMap&
     if (agentState == CROWD_AGENT_INVALID)
     if (agentState == CROWD_AGENT_INVALID)
     {
     {
         // Get a point on the navmesh using more generous extents
         // Get a point on the navmesh using more generous extents
-        Vector3 newPos = navMesh_->FindNearestPoint(node->GetPosition(), Vector3(5.0f, 5.0f, 5.0f));
+        Vector3 newPos = scene_->GetComponent<DynamicNavigationMesh>()->FindNearestPoint(node->GetPosition(), Vector3(5.0f, 5.0f, 5.0f));
         // Set the new node position, CrowdAgent component will automatically reset the state of the agent
         // Set the new node position, CrowdAgent component will automatically reset the state of the agent
         node->SetPosition(newPos);
         node->SetPosition(newPos);
     }
     }
-}
+}
+
+void CrowdNavigation::HandleCrowdAgentReposition(StringHash eventType, VariantMap& eventData)
+{
+    const char* WALKING_ANI = "Models/Jack_Walk.ani";
+
+    using namespace CrowdAgentReposition;
+
+    Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
+    CrowdAgent* agent = static_cast<CrowdAgent*>(eventData[P_CROWD_AGENT].GetPtr());
+    Vector3 velocity = eventData[P_VELOCITY].GetVector3();
+
+    // Only Jack agent has animation controller
+    AnimationController* animCtrl = node->GetComponent<AnimationController>();
+    if (animCtrl)
+    {
+        float speed = velocity.Length();
+        if (speed < agent->GetRadius())
+            // If speed is too low then stopping the animation
+            animCtrl->Stop(WALKING_ANI, 0.8f);
+        else
+        {
+            // Face the direction of its velocity
+            node->SetWorldDirection(velocity);
+            animCtrl->Play(WALKING_ANI, 0, true);
+            // Throttle the animation speed based on agent speed ratio (ratio = 1 is full throttle)
+            animCtrl->SetSpeed(WALKING_ANI, speed / agent->GetMaxSpeed());
+        }
+    }
+}

+ 6 - 12
Source/Samples/39_CrowdNavigation/CrowdNavigation.h

@@ -33,8 +33,6 @@ class Scene;
 
 
 }
 }
 
 
-const int NUM_BARRELS = 20;
-
 /// CrowdNavigation example.
 /// CrowdNavigation example.
 /// This sample demonstrates:
 /// This sample demonstrates:
 ///     - Generating a dynamic navigation mesh into the scene
 ///     - Generating a dynamic navigation mesh into the scene
@@ -140,8 +138,8 @@ private:
     void SubscribeToEvents();
     void SubscribeToEvents();
     /// Read input and moves the camera.
     /// Read input and moves the camera.
     void MoveCamera(float timeStep);
     void MoveCamera(float timeStep);
-    /// Set path start or end point.
-    void SetPathPoint();
+    /// Set crowd agents target or spawn another jack.
+    void SetPathPoint(bool spawning);
     /// 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.
@@ -149,9 +147,9 @@ private:
     /// 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.
-    void CreateBoxOffMeshConnections(Vector< SharedPtr<Node> > boxes);
+    void CreateBoxOffMeshConnections(DynamicNavigationMesh* navMesh, Node* boxGroup);
     /// Create some movable barrels as crowd agents.
     /// Create some movable barrels as crowd agents.
-    void CreateMovingBarrels();
+    void CreateMovingBarrels(DynamicNavigationMesh* navMesh);
     /// Utility function to raycast to the cursor position. Return true if hit.
     /// Utility function to raycast to the cursor position. Return true if hit.
     bool Raycast(float maxDistance, Vector3& hitPos, Drawable*& hitDrawable);
     bool Raycast(float maxDistance, Vector3& hitPos, Drawable*& hitDrawable);
     /// Handle the logic update event.
     /// Handle the logic update event.
@@ -160,13 +158,9 @@ private:
     void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
     void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle problems with crowd agent placement.
     /// Handle problems with crowd agent placement.
     void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData);
     void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData);
+    /// Handle crowd agent reposition.
+    void HandleCrowdAgentReposition(StringHash eventType, VariantMap& eventData);
 
 
-    /// Dynamic navigation mesh.
-    DynamicNavigationMesh* navMesh_;
-    /// Crowd Manager.
-    DetourCrowdManager* crowdManager_;
-    /// Crowd Agents.
-    PODVector<CrowdAgent*> agents_;
     /// Flag for drawing debug geometry.
     /// Flag for drawing debug geometry.
     bool drawDebug_;
     bool drawDebug_;
 };
 };

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

@@ -6,10 +6,9 @@ enum CrowdTargetState
     CROWD_AGENT_TARGET_FAILED,
     CROWD_AGENT_TARGET_FAILED,
     CROWD_AGENT_TARGET_VALID,
     CROWD_AGENT_TARGET_VALID,
     CROWD_AGENT_TARGET_REQUESTING,
     CROWD_AGENT_TARGET_REQUESTING,
-    CROWD_AGENT_TARGET_WAITINGFORPATH,
     CROWD_AGENT_TARGET_WAITINGFORQUEUE,
     CROWD_AGENT_TARGET_WAITINGFORQUEUE,
-    CROWD_AGENT_TARGET_VELOCITY,
-    CROWD_AGENT_TARGET_ARRIVED
+    CROWD_AGENT_TARGET_WAITINGFORPATH,
+    CROWD_AGENT_TARGET_VELOCITY
 };
 };
 
 
 enum CrowdAgentState
 enum CrowdAgentState
@@ -24,8 +23,8 @@ enum CrowdAgentState
 class CrowdAgent : public Component
 class CrowdAgent : public Component
 {
 {
     void SetNavigationFilterType(unsigned filterID);
     void SetNavigationFilterType(unsigned filterID);
-    bool SetMoveTarget(const Vector3& position);
-    bool SetMoveVelocity(const Vector3& velocity);
+    void SetMoveTarget(const Vector3& position);
+    void SetMoveVelocity(const Vector3& velocity);
     void SetUpdateNodePosition(bool unodepos);
     void SetUpdateNodePosition(bool unodepos);
     void SetMaxAccel(float val);
     void SetMaxAccel(float val);
     void SetMaxSpeed(float val);
     void SetMaxSpeed(float val);
@@ -45,6 +44,7 @@ class CrowdAgent : public Component
     float GetMaxAccel() const;
     float GetMaxAccel() const;
     NavigationQuality GetNavigationQuality() const;
     NavigationQuality GetNavigationQuality() const;
     NavigationPushiness GetNavigationPushiness() const;
     NavigationPushiness GetNavigationPushiness() const;
+    bool HasArrived() const;
     float GetRadius() const;
     float GetRadius() const;
     float GetHeight() const;
     float GetHeight() const;
     Vector3 GetPosition() const;
     Vector3 GetPosition() const;

+ 6 - 14
Source/Urho3D/LuaScript/pkgs/Navigation/DetourCrowdManager.pkg

@@ -19,27 +19,19 @@ class DetourCrowdManager : public Component
     void DrawDebugGeometry(bool depthTest);
     void DrawDebugGeometry(bool depthTest);
 
 
     void SetNavigationMesh(NavigationMesh *navMesh);
     void SetNavigationMesh(NavigationMesh *navMesh);
-    void SetMaxAgents(unsigned agentCt);
     void SetAreaCost(unsigned filterID, unsigned areaID, float cost);
     void SetAreaCost(unsigned filterID, unsigned areaID, float cost);
+    void SetMaxAgents(unsigned agentCt);
+    void SetCrowdTarget(const Vector3& position, int startId = 0, int endId = M_MAX_INT);
+    void ResetCrowdTarget(int startId = 0, int endId = M_MAX_INT);
+    void SetCrowdVelocity(const Vector3& velocity, int startId = 0, int endId = M_MAX_INT);
 
 
-    NavigationMesh* GetNavigationMesh();
+    NavigationMesh* GetNavigationMesh() const;
     unsigned GetMaxAgents() const;
     unsigned GetMaxAgents() const;
     float GetAreaCost(unsigned filterID, unsigned areaID) const;
     float GetAreaCost(unsigned filterID, unsigned areaID) const;
     unsigned GetAgentCount() const;
     unsigned GetAgentCount() const;
-    tolua_outside const PODVector<CrowdAgent*>& DetourCrowdManagerGetActiveAgents @ GetActiveAgents();
+    const PODVector<CrowdAgent*>& GetActiveAgents() const;
 
 
     tolua_property__get_set NavigationMesh* navigationMesh;
     tolua_property__get_set NavigationMesh* navigationMesh;
     tolua_property__get_set int maxAgents;
     tolua_property__get_set int maxAgents;
     tolua_readonly tolua_property__get_set unsigned agentCount;
     tolua_readonly tolua_property__get_set unsigned agentCount;
 };
 };
-
-${
-
-static const PODVector<CrowdAgent*>& DetourCrowdManagerGetActiveAgents(DetourCrowdManager* crowdManager)
-{
-    static PODVector<CrowdAgent*> result;
-    result = crowdManager->GetActiveAgents();
-    return result;
-}
-
-$}

+ 55 - 52
Source/Urho3D/Navigation/CrowdAgent.cpp

@@ -45,8 +45,8 @@ namespace Urho3D
 extern const char* NAVIGATION_CATEGORY;
 extern const char* NAVIGATION_CATEGORY;
 
 
 static const unsigned DEFAULT_AGENT_NAVIGATION_FILTER_TYPE = 0;
 static const unsigned DEFAULT_AGENT_NAVIGATION_FILTER_TYPE = 0;
-static const float DEFAULT_AGENT_MAX_SPEED = 5.0f;
-static const float DEFAULT_AGENT_MAX_ACCEL = 3.6f;
+static const float DEFAULT_AGENT_MAX_SPEED = 0.f;
+static const float DEFAULT_AGENT_MAX_ACCEL = 0.f;
 static const NavigationQuality DEFAULT_AGENT_AVOIDANCE_QUALITY = NAVIGATIONQUALITY_HIGH;
 static const NavigationQuality DEFAULT_AGENT_AVOIDANCE_QUALITY = NAVIGATIONQUALITY_HIGH;
 static const NavigationPushiness DEFAULT_AGENT_NAVIGATION_PUSHINESS = PUSHINESS_MEDIUM;
 static const NavigationPushiness DEFAULT_AGENT_NAVIGATION_PUSHINESS = PUSHINESS_MEDIUM;
 
 
@@ -97,6 +97,7 @@ void CrowdAgent::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE("Max Speed", GetMaxSpeed, SetMaxSpeed, float, DEFAULT_AGENT_MAX_SPEED, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Max Speed", GetMaxSpeed, SetMaxSpeed, float, DEFAULT_AGENT_MAX_SPEED, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Radius", GetRadius, SetRadius, float, 0.0f, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Radius", GetRadius, SetRadius, float, 0.0f, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Height", GetHeight, SetHeight, float, 0.0f, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Height", GetHeight, SetHeight, float, 0.0f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Target Position", GetTargetPosition, SetMoveTarget, Vector3, Vector3::ZERO, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Navigation Filter", GetNavigationFilterType, SetNavigationFilterType, unsigned, DEFAULT_AGENT_NAVIGATION_FILTER_TYPE, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Navigation Filter", GetNavigationFilterType, SetNavigationFilterType, unsigned, DEFAULT_AGENT_NAVIGATION_FILTER_TYPE, AM_DEFAULT);
     ENUM_ACCESSOR_ATTRIBUTE("Navigation Pushiness", GetNavigationPushiness, SetNavigationPushiness, NavigationPushiness, crowdAgentPushinessNames, PUSHINESS_LOW, AM_DEFAULT);
     ENUM_ACCESSOR_ATTRIBUTE("Navigation Pushiness", GetNavigationPushiness, SetNavigationPushiness, NavigationPushiness, crowdAgentPushinessNames, PUSHINESS_LOW, AM_DEFAULT);
     ENUM_ACCESSOR_ATTRIBUTE("Navigation Quality", GetNavigationQuality, SetNavigationQuality, NavigationQuality, crowdAgentAvoidanceQualityNames, NAVIGATIONQUALITY_LOW, AM_DEFAULT);
     ENUM_ACCESSOR_ATTRIBUTE("Navigation Quality", GetNavigationQuality, SetNavigationQuality, NavigationQuality, crowdAgentAvoidanceQualityNames, NAVIGATIONQUALITY_LOW, AM_DEFAULT);
@@ -154,7 +155,7 @@ void CrowdAgent::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 
 
         debug->AddLine(pos, pos + vel, Color::GREEN, depthTest);
         debug->AddLine(pos, pos + vel, Color::GREEN, depthTest);
         debug->AddLine(pos + agentHeightVec, pos + desiredVel + agentHeightVec, Color::RED, depthTest);
         debug->AddLine(pos + agentHeightVec, pos + desiredVel + agentHeightVec, Color::RED, depthTest);
-        debug->AddCylinder(pos, radius_, height_, Color::GREEN, depthTest);
+        debug->AddCylinder(pos, radius_, height_, HasArrived() ? Color::WHITE : Color::GREEN, depthTest);
     }
     }
 }
 }
 
 
@@ -175,8 +176,6 @@ void CrowdAgent::AddAgentToCrowd()
             LOGERROR("AddAgentToCrowd: Could not add agent to crowd");
             LOGERROR("AddAgentToCrowd: Could not add agent to crowd");
             return;
             return;
         }
         }
-        dtCrowdAgentParams& params = crowdManager_->GetCrowd()->getEditableAgent(agentCrowdId_)->params;
-        params.userData = this;
         crowdManager_->UpdateAgentNavigationQuality(this, navQuality_);
         crowdManager_->UpdateAgentNavigationQuality(this, navQuality_);
         crowdManager_->UpdateAgentPushiness(this, navPushiness_);
         crowdManager_->UpdateAgentPushiness(this, navPushiness_);
         previousAgentState_ = GetAgentState();
         previousAgentState_ = GetAgentState();
@@ -198,6 +197,9 @@ void CrowdAgent::AddAgentToCrowd()
             previousAgentState_ = GetAgentState();
             previousAgentState_ = GetAgentState();
             previousTargetState_ = GetTargetState();
             previousTargetState_ = GetTargetState();
         }
         }
+
+        // Save the initial position to prevent CrowdAgentReposition event being triggered unnecessarily
+        previousPosition_ = GetPosition();
     }
     }
 }
 }
 
 
@@ -224,23 +226,32 @@ void CrowdAgent::SetNavigationFilterType(unsigned filterType)
     }
     }
 }
 }
 
 
-bool CrowdAgent::SetMoveTarget(const Vector3& position)
+void CrowdAgent::SetMoveTarget(const Vector3& position)
 {
 {
-    if (crowdManager_ && !inCrowd_)
-        AddAgentToCrowd();
-    if (crowdManager_ && inCrowd_)
-    {
+    if (crowdManager_) {
+        if (!inCrowd_)
+            AddAgentToCrowd();
         targetPosition_ = position;
         targetPosition_ = position;
         if (crowdManager_->SetAgentTarget(this, position, targetRef_))
         if (crowdManager_->SetAgentTarget(this, position, targetRef_))
+            MarkNetworkUpdate();
+    }
+}
+
+void CrowdAgent::ResetMoveTarget()
+{
+    if (crowdManager_ && inCrowd_)
+    {
+        const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
+        if (agent && agent->active)
         {
         {
+            targetPosition_ = Vector3::ZERO;
+            crowdManager_->GetCrowd()->resetMoveTarget(agentCrowdId_);
             MarkNetworkUpdate();
             MarkNetworkUpdate();
-            return true;
         }
         }
     }
     }
-    return false;
 }
 }
 
 
-bool CrowdAgent::SetMoveVelocity(const Vector3& velocity)
+void CrowdAgent::SetMoveVelocity(const Vector3& velocity)
 {
 {
     if (crowdManager_ && inCrowd_)
     if (crowdManager_ && inCrowd_)
     {
     {
@@ -251,7 +262,6 @@ bool CrowdAgent::SetMoveVelocity(const Vector3& velocity)
             MarkNetworkUpdate();
             MarkNetworkUpdate();
         }
         }
     }
     }
-    return false;
 }
 }
 
 
 void CrowdAgent::SetMaxSpeed(float speed)
 void CrowdAgent::SetMaxSpeed(float speed)
@@ -369,30 +379,17 @@ Urho3D::CrowdAgentState CrowdAgent::GetAgentState() const
 
 
 Urho3D::CrowdTargetState CrowdAgent::GetTargetState() const
 Urho3D::CrowdTargetState CrowdAgent::GetTargetState() const
 {
 {
-    if (crowdManager_ && inCrowd_)
-    {
-        const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
-        if (!agent || !agent->active)
-            return CROWD_AGENT_TARGET_NONE;
+    const dtCrowdAgent* agent = crowdManager_ && inCrowd_ ? crowdManager_->GetCrowdAgent(agentCrowdId_) : 0;
+    return agent && agent->active ? (CrowdTargetState)agent->targetState : CROWD_AGENT_TARGET_NONE;
+}
 
 
-        // Determine if we've arrived at the target
-        if (agent->targetState == DT_CROWDAGENT_TARGET_VALID)
-        {
-            if (agent->ncorners)
-            {
-                // Is the agent at the end of its path?
-                if (agent->cornerFlags[agent->ncorners - 1] & DT_STRAIGHTPATH_END)
-                {
-                    // Within its own radius of the goal?
-                    if (dtVdist2D(agent->npos, &agent->cornerVerts[(agent->ncorners - 1) * 3]) <= agent->params.radius)
-                        return CROWD_AGENT_TARGET_ARRIVED;
-            
-                }
-            }
-        }
-        return (CrowdTargetState)agent->targetState;
-    }
-    return CROWD_AGENT_TARGET_NONE;
+bool CrowdAgent::HasArrived() const
+{
+    // Is the agent at the end of its path and within its own radius of the goal?
+    const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
+    return agent && agent->active && (!agent->ncorners ||
+        (agent->cornerFlags[agent->ncorners - 1] & DT_STRAIGHTPATH_END &&
+            Equals(dtVdist2D(agent->npos, &agent->cornerVerts[(agent->ncorners - 1) * 3]), 0.f)));
 }
 }
 
 
 void CrowdAgent::SetUpdateNodePosition(bool unodepos)
 void CrowdAgent::SetUpdateNodePosition(bool unodepos)
@@ -401,23 +398,29 @@ void CrowdAgent::SetUpdateNodePosition(bool unodepos)
     MarkNetworkUpdate();
     MarkNetworkUpdate();
 }
 }
 
 
-void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newDirection)
+void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newVel)
 {
 {
     if (node_)
     if (node_)
     {
     {
         // Notify parent node of the reposition
         // Notify parent node of the reposition
-        VariantMap& map = GetContext()->GetEventDataMap();
-        map[CrowdAgentReposition::P_NODE] = GetNode();
-        map[CrowdAgentReposition::P_CROWD_AGENT] = this;
-        map[CrowdAgentReposition::P_POSITION] = newPos;
-        map[CrowdAgentReposition::P_VELOCITY] = GetActualVelocity();
-        SendEvent(E_CROWD_AGENT_REPOSITION, map);
-        
-        if (updateNodePosition_)
+        if (newPos != previousPosition_)
         {
         {
-            ignoreTransformChanges_ = true;
-            node_->SetPosition(newPos);
-            ignoreTransformChanges_ = false;
+            previousPosition_ = newPos;
+
+            VariantMap& map = GetContext()->GetEventDataMap();
+            map[CrowdAgentReposition::P_NODE] = GetNode();
+            map[CrowdAgentReposition::P_CROWD_AGENT] = this;
+            map[CrowdAgentReposition::P_POSITION] = newPos;
+            map[CrowdAgentReposition::P_VELOCITY] = newVel;
+            map[CrowdAgentReposition::P_ARRIVED] = HasArrived();
+            SendEvent(E_CROWD_AGENT_REPOSITION, map);
+
+            if (updateNodePosition_)
+            {
+                ignoreTransformChanges_ = true;
+                node_->SetPosition(newPos);
+                ignoreTransformChanges_ = false;
+            }
         }
         }
 
 
         // Send a notification event if we've reached the destination
         // Send a notification event if we've reached the destination
@@ -431,7 +434,7 @@ void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& ne
             map[CrowdAgentStateChanged::P_CROWD_TARGET_STATE] = newTargetState;
             map[CrowdAgentStateChanged::P_CROWD_TARGET_STATE] = newTargetState;
             map[CrowdAgentStateChanged::P_CROWD_AGENT_STATE] = newAgentState;
             map[CrowdAgentStateChanged::P_CROWD_AGENT_STATE] = newAgentState;
             map[CrowdAgentStateChanged::P_POSITION] = newPos;
             map[CrowdAgentStateChanged::P_POSITION] = newPos;
-            map[CrowdAgentStateChanged::P_VELOCITY] = GetActualVelocity();
+            map[CrowdAgentStateChanged::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
@@ -443,7 +446,7 @@ void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& ne
                 map[CrowdAgentFailure::P_CROWD_TARGET_STATE] = newTargetState;
                 map[CrowdAgentFailure::P_CROWD_TARGET_STATE] = newTargetState;
                 map[CrowdAgentFailure::P_CROWD_AGENT_STATE] = newAgentState;
                 map[CrowdAgentFailure::P_CROWD_AGENT_STATE] = newAgentState;
                 map[CrowdAgentFailure::P_POSITION] = newPos;
                 map[CrowdAgentFailure::P_POSITION] = newPos;
-                map[CrowdAgentFailure::P_VELOCITY] = GetActualVelocity();
+                map[CrowdAgentFailure::P_VELOCITY] = newVel;
                 SendEvent(E_CROWD_AGENT_FAILURE, map);
                 SendEvent(E_CROWD_AGENT_FAILURE, map);
             }
             }
 
 
@@ -460,7 +463,7 @@ PODVector<unsigned char> CrowdAgent::GetAgentDataAttr() const
         return Variant::emptyBuffer;
         return Variant::emptyBuffer;
     dtCrowd* crowd = crowdManager_->GetCrowd();
     dtCrowd* crowd = crowdManager_->GetCrowd();
     const dtCrowdAgent* agent = crowd->getAgent(agentCrowdId_);
     const dtCrowdAgent* agent = crowd->getAgent(agentCrowdId_);
-    
+
     // Reading it back in isn't this simple, see SetAgentDataAttr
     // Reading it back in isn't this simple, see SetAgentDataAttr
     VectorBuffer ret;
     VectorBuffer ret;
     ret.Write(agent, sizeof(dtCrowdAgent));
     ret.Write(agent, sizeof(dtCrowdAgent));

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

@@ -34,17 +34,16 @@ enum CrowdTargetState
     CROWD_AGENT_TARGET_FAILED,
     CROWD_AGENT_TARGET_FAILED,
     CROWD_AGENT_TARGET_VALID,
     CROWD_AGENT_TARGET_VALID,
     CROWD_AGENT_TARGET_REQUESTING,
     CROWD_AGENT_TARGET_REQUESTING,
-    CROWD_AGENT_TARGET_WAITINGFORPATH,
     CROWD_AGENT_TARGET_WAITINGFORQUEUE,
     CROWD_AGENT_TARGET_WAITINGFORQUEUE,
-    CROWD_AGENT_TARGET_VELOCITY,
-    CROWD_AGENT_TARGET_ARRIVED
+    CROWD_AGENT_TARGET_WAITINGFORPATH,
+    CROWD_AGENT_TARGET_VELOCITY
 };
 };
 
 
 enum CrowdAgentState
 enum CrowdAgentState
 {
 {
-    CROWD_AGENT_INVALID = 0,	///< The agent is not in a valid state.
-    CROWD_AGENT_READY,			///< The agent is traversing a normal navigation mesh polygon.
-    CROWD_AGENT_TRAVERSINGLINK	///< The agent is traversing an off-mesh connection.
+    CROWD_AGENT_INVALID = 0,      ///< The agent is not in a valid state.
+    CROWD_AGENT_READY,            ///< The agent is traversing a normal navigation mesh polygon.
+    CROWD_AGENT_TRAVERSINGLINK    ///< The agent is traversing an off-mesh connection.
 };
 };
 
 
 /// DetourCrowd Agent, requires a DetourCrowdManager in the scene. Agent's radius and height is set through the navigation mesh.
 /// DetourCrowd Agent, requires a DetourCrowdManager in the scene. Agent's radius and height is set through the navigation mesh.
@@ -71,9 +70,11 @@ public:
     /// Set the navigation filter type the agent will use.
     /// Set the navigation filter type the agent will use.
     void SetNavigationFilterType(unsigned filterTypeID);
     void SetNavigationFilterType(unsigned filterTypeID);
     /// Submit a new move request for this agent.
     /// Submit a new move request for this agent.
-    bool SetMoveTarget(const Vector3& position);
+    void SetMoveTarget(const Vector3& position);
+    /// Reset any request for the specified agent.
+    void ResetMoveTarget();
     /// Submit a new move velocity request for this agent.
     /// Submit a new move velocity request for this agent.
-    bool SetMoveVelocity(const Vector3& velocity);
+    void SetMoveVelocity(const Vector3& velocity);
     /// Update the node position. When set to false, the node position should be updated by other means (e.g. using Physics) in response to the E_CROWD_AGENT_REPOSITION event.
     /// Update the node position. When set to false, the node position should be updated by other means (e.g. using Physics) in response to the E_CROWD_AGENT_REPOSITION event.
     void SetUpdateNodePosition(bool unodepos);
     void SetUpdateNodePosition(bool unodepos);
     /// Set the agent's max acceleration.
     /// Set the agent's max acceleration.
@@ -110,7 +111,7 @@ public:
     /// Get the agent's max velocity.
     /// Get the agent's max velocity.
     float GetMaxSpeed() const { return maxSpeed_; }
     float GetMaxSpeed() const { return maxSpeed_; }
     /// Get the agent's max acceleration.
     /// Get the agent's max acceleration.
-    float GetMaxAccel()	const { return maxAccel_; }
+    float GetMaxAccel() const { return maxAccel_; }
     /// Get the agent's radius.
     /// Get the agent's radius.
     float GetRadius() const { return radius_; }
     float GetRadius() const { return radius_; }
     /// Get the agent's height.
     /// Get the agent's height.
@@ -119,6 +120,8 @@ public:
     NavigationQuality GetNavigationQuality() const {return navQuality_; }
     NavigationQuality GetNavigationQuality() const {return navQuality_; }
     /// Get the agent's navigation pushiness.
     /// Get the agent's navigation pushiness.
     NavigationPushiness GetNavigationPushiness() const {return navPushiness_; }
     NavigationPushiness GetNavigationPushiness() const {return navPushiness_; }
+    /// Return true when the agent has arrived at its target.
+    bool HasArrived() const;
 
 
     /// Get serialized data of internal state.
     /// Get serialized data of internal state.
     PODVector<unsigned char> GetAgentDataAttr() const;
     PODVector<unsigned char> GetAgentDataAttr() const;
@@ -127,7 +130,7 @@ public:
 
 
 protected:
 protected:
     /// Update the nodes position if updateNodePosition is set. Is called in DetourCrowdManager::Update().
     /// Update the nodes position if updateNodePosition is set. Is called in DetourCrowdManager::Update().
-    virtual void OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newDirection);
+    virtual void OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newVel);
     /// Handle node being assigned.
     /// Handle node being assigned.
     virtual void OnNodeSet(Node* node);
     virtual void OnNodeSet(Node* node);
     /// \todo Handle node transform being dirtied.
     /// \todo Handle node transform being dirtied.
@@ -144,9 +147,9 @@ private:
     /// DetourCrowd reference to this agent.
     /// DetourCrowd reference to this agent.
     int agentCrowdId_;
     int agentCrowdId_;
     /// Reference to poly closest to requested target position.
     /// Reference to poly closest to requested target position.
-    unsigned int targetRef_;         
+    unsigned int targetRef_;
     /// Actual target position, closest to that requested.
     /// Actual target position, closest to that requested.
-    Vector3 targetPosition_;   
+    Vector3 targetPosition_;
     /// Flag indicating the node's position should be updated by Detour crowd manager.
     /// Flag indicating the node's position should be updated by Detour crowd manager.
     bool updateNodePosition_;
     bool updateNodePosition_;
     /// Agent's max acceleration.
     /// Agent's max acceleration.
@@ -163,6 +166,8 @@ private:
     NavigationQuality navQuality_;
     NavigationQuality navQuality_;
     /// Agent's Navigation Pushiness.
     /// Agent's Navigation Pushiness.
     NavigationPushiness navPushiness_;
     NavigationPushiness navPushiness_;
+    /// Agent's previous position used to check for position changes.
+    Vector3 previousPosition_;
     /// Agent's previous target state used to check for state changes.
     /// Agent's previous target state used to check for state changes.
     CrowdTargetState previousTargetState_;
     CrowdTargetState previousTargetState_;
     /// Agent's previous agent state used to check for state changes.
     /// Agent's previous agent state used to check for state changes.

+ 48 - 13
Source/Urho3D/Navigation/DetourCrowdManager.cpp

@@ -117,9 +117,44 @@ void DetourCrowdManager::SetMaxAgents(unsigned agentCt)
     MarkNetworkUpdate();
     MarkNetworkUpdate();
 }
 }
 
 
-NavigationMesh* DetourCrowdManager::GetNavigationMesh()
+void DetourCrowdManager::SetCrowdTarget(const Vector3& position, int startId, int endId)
 {
 {
-    return navigationMesh_.Get();
+    startId = Max(0, startId);
+    endId = Clamp(endId, startId, agents_.Size() - 1);
+    Vector3 moveTarget(position);
+    for (int i = startId; i <= endId; ++i)
+    {
+        // Skip agent that does not have acceleration
+        if (agents_[i]->GetMaxAccel() > 0.f)
+        {
+            agents_[i]->SetMoveTarget(moveTarget);
+            // FIXME: Should reimplement this using event callback, i.e. it should be application-specific to decide what is the desired crowd formation when they reach the target
+            if (navigationMesh_)
+                moveTarget = navigationMesh_->FindNearestPoint(position + Vector3(Random(-4.5f, 4.5f), 0.0f, Random(-4.5f, 4.5f)), Vector3(1.0f, 1.0f, 1.0f));
+        }
+    }
+}
+
+void DetourCrowdManager::ResetCrowdTarget(int startId, int endId)
+{
+    startId = Max(0, startId);
+    endId = Clamp(endId, startId, agents_.Size() - 1);
+    for (int i = startId; i <= endId; ++i)
+    {
+        if (agents_[i]->GetMaxAccel() > 0.f)
+            agents_[i]->ResetMoveTarget();
+    }
+}
+
+void DetourCrowdManager::SetCrowdVelocity(const Vector3& velocity, int startId, int endId)
+{
+    startId = Max(0, startId);
+    endId = Clamp(endId, startId, agents_.Size() - 1);
+    for (int i = startId; i <= endId; ++i)
+    {
+        if (agents_[i]->GetMaxAccel() > 0.f)
+            agents_[i]->SetMoveVelocity(velocity);
+    }
 }
 }
 
 
 float DetourCrowdManager::GetAreaCost(unsigned filterID, unsigned areaID) const
 float DetourCrowdManager::GetAreaCost(unsigned filterID, unsigned areaID) const
@@ -149,9 +184,17 @@ void DetourCrowdManager::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
             if (!ag->active)
             if (!ag->active)
                 continue;
                 continue;
 
 
+            // Draw CrowdAgent shape (from its radius & height)
+            CrowdAgent* crowdAgent = static_cast<CrowdAgent*>(ag->params.userData);
+            crowdAgent->DrawDebugGeometry(debug, depthTest);
+
+            // Draw move target if any
+            if (crowdAgent->GetTargetState() == CROWD_AGENT_TARGET_NONE)
+                continue;
+
             Color color(0.6f, 0.2f, 0.2f, 1.0f);
             Color color(0.6f, 0.2f, 0.2f, 1.0f);
 
 
-            // Render line to target:
+            // Draw line to target
             Vector3 pos1(ag->npos[0], ag->npos[1], ag->npos[2]);
             Vector3 pos1(ag->npos[0], ag->npos[1], ag->npos[2]);
             Vector3 pos2;
             Vector3 pos2;
             for (int i = 0; i < ag->ncorners; ++i)
             for (int i = 0; i < ag->ncorners; ++i)
@@ -167,12 +210,8 @@ void DetourCrowdManager::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
             pos2.z_ = ag->targetPos[2];
             pos2.z_ = ag->targetPos[2];
             debug->AddLine(pos1, pos2, color, depthTest);
             debug->AddLine(pos1, pos2, color, depthTest);
 
 
-            // Target circle
+            // Draw target circle
             debug->AddSphere(Sphere(pos2, 0.5f), color, depthTest);
             debug->AddSphere(Sphere(pos2, 0.5f), color, depthTest);
-
-            // Draw CrowdAgent shape (from its radius & height)
-            CrowdAgent* crowdAgent = static_cast<CrowdAgent*>(ag->params.userData);
-            crowdAgent->DrawDebugGeometry(debug, depthTest);
         }
         }
     }
     }
 }
 }
@@ -246,6 +285,7 @@ int DetourCrowdManager::AddAgent(CrowdAgent* agent, const Vector3& pos)
     if (!crowd_ || navigationMesh_.Expired())
     if (!crowd_ || navigationMesh_.Expired())
         return -1;
         return -1;
     dtCrowdAgentParams params;
     dtCrowdAgentParams params;
+    params.userData = agent;
     if (agent->radius_ <= 0.0f)
     if (agent->radius_ <= 0.0f)
         agent->radius_ = navigationMesh_->GetAgentRadius();
         agent->radius_ = navigationMesh_->GetAgentRadius();
     params.radius = agent->radius_;
     params.radius = agent->radius_;
@@ -444,11 +484,6 @@ const dtCrowdAgent* DetourCrowdManager::GetCrowdAgent(int agent)
     return crowd_ ? crowd_->getAgent(agent) : 0;
     return crowd_ ? crowd_->getAgent(agent) : 0;
 }
 }
 
 
-dtCrowd* DetourCrowdManager::GetCrowd()
-{
-    return crowd_;
-}
-
 void DetourCrowdManager::HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData)
 void DetourCrowdManager::HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
     using namespace SceneSubsystemUpdate;
     using namespace SceneSubsystemUpdate;

+ 10 - 4
Source/Urho3D/Navigation/DetourCrowdManager.h

@@ -43,7 +43,7 @@ enum NavigationQuality
 
 
 enum NavigationPushiness
 enum NavigationPushiness
 {
 {
-    PUSHINESS_LOW,
+    PUSHINESS_LOW = 0,
     PUSHINESS_MEDIUM,
     PUSHINESS_MEDIUM,
     PUSHINESS_HIGH
     PUSHINESS_HIGH
 };
 };
@@ -69,9 +69,15 @@ public:
     void SetAreaCost(unsigned filterTypeID, unsigned areaID, float weight);
     void SetAreaCost(unsigned filterTypeID, unsigned areaID, float weight);
     /// Set the maximum number of agents.
     /// Set the maximum number of agents.
     void SetMaxAgents(unsigned agentCt);
     void SetMaxAgents(unsigned agentCt);
+    /// Set the crowd move target. The move target is applied to all crowd agents within the id range, excluding crowd agent which does not have acceleration.
+    void SetCrowdTarget(const Vector3& position, int startId = 0, int endId = M_MAX_INT);
+    /// Reset the crowd move target to all crowd agents within the id range, excluding crowd agent which does not have acceleration.
+    void ResetCrowdTarget(int startId = 0, int endId = M_MAX_INT);
+    /// Set the crowd move velocity. The move velocity is applied to all crowd agents within the id range, excluding crowd agent which does not have acceleration.
+    void SetCrowdVelocity(const Vector3& velocity, int startId = 0, int endId = M_MAX_INT);
 
 
     /// Get the Navigation mesh assigned to the crowd.
     /// Get the Navigation mesh assigned to the crowd.
-    NavigationMesh* GetNavigationMesh();
+    NavigationMesh* GetNavigationMesh() const { return navigationMesh_; }
     /// 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 the maximum number of agents.
     /// Get the maximum number of agents.
@@ -84,7 +90,7 @@ public:
     /// Add debug geometry to the debug renderer.
     /// Add debug geometry to the debug renderer.
     void DrawDebugGeometry(bool depthTest);
     void DrawDebugGeometry(bool depthTest);
     /// Get the currently included agents.
     /// Get the currently included agents.
-    PODVector<CrowdAgent*> GetActiveAgents() const { return agents_; }
+    const PODVector<CrowdAgent*>& GetActiveAgents() const { return agents_; }
     /// Create detour crowd component for the specified navigation mesh.
     /// Create detour crowd component for the specified navigation mesh.
     bool CreateCrowd();
     bool CreateCrowd();
 
 
@@ -115,7 +121,7 @@ protected:
     /// Get the detour crowd agent.
     /// Get the detour crowd agent.
     const dtCrowdAgent* GetCrowdAgent(int agent);
     const dtCrowdAgent* GetCrowdAgent(int agent);
     /// Get the internal detour crowd component.
     /// Get the internal detour crowd component.
-    dtCrowd* GetCrowd();
+    dtCrowd* GetCrowd() const { return crowd_; }
 
 
 private:
 private:
     /// Handle the scene subsystem update event.
     /// Handle the scene subsystem update event.

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

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

+ 6 - 6
Source/Urho3D/Navigation/OffMeshConnection.cpp

@@ -41,9 +41,9 @@ OffMeshConnection::OffMeshConnection(Context* context) :
     endPointID_(0),
     endPointID_(0),
     radius_(DEFAULT_RADIUS),
     radius_(DEFAULT_RADIUS),
     bidirectional_(true),
     bidirectional_(true),
+    endPointDirty_(false),
     mask_(DEFAULT_MASK_FLAG),
     mask_(DEFAULT_MASK_FLAG),
-    areaId_(DEFAULT_AREA),
-    endPointDirty_(false)
+    areaId_(DEFAULT_AREA)
 {
 {
 }
 }
 
 
@@ -54,7 +54,7 @@ OffMeshConnection::~OffMeshConnection()
 void OffMeshConnection::RegisterObject(Context* context)
 void OffMeshConnection::RegisterObject(Context* context)
 {
 {
     context->RegisterFactory<OffMeshConnection>(NAVIGATION_CATEGORY);
     context->RegisterFactory<OffMeshConnection>(NAVIGATION_CATEGORY);
-    
+
     ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
     ATTRIBUTE("Endpoint NodeID", int, endPointID_, 0, AM_DEFAULT | AM_NODEID);
     ATTRIBUTE("Endpoint NodeID", int, endPointID_, 0, AM_DEFAULT | AM_NODEID);
     ATTRIBUTE("Radius", float, radius_, DEFAULT_RADIUS, AM_DEFAULT);
     ATTRIBUTE("Radius", float, radius_, DEFAULT_RADIUS, AM_DEFAULT);
@@ -66,7 +66,7 @@ void OffMeshConnection::RegisterObject(Context* context)
 void OffMeshConnection::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 void OffMeshConnection::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 {
 {
     Serializable::OnSetAttribute(attr, src);
     Serializable::OnSetAttribute(attr, src);
-    
+
     if (attr.offset_ == offsetof(OffMeshConnection, endPointID_))
     if (attr.offset_ == offsetof(OffMeshConnection, endPointID_))
         endPointDirty_ = true;
         endPointDirty_ = true;
 }
 }
@@ -85,10 +85,10 @@ void OffMeshConnection::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
 {
     if (!node_ || !endPoint_)
     if (!node_ || !endPoint_)
         return;
         return;
-    
+
     Vector3 start = node_->GetWorldPosition();
     Vector3 start = node_->GetWorldPosition();
     Vector3 end = endPoint_->GetWorldPosition();
     Vector3 end = endPoint_->GetWorldPosition();
-    
+
     debug->AddSphere(Sphere(start, radius_), Color::WHITE, depthTest);
     debug->AddSphere(Sphere(start, radius_), Color::WHITE, depthTest);
     debug->AddSphere(Sphere(end, radius_), Color::WHITE, depthTest);
     debug->AddSphere(Sphere(end, radius_), Color::WHITE, depthTest);
     debug->AddLine(start, end, Color::WHITE, depthTest);
     debug->AddLine(start, end, Color::WHITE, depthTest);

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

@@ -57,7 +57,7 @@ static CScriptArray* DynamicNavigationMeshFindPath(const Vector3& start, const V
 
 
 static CScriptArray* DetourCrowdManagerGetActiveAgents(DetourCrowdManager* crowd)
 static CScriptArray* DetourCrowdManagerGetActiveAgents(DetourCrowdManager* crowd)
 {
 {
-    PODVector<CrowdAgent*> agents = crowd->GetActiveAgents();
+    const PODVector<CrowdAgent*>& agents = crowd->GetActiveAgents();
     return VectorToHandleArray<CrowdAgent>(agents, "Array<CrowdAgent@>");
     return VectorToHandleArray<CrowdAgent>(agents, "Array<CrowdAgent@>");
 }
 }
 
 
@@ -185,6 +185,9 @@ void RegisterDetourCrowdManager(asIScriptEngine* engine)
     engine->RegisterObjectMethod("DetourCrowdManager", "Array<CrowdAgent@>@ GetActiveAgents()", asFUNCTION(DetourCrowdManagerGetActiveAgents), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("DetourCrowdManager", "Array<CrowdAgent@>@ GetActiveAgents()", asFUNCTION(DetourCrowdManagerGetActiveAgents), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("DetourCrowdManager", "void SetAreaCost(uint, uint, float)", asMETHOD(DetourCrowdManager, SetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod("DetourCrowdManager", "void SetAreaCost(uint, uint, float)", asMETHOD(DetourCrowdManager, SetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod("DetourCrowdManager", "float GetAreaCost(uint, uint)", asMETHOD(DetourCrowdManager, GetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod("DetourCrowdManager", "float GetAreaCost(uint, uint)", asMETHOD(DetourCrowdManager, GetAreaCost), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DetourCrowdManager", "void SetCrowdTarget(const Vector3&in, int startId = 0, int endId = M_MAX_INT)", asMETHOD(DetourCrowdManager, SetCrowdTarget), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DetourCrowdManager", "void ResetCrowdTarget(int startId = 0, int endId = M_MAX_INT)", asMETHOD(DetourCrowdManager, ResetCrowdTarget), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DetourCrowdManager", "void SetCrowdVelocity(const Vector3&in, int startId = 0, int endId = M_MAX_INT)", asMETHOD(DetourCrowdManager, SetCrowdVelocity), asCALL_THISCALL);
 }
 }
 
 
 void RegisterCrowdAgent(asIScriptEngine* engine)
 void RegisterCrowdAgent(asIScriptEngine* engine)
@@ -197,7 +200,6 @@ void RegisterCrowdAgent(asIScriptEngine* engine)
     engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_WAITINGFORPATH", CROWD_AGENT_TARGET_WAITINGFORPATH);
     engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_WAITINGFORPATH", CROWD_AGENT_TARGET_WAITINGFORPATH);
     engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_WAITINGFORQUEUE", CROWD_AGENT_TARGET_WAITINGFORQUEUE);
     engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_WAITINGFORQUEUE", CROWD_AGENT_TARGET_WAITINGFORQUEUE);
     engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_VELOCITY", CROWD_AGENT_TARGET_VELOCITY);
     engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_VELOCITY", CROWD_AGENT_TARGET_VELOCITY);
-    engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_ARRIVED", CROWD_AGENT_TARGET_ARRIVED);
 
 
     engine->RegisterEnum("CrowdAgentState");
     engine->RegisterEnum("CrowdAgentState");
     engine->RegisterEnumValue("CrowdAgentState", "CROWD_AGENT_INVALID", CROWD_AGENT_INVALID);
     engine->RegisterEnumValue("CrowdAgentState", "CROWD_AGENT_INVALID", CROWD_AGENT_INVALID);
@@ -218,8 +220,8 @@ void RegisterCrowdAgent(asIScriptEngine* engine)
     engine->RegisterObjectMethod("CrowdAgent", "void DrawDebugGeometry(bool)", asMETHODPR(CrowdAgent, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "void DrawDebugGeometry(bool)", asMETHODPR(CrowdAgent, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "uint get_navigationFilterType()", asMETHOD(CrowdAgent, GetNavigationFilterType), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "uint get_navigationFilterType()", asMETHOD(CrowdAgent, GetNavigationFilterType), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "void set_navigationFilterType(uint)", asMETHOD(CrowdAgent, SetNavigationFilterType), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "void set_navigationFilterType(uint)", asMETHOD(CrowdAgent, SetNavigationFilterType), asCALL_THISCALL);
-    engine->RegisterObjectMethod("CrowdAgent", "bool SetMoveTarget(const Vector3&in)", asMETHOD(CrowdAgent, SetMoveTarget), asCALL_THISCALL);
-    engine->RegisterObjectMethod("CrowdAgent", "bool SetMoveVelocity(const Vector3&in)", asMETHOD(CrowdAgent, SetMoveVelocity), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdAgent", "void SetMoveTarget(const Vector3&in)", asMETHOD(CrowdAgent, SetMoveTarget), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdAgent", "void SetMoveVelocity(const Vector3&in)", asMETHOD(CrowdAgent, SetMoveVelocity), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "void set_updateNodePosition(bool)", asMETHOD(CrowdAgent, SetUpdateNodePosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "void set_updateNodePosition(bool)", asMETHOD(CrowdAgent, SetUpdateNodePosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "bool get_updateNodePosition() const", asMETHOD(CrowdAgent, GetUpdateNodePosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "bool get_updateNodePosition() const", asMETHOD(CrowdAgent, GetUpdateNodePosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "void set_maxAccel(float)", asMETHOD(CrowdAgent, SetMaxAccel), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "void set_maxAccel(float)", asMETHOD(CrowdAgent, SetMaxAccel), asCALL_THISCALL);
@@ -230,6 +232,7 @@ void RegisterCrowdAgent(asIScriptEngine* engine)
     engine->RegisterObjectMethod("CrowdAgent", "NavigationAvoidanceQuality get_navigationQuality()", asMETHOD(CrowdAgent, GetNavigationQuality), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "NavigationAvoidanceQuality get_navigationQuality()", asMETHOD(CrowdAgent, GetNavigationQuality), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "void set_navigationPushiness(NavigationPushiness)", asMETHOD(CrowdAgent, SetNavigationPushiness), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "void set_navigationPushiness(NavigationPushiness)", asMETHOD(CrowdAgent, SetNavigationPushiness), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "NavigationPushiness get_navigationPushiness()", asMETHOD(CrowdAgent, GetNavigationPushiness), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "NavigationPushiness get_navigationPushiness()", asMETHOD(CrowdAgent, GetNavigationPushiness), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CrowdAgent", "bool get_arrived() const", asMETHOD(CrowdAgent, HasArrived), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "Vector3 get_desiredVelocity() const", asMETHOD(CrowdAgent, GetDesiredVelocity), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "Vector3 get_desiredVelocity() const", asMETHOD(CrowdAgent, GetDesiredVelocity), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "Vector3 get_actualVelocity() const", asMETHOD(CrowdAgent, GetActualVelocity), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "Vector3 get_actualVelocity() const", asMETHOD(CrowdAgent, GetActualVelocity), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "Vector3 get_targetPosition() const", asMETHOD(CrowdAgent, GetTargetPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("CrowdAgent", "Vector3 get_targetPosition() const", asMETHOD(CrowdAgent, GetTargetPosition), asCALL_THISCALL);

+ 67 - 79
bin/Data/LuaScripts/39_CrowdNavigation.lua

@@ -11,10 +11,7 @@
 
 
 require "LuaScripts/Utilities/Sample"
 require "LuaScripts/Utilities/Sample"
 
 
-local navMesh = nil
-local crowdManager = nil
-local agents = {}
-local NUM_BARRELS = 20
+local INSTRUCTION = "instructionText"
 
 
 function Start()
 function Start()
     -- Execute the common startup for samples
     -- Execute the common startup for samples
@@ -68,9 +65,9 @@ function CreateScene()
 
 
     -- Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
     -- Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
     -- rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
     -- rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
-    local boxes = {}
+    local boxGroup = scene_:CreateChild("Boxes")
     for i = 1, 20 do
     for i = 1, 20 do
-        local boxNode = scene_:CreateChild("Box")
+        local boxNode = boxGroup:CreateChild("Box")
         local size = 1.0 + Random(10.0)
         local size = 1.0 + Random(10.0)
         boxNode.position = Vector3(Random(80.0) - 40.0, size * 0.5, Random(80.0) - 40.0)
         boxNode.position = Vector3(Random(80.0) - 40.0, size * 0.5, Random(80.0) - 40.0)
         boxNode:SetScale(size)
         boxNode:SetScale(size)
@@ -80,12 +77,11 @@ function CreateScene()
         boxObject.castShadows = true
         boxObject.castShadows = true
         if size >= 3.0 then
         if size >= 3.0 then
             boxObject.occluder = true
             boxObject.occluder = true
-            table.insert(boxes, boxNode)
         end
         end
     end
     end
 
 
     -- Create a DynamicNavigationMesh component to the scene root
     -- Create a DynamicNavigationMesh component to the scene root
-    navMesh = scene_:CreateComponent("DynamicNavigationMesh")
+    local navMesh = scene_:CreateComponent("DynamicNavigationMesh")
     -- Enable drawing debug geometry for obstacles and off-mesh connections
     -- Enable drawing debug geometry for obstacles and off-mesh connections
     navMesh.drawObstacles = true
     navMesh.drawObstacles = true
     navMesh.drawOffMeshConnections = true
     navMesh.drawOffMeshConnections = true
@@ -107,7 +103,7 @@ function CreateScene()
     -- Create an off-mesh connection for each box to make it climbable (tiny boxes are skipped).
     -- Create an off-mesh connection for each box to make it climbable (tiny boxes are skipped).
     -- Note that OffMeshConnections must be added before building the navMesh, but as we are adding Obstacles next, tiles will be automatically rebuilt.
     -- Note that OffMeshConnections must be added before building the navMesh, but as we are adding Obstacles next, tiles will be automatically rebuilt.
     -- Creating connections post-build here allows us to use FindNearestPoint() to procedurally set accurate positions for the connection
     -- Creating connections post-build here allows us to use FindNearestPoint() to procedurally set accurate positions for the connection
-    CreateBoxOffMeshConnections(boxes)
+    CreateBoxOffMeshConnections(navMesh, boxGroup)
 
 
     -- Create some mushrooms as obstacles. Note that obstacles are non-walkable areas
     -- Create some mushrooms as obstacles. Note that obstacles are non-walkable areas
     for i = 1, 100 do
     for i = 1, 100 do
@@ -115,10 +111,10 @@ function CreateScene()
     end
     end
 
 
     -- Create a DetourCrowdManager component to the scene root (mandatory for crowd agents)
     -- Create a DetourCrowdManager component to the scene root (mandatory for crowd agents)
-    crowdManager = scene_:CreateComponent("DetourCrowdManager")
+    scene_:CreateComponent("DetourCrowdManager")
 
 
     -- Create some movable barrels. We create them as crowd agents, as for moving entities it is less expensive and more convenient than using obstacles
     -- Create some movable barrels. We create them as crowd agents, as for moving entities it is less expensive and more convenient than using obstacles
-    CreateMovingBarrels()
+    CreateMovingBarrels(navMesh)
 
 
     -- Create Jack node as crowd agent
     -- Create Jack node as crowd agent
     SpawnJack(Vector3(-5, 0, 20))
     SpawnJack(Vector3(-5, 0, 20))
@@ -129,8 +125,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()
@@ -144,14 +142,15 @@ function CreateUI()
     cursor:SetPosition(graphics.width / 2, graphics.height / 2)
     cursor:SetPosition(graphics.width / 2, graphics.height / 2)
 
 
     -- Construct new Text object, set string to display and font to use
     -- Construct new Text object, set string to display and font to use
-    local instructionText = ui.root:CreateChild("Text")
+    local instructionText = ui.root:CreateChild("Text", INSTRUCTION)
     instructionText.text = "Use WASD keys to move, RMB to rotate view\n"..
     instructionText.text = "Use WASD keys to move, RMB to rotate view\n"..
         "LMB to set destination, SHIFT+LMB to spawn a Jack\n"..
         "LMB to set destination, SHIFT+LMB to spawn a Jack\n"..
         "CTRL+LMB to teleport main agent\n"..
         "CTRL+LMB to teleport main agent\n"..
         "MMB to add obstacles or remove obstacles/agents\n"..
         "MMB to add obstacles or remove obstacles/agents\n"..
         "F5 to save scene, F7 to load\n"..
         "F5 to save scene, F7 to load\n"..
-        "Space to toggle debug geometry"
-    instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 12)
+        "Space to toggle debug geometry\n"..
+        "F12 to toggle this instruction text"
+    instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
     -- The text has multiple rows. Center them in relation to each other
     -- The text has multiple rows. Center them in relation to each other
     instructionText.textAlignment = HA_CENTER
     instructionText.textAlignment = HA_CENTER
 
 
@@ -171,13 +170,15 @@ function SubscribeToEvents()
     -- Subscribe HandleUpdate() function for processing update events
     -- Subscribe HandleUpdate() function for processing update events
     SubscribeToEvent("Update", "HandleUpdate")
     SubscribeToEvent("Update", "HandleUpdate")
 
 
-    -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
-    -- debug geometry
+    -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request debug geometry
     SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
     SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
 
 
     -- Subscribe HandleCrowdAgentFailure() function for resolving invalidation issues with agents, during which we
     -- Subscribe HandleCrowdAgentFailure() function for resolving invalidation issues with agents, during which we
     -- use a larger extents for finding a point on the navmesh to fix the agent's position
     -- use a larger extents for finding a point on the navmesh to fix the agent's position
     SubscribeToEvent("CrowdAgentFailure", "HandleCrowdAgentFailure")
     SubscribeToEvent("CrowdAgentFailure", "HandleCrowdAgentFailure")
+
+    -- Subscribe HandleCrowdAgentReposition() function for controlling the animation
+    SubscribeToEvent("CrowdAgentReposition", "HandleCrowdAgentReposition")
 end
 end
 
 
 function SpawnJack(pos)
 function SpawnJack(pos)
@@ -192,9 +193,8 @@ function SpawnJack(pos)
     -- Create a CrowdAgent component and set its height and realistic max speed/acceleration. Use default radius
     -- Create a CrowdAgent component and set its height and realistic max speed/acceleration. Use default radius
     local agent = jackNode:CreateComponent("CrowdAgent")
     local agent = jackNode:CreateComponent("CrowdAgent")
     agent.height = 2.0
     agent.height = 2.0
-    agent.maxSpeed = 4.0
+    agent.maxSpeed = 3.0
     agent.maxAccel = 100.0
     agent.maxAccel = 100.0
-    agents = crowdManager:GetActiveAgents() -- Update agents container
 end
 end
 
 
 function CreateMushroom(pos)
 function CreateMushroom(pos)
@@ -213,7 +213,8 @@ function CreateMushroom(pos)
     obstacle.height = mushroomNode.scale.y
     obstacle.height = mushroomNode.scale.y
 end
 end
 
 
-function CreateBoxOffMeshConnections(boxes)
+function CreateBoxOffMeshConnections(navMesh, boxGroup)
+    boxes = boxGroup:GetChildren()
     for i, box in ipairs(boxes) do
     for i, box in ipairs(boxes) do
         local boxPos = box.position
         local boxPos = box.position
         local boxHalfSize = box.scale.x / 2
         local boxHalfSize = box.scale.x / 2
@@ -230,14 +231,14 @@ function CreateBoxOffMeshConnections(boxes)
     end
     end
 end
 end
 
 
-function CreateMovingBarrels()
+function CreateMovingBarrels(navMesh)
     local barrel = scene_:CreateChild("Barrel")
     local barrel = scene_:CreateChild("Barrel")
     local model = barrel:CreateComponent("StaticModel")
     local model = barrel:CreateComponent("StaticModel")
     model.model = cache:GetResource("Model", "Models/Cylinder.mdl")
     model.model = cache:GetResource("Model", "Models/Cylinder.mdl")
     model.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
     model.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
     model.material:SetTexture(TU_DIFFUSE, cache:GetResource("Texture2D", "Textures/TerrainDetail2.dds"))
     model.material:SetTexture(TU_DIFFUSE, cache:GetResource("Texture2D", "Textures/TerrainDetail2.dds"))
     model.castShadows = true
     model.castShadows = true
-    for i = 1, NUM_BARRELS do
+    for i = 1, 20 do
         local clone = barrel:Clone()
         local clone = barrel:Clone()
         local size = 0.5 + Random(1)
         local size = 0.5 + Random(1)
         clone.scale = Vector3(size / 1.5, size * 2, size / 1.5)
         clone.scale = Vector3(size / 1.5, size * 2, size / 1.5)
@@ -249,38 +250,19 @@ function CreateMovingBarrels()
     barrel:Remove()
     barrel:Remove()
 end
 end
 
 
-function SetPathPoint()
+function SetPathPoint(spawning)
     local hitPos, hitDrawable = Raycast(250.0)
     local hitPos, hitDrawable = Raycast(250.0)
 
 
     if hitDrawable then
     if hitDrawable then
+        local navMesh = scene_:GetComponent("DynamicNavigationMesh")
         local pathPos = navMesh:FindNearestPoint(hitPos, Vector3.ONE)
         local pathPos = navMesh:FindNearestPoint(hitPos, Vector3.ONE)
 
 
-        if input:GetQualifierDown(QUAL_SHIFT) then
-            -- Spawn a Jack
+        if spawning then
+            -- Spawn a jack at the target position
             SpawnJack(pathPos)
             SpawnJack(pathPos)
-
-        elseif input:GetQualifierDown(QUAL_CTRL) and table.maxn(agents) > NUM_BARRELS then
-            -- Teleport
-            local agent = agents[NUM_BARRELS + 1] -- Get first Jack agent
-            local node = agent.node
-            node:LookAt(pathPos) -- Face target
-            agent:SetMoveVelocity(Vector3.ZERO) -- Stop agent
-            node.position = pathPos
-
         else
         else
-            -- Set target position and init agents' move
-            for i = NUM_BARRELS + 1, table.maxn(agents) do
-                local agent = agents[i]
-                if i == NUM_BARRELS + 1 then
-                    -- The first Jack agent will always move to the exact position and is strong enough to push barrels and his siblings (no avoidance)
-                    agent.navigationPushiness = PUSHINESS_HIGH
-                    agent:SetMoveTarget(pathPos)
-                else
-                    -- Other Jack agents will move to a random point nearby
-                    local targetPos = navMesh:FindNearestPoint(pathPos + Vector3(Random(-4.5, 4.5), 0, Random(-4.5, 4.5)), Vector3.ONE)
-                    agent:SetMoveTarget(targetPos)
-                end
-            end
+            -- Set crowd agents target position
+            scene_:GetComponent("DetourCrowdManager"):SetCrowdTarget(pathPos)
         end
         end
     end
     end
 end
 end
@@ -295,7 +277,6 @@ function AddOrRemoveObject()
             hitNode:Remove()
             hitNode:Remove()
         elseif hitNode.name == "Jack" then
         elseif hitNode.name == "Jack" then
             hitNode:Remove()
             hitNode:Remove()
-            agents = crowdManager:GetActiveAgents() -- Update agents container
         else
         else
             CreateMushroom(hitPos)
             CreateMushroom(hitPos)
         end
         end
@@ -360,30 +341,29 @@ function MoveCamera(timeStep)
     if input:GetKeyDown(KEY_D) then
     if input:GetKeyDown(KEY_D) then
         cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
         cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
     end
     end
+
     -- Set destination or spawn a jack with left mouse button
     -- Set destination or spawn a jack with left mouse button
     if input:GetMouseButtonPress(MOUSEB_LEFT) then
     if input:GetMouseButtonPress(MOUSEB_LEFT) then
-        SetPathPoint()
-    end
+        SetPathPoint(input:GetQualifierDown(QUAL_SHIFT))
     -- Add new obstacle or remove existing obstacle/agent with middle mouse button
     -- Add new obstacle or remove existing obstacle/agent with middle mouse button
-    if input:GetMouseButtonPress(MOUSEB_MIDDLE) then
+    elseif input:GetMouseButtonPress(MOUSEB_MIDDLE) then
         AddOrRemoveObject()
         AddOrRemoveObject()
     end
     end
 
 
     -- Check for loading/saving the scene from/to the file Data/Scenes/CrowdNavigation.xml relative to the executable directory
     -- Check for loading/saving the scene from/to the file Data/Scenes/CrowdNavigation.xml relative to the executable directory
     if input:GetKeyPress(KEY_F5) then
     if input:GetKeyPress(KEY_F5) then
         scene_:SaveXML(fileSystem:GetProgramDir().."Data/Scenes/CrowdNavigation.xml")
         scene_:SaveXML(fileSystem:GetProgramDir().."Data/Scenes/CrowdNavigation.xml")
-    end
-    if input:GetKeyPress(KEY_F7) then
+    elseif input:GetKeyPress(KEY_F7) then
         scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/CrowdNavigation.xml")
         scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/CrowdNavigation.xml")
-        -- After reload, reacquire navMesh, crowd manager & agents
-        navMesh = scene_:GetComponent("DynamicNavigationMesh")
-        crowdManager = scene_:GetComponent("DetourCrowdManager")
-        agents = crowdManager:GetActiveAgents()
-    end
 
 
     -- Toggle debug geometry with space
     -- Toggle debug geometry with space
-    if input:GetKeyPress(KEY_SPACE) then
+    elseif input:GetKeyPress(KEY_SPACE) then
         drawDebug = not drawDebug
         drawDebug = not drawDebug
+
+    -- Toggle instruction text with F12
+    elseif input:GetKeyPress(KEY_F12) then
+        instruction = ui.root:GetChild(INSTRUCTION)
+        instruction.visible = not instruction.visible
     end
     end
 end
 end
 
 
@@ -393,30 +373,14 @@ function HandleUpdate(eventType, eventData)
 
 
     -- Move the camera, scale movement with time step
     -- Move the camera, scale movement with time step
     MoveCamera(timeStep)
     MoveCamera(timeStep)
-
-    -- Make the Jack CrowdAgents face the direction of their velocity and update animation
-    for i = NUM_BARRELS + 1, table.maxn(agents) do
-        local agent = agents[i]
-        local node = agent.node
-        local animCtrl = node:GetComponent("AnimationController")
-        local velocity = agent.actualVelocity
-
-        if velocity:Length() < 0.6 then
-            animCtrl:Stop("Models/Jack_Walk.ani", 0.2)
-        else
-            node.worldDirection = velocity
-            animCtrl:PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.2)
-            animCtrl:SetSpeed("Models/Jack_Walk.ani", velocity:Length() * 0.3)
-        end
-    end
 end
 end
 
 
 function HandlePostRenderUpdate(eventType, eventData)
 function HandlePostRenderUpdate(eventType, eventData)
     if drawDebug then
     if drawDebug then
         -- Visualize navigation mesh, obstacles and off-mesh connections
         -- Visualize navigation mesh, obstacles and off-mesh connections
-        navMesh:DrawDebugGeometry(true)
+        scene_:GetComponent("DynamicNavigationMesh"):DrawDebugGeometry(true)
         -- Visualize agents' path and position to reach
         -- Visualize agents' path and position to reach
-        crowdManager:DrawDebugGeometry(true)
+        scene_:GetComponent("DetourCrowdManager"):DrawDebugGeometry(true)
     end
     end
 end
 end
 
 
@@ -427,12 +391,36 @@ function HandleCrowdAgentFailure(eventType, eventData)
     -- If the agent's state is invalid, likely from spawning on the side of a box, find a point in a larger area
     -- If the agent's state is invalid, likely from spawning on the side of a box, find a point in a larger area
     if agentState == CROWD_AGENT_INVALID then
     if agentState == CROWD_AGENT_INVALID then
         -- Get a point on the navmesh using more generous extents
         -- Get a point on the navmesh using more generous extents
-        local newPos = navMesh:FindNearestPoint(node.position, Vector3(5, 5, 5))
+        local newPos = scene_:GetComponent("DynamicNavigationMesh"):FindNearestPoint(node.position, Vector3(5, 5, 5))
         -- Set the new node position, CrowdAgent component will automatically reset the state of the agent
         -- Set the new node position, CrowdAgent component will automatically reset the state of the agent
         node.position = newPos
         node.position = newPos
     end
     end
 end
 end
 
 
+function HandleCrowdAgentReposition(eventType, eventData)
+    local WALKING_ANI = "Models/Jack_Walk.ani"
+
+    local node = eventData:GetPtr("Node", "Node")
+    local agent = eventData:GetPtr("CrowdAgent", "CrowdAgent")
+    local velocity = eventData:GetVector3("Velocity")
+
+    -- Only Jack agent has animation controller
+    local animCtrl = node:GetComponent("AnimationController")
+    if animCtrl ~= nil then
+        local speed = velocity:Length()
+        if speed < agent.radius then
+            -- If speed is too low then stopping the animation
+            animCtrl:Stop(WALKING_ANI, 0.8)
+        else
+            -- Face the direction of its velocity
+            node.worldDirection = velocity
+            animCtrl:Play(WALKING_ANI, 0, true)
+            -- Throttle the animation speed based on agent speed ratio (ratio = 1 is full throttle)
+            animCtrl:SetSpeed(WALKING_ANI, speed / agent.maxSpeed)
+        end
+    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

+ 71 - 90
bin/Data/Scripts/39_CrowdNavigation.as

@@ -11,9 +11,7 @@
 
 
 #include "Scripts/Utilities/Sample.as"
 #include "Scripts/Utilities/Sample.as"
 
 
-DynamicNavigationMesh@ navMesh;
-DetourCrowdManager@ crowdManager;
-const int NUM_BARRELS = 20;
+const String INSTRUCTION("instructionText");
 
 
 void Start()
 void Start()
 {
 {
@@ -70,10 +68,10 @@ void CreateScene()
 
 
     // Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
     // Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
     // rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
     // rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
-    Array<Node@> boxes;
+    Node@ boxGroup = scene_.CreateChild("Boxes");
     for (uint i = 0; i < 20; ++i)
     for (uint i = 0; i < 20; ++i)
     {
     {
-        Node@ boxNode = scene_.CreateChild("Box");
+        Node@ boxNode = boxGroup.CreateChild("Box");
         float size = 1.0f + Random(10.0f);
         float size = 1.0f + Random(10.0f);
         boxNode.position = Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f);
         boxNode.position = Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f);
         boxNode.SetScale(size);
         boxNode.SetScale(size);
@@ -82,14 +80,11 @@ void CreateScene()
         boxObject.material = cache.GetResource("Material", "Materials/Stone.xml");
         boxObject.material = cache.GetResource("Material", "Materials/Stone.xml");
         boxObject.castShadows = true;
         boxObject.castShadows = true;
         if (size >= 3.0f)
         if (size >= 3.0f)
-        {
             boxObject.occluder = true;
             boxObject.occluder = true;
-            boxes.Push(boxNode);
-        }
     }
     }
 
 
     // Create a DynamicNavigationMesh component to the scene root
     // Create a DynamicNavigationMesh component to the scene root
-    navMesh = scene_.CreateComponent("DynamicNavigationMesh");
+    DynamicNavigationMesh@ navMesh = scene_.CreateComponent("DynamicNavigationMesh");
     // Enable drawing debug geometry for obstacles and off-mesh connections
     // Enable drawing debug geometry for obstacles and off-mesh connections
     navMesh.drawObstacles = true;
     navMesh.drawObstacles = true;
     navMesh.drawOffMeshConnections = true;
     navMesh.drawOffMeshConnections = true;
@@ -111,17 +106,17 @@ void CreateScene()
     // Create an off-mesh connection to each box to make it climbable (tiny boxes are skipped). A connection is built from 2 nodes.
     // Create an off-mesh connection to each box to make it climbable (tiny boxes are skipped). A connection is built from 2 nodes.
     // Note that OffMeshConnections must be added before building the navMesh, but as we are adding Obstacles next, tiles will be automatically rebuilt.
     // Note that OffMeshConnections must be added before building the navMesh, but as we are adding Obstacles next, tiles will be automatically rebuilt.
     // Creating connections post-build here allows us to use FindNearestPoint() to procedurally set accurate positions for the connection
     // Creating connections post-build here allows us to use FindNearestPoint() to procedurally set accurate positions for the connection
-    CreateBoxOffMeshConnections(boxes);
+    CreateBoxOffMeshConnections(navMesh, boxGroup);
 
 
     // Create some mushrooms as obstacles. Note that obstacles are non-walkable areas
     // Create some mushrooms as obstacles. Note that obstacles are non-walkable areas
     for (uint i = 0; i < 100; ++i)
     for (uint i = 0; i < 100; ++i)
         CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
         CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
 
 
     // Create a DetourCrowdManager component to the scene root (mandatory for crowd agents)
     // Create a DetourCrowdManager component to the scene root (mandatory for crowd agents)
-    crowdManager = scene_.CreateComponent("DetourCrowdManager");
+    scene_.CreateComponent("DetourCrowdManager");
 
 
     // Create some movable barrels. We create them as crowd agents, as for moving entities it is less expensive and more convenient than using obstacles
     // Create some movable barrels. We create them as crowd agents, as for moving entities it is less expensive and more convenient than using obstacles
-    CreateMovingBarrels();
+    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));
@@ -132,8 +127,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()
@@ -148,15 +145,16 @@ void CreateUI()
     cursor.SetPosition(graphics.width / 2, graphics.height / 2);
     cursor.SetPosition(graphics.width / 2, graphics.height / 2);
 
 
     // Construct new Text object, set string to display and font to use
     // Construct new Text object, set string to display and font to use
-    Text@ instructionText = ui.root.CreateChild("Text");
+    Text@ instructionText = ui.root.CreateChild("Text", INSTRUCTION);
     instructionText.text =
     instructionText.text =
         "Use WASD keys to move, RMB to rotate view\n"
         "Use WASD keys to move, RMB to rotate view\n"
         "LMB to set destination, SHIFT+LMB to spawn a Jack\n"
         "LMB to set destination, SHIFT+LMB to spawn a Jack\n"
         "CTRL+LMB to teleport main agent\n"
         "CTRL+LMB to teleport main agent\n"
         "MMB to add obstacles or remove obstacles/agents\n"
         "MMB to add obstacles or remove obstacles/agents\n"
         "F5 to save scene, F7 to load\n"
         "F5 to save scene, F7 to load\n"
-        "Space to toggle debug geometry";
-    instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 12);
+        "Space to toggle debug geometry\n"
+        "F12 to toggle this instruction text";
+    instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
     // The text has multiple rows. Center them in relation to each other
     // The text has multiple rows. Center them in relation to each other
     instructionText.textAlignment = HA_CENTER;
     instructionText.textAlignment = HA_CENTER;
 
 
@@ -178,13 +176,15 @@ void SubscribeToEvents()
     // Subscribe HandleUpdate() function for processing update events
     // Subscribe HandleUpdate() function for processing update events
     SubscribeToEvent("Update", "HandleUpdate");
     SubscribeToEvent("Update", "HandleUpdate");
 
 
-    // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
-    // debug geometry
+    // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request debug geometry
     SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
     SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
 
 
     // Subscribe HandleCrowdAgentFailure() function for resolving invalidation issues with agents, during which we
     // Subscribe HandleCrowdAgentFailure() function for resolving invalidation issues with agents, during which we
     // use a larger extents for finding a point on the navmesh to fix the agent's position
     // use a larger extents for finding a point on the navmesh to fix the agent's position
     SubscribeToEvent("CrowdAgentFailure", "HandleCrowdAgentFailure");
     SubscribeToEvent("CrowdAgentFailure", "HandleCrowdAgentFailure");
+
+    // Subscribe HandleCrowdAgentReposition() function for controlling the animation
+    SubscribeToEvent("CrowdAgentReposition", "HandleCrowdAgentReposition");
 }
 }
 
 
 void CreateMushroom(const Vector3& pos)
 void CreateMushroom(const Vector3& pos)
@@ -217,12 +217,13 @@ void SpawnJack(const Vector3& pos)
     // Create a CrowdAgent component and set its height and realistic max speed/acceleration. Use default radius
     // Create a CrowdAgent component and set its height and realistic max speed/acceleration. Use default radius
     CrowdAgent@ agent = jackNode.CreateComponent("CrowdAgent");
     CrowdAgent@ agent = jackNode.CreateComponent("CrowdAgent");
     agent.height = 2.0f;
     agent.height = 2.0f;
-    agent.maxSpeed = 4.0f;
+    agent.maxSpeed = 3.0f;
     agent.maxAccel = 100.0f;
     agent.maxAccel = 100.0f;
 }
 }
 
 
-void CreateBoxOffMeshConnections(Array<Node@> boxes)
+void CreateBoxOffMeshConnections(DynamicNavigationMesh@ navMesh, Node@ boxGroup)
 {
 {
+    Array<Node@>@ boxes = boxGroup.GetChildren();
     for (uint i=0; i < boxes.length; ++i)
     for (uint i=0; i < boxes.length; ++i)
     {
     {
         Node@ box = boxes[i];
         Node@ box = boxes[i];
@@ -241,7 +242,7 @@ void CreateBoxOffMeshConnections(Array<Node@> boxes)
     }
     }
 }
 }
 
 
-void CreateMovingBarrels()
+void CreateMovingBarrels(DynamicNavigationMesh@ navMesh)
 {
 {
     Node@ barrel = scene_.CreateChild("Barrel");
     Node@ barrel = scene_.CreateChild("Barrel");
     StaticModel@ model = barrel.CreateComponent("StaticModel");
     StaticModel@ model = barrel.CreateComponent("StaticModel");
@@ -250,7 +251,7 @@ void CreateMovingBarrels()
     model.material = material;
     model.material = material;
     material.textures[TU_DIFFUSE] = cache.GetResource("Texture2D", "Textures/TerrainDetail2.dds");
     material.textures[TU_DIFFUSE] = cache.GetResource("Texture2D", "Textures/TerrainDetail2.dds");
     model.castShadows = true;
     model.castShadows = true;
-    for (uint i = 0;  i < NUM_BARRELS; ++i)
+    for (uint i = 0;  i < 20; ++i)
     {
     {
         Node@ clone = barrel.Clone();
         Node@ clone = barrel.Clone();
         float size = 0.5 + Random(1);
         float size = 0.5 + Random(1);
@@ -263,52 +264,21 @@ void CreateMovingBarrels()
     barrel.Remove();
     barrel.Remove();
 }
 }
 
 
-void SetPathPoint()
+void SetPathPoint(bool spawning)
 {
 {
     Vector3 hitPos;
     Vector3 hitPos;
     Drawable@ hitDrawable;
     Drawable@ hitDrawable;
 
 
     if (Raycast(250.0f, hitPos, hitDrawable))
     if (Raycast(250.0f, hitPos, hitDrawable))
     {
     {
+        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));
-
-        if (input.qualifierDown[QUAL_SHIFT])
-            // Spawn a jack
+        if (spawning)
+            // Spawn a jack at the target position
             SpawnJack(pathPos);
             SpawnJack(pathPos);
-        else if (input.qualifierDown[QUAL_CTRL])
-        {
-            // Teleport
-            Array<CrowdAgent@>@ agents = crowdManager.GetActiveAgents();
-            if (agents.length <= NUM_BARRELS)
-                return;
-            CrowdAgent@ agent = agents[NUM_BARRELS]; // Get first Jack agent
-            Node@ node = agent.node;
-            node.LookAt(pathPos); // Face target
-            agent.SetMoveVelocity(Vector3(0.0, 0.0, 0.0)); // Stop agent
-            node.position = pathPos;
-        }
         else
         else
-        {
-            // Set target position and init agents' move
-            Array<CrowdAgent@>@ agents = crowdManager.GetActiveAgents();
-            for (uint i = NUM_BARRELS; i < agents.length; ++i)
-            {
-                CrowdAgent@ agent = agents[i];
-
-                if (i == NUM_BARRELS)
-                {
-                    // The first Jack agent will always move to the exact position and is strong enough to push barrels and his siblings (no avoidance)
-                    agent.navigationPushiness = PUSHINESS_HIGH;
-                    agent.SetMoveTarget(pathPos);
-                }
-                else
-                {
-                    // Other Jack agents will move to a random point nearby
-                    Vector3 targetPos = navMesh.FindNearestPoint(pathPos + Vector3(Random(-4.5f, 4.5f), 0.0f, Random(-4.5, 4.5f)), Vector3(1.0f, 1.0f, 1.0f));
-                    agent.SetMoveTarget(targetPos);
-                }
-            }
-        }
+            // Set crowd agents target position
+            cast<DetourCrowdManager>(scene_.GetComponent("DetourCrowdManager")).SetCrowdTarget(pathPos);
     }
     }
 }
 }
 
 
@@ -395,9 +365,9 @@ void MoveCamera(float timeStep)
 
 
     // Set destination or spawn a jack with left mouse button
     // Set destination or spawn a jack with left mouse button
     if (input.mouseButtonPress[MOUSEB_LEFT])
     if (input.mouseButtonPress[MOUSEB_LEFT])
-        SetPathPoint();
+        SetPathPoint(input.qualifierDown[QUAL_SHIFT]);
     // Add new obstacle or remove existing obstacle/agent with middle mouse button
     // Add new obstacle or remove existing obstacle/agent with middle mouse button
-    if (input.mouseButtonPress[MOUSEB_MIDDLE])
+    else if (input.mouseButtonPress[MOUSEB_MIDDLE])
         AddOrRemoveObject();
         AddOrRemoveObject();
 
 
     // Check for loading/saving the scene from/to the file Data/Scenes/CrowdNavigation.xml relative to the executable directory
     // Check for loading/saving the scene from/to the file Data/Scenes/CrowdNavigation.xml relative to the executable directory
@@ -406,19 +376,22 @@ void MoveCamera(float timeStep)
         File saveFile(fileSystem.programDir + "Data/Scenes/CrowdNavigation.xml", FILE_WRITE);
         File saveFile(fileSystem.programDir + "Data/Scenes/CrowdNavigation.xml", FILE_WRITE);
         scene_.SaveXML(saveFile);
         scene_.SaveXML(saveFile);
     }
     }
-    if (input.keyPress[KEY_F7])
+    else if (input.keyPress[KEY_F7])
     {
     {
         File loadFile(fileSystem.programDir + "Data/Scenes/CrowdNavigation.xml", FILE_READ);
         File loadFile(fileSystem.programDir + "Data/Scenes/CrowdNavigation.xml", FILE_READ);
         scene_.LoadXML(loadFile);
         scene_.LoadXML(loadFile);
-
-        // After reload, reacquire navMesh & crowd manager
-        navMesh = scene_.GetComponent("DynamicNavigationMesh");
-        crowdManager = scene_.GetComponent("DetourCrowdManager");
     }
     }
 
 
     // Toggle debug geometry with space
     // Toggle debug geometry with space
-    if (input.keyPress[KEY_SPACE])
+    else if (input.keyPress[KEY_SPACE])
         drawDebug = !drawDebug;
         drawDebug = !drawDebug;
+
+    // Toggle instruction text with F12
+    else if (input.keyPress[KEY_F12])
+    {
+        UIElement@ instruction = ui.root.GetChild(INSTRUCTION);
+        instruction.visible = !instruction.visible;
+    }
 }
 }
 
 
 void HandleUpdate(StringHash eventType, VariantMap& eventData)
 void HandleUpdate(StringHash eventType, VariantMap& eventData)
@@ -428,25 +401,6 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
 
 
     // Move the camera, scale movement with time step
     // Move the camera, scale movement with time step
     MoveCamera(timeStep);
     MoveCamera(timeStep);
-
-    // Make the Jack CrowdAgents face the direction of their velocity and update animation
-    Array<CrowdAgent@>@ agents = crowdManager.GetActiveAgents();
-    for (uint i = NUM_BARRELS; i < agents.length; ++i)
-    {
-        CrowdAgent@ agent = agents[i];
-        Node@ node = agent.node;
-        AnimationController@ animCtrl = node.GetComponent("AnimationController");
-        Vector3 velocity = agent.actualVelocity;
-
-        if (velocity.length < 0.6)
-            animCtrl.Stop("Models/Jack_Walk.ani", 0.2);
-        else
-        {
-            node.worldDirection = velocity;
-            animCtrl.PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.2);
-            animCtrl.SetSpeed("Models/Jack_Walk.ani", velocity.length * 0.3);
-        }
-    }
 }
 }
 
 
 void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
 void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
@@ -454,9 +408,9 @@ void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
     if (drawDebug)
     if (drawDebug)
     {
     {
         // Visualize navigation mesh, obstacles and off-mesh connections
         // Visualize navigation mesh, obstacles and off-mesh connections
-        navMesh.DrawDebugGeometry(true);
+        cast<DynamicNavigationMesh>(scene_.GetComponent("DynamicNavigationMesh")).DrawDebugGeometry(true);
         // Visualize agents' path and position to reach
         // Visualize agents' path and position to reach
-        crowdManager.DrawDebugGeometry(true);
+        cast<DetourCrowdManager>(scene_.GetComponent("DetourCrowdManager")).DrawDebugGeometry(true);
     }
     }
 }
 }
 
 
@@ -469,12 +423,39 @@ void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData)
     if (state == CrowdAgentState::CROWD_AGENT_INVALID)
     if (state == CrowdAgentState::CROWD_AGENT_INVALID)
     {
     {
         // Get a point on the navmesh using more generous extents
         // Get a point on the navmesh using more generous extents
-        Vector3 newPos = navMesh.FindNearestPoint(node.position, Vector3(5.0f,5.0f,5.0f));
+        Vector3 newPos = cast<DynamicNavigationMesh>(scene_.GetComponent("DynamicNavigationMesh")).FindNearestPoint(node.position, Vector3(5.0f,5.0f,5.0f));
         // Set the new node position, CrowdAgent component will automatically reset the state of the agent
         // Set the new node position, CrowdAgent component will automatically reset the state of the agent
         node.position = newPos;
         node.position = newPos;
     }
     }
 }
 }
 
 
+void HandleCrowdAgentReposition(StringHash eventType, VariantMap& eventData)
+{
+    const String WALKING_ANI = "Models/Jack_Walk.ani";
+
+    Node@ node = eventData["Node"].GetPtr();
+    CrowdAgent@ agent = eventData["CrowdAgent"].GetPtr();
+    Vector3 velocity = eventData["Velocity"].GetVector3();
+
+    // Only Jack agent has animation controller
+    AnimationController@ animCtrl = node.GetComponent("AnimationController");
+    if (animCtrl !is null)
+    {
+        float speed = velocity.length;
+        if (speed < agent.radius)
+            // If speed is too low then stopping the animation
+            animCtrl.Stop(WALKING_ANI, 0.8f);
+        else
+        {
+            // Face the direction of its velocity
+            node.worldDirection = velocity;
+            animCtrl.Play(WALKING_ANI, 0, true);
+            // Throttle the animation speed based on agent speed ratio (ratio = 1 is full throttle)
+            animCtrl.SetSpeed(WALKING_ANI, speed / agent.maxSpeed);
+        }
+    }
+}
+
 // 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
 String patchInstructions =
 String patchInstructions =
         "<patch>" +
         "<patch>" +