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/Graphics/AnimatedModel.h>
+#include <Urho3D/Graphics/AnimationController.h>
 #include <Urho3D/Graphics/Camera.h>
 #include <Urho3D/Core/CoreEvents.h>
 #include <Urho3D/UI/Cursor.h>
@@ -139,42 +140,43 @@ void CrowdNavigation::CreateScene()
     }
 
     // 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
-    navMesh->SetDrawObstacles(true);
-    navMesh->SetDrawOffMeshConnections(true);
+    navMesh_->SetDrawObstacles(true);
+    navMesh_->SetDrawOffMeshConnections(true);
     // 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)
-    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
     // navigation mesh. By default this is recursive, but the recursion could be turned off from 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
     // 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
     // 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
-    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.
     // 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
-    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
     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
     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
     // we want it to be unaffected by scene load / save
     cameraNode_ = new Node(context_);
@@ -206,11 +208,12 @@ void CrowdNavigation::CreateUI()
     instructionText->SetText(
         "Use WASD keys to move, RMB to rotate view\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"
         "F5 to save scene, F7 to load\n"
         "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
     instructionText->SetTextAlignment(HA_CENTER);
 
@@ -252,14 +255,17 @@ void CrowdNavigation::SpawnJack(const Vector3& pos)
     modelObject->SetModel(cache->GetResource<Model>("Models/Jack.mdl"));
     modelObject->SetMaterial(cache->GetResource<Material>("Materials/Jack.xml"));
     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>();
     agent->SetHeight(2.0f);
+    agent->SetMaxSpeed(4.0f);
+    agent->SetMaxAccel(100.0f);
     agents_ = crowdManager_->GetActiveAgents(); // Update agents container
 }
 
-Node* CrowdNavigation::CreateMushroom(const Vector3& pos)
+void CrowdNavigation::CreateMushroom(const Vector3& pos)
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
 
@@ -276,11 +282,9 @@ Node* CrowdNavigation::CreateMushroom(const Vector3& pos)
     Obstacle* obstacle = mushroomNode->CreateComponent<Obstacle>();
     obstacle->SetRadius(mushroomNode->GetScale().x_);
     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)
     {
@@ -289,10 +293,10 @@ void CrowdNavigation::CreateBoxOffMeshConnections(DynamicNavigationMesh* navMesh
         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.
-        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");
-        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
         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()
 {
     Vector3 hitPos;
     Drawable* hitDrawable;
-    DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
 
     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))
             // Spawn a jack at the target position
             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
         {
-            // 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];
-
-                // 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);
+                }
                 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);
                 }
             }
@@ -442,14 +481,10 @@ void CrowdNavigation::MoveCamera(float timeStep)
         File loadFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CrowdNavigation.xml", FILE_READ);
         scene_->LoadXML(loadFile);
 
-        // After reload, reacquire crowd manager & agents
+        // After reload, reacquire navMesh, crowd manager & agents
+        navMesh_ = scene_->GetComponent<DynamicNavigationMesh>();
         crowdManager_ = scene_->GetComponent<DetourCrowdManager>();
         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
@@ -467,27 +502,33 @@ void CrowdNavigation::HandleUpdate(StringHash eventType, VariantMap& eventData)
     // Move the camera, scale movement with time step
     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];
-        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)
 {
-    // If draw debug mode is enabled, draw navigation debug geometry
     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
-        DetourCrowdManager* crowdManager = scene_->GetComponent<DetourCrowdManager>();
-        crowdManager->DrawDebugGeometry(true);
+        crowdManager_->DrawDebugGeometry(true);
     }
 }
 
@@ -496,16 +537,14 @@ void CrowdNavigation::HandleCrowdAgentFailure(StringHash eventType, VariantMap&
     using namespace CrowdAgentFailure;
 
     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();
 
     // 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)
     {
-        DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
         // 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
-        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.
 /// This sample demonstrates:
 ///     - Generating a dynamic navigation mesh into the scene
 ///     - 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
 ///     - Crowd movement management
 ///     - Accessing crowd agents with the crowd manager
 ///     - Using off-mesh connections to make boxes climbable
+///     - Using agents to simulate moving obstacles
 class CrowdNavigation : public Sample
 {
     OBJECT(CrowdNavigation);
@@ -145,9 +147,11 @@ private:
     /// Create a "Jack" object at position.
     void SpawnJack(const Vector3& pos);
     /// 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.
-    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.
     bool Raycast(float maxDistance, Vector3& hitPos, Drawable*& hitDrawable);
     /// Handle the logic update event.
@@ -157,6 +161,8 @@ private:
     /// Handle problems with crowd agent placement.
     void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData);
 
+    /// Dynamic navigation mesh.
+    DynamicNavigationMesh* navMesh_;
     /// Crowd Manager.
     DetourCrowdManager* crowdManager_;
     /// 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 unsigned DEFAULT_MASK_FLAG = 1;
-static const unsigned DEFAULT_AREA = 0;
+static const unsigned DEFAULT_AREA = 1;
 
 OffMeshConnection::OffMeshConnection(Context* context) :
     Component(context),
     endPointID_(0),
     radius_(DEFAULT_RADIUS),
     bidirectional_(true),
+    mask_(DEFAULT_MASK_FLAG),
+    areaId_(DEFAULT_AREA),
     endPointDirty_(false)
 {
 }

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

@@ -31,7 +31,7 @@ namespace Urho3D
 class URHO3D_API OffMeshConnection : public Component
 {
     OBJECT(OffMeshConnection);
-    
+
 public:
     /// Construct.
     OffMeshConnection(Context* context);
@@ -39,14 +39,14 @@ public:
     virtual ~OffMeshConnection();
     /// Register object factory.
     static void RegisterObject(Context* context);
-    
+
     /// Handle attribute write access.
     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.
     virtual void ApplyAttributes();
     /// Visualize the component as debug geometry.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
-    
+
     /// Set endpoint node.
     void SetEndPoint(Node* node);
     /// Set radius.
@@ -57,7 +57,7 @@ public:
     void SetMask(unsigned newMask);
     /// Sets the assigned area Id for the connection
     void SetAreaID(unsigned newAreaID);
-    
+
     /// Return endpoint node.
     Node* GetEndPoint() const;
     /// Return radius.
@@ -68,7 +68,7 @@ public:
     unsigned GetMask() const { return mask_; }
     /// Return the user assigned area ID
     unsigned GetAreaID() const { return areaId_; }
-    
+
 private:
     /// Endpoint node.
     WeakPtr<Node> endPoint_;

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

@@ -2,17 +2,19 @@
 -- This sample demonstrates:
 --     - Generating a dynamic navigation mesh into the scene
 --     - 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
 --     - Crowd movement management
 --     - Accessing crowd agents with the crowd manager
 --     - Using off-mesh connections to make boxes climbable
+--     - Using agents to simulate moving obstacles
 
 require "LuaScripts/Utilities/Sample"
 
+local navMesh = nil
 local crowdManager = nil
 local agents = {}
+local NUM_BARRELS = 20
 
 function Start()
     -- Execute the common startup for samples
@@ -83,14 +85,12 @@ function CreateScene()
     end
 
     -- 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
     navMesh.drawObstacles = true
     navMesh.drawOffMeshConnections = true
     -- Set the agent height large enough to exclude the layers under boxes
     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)
     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
@@ -107,19 +107,22 @@ function CreateScene()
     -- 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.
     -- 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")
 
+    -- 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
     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
     -- the scene, because we want it to be unaffected by scene load / save
     cameraNode = Node()
@@ -144,10 +147,11 @@ function CreateUI()
     local instructionText = ui.root:CreateChild("Text")
     instructionText.text = "Use WASD keys to move, RMB to rotate view\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"..
         "F5 to save scene, F7 to load\n"..
         "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
     instructionText.textAlignment = HA_CENTER
 
@@ -183,10 +187,13 @@ function SpawnJack(pos)
     modelObject.model = cache:GetResource("Model", "Models/Jack.mdl")
     modelObject.material = cache:GetResource("Material", "Materials/Jack.xml")
     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")
     agent.height = 2.0
+    agent.maxSpeed = 4.0
+    agent.maxAccel = 100.0
     agents = crowdManager:GetActiveAgents() -- Update agents container
 end
 
@@ -204,17 +211,16 @@ function CreateMushroom(pos)
     local obstacle = mushroomNode:CreateComponent("Obstacle")
     obstacle.radius = mushroomNode.scale.x
     obstacle.height = mushroomNode.scale.y
-    return mushroomNode
 end
 
-function CreateBoxOffMeshConnections(navMesh, boxes)
+function CreateBoxOffMeshConnections(boxes)
     for i, box in ipairs(boxes) do
         local boxPos = box.position
         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.
-        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")
         connectionEnd.worldPosition = navMesh:FindNearestPoint(boxPos + Vector3(boxHalfSize, boxHalfSize, 0)) -- Top of box
 
@@ -224,9 +230,27 @@ function CreateBoxOffMeshConnections(navMesh, boxes)
     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()
     local hitPos, hitDrawable = Raycast(250.0)
-    local navMesh = scene_:GetComponent("DynamicNavigationMesh")
 
     if hitDrawable then
         local pathPos = navMesh:FindNearestPoint(hitPos, Vector3.ONE)
@@ -234,16 +258,25 @@ function SetPathPoint()
         if input:GetQualifierDown(QUAL_SHIFT) then
             -- Spawn a Jack
             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
-            -- 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]
-
-                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)
                 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)
                     agent:SetMoveTarget(targetPos)
                 end
@@ -336,26 +369,21 @@ function MoveCamera(timeStep)
         AddOrRemoveObject()
     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
     if input:GetKeyPress(KEY_F5) then
         scene_:SaveXML(fileSystem:GetProgramDir().."Data/Scenes/CrowdNavigation.xml")
     end
     if input:GetKeyPress(KEY_F7) then
         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")
         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
 
@@ -366,40 +394,43 @@ function HandleUpdate(eventType, eventData)
     -- Move the camera, scale movement with time step
     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]
-        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
 
 function HandlePostRenderUpdate(eventType, eventData)
-    -- If draw debug mode is enabled, draw navigation debug geometry
     if drawDebug then
-        -- Visualize navigation mesh and obstacles
-        local navMesh = scene_:GetComponent("DynamicNavigationMesh")
+        -- Visualize navigation mesh, obstacles and off-mesh connections
         navMesh:DrawDebugGeometry(true)
-
         -- Visualize agents' path and position to reach
         crowdManager:DrawDebugGeometry(true)
     end
 end
 
 function HandleCrowdAgentFailure(eventType, eventData)
-
     local node = eventData:GetPtr("Node", "Node")
-    local agent = eventData:GetPtr("CrowdAgent", "CrowdAgent")
     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 agentState == CROWD_AGENT_INVALID then
-        local navMesh = scene_:GetComponent("DynamicNavigationMesh")
         -- 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
-        node:SetWorldPosition(newPos)
+        node.position = newPos
     end
-
 end
 
 -- 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:
 //     - Generating a dynamic navigation mesh into the scene
 //     - 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
 //     - Crowd movement management
 //     - Accessing crowd agents with the crowd manager
 //     - Using off-mesh connections to make boxes climbable
+//     - Using agents to simulate moving obstacles
 
 #include "Scripts/Utilities/Sample.as"
 
+DynamicNavigationMesh@ navMesh;
 DetourCrowdManager@ crowdManager;
+const int NUM_BARRELS = 20;
 
 void Start()
 {
@@ -87,14 +89,12 @@ void CreateScene()
     }
 
     // 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
     navMesh.drawObstacles = true;
     navMesh.drawOffMeshConnections = true;
     // Set the agent height large enough to exclude the layers under boxes
     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)
     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
@@ -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.
     // 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
-    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");
 
+    // 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
     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
     // the scene, because we want it to be unaffected by scene load / save
     cameraNode = Node();
@@ -149,10 +152,11 @@ void CreateUI()
     instructionText.text =
         "Use WASD keys to move, RMB to rotate view\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"
         "F5 to save scene, F7 to load\n"
         "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
     instructionText.textAlignment = HA_CENTER;
 
@@ -183,7 +187,7 @@ void SubscribeToEvents()
     SubscribeToEvent("CrowdAgentFailure", "HandleCrowdAgentFailure");
 }
 
-Node@ CreateMushroom(const Vector3& pos)
+void CreateMushroom(const Vector3& pos)
 {
     Node@ mushroomNode = scene_.CreateChild("Mushroom");
     mushroomNode.position = pos;
@@ -198,11 +202,9 @@ Node@ CreateMushroom(const Vector3& pos)
     Obstacle@ obstacle = mushroomNode.CreateComponent("Obstacle");
     obstacle.radius = mushroomNode.scale.x;
     obstacle.height = mushroomNode.scale.y;
-
-    return mushroomNode;
 }
 
-Node@ SpawnJack(const Vector3& pos)
+void SpawnJack(const Vector3& pos)
 {
     Node@ jackNode = scene_.CreateChild("Jack");
     jackNode.position = pos;
@@ -210,15 +212,16 @@ Node@ SpawnJack(const Vector3& pos)
     modelObject.model = cache.GetResource("Model", "Models/Jack.mdl");
     modelObject.material = cache.GetResource("Material", "Materials/Jack.xml");
     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");
     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)
     {
@@ -227,8 +230,8 @@ void CreateBoxOffMeshConnections(DynamicNavigationMesh@ navMesh, Array<Node@> bo
         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.
-        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");
         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()
 {
     Vector3 hitPos;
     Drawable@ hitDrawable;
-    DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
 
     if (Raycast(250.0f, hitPos, hitDrawable))
     {
@@ -251,20 +275,35 @@ void SetPathPoint()
         if (input.qualifierDown[QUAL_SHIFT])
             // Spawn a jack
             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
         {
             // Set target position and init agents' move
             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];
 
-                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);
+                }
                 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));
                     agent.SetMoveTarget(targetPos);
                 }
@@ -361,6 +400,22 @@ void MoveCamera(float timeStep)
     if (input.mouseButtonPress[MOUSEB_MIDDLE])
         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
     if (input.keyPress[KEY_SPACE])
         drawDebug = !drawDebug;
@@ -374,44 +429,32 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
     // Move the camera, scale movement with time step
     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();
-    for (uint i = 0; i < agents.length; ++i)
+    for (uint i = NUM_BARRELS; i < agents.length; ++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)
 {
-    // If draw debug mode is enabled, draw navigation debug geometry
     if (drawDebug)
     {
-        // Visualize navigation mesh and obstacles
-        DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
+        // Visualize navigation mesh, obstacles and off-mesh connections
         navMesh.DrawDebugGeometry(true);
-
         // Visualize agents' path and position to reach
         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 (state == CrowdAgentState::CROWD_AGENT_INVALID)
     {
-        DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
         // Get a point on the navmesh using more generous extents
         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