Browse Source

Added moving obstacles and teleportation to sample 39. Also various behavior tweaks and ground texture modified to break monotony and discriminate with sample 15. If something doesn't appeal it can be reverted.
Also set default mask and areaID at OffMeshConnection initialization.

Mike3D 10 years ago
parent
commit
e166e7a84d

+ 92 - 53
Source/Samples/39_CrowdNavigation/CrowdNavigation.cpp

@@ -23,6 +23,7 @@
 #include <Urho3D/Urho3D.h>
 #include <Urho3D/Urho3D.h>
 
 
 #include <Urho3D/Graphics/AnimatedModel.h>
 #include <Urho3D/Graphics/AnimatedModel.h>
+#include <Urho3D/Graphics/AnimationController.h>
 #include <Urho3D/Graphics/Camera.h>
 #include <Urho3D/Graphics/Camera.h>
 #include <Urho3D/Core/CoreEvents.h>
 #include <Urho3D/Core/CoreEvents.h>
 #include <Urho3D/UI/Cursor.h>
 #include <Urho3D/UI/Cursor.h>
@@ -139,42 +140,43 @@ void CrowdNavigation::CreateScene()
     }
     }
 
 
     // Create a DynamicNavigationMesh component to the scene root
     // Create a DynamicNavigationMesh component to the scene root
-    DynamicNavigationMesh* navMesh = scene_->CreateComponent<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);
-    // Set nav mesh tilesize to something reasonable
-    navMesh->SetTileSize(64);
+    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(navMesh, boxes);
+    CreateBoxOffMeshConnections(boxes);
+
+    // Create some mushrooms as obstacles. Note that obstacles are non-walkable areas
+    for (unsigned i = 0; i < 100; ++i)
+        CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
 
 
     // Create a DetourCrowdManager component to the scene root
     // Create a DetourCrowdManager component to the scene root
     crowdManager_ = scene_->CreateComponent<DetourCrowdManager>();
     crowdManager_ = 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
+    CreateMovingBarrels();
+
     // 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));
 
 
-    // Create some mushrooms as obstacles. Note that obstacles are added onto an already buit navigation mesh
-    for (unsigned i = 0; i < 100; ++i)
-        CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
-
     // Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside the scene, because
     // Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside the scene, because
     // we want it to be unaffected by scene load / save
     // we want it to be unaffected by scene load / save
     cameraNode_ = new Node(context_);
     cameraNode_ = new Node(context_);
@@ -206,11 +208,12 @@ void CrowdNavigation::CreateUI()
     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"
         "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"
     );
     );
-    instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
+    instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 12);
     // 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);
 
 
@@ -252,14 +255,17 @@ void CrowdNavigation::SpawnJack(const Vector3& pos)
     modelObject->SetModel(cache->GetResource<Model>("Models/Jack.mdl"));
     modelObject->SetModel(cache->GetResource<Model>("Models/Jack.mdl"));
     modelObject->SetMaterial(cache->GetResource<Material>("Materials/Jack.xml"));
     modelObject->SetMaterial(cache->GetResource<Material>("Materials/Jack.xml"));
     modelObject->SetCastShadows(true);
     modelObject->SetCastShadows(true);
+    jackNode->CreateComponent<AnimationController>();
 
 
-    // Create a CrowdAgent component and set its height (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->SetMaxAccel(100.0f);
     agents_ = crowdManager_->GetActiveAgents(); // Update agents container
     agents_ = crowdManager_->GetActiveAgents(); // Update agents container
 }
 }
 
 
-Node* CrowdNavigation::CreateMushroom(const Vector3& pos)
+void CrowdNavigation::CreateMushroom(const Vector3& pos)
 {
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     ResourceCache* cache = GetSubsystem<ResourceCache>();
 
 
@@ -276,11 +282,9 @@ Node* CrowdNavigation::CreateMushroom(const Vector3& pos)
     Obstacle* obstacle = mushroomNode->CreateComponent<Obstacle>();
     Obstacle* obstacle = mushroomNode->CreateComponent<Obstacle>();
     obstacle->SetRadius(mushroomNode->GetScale().x_);
     obstacle->SetRadius(mushroomNode->GetScale().x_);
     obstacle->SetHeight(mushroomNode->GetScale().y_);
     obstacle->SetHeight(mushroomNode->GetScale().y_);
-
-    return mushroomNode;
 }
 }
 
 
-void CrowdNavigation::CreateBoxOffMeshConnections(DynamicNavigationMesh* navMesh, Vector< SharedPtr<Node> > boxes)
+void CrowdNavigation::CreateBoxOffMeshConnections(Vector< SharedPtr<Node> > boxes)
 {
 {
     for (unsigned i=0; i < boxes.Size(); ++i)
     for (unsigned i=0; i < boxes.Size(); ++i)
     {
     {
@@ -289,10 +293,10 @@ void CrowdNavigation::CreateBoxOffMeshConnections(DynamicNavigationMesh* navMesh
         float boxHalfSize = box->GetScale().x_ / 2;
         float boxHalfSize = box->GetScale().x_ / 2;
 
 
         // 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 = scene_->CreateChild("ConnectionStart");
-        connectionStart->SetPosition(navMesh->FindNearestPoint(boxPos + Vector3(boxHalfSize, -boxHalfSize, 0))); // Base of box
+        Node* connectionStart = box->CreateChild("ConnectionStart");
+        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>();
@@ -300,33 +304,68 @@ void CrowdNavigation::CreateBoxOffMeshConnections(DynamicNavigationMesh* navMesh
     }
     }
 }
 }
 
 
+void CrowdNavigation::CreateMovingBarrels()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    Node* barrel = scene_->CreateChild("Barrel");
+    StaticModel* model = barrel->CreateComponent<StaticModel>();
+    model->SetModel(cache->GetResource<Model>("Models/Cylinder.mdl"));
+    Material* material = cache->GetResource<Material>("Materials/StoneTiled.xml");
+    model->SetMaterial(material);
+    material->SetTexture(TU_DIFFUSE, cache->GetResource<Texture2D>("Textures/TerrainDetail2.dds"));
+    model->SetCastShadows(true);
+    for (unsigned i = 0;  i < NUM_BARRELS; ++i)
+    {
+        Node* clone = barrel->Clone();
+        float size = 0.5f + Random(1.0f);
+        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)));
+        CrowdAgent* agent = clone->CreateComponent<CrowdAgent>();
+        agent->SetRadius(clone->GetScale().x_ * 0.5f);
+        agent->SetHeight(size);
+    }
+    barrel->Remove();
+}
+
 void CrowdNavigation::SetPathPoint()
 void CrowdNavigation::SetPathPoint()
 {
 {
     Vector3 hitPos;
     Vector3 hitPos;
     Drawable* hitDrawable;
     Drawable* hitDrawable;
-    DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
 
 
     if (Raycast(250.0f, hitPos, hitDrawable))
     if (Raycast(250.0f, hitPos, hitDrawable))
     {
     {
-        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 (GetSubsystem<Input>()->GetQualifierDown(QUAL_SHIFT))
         if (GetSubsystem<Input>()->GetQualifierDown(QUAL_SHIFT))
             // 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 ignit agents' move
-            for (unsigned i = 0; i < agents_.Size(); ++i)
+            // Set target position and init agents' move
+            for (unsigned i = NUM_BARRELS; i < agents_.Size(); ++i)
             {
             {
                 CrowdAgent* agent = agents_[i];
                 CrowdAgent* agent = agents_[i];
-
-                // The first agent will always move to the exact position
-                if (i == 0)
+                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);
                     agent->SetMoveTarget(pathPos);
+                }
                 else
                 else
                 {
                 {
-                    // Other 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));
+                    // 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);
                     agent->SetMoveTarget(targetPos);
                 }
                 }
             }
             }
@@ -442,14 +481,10 @@ void CrowdNavigation::MoveCamera(float timeStep)
         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 crowd manager & agents
+        // After reload, reacquire navMesh, crowd manager & agents
+        navMesh_ = scene_->GetComponent<DynamicNavigationMesh>();
         crowdManager_ = scene_->GetComponent<DetourCrowdManager>();
         crowdManager_ = scene_->GetComponent<DetourCrowdManager>();
         agents_ = crowdManager_->GetActiveAgents();
         agents_ = crowdManager_->GetActiveAgents();
-
-        // Re-enable debug draw for obstacles & off-mesh connections
-        DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
-        navMesh->SetDrawObstacles(true);
-        navMesh->SetDrawOffMeshConnections(true);
     }
     }
 
 
     // Toggle debug geometry with space
     // Toggle debug geometry with space
@@ -467,27 +502,33 @@ 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 CrowdAgents face the direction of their velocity
-    for (unsigned i = 0; i < agents_.Size(); ++i)
+    // 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];
         CrowdAgent* agent = agents_[i];
-        Vector3 vel = agent->GetActualVelocity();
-        agent->GetNode()->SetWorldDirection(vel);
+        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)
 {
 {
-    // If draw debug mode is enabled, draw navigation debug geometry
     if (drawDebug_)
     if (drawDebug_)
     {
     {
-        // Visualize navigation mesh and obstacles
-        DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
-        navMesh->DrawDebugGeometry(true);
-
+        // Visualize navigation mesh, obstacles and off-mesh connections
+        navMesh_->DrawDebugGeometry(true);
         // Visualize agents' path and position to reach
         // Visualize agents' path and position to reach
-        DetourCrowdManager* crowdManager = scene_->GetComponent<DetourCrowdManager>();
-        crowdManager->DrawDebugGeometry(true);
+        crowdManager_->DrawDebugGeometry(true);
     }
     }
 }
 }
 
 
@@ -496,16 +537,14 @@ void CrowdNavigation::HandleCrowdAgentFailure(StringHash eventType, VariantMap&
     using namespace CrowdAgentFailure;
     using namespace CrowdAgentFailure;
 
 
     Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
     Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
-    CrowdAgent* agent = static_cast<CrowdAgent*>(eventData[P_CROWD_AGENT].GetPtr());
     CrowdAgentState agentState = (CrowdAgentState)eventData[P_CROWD_AGENT_STATE].GetInt();
     CrowdAgentState agentState = (CrowdAgentState)eventData[P_CROWD_AGENT_STATE].GetInt();
 
 
     // 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)
     if (agentState == CROWD_AGENT_INVALID)
     {
     {
-        DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
         // Get a point on the navmesh using more generous extents
         // Get a point on the navmesh using more generous extents
-        Vector3 newPt = navMesh->FindNearestPoint(node->GetWorldPosition(), Vector3(5.0f, 5.0f, 5.0f));
+        Vector3 newPos = navMesh_->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->SetWorldPosition(newPt);
+        node->SetPosition(newPos);
     }
     }
 }
 }

+ 10 - 4
Source/Samples/39_CrowdNavigation/CrowdNavigation.h

@@ -33,16 +33,18 @@ 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
 ///     - Performing path queries to the navigation mesh
 ///     - Performing path queries to the navigation mesh
-///     - Adding and removing obstacles at runtime from the dynamic mesh
-///     - Adding and removing crowd agents at runtime
+///     - Adding and removing obstacles/agents at runtime
 ///     - Raycasting drawable components
 ///     - Raycasting drawable components
 ///     - Crowd movement management
 ///     - Crowd movement management
 ///     - Accessing crowd agents with the crowd manager
 ///     - Accessing crowd agents with the crowd manager
 ///     - Using off-mesh connections to make boxes climbable
 ///     - Using off-mesh connections to make boxes climbable
+///     - Using agents to simulate moving obstacles
 class CrowdNavigation : public Sample
 class CrowdNavigation : public Sample
 {
 {
     OBJECT(CrowdNavigation);
     OBJECT(CrowdNavigation);
@@ -145,9 +147,11 @@ private:
     /// Create a "Jack" object at position.
     /// Create a "Jack" object at position.
     void SpawnJack(const Vector3& pos);
     void SpawnJack(const Vector3& pos);
     /// Create a mushroom object at position.
     /// Create a mushroom object at position.
-    Node* 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(DynamicNavigationMesh* navMesh, Vector< SharedPtr<Node> > boxes);
+    void CreateBoxOffMeshConnections(Vector< SharedPtr<Node> > boxes);
+    /// Create some movable barrels as crowd agents.
+    void CreateMovingBarrels();
     /// 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.
@@ -157,6 +161,8 @@ private:
     /// Handle problems with crowd agent placement.
     /// Handle problems with crowd agent placement.
     void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData);
     void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData);
 
 
+    /// Dynamic navigation mesh.
+    DynamicNavigationMesh* navMesh_;
     /// Crowd Manager.
     /// Crowd Manager.
     DetourCrowdManager* crowdManager_;
     DetourCrowdManager* crowdManager_;
     /// Crowd Agents.
     /// Crowd Agents.

+ 3 - 1
Source/Urho3D/Navigation/OffMeshConnection.cpp

@@ -34,13 +34,15 @@ extern const char* NAVIGATION_CATEGORY;
 
 
 static const float DEFAULT_RADIUS = 1.0f;
 static const float DEFAULT_RADIUS = 1.0f;
 static const unsigned DEFAULT_MASK_FLAG = 1;
 static const unsigned DEFAULT_MASK_FLAG = 1;
-static const unsigned DEFAULT_AREA = 0;
+static const unsigned DEFAULT_AREA = 1;
 
 
 OffMeshConnection::OffMeshConnection(Context* context) :
 OffMeshConnection::OffMeshConnection(Context* context) :
     Component(context),
     Component(context),
     endPointID_(0),
     endPointID_(0),
     radius_(DEFAULT_RADIUS),
     radius_(DEFAULT_RADIUS),
     bidirectional_(true),
     bidirectional_(true),
+    mask_(DEFAULT_MASK_FLAG),
+    areaId_(DEFAULT_AREA),
     endPointDirty_(false)
     endPointDirty_(false)
 {
 {
 }
 }

+ 5 - 5
Source/Urho3D/Navigation/OffMeshConnection.h

@@ -31,7 +31,7 @@ namespace Urho3D
 class URHO3D_API OffMeshConnection : public Component
 class URHO3D_API OffMeshConnection : public Component
 {
 {
     OBJECT(OffMeshConnection);
     OBJECT(OffMeshConnection);
-    
+
 public:
 public:
     /// Construct.
     /// Construct.
     OffMeshConnection(Context* context);
     OffMeshConnection(Context* context);
@@ -39,14 +39,14 @@ public:
     virtual ~OffMeshConnection();
     virtual ~OffMeshConnection();
     /// Register object factory.
     /// Register object factory.
     static void RegisterObject(Context* context);
     static void RegisterObject(Context* context);
-    
+
     /// Handle attribute write access.
     /// Handle attribute write access.
     virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
     virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes();
     virtual void ApplyAttributes();
     /// Visualize the component as debug geometry.
     /// Visualize the component as debug geometry.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
-    
+
     /// Set endpoint node.
     /// Set endpoint node.
     void SetEndPoint(Node* node);
     void SetEndPoint(Node* node);
     /// Set radius.
     /// Set radius.
@@ -57,7 +57,7 @@ public:
     void SetMask(unsigned newMask);
     void SetMask(unsigned newMask);
     /// Sets the assigned area Id for the connection
     /// Sets the assigned area Id for the connection
     void SetAreaID(unsigned newAreaID);
     void SetAreaID(unsigned newAreaID);
-    
+
     /// Return endpoint node.
     /// Return endpoint node.
     Node* GetEndPoint() const;
     Node* GetEndPoint() const;
     /// Return radius.
     /// Return radius.
@@ -68,7 +68,7 @@ public:
     unsigned GetMask() const { return mask_; }
     unsigned GetMask() const { return mask_; }
     /// Return the user assigned area ID
     /// Return the user assigned area ID
     unsigned GetAreaID() const { return areaId_; }
     unsigned GetAreaID() const { return areaId_; }
-    
+
 private:
 private:
     /// Endpoint node.
     /// Endpoint node.
     WeakPtr<Node> endPoint_;
     WeakPtr<Node> endPoint_;

+ 80 - 49
bin/Data/LuaScripts/39_CrowdNavigation.lua

@@ -2,17 +2,19 @@
 -- This sample demonstrates:
 -- This sample demonstrates:
 --     - Generating a dynamic navigation mesh into the scene
 --     - Generating a dynamic navigation mesh into the scene
 --     - Performing path queries to the navigation mesh
 --     - Performing path queries to the navigation mesh
---     - Adding and removing obstacles at runtime from the dynamic mesh
---     - Adding and removing crowd agents at runtime
+--     - Adding and removing obstacles/agents at runtime
 --     - Raycasting drawable components
 --     - Raycasting drawable components
 --     - Crowd movement management
 --     - Crowd movement management
 --     - Accessing crowd agents with the crowd manager
 --     - Accessing crowd agents with the crowd manager
 --     - Using off-mesh connections to make boxes climbable
 --     - Using off-mesh connections to make boxes climbable
+--     - Using agents to simulate moving obstacles
 
 
 require "LuaScripts/Utilities/Sample"
 require "LuaScripts/Utilities/Sample"
 
 
+local navMesh = nil
 local crowdManager = nil
 local crowdManager = nil
 local agents = {}
 local agents = {}
+local NUM_BARRELS = 20
 
 
 function Start()
 function Start()
     -- Execute the common startup for samples
     -- Execute the common startup for samples
@@ -83,14 +85,12 @@ function CreateScene()
     end
     end
 
 
     -- Create a DynamicNavigationMesh component to the scene root
     -- Create a DynamicNavigationMesh component to the scene root
-    local navMesh = scene_:CreateComponent("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
     -- 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.agentHeight = 10
     navMesh.agentHeight = 10
-    -- Set nav mesh tilesize to something reasonable
-    navMesh.tileSize = 64
     -- 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.cellHeight = 0.05
     navMesh.cellHeight = 0.05
     -- 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
@@ -107,19 +107,22 @@ 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(navMesh, boxes)
+    CreateBoxOffMeshConnections(boxes)
 
 
-    -- Create a DetourCrowdManager component to the scene root
+    -- Create some mushrooms as obstacles. Note that obstacles are non-walkable areas
+    for i = 1, 100 do
+        CreateMushroom(Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0))
+    end
+
+    -- Create a DetourCrowdManager component to the scene root (mandatory for crowd agents)
     crowdManager = scene_:CreateComponent("DetourCrowdManager")
     crowdManager = 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
+    CreateMovingBarrels()
+
     -- Create Jack node as crowd agent
     -- Create Jack node as crowd agent
     SpawnJack(Vector3(-5, 0, 20))
     SpawnJack(Vector3(-5, 0, 20))
 
 
-    -- Create some mushrooms as obstacles. Note that obstacles are added onto an already buit navigation mesh
-    for i = 1, 100 do
-        CreateMushroom(Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0))
-    end
-
     -- Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     -- Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     -- the scene, because we want it to be unaffected by scene load / save
     -- the scene, because we want it to be unaffected by scene load / save
     cameraNode = Node()
     cameraNode = Node()
@@ -144,10 +147,11 @@ function CreateUI()
     local instructionText = ui.root:CreateChild("Text")
     local instructionText = ui.root:CreateChild("Text")
     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"..
         "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"
-    instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+    instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 12)
     -- 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
 
 
@@ -183,10 +187,13 @@ function SpawnJack(pos)
     modelObject.model = cache:GetResource("Model", "Models/Jack.mdl")
     modelObject.model = cache:GetResource("Model", "Models/Jack.mdl")
     modelObject.material = cache:GetResource("Material", "Materials/Jack.xml")
     modelObject.material = cache:GetResource("Material", "Materials/Jack.xml")
     modelObject.castShadows = true
     modelObject.castShadows = true
+    jackNode:CreateComponent("AnimationController")
 
 
-    -- Create a CrowdAgent component and set its height (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.maxAccel = 100.0
     agents = crowdManager:GetActiveAgents() -- Update agents container
     agents = crowdManager:GetActiveAgents() -- Update agents container
 end
 end
 
 
@@ -204,17 +211,16 @@ function CreateMushroom(pos)
     local obstacle = mushroomNode:CreateComponent("Obstacle")
     local obstacle = mushroomNode:CreateComponent("Obstacle")
     obstacle.radius = mushroomNode.scale.x
     obstacle.radius = mushroomNode.scale.x
     obstacle.height = mushroomNode.scale.y
     obstacle.height = mushroomNode.scale.y
-    return mushroomNode
 end
 end
 
 
-function CreateBoxOffMeshConnections(navMesh, boxes)
+function CreateBoxOffMeshConnections(boxes)
     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
 
 
         -- 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.
-        local connectionStart = scene_:CreateChild("ConnectionStart")
-        connectionStart.position = navMesh:FindNearestPoint(boxPos + Vector3(boxHalfSize, -boxHalfSize, 0)) -- Base of box
+        local connectionStart = box:CreateChild("ConnectionStart")
+        connectionStart.worldPosition = navMesh:FindNearestPoint(boxPos + Vector3(boxHalfSize, -boxHalfSize, 0)) -- Base of box
         local connectionEnd = connectionStart:CreateChild("ConnectionEnd")
         local connectionEnd = connectionStart:CreateChild("ConnectionEnd")
         connectionEnd.worldPosition = navMesh:FindNearestPoint(boxPos + Vector3(boxHalfSize, boxHalfSize, 0)) -- Top of box
         connectionEnd.worldPosition = navMesh:FindNearestPoint(boxPos + Vector3(boxHalfSize, boxHalfSize, 0)) -- Top of box
 
 
@@ -224,9 +230,27 @@ function CreateBoxOffMeshConnections(navMesh, boxes)
     end
     end
 end
 end
 
 
+function CreateMovingBarrels()
+    local barrel = scene_:CreateChild("Barrel")
+    local model = barrel:CreateComponent("StaticModel")
+    model.model = cache:GetResource("Model", "Models/Cylinder.mdl")
+    model.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+    model.material:SetTexture(TU_DIFFUSE, cache:GetResource("Texture2D", "Textures/TerrainDetail2.dds"))
+    model.castShadows = true
+    for i = 1, NUM_BARRELS do
+        local clone = barrel:Clone()
+        local size = 0.5 + Random(1)
+        clone.scale = Vector3(size / 1.5, size * 2, size / 1.5)
+        clone.position = navMesh:FindNearestPoint(Vector3(Random(80.0) - 40.0, size * 0.5 , Random(80.0) - 40.0))
+        local agent = clone:CreateComponent("CrowdAgent")
+        agent.radius = clone.scale.x * 0.5
+        agent.height = size
+    end
+    barrel:Remove()
+end
+
 function SetPathPoint()
 function SetPathPoint()
     local hitPos, hitDrawable = Raycast(250.0)
     local hitPos, hitDrawable = Raycast(250.0)
-    local navMesh = scene_:GetComponent("DynamicNavigationMesh")
 
 
     if hitDrawable then
     if hitDrawable then
         local pathPos = navMesh:FindNearestPoint(hitPos, Vector3.ONE)
         local pathPos = navMesh:FindNearestPoint(hitPos, Vector3.ONE)
@@ -234,16 +258,25 @@ function SetPathPoint()
         if input:GetQualifierDown(QUAL_SHIFT) then
         if input:GetQualifierDown(QUAL_SHIFT) then
             -- Spawn a Jack
             -- Spawn a Jack
             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 ignit agents' move
-            for i = 1, table.maxn(agents) do
+            -- Set target position and init agents' move
+            for i = NUM_BARRELS + 1, table.maxn(agents) do
                 local agent = agents[i]
                 local agent = agents[i]
-
-                if i == 1 then
-                    -- The first agent will always move to the exact position
+                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)
                     agent:SetMoveTarget(pathPos)
                 else
                 else
-                    -- Other agents will move to a random point nearby
+                    -- 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)
                     local targetPos = navMesh:FindNearestPoint(pathPos + Vector3(Random(-4.5, 4.5), 0, Random(-4.5, 4.5)), Vector3.ONE)
                     agent:SetMoveTarget(targetPos)
                     agent:SetMoveTarget(targetPos)
                 end
                 end
@@ -336,26 +369,21 @@ function MoveCamera(timeStep)
         AddOrRemoveObject()
         AddOrRemoveObject()
     end
     end
 
 
-    -- Toggle debug geometry with space
-    if input:GetKeyPress(KEY_SPACE) then
-        drawDebug = not drawDebug
-    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
     end
     if input:GetKeyPress(KEY_F7) then
     if input:GetKeyPress(KEY_F7) then
         scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/CrowdNavigation.xml")
         scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/CrowdNavigation.xml")
-
-        -- After reload, reacquire crowd manager & agents
+        -- After reload, reacquire navMesh, crowd manager & agents
+        navMesh = scene_:GetComponent("DynamicNavigationMesh")
         crowdManager = scene_:GetComponent("DetourCrowdManager")
         crowdManager = scene_:GetComponent("DetourCrowdManager")
         agents = crowdManager:GetActiveAgents()
         agents = crowdManager:GetActiveAgents()
+    end
 
 
-        -- Re-enable debug draw for obstacles & off-mesh connections
-        local navMesh = scene_:GetComponent("DynamicNavigationMesh")
-        navMesh.drawObstacles = true
-        navMesh.drawOffMeshConnections = true
+    -- Toggle debug geometry with space
+    if input:GetKeyPress(KEY_SPACE) then
+        drawDebug = not drawDebug
     end
     end
 end
 end
 
 
@@ -366,40 +394,43 @@ 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 CrowdAgents face the direction of their velocity
-    for i = 1, table.maxn(agents) do
+    -- 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 agent = agents[i]
-        agent.node.worldDirection = agent.actualVelocity
+        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
 end
 
 
 function HandlePostRenderUpdate(eventType, eventData)
 function HandlePostRenderUpdate(eventType, eventData)
-    -- If draw debug mode is enabled, draw navigation debug geometry
     if drawDebug then
     if drawDebug then
-        -- Visualize navigation mesh and obstacles
-        local navMesh = scene_:GetComponent("DynamicNavigationMesh")
+        -- Visualize navigation mesh, obstacles and off-mesh connections
         navMesh:DrawDebugGeometry(true)
         navMesh:DrawDebugGeometry(true)
-
         -- Visualize agents' path and position to reach
         -- Visualize agents' path and position to reach
         crowdManager:DrawDebugGeometry(true)
         crowdManager:DrawDebugGeometry(true)
     end
     end
 end
 end
 
 
 function HandleCrowdAgentFailure(eventType, eventData)
 function HandleCrowdAgentFailure(eventType, eventData)
-
     local node = eventData:GetPtr("Node", "Node")
     local node = eventData:GetPtr("Node", "Node")
-    local agent = eventData:GetPtr("CrowdAgent", "CrowdAgent")
     local agentState = eventData:GetInt("CrowdAgentState")
     local agentState = eventData:GetInt("CrowdAgentState")
 
 
     -- 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
-        local navMesh = scene_:GetComponent("DynamicNavigationMesh")
         -- 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:GetWorldPosition(), Vector3(5, 5, 5))
+        local newPos = navMesh: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:SetWorldPosition(newPos)
+        node.position = newPos
     end
     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

+ 96 - 54
bin/Data/Scripts/39_CrowdNavigation.as

@@ -2,16 +2,18 @@
 // This sample demonstrates:
 // This sample demonstrates:
 //     - Generating a dynamic navigation mesh into the scene
 //     - Generating a dynamic navigation mesh into the scene
 //     - Performing path queries to the navigation mesh
 //     - Performing path queries to the navigation mesh
-//     - Adding and removing obstacles at runtime from the dynamic mesh
-//     - Adding and removing crowd agents at runtime
+//     - Adding and removing obstacles/agents at runtime
 //     - Raycasting drawable components
 //     - Raycasting drawable components
 //     - Crowd movement management
 //     - Crowd movement management
 //     - Accessing crowd agents with the crowd manager
 //     - Accessing crowd agents with the crowd manager
 //     - Using off-mesh connections to make boxes climbable
 //     - Using off-mesh connections to make boxes climbable
+//     - Using agents to simulate moving obstacles
 
 
 #include "Scripts/Utilities/Sample.as"
 #include "Scripts/Utilities/Sample.as"
 
 
+DynamicNavigationMesh@ navMesh;
 DetourCrowdManager@ crowdManager;
 DetourCrowdManager@ crowdManager;
+const int NUM_BARRELS = 20;
 
 
 void Start()
 void Start()
 {
 {
@@ -87,14 +89,12 @@ void CreateScene()
     }
     }
 
 
     // Create a DynamicNavigationMesh component to the scene root
     // Create a DynamicNavigationMesh component to the scene root
-    DynamicNavigationMesh@ navMesh = scene_.CreateComponent("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;
     // 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.agentHeight = 10;
     navMesh.agentHeight = 10;
-    // Set nav mesh tilesize to something reasonable
-    navMesh.tileSize = 64;
     // 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.cellHeight = 0.05f;
     navMesh.cellHeight = 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
@@ -111,18 +111,21 @@ 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(navMesh, boxes);
+    CreateBoxOffMeshConnections(boxes);
 
 
-    // Create a DetourCrowdManager component to the scene root
+    // Create some mushrooms as obstacles. Note that obstacles are non-walkable areas
+    for (uint i = 0; i < 100; ++i)
+        CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
+
+    // Create a DetourCrowdManager component to the scene root (mandatory for crowd agents)
     crowdManager = scene_.CreateComponent("DetourCrowdManager");
     crowdManager = 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
+    CreateMovingBarrels();
+
     // 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));
 
 
-    // Create some mushrooms as obstacles. Note that obstacles are added onto an already buit navigation mesh
-    for (uint i = 0; i < 100; ++i)
-        CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
-
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     // the scene, because we want it to be unaffected by scene load / save
     // the scene, because we want it to be unaffected by scene load / save
     cameraNode = Node();
     cameraNode = Node();
@@ -149,10 +152,11 @@ void CreateUI()
     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"
         "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";
-    instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+    instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 12);
     // 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;
 
 
@@ -183,7 +187,7 @@ void SubscribeToEvents()
     SubscribeToEvent("CrowdAgentFailure", "HandleCrowdAgentFailure");
     SubscribeToEvent("CrowdAgentFailure", "HandleCrowdAgentFailure");
 }
 }
 
 
-Node@ CreateMushroom(const Vector3& pos)
+void CreateMushroom(const Vector3& pos)
 {
 {
     Node@ mushroomNode = scene_.CreateChild("Mushroom");
     Node@ mushroomNode = scene_.CreateChild("Mushroom");
     mushroomNode.position = pos;
     mushroomNode.position = pos;
@@ -198,11 +202,9 @@ Node@ CreateMushroom(const Vector3& pos)
     Obstacle@ obstacle = mushroomNode.CreateComponent("Obstacle");
     Obstacle@ obstacle = mushroomNode.CreateComponent("Obstacle");
     obstacle.radius = mushroomNode.scale.x;
     obstacle.radius = mushroomNode.scale.x;
     obstacle.height = mushroomNode.scale.y;
     obstacle.height = mushroomNode.scale.y;
-
-    return mushroomNode;
 }
 }
 
 
-Node@ SpawnJack(const Vector3& pos)
+void SpawnJack(const Vector3& pos)
 {
 {
     Node@ jackNode = scene_.CreateChild("Jack");
     Node@ jackNode = scene_.CreateChild("Jack");
     jackNode.position = pos;
     jackNode.position = pos;
@@ -210,15 +212,16 @@ Node@ SpawnJack(const Vector3& pos)
     modelObject.model = cache.GetResource("Model", "Models/Jack.mdl");
     modelObject.model = cache.GetResource("Model", "Models/Jack.mdl");
     modelObject.material = cache.GetResource("Material", "Materials/Jack.xml");
     modelObject.material = cache.GetResource("Material", "Materials/Jack.xml");
     modelObject.castShadows = true;
     modelObject.castShadows = true;
+    jackNode.CreateComponent("AnimationController");
 
 
-    // Create a CrowdAgent component and set its height (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;
-
-    return jackNode;
+    agent.maxSpeed = 4.0f;
+    agent.maxAccel = 100.0f;
 }
 }
 
 
-void CreateBoxOffMeshConnections(DynamicNavigationMesh@ navMesh, Array<Node@> boxes)
+void CreateBoxOffMeshConnections(Array<Node@> boxes)
 {
 {
     for (uint i=0; i < boxes.length; ++i)
     for (uint i=0; i < boxes.length; ++i)
     {
     {
@@ -227,8 +230,8 @@ void CreateBoxOffMeshConnections(DynamicNavigationMesh@ navMesh, Array<Node@> bo
         float boxHalfSize = box.scale.x / 2;
         float boxHalfSize = box.scale.x / 2;
 
 
         // 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 = scene_.CreateChild("ConnectionStart");
-        connectionStart.position = navMesh.FindNearestPoint(boxPos + Vector3(boxHalfSize, -boxHalfSize, 0)); // Base of box
+        Node@ connectionStart = box.CreateChild("ConnectionStart");
+        connectionStart.worldPosition = navMesh.FindNearestPoint(boxPos + Vector3(boxHalfSize, -boxHalfSize, 0)); // Base of box
         Node@ connectionEnd = connectionStart.CreateChild("ConnectionEnd");
         Node@ connectionEnd = connectionStart.CreateChild("ConnectionEnd");
         connectionEnd.worldPosition = navMesh.FindNearestPoint(boxPos + Vector3(boxHalfSize, boxHalfSize, 0)); // Top of box
         connectionEnd.worldPosition = navMesh.FindNearestPoint(boxPos + Vector3(boxHalfSize, boxHalfSize, 0)); // Top of box
 
 
@@ -238,11 +241,32 @@ void CreateBoxOffMeshConnections(DynamicNavigationMesh@ navMesh, Array<Node@> bo
     }
     }
 }
 }
 
 
+void CreateMovingBarrels()
+{
+    Node@ barrel = scene_.CreateChild("Barrel");
+    StaticModel@ model = barrel.CreateComponent("StaticModel");
+    model.model = cache.GetResource("Model", "Models/Cylinder.mdl");
+    Material@ material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+    model.material = material;
+    material.textures[TU_DIFFUSE] = cache.GetResource("Texture2D", "Textures/TerrainDetail2.dds");
+    model.castShadows = true;
+    for (uint i = 0;  i < NUM_BARRELS; ++i)
+    {
+        Node@ clone = barrel.Clone();
+        float size = 0.5 + Random(1);
+        clone.scale = Vector3(size / 1.5, size * 2.0, size / 1.5);
+        clone.position = navMesh.FindNearestPoint(Vector3(Random(80.0) - 40.0, size * 0.5 , Random(80.0) - 40.0));
+        CrowdAgent@ agent = clone.CreateComponent("CrowdAgent");
+        agent.radius = clone.scale.x * 0.5f;
+        agent.height = size;
+    }
+    barrel.Remove();
+}
+
 void SetPathPoint()
 void SetPathPoint()
 {
 {
     Vector3 hitPos;
     Vector3 hitPos;
     Drawable@ hitDrawable;
     Drawable@ hitDrawable;
-    DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
 
 
     if (Raycast(250.0f, hitPos, hitDrawable))
     if (Raycast(250.0f, hitPos, hitDrawable))
     {
     {
@@ -251,20 +275,35 @@ void SetPathPoint()
         if (input.qualifierDown[QUAL_SHIFT])
         if (input.qualifierDown[QUAL_SHIFT])
             // Spawn a jack
             // Spawn a jack
             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
             // Set target position and init agents' move
             Array<CrowdAgent@>@ agents = crowdManager.GetActiveAgents();
             Array<CrowdAgent@>@ agents = crowdManager.GetActiveAgents();
-            for (uint i = 0; i < agents.length; ++i)
+            for (uint i = NUM_BARRELS; i < agents.length; ++i)
             {
             {
                 CrowdAgent@ agent = agents[i];
                 CrowdAgent@ agent = agents[i];
 
 
-                if (i == 0)
-                    // The first agent will always move to the exact target
+                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);
                     agent.SetMoveTarget(pathPos);
+                }
                 else
                 else
                 {
                 {
-                    // Other agents will move to a random point nearby
+                    // 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));
                     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);
                     agent.SetMoveTarget(targetPos);
                 }
                 }
@@ -361,6 +400,22 @@ void MoveCamera(float timeStep)
     if (input.mouseButtonPress[MOUSEB_MIDDLE])
     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
+    if (input.keyPress[KEY_F5])
+    {
+        File saveFile(fileSystem.programDir + "Data/Scenes/CrowdNavigation.xml", FILE_WRITE);
+        scene_.SaveXML(saveFile);
+    }
+    if (input.keyPress[KEY_F7])
+    {
+        File loadFile(fileSystem.programDir + "Data/Scenes/CrowdNavigation.xml", FILE_READ);
+        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])
     if (input.keyPress[KEY_SPACE])
         drawDebug = !drawDebug;
         drawDebug = !drawDebug;
@@ -374,44 +429,32 @@ 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 CrowdAgents face the direction of their velocity
+    // Make the Jack CrowdAgents face the direction of their velocity and update animation
     Array<CrowdAgent@>@ agents = crowdManager.GetActiveAgents();
     Array<CrowdAgent@>@ agents = crowdManager.GetActiveAgents();
-    for (uint i = 0; i < agents.length; ++i)
+    for (uint i = NUM_BARRELS; i < agents.length; ++i)
     {
     {
         CrowdAgent@ agent = agents[i];
         CrowdAgent@ agent = agents[i];
-        agent.node.worldDirection = agent.actualVelocity;
-    }
+        Node@ node = agent.node;
+        AnimationController@ animCtrl = node.GetComponent("AnimationController");
+        Vector3 velocity = agent.actualVelocity;
 
 
-    // Check for loading/saving the scene from/to the file Data/Scenes/CrowdNavigation.xml relative to the executable directory
-    if (input.keyPress[KEY_F5])
-    {
-        File saveFile(fileSystem.programDir + "Data/Scenes/CrowdNavigation.xml", FILE_WRITE);
-        scene_.SaveXML(saveFile);
-    }
-    if (input.keyPress[KEY_F7])
-    {
-        File loadFile(fileSystem.programDir + "Data/Scenes/CrowdNavigation.xml", FILE_READ);
-        scene_.LoadXML(loadFile);
-
-        // After reload, reacquire crowd manager
-        crowdManager = scene_.GetComponent("DetourCrowdManager");
-
-        // Re-enable debug draw for obstacles & off-mesh connections
-        DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
-        navMesh.drawObstacles = true;
-        navMesh.drawOffMeshConnections = true;
+        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)
 {
 {
-    // If draw debug mode is enabled, draw navigation debug geometry
     if (drawDebug)
     if (drawDebug)
     {
     {
-        // Visualize navigation mesh and obstacles
-        DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
+        // Visualize navigation mesh, obstacles and off-mesh connections
         navMesh.DrawDebugGeometry(true);
         navMesh.DrawDebugGeometry(true);
-
         // Visualize agents' path and position to reach
         // Visualize agents' path and position to reach
         crowdManager.DrawDebugGeometry(true);
         crowdManager.DrawDebugGeometry(true);
     }
     }
@@ -425,7 +468,6 @@ void HandleCrowdAgentFailure(StringHash eventType, VariantMap& 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 (state == CrowdAgentState::CROWD_AGENT_INVALID)
     if (state == CrowdAgentState::CROWD_AGENT_INVALID)
     {
     {
-        DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
         // 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 = navMesh.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