Browse Source

CrowdAgent state handling

- CrowdAgentStateChanged event includes Node and Agent parameters
- CrowdAgentFailure event for target/agent-state failures
- GetAreaCost in NavigationMesh, script bindings updated
- Fixed error spawning Jack nodes on top of boxes in Angelscript sample
- DebugRendering for DynamicNavigationMesh layers
- Rename all references to AreaType as AreaID
- CrowdAgent angelscript enum bindings made consistent
- Include simple example handling of CrowdAgentFailure for faulty
initial state (sides of boxes) in all samples
- Consistent values in samples (box/mushroom counts)
- Completed "todo" for investigating rcFilterLedgeSpans in
DynamicNavigationMesh
JSandusky 10 years ago
parent
commit
ca7a6af1f3

+ 32 - 3
Source/Samples/39_CrowdNavigation/CrowdNavigation.cpp

@@ -39,6 +39,7 @@
 #include <Urho3D/Navigation/DetourCrowdManager.h>
 #include <Urho3D/Navigation/DynamicNavigationMesh.h>
 #include <Urho3D/Navigation/Navigable.h>
+#include <Urho3D/Navigation/NavigationEvents.h>
 #include <Urho3D/Navigation/Obstacle.h>
 #include <Urho3D/Graphics/Octree.h>
 #include <Urho3D/Graphics/Renderer.h>
@@ -135,6 +136,8 @@ void CrowdNavigation::CreateScene()
 
     // Create a DynamicNavigationMesh component to the scene root
     DynamicNavigationMesh* navMesh = scene_->CreateComponent<DynamicNavigationMesh>();
+    // Set the agent height large enough to exclude the layers under boxes
+    navMesh->SetAgentHeight(10.0f);
     navMesh->SetTileSize(64);
     // 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
@@ -153,7 +156,7 @@ void CrowdNavigation::CreateScene()
     CreateJack(Vector3(-5.0f, 0.0f, 20.0f));
     
     // Create some mushrooms
-    const unsigned NUM_MUSHROOMS = 75;
+    const unsigned NUM_MUSHROOMS = 100;
     for (unsigned i = 0; i < NUM_MUSHROOMS; ++i)
         CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
     
@@ -218,6 +221,10 @@ void CrowdNavigation::SubscribeToEvents()
     // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
     // debug geometry
     SubscribeToEvent(E_POSTRENDERUPDATE, HANDLER(CrowdNavigation, HandlePostRenderUpdate));
+
+    // Subscribe HandleCrowdAgentFailure() function for resolving invalidation issues with agents, during which we
+    // use a larger extents for finding a point on the navmesh to fix the agent's position
+    SubscribeToEvent(E_CROWD_AGENT_FAILURE, HANDLER(CrowdNavigation, HandleCrowdAgentFailure));
 }
 
 void CrowdNavigation::MoveCamera(float timeStep)
@@ -328,7 +335,7 @@ void CrowdNavigation::SetPathPoint()
                 else
                 {
                     // Keep the random point somewhere on the navigation mesh
-                    Vector3 targetPos = navMesh->FindNearestPoint(pathPos + Vector3(Random(7.0f), 0.0f, Random(7.0f)), Vector3(1.0f, 1.0f, 1.0f));
+                    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);
                 }
             }
@@ -369,6 +376,7 @@ void CrowdNavigation::CreateJack(const Vector3& pos)
 
     // Create the CrowdAgent
     CrowdAgent* agent = jackNode->CreateComponent<CrowdAgent>();
+    agent->SetHeight(2.0f);
     jackNodes_.Push(jackNode);
 }
 
@@ -443,7 +451,9 @@ void CrowdNavigation::HandlePostRenderUpdate(StringHash eventType, VariantMap& e
     // If draw debug mode is enabled, draw navigation mesh debug geometry
     if (drawDebug_)
     {
-        scene_->GetComponent<DynamicNavigationMesh>()->DrawDebugGeometry(true);
+        DebugRenderer* debugRenderer = scene_->GetComponent<DebugRenderer>();
+        DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
+        navMesh->DrawDebugGeometry(debugRenderer, true);
     
         for (unsigned i = 0; i < jackNodes_.Size(); ++i)
         {
@@ -458,3 +468,22 @@ void CrowdNavigation::HandlePostRenderUpdate(StringHash eventType, VariantMap& e
         }
     }
 }
+
+void CrowdNavigation::HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData)
+{
+    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));
+        // Set the new node position, CrowdAgent component will automatically reset the state of the agent
+        node->SetWorldPosition(newPt);
+    }
+}

+ 2 - 0
Source/Samples/39_CrowdNavigation/CrowdNavigation.h

@@ -150,6 +150,8 @@ private:
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle the post-render update event.
     void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
+    /// Handle problems with crowd agent placement.
+    void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData);
 
     /// Last calculated path.
     PODVector<Vector3> currentPath_;

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

@@ -19,11 +19,11 @@ class DetourCrowdManager : public Component
 
     void SetNavigationMesh(NavigationMesh *navMesh);
     void SetMaxAgents(unsigned agentCt);
-    void SetAreaTypeCost(unsigned filterID, unsigned areaType, float cost);
+    void SetAreaCost(unsigned filterID, unsigned areaID, float cost);
     
     NavigationMesh* GetNavigationMesh();
     unsigned GetMaxAgents() const;
-    float GetAreaTypeCost(unsigned filterID, unsigned areaType) const;
+    float GetAreaCost(unsigned filterID, unsigned areaID) const;
     unsigned GetAgentCount() const;
     
     tolua_property__get_set NavigationMesh* navigationMesh;

+ 3 - 3
Source/Urho3D/LuaScript/pkgs/Navigation/NavArea.pkg

@@ -2,13 +2,13 @@ $#include "Navigation/NavArea.h"
 
 class NavArea : public Component
 {
-    unsigned GetAreaType() const;
-    void SetAreaType(unsigned);
+    unsigned GetAreaID() const;
+    void SetAreaID(unsigned);
     BoundingBox GetBoundingBox();
     void SetBoundingBox(const BoundingBox& bnds);
     BoundingBox GetWorldBoundingBox() const;
     
-    tolua_property__get_set unsigned areaType;
+    tolua_property__get_set unsigned areaID;
     tolua_property__get_set BoundingBox boundingBox;
     tolua_readonly tolua_property__get_set BoundingBox worldBoundingBox;
 };

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

@@ -25,6 +25,7 @@ class NavigationMesh : public Component
     void SetDetailSampleDistance(float distance);
     void SetDetailSampleMaxError(float error);
     void SetPadding(const Vector3& padding);
+    void SetAreaCost(unsigned areaID, float cost);
     bool Build();
     bool Build(const BoundingBox& boundingBox);
     
@@ -54,6 +55,7 @@ class NavigationMesh : public Component
     float GetDetailSampleDistance() const;
     float GetDetailSampleMaxError() const;
     const Vector3& GetPadding() const;
+    float GetAreaCost(unsigned areaID) const;
     bool IsInitialized() const;
     const BoundingBox& GetBoundingBox() const;
     BoundingBox GetWorldBoundingBox() const;

+ 59 - 9
Source/Urho3D/Navigation/CrowdAgent.cpp

@@ -77,7 +77,10 @@ CrowdAgent::CrowdAgent(Context* context) :
     height_(0.0f),
     filterType_(DEFAULT_AGENT_NAVIGATION_FILTER_TYPE),
     navQuality_(DEFAULT_AGENT_AVOIDANCE_QUALITY),
-    navPushiness_(DEFAULT_AGENT_NAVIGATION_PUSHINESS)
+    navPushiness_(DEFAULT_AGENT_NAVIGATION_PUSHINESS),
+    previousTargetState_(CROWD_AGENT_TARGET_NONE),
+    previousAgentState_(CROWD_AGENT_READY),
+    ignoreTransformChanges_(false)
 {
 }
 
@@ -173,6 +176,25 @@ void CrowdAgent::AddAgentToCrowd()
         params.userData = this;
         crowdManager_->UpdateAgentNavigationQuality(this, navQuality_);
         crowdManager_->UpdateAgentPushiness(this, navPushiness_);
+        previousAgentState_ = GetAgentState();
+        previousTargetState_ = GetTargetState();
+
+        // Agent created, but initial state is invalid and needs to be addressed
+        if (previousAgentState_ == CROWD_AGENT_INVALID)
+        {
+            VariantMap& map = GetContext()->GetEventDataMap();
+            map[CrowdAgentFailure::P_NODE] = GetNode();
+            map[CrowdAgentFailure::P_CROWD_AGENT] = this;
+            map[CrowdAgentFailure::P_CROWD_TARGET_STATE] = previousTargetState_;
+            map[CrowdAgentFailure::P_CROWD_AGENT_STATE] = previousAgentState_;
+            map[CrowdAgentFailure::P_POSITION] = GetPosition();
+            map[CrowdAgentFailure::P_VELOCITY] = GetActualVelocity();
+            SendEvent(E_CROWD_AGENT_FAILURE, map);
+
+            // Reevaluate states as handling of event may have resulted in changes
+            previousAgentState_ = GetAgentState();
+            previousTargetState_ = GetTargetState();
+        }
     }
 }
 
@@ -361,7 +383,7 @@ Urho3D::CrowdTargetState CrowdAgent::GetTargetState() const
                     // Within its own radius of the goal?
                     if (dtVdist2D(agent->npos, &agent->cornerVerts[(agent->ncorners - 1) * 3]) <= agent->params.radius)
                         return CROWD_AGENT_TARGET_ARRIVED;
-
+            
                 }
             }
         }
@@ -382,6 +404,8 @@ void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& ne
     {
         // Notify parent node of the reposition
         VariantMap& map = GetContext()->GetEventDataMap();
+        map[CrowdAgentReposition::P_NODE] = GetNode();
+        map[CrowdAgentReposition::P_CROWD_AGENT] = this;
         map[CrowdAgentReposition::P_POSITION] = newPos;
         map[CrowdAgentReposition::P_VELOCITY] = GetActualVelocity();
         SendEvent(E_CROWD_AGENT_REPOSITION, map);
@@ -394,16 +418,35 @@ void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& ne
         }
 
         // Send a notification event if we've reached the destination
-        CrowdTargetState targetState = GetTargetState();
-        switch (targetState)
+        CrowdTargetState newTargetState = GetTargetState();
+        CrowdAgentState newAgentState = GetAgentState();
+        if (newAgentState != previousAgentState_ || newTargetState != previousTargetState_)
         {
-        case CROWD_AGENT_TARGET_ARRIVED:
             VariantMap& map = GetContext()->GetEventDataMap();
-            map[CrowdAgentStateChanged::P_STATE] = targetState;
+            map[CrowdAgentStateChanged::P_NODE] = GetNode();
+            map[CrowdAgentStateChanged::P_CROWD_AGENT] = this;
+            map[CrowdAgentStateChanged::P_CROWD_TARGET_STATE] = newTargetState;
+            map[CrowdAgentStateChanged::P_CROWD_AGENT_STATE] = newAgentState;
             map[CrowdAgentStateChanged::P_POSITION] = newPos;
             map[CrowdAgentStateChanged::P_VELOCITY] = GetActualVelocity();
             SendEvent(E_CROWD_AGENT_STATE_CHANGED, map);
-            break;
+
+            // Send a failure event if either state is a failed status
+            if (newAgentState == CROWD_AGENT_INVALID || newTargetState == CROWD_AGENT_TARGET_FAILED)
+            {
+                VariantMap& map = GetContext()->GetEventDataMap();
+                map[CrowdAgentFailure::P_NODE] = GetNode();
+                map[CrowdAgentFailure::P_CROWD_AGENT] = this;
+                map[CrowdAgentFailure::P_CROWD_TARGET_STATE] = newTargetState;
+                map[CrowdAgentFailure::P_CROWD_AGENT_STATE] = newAgentState;
+                map[CrowdAgentFailure::P_POSITION] = newPos;
+                map[CrowdAgentFailure::P_VELOCITY] = GetActualVelocity();
+                SendEvent(E_CROWD_AGENT_FAILURE, map);
+            }
+
+            // State may have been altered during the handling of the event
+            previousAgentState_ = GetAgentState();
+            previousTargetState_ = GetTargetState();
         }
     }
 }
@@ -448,9 +491,16 @@ void CrowdAgent::SetAgentDataAttr(const PODVector<unsigned char>& value)
 
 void CrowdAgent::OnMarkedDirty(Node* node)
 {
-    if (inCrowd_ && crowdManager_ && !ignoreTransformChanges_) {
+    if (inCrowd_ && crowdManager_ && !ignoreTransformChanges_ && IsEnabledEffective()) {
         dtCrowdAgent* agt = crowdManager_->GetCrowd()->getEditableAgent(agentCrowdId_);
-        memcpy(agt->npos, node->GetPosition().Data(), sizeof(float) * 3);
+        if (agt)
+        {
+            memcpy(agt->npos, node->GetWorldPosition().Data(), sizeof(float) * 3);
+
+            // If the node has been externally altered, provide the opportunity for DetourCrowd to reevaluate the crowd agent
+            if (agt->state == CROWD_AGENT_INVALID)
+                agt->state = CROWD_AGENT_READY;
+        }
     }
 }
 

+ 4 - 0
Source/Urho3D/Navigation/CrowdAgent.h

@@ -163,6 +163,10 @@ private:
     NavigationQuality navQuality_;
     /// Agent's Navigation Pushiness.
     NavigationPushiness navPushiness_;
+    /// Agent's previous target state used to check for state changes.
+    CrowdTargetState previousTargetState_;
+    /// Agent's previous agent state used to check for state changes.
+    CrowdAgentState previousAgentState_;
     /// Internal flag to ignore transform changes because it came from us, used in OnCrowdAgentReposition().
     bool ignoreTransformChanges_;
 };

+ 4 - 4
Source/Urho3D/Navigation/DetourCrowdManager.cpp

@@ -85,11 +85,11 @@ void DetourCrowdManager::SetNavigationMesh(NavigationMesh* navMesh)
     MarkNetworkUpdate();
 }
 
-void DetourCrowdManager::SetAreaTypeCost(unsigned filterID, unsigned areaType, float weight)
+void DetourCrowdManager::SetAreaCost(unsigned filterID, unsigned areaID, float weight)
 {
     dtQueryFilter* filter = crowd_->getEditableFilter(filterID);
     if (filter)
-        filter->setAreaCost((int)areaType, weight);
+        filter->setAreaCost((int)areaID, weight);
 }
 
 void DetourCrowdManager::SetMaxAgents(unsigned agentCt)
@@ -122,13 +122,13 @@ NavigationMesh* DetourCrowdManager::GetNavigationMesh()
     return navigationMesh_.Get();
 }
 
-float DetourCrowdManager::GetAreaTypeCost(unsigned filterID, unsigned areaType) const
+float DetourCrowdManager::GetAreaCost(unsigned filterID, unsigned areaID) const
 {
     if (crowd_ && navigationMesh_)
     {
         const dtQueryFilter* filter = crowd_->getFilter((int)filterID);
         if (filter)
-            return filter->getAreaCost((int)areaType);
+            return filter->getAreaCost((int)areaID);
     }
     return 0.0f;
 }

+ 2 - 2
Source/Urho3D/Navigation/DetourCrowdManager.h

@@ -66,14 +66,14 @@ public:
     /// Assigns the navigation mesh for the crowd.
     void SetNavigationMesh(NavigationMesh* navMesh);
     /// Set the cost of an area-type for the specified navigation filter type.
-    void SetAreaTypeCost(unsigned filterTypeID, unsigned areaType, float weight);
+    void SetAreaCost(unsigned filterTypeID, unsigned areaID, float weight);
     /// Set the maximum number of agents.
     void SetMaxAgents(unsigned agentCt);
 
     /// Get the Navigation mesh assigned to the crowd.
     NavigationMesh* GetNavigationMesh();
     /// Get the cost of an area-type for the specified navigation filter type.
-    float GetAreaTypeCost(unsigned filterTypeID, unsigned areaType) const;
+    float GetAreaCost(unsigned filterTypeID, unsigned areaID) const;
     /// Get the maximum number of agents.
     unsigned GetMaxAgents() const { return maxAgents_; }
     /// Get the current number of active agents.

+ 47 - 2
Source/Urho3D/Navigation/DynamicNavigationMesh.cpp

@@ -458,6 +458,47 @@ bool DynamicNavigationMesh::Build(const BoundingBox& boundingBox)
     return true;
 }
 
+
+void DynamicNavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
+{
+    if (!debug || !navMesh_ || !node_)
+        return;
+
+    const Matrix3x4& worldTransform = node_->GetWorldTransform();
+
+    const dtNavMesh* navMesh = navMesh_;
+
+    for (int z = 0; z < numTilesZ_; ++z)
+    {
+        for (int x = 0; x < numTilesX_; ++x)
+        {
+            // Get the layers from the tile-cache
+            const dtMeshTile* tiles[TILECACHE_MAXLAYERS];
+            int tileCount = navMesh->getTilesAt(x, z, tiles, TILECACHE_MAXLAYERS);
+            for (int i = 0; i < tileCount; ++i) 
+            {
+                const dtMeshTile* tile = tiles[i];
+                if (!tile)
+                    continue;
+
+                for (int i = 0; i < tile->header->polyCount; ++i)
+                {
+                    dtPoly* poly = tile->polys + i;
+                    for (unsigned j = 0; j < poly->vertCount; ++j)
+                    {
+                        debug->AddLine(
+                            worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[j] * 3]),
+                            worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[(j + 1) % poly->vertCount] * 3]),
+                            Color::YELLOW,
+                            depthTest
+                            );
+                    }
+                }
+            }
+        }
+    }
+}
+
 void DynamicNavigationMesh::SetNavigationDataAttr(const PODVector<unsigned char>& value)
 {
     ReleaseNavigationMesh();
@@ -643,8 +684,8 @@ int DynamicNavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryLis
     rcRasterizeTriangles(build.ctx_, &build.vertices_[0].x_, build.vertices_.Size(), &build.indices_[0],
         triAreas.Get(), numTriangles, *build.heightField_, cfg.walkableClimb);
     rcFilterLowHangingWalkableObstacles(build.ctx_, cfg.walkableClimb, *build.heightField_);
-    //\todo figure out why behavior of rcFilterLedgeSpans differs between regular tiled nav mesh and cached tiled nav mesh
-    //rcFilterLedgeSpans(build.ctx_, cfg.walkableHeight, cfg.walkableClimb, *build.heightField_);
+    
+    rcFilterLedgeSpans(build.ctx_, cfg.walkableHeight, cfg.walkableClimb, *build.heightField_);
     rcFilterWalkableLowHeightSpans(build.ctx_, cfg.walkableHeight, *build.heightField_);
 
     build.compactHeightField_ = rcAllocCompactHeightfield();
@@ -742,6 +783,8 @@ int DynamicNavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryLis
     {
         using namespace NavigationAreaRebuilt;
         VariantMap& eventData = GetContext()->GetEventDataMap();
+        eventData[P_NODE] = GetNode();
+        eventData[P_MESH] = this;
         eventData[P_BOUNDSMIN] = Variant(tileBoundingBox.min_);
         eventData[P_BOUNDSMAX] = Variant(tileBoundingBox.max_);
         SendEvent(E_NAVIGATION_AREA_REBUILT, eventData);
@@ -809,6 +852,7 @@ void DynamicNavigationMesh::AddObstacle(Obstacle* obstacle, bool silent)
             using namespace NavigationObstacleAdded;
             VariantMap& eventData = GetContext()->GetEventDataMap();
             eventData[P_NODE] = obstacle->GetNode();
+            eventData[P_OBSTACLE] = obstacle;
             eventData[P_POSITION] = obstacle->GetNode()->GetWorldPosition();
             eventData[P_RADIUS] = obstacle->GetRadius();
             eventData[P_HEIGHT] = obstacle->GetHeight();
@@ -842,6 +886,7 @@ void DynamicNavigationMesh::RemoveObstacle(Obstacle* obstacle, bool silent)
             using namespace NavigationObstacleRemoved;
             VariantMap& eventData = GetContext()->GetEventDataMap();
             eventData[P_NODE] = obstacle->GetNode();
+            eventData[P_OBSTACLE] = obstacle;
             eventData[P_POSITION] = obstacle->GetNode()->GetWorldPosition();
             eventData[P_RADIUS] = obstacle->GetRadius();
             eventData[P_HEIGHT] = obstacle->GetHeight();

+ 67 - 65
Source/Urho3D/Navigation/DynamicNavigationMesh.h

@@ -35,70 +35,72 @@ struct dtTileCachePolyMesh;
 namespace Urho3D
 {
 
-    class OffMeshConnection;
-    class Obstacle;
-
-    class URHO3D_API DynamicNavigationMesh : public NavigationMesh
-    {
-        OBJECT(DynamicNavigationMesh)
-        friend class Obstacle;
-        friend struct MeshProcess;
-
-    public:
-        /// Constructor.
-        DynamicNavigationMesh(Context*);
-        /// Destructor.
-        virtual ~DynamicNavigationMesh();
-
-        /// Register with engine context.
-        static void RegisterObject(Context*);
-
-        /// Build/rebuild the entire navigation mesh.
-        virtual bool Build();
-        /// Build/rebuild a portion of the navigation mesh.
-        virtual bool Build(const BoundingBox& boundingBox);
-
-        /// Set navigation data attribute.
-        virtual void SetNavigationDataAttr(const PODVector<unsigned char>& value);
-        /// Return navigation data attribute.
-        virtual PODVector<unsigned char> GetNavigationDataAttr() const;
-
-    protected:
-        struct TileCacheData;
-
-        /// Subscribe to events when assigned to a node.
-        virtual void OnNodeSet(Node*);
-        /// Trigger the tile cache to make updates to the nav mesh if necessary.
-        void HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData);
-
-        /// Used by Obstacle class to add itself to the tile cache, if 'silent' an event will not be raised.
-        void AddObstacle(Obstacle* obstacle, bool silent = false);
-        /// Used by Obstacle class to update itself.
-        void ObstacleChanged(Obstacle* obstacle);
-        /// Used by Obstacle class to remove itself from the tile cache, if 'silent' an event will not be raised.
-        void RemoveObstacle(Obstacle*, bool silent = false);
-
-        /// Build one tile of the navigation mesh. Return true if successful.
-        int BuildTile(Vector<NavigationGeometryInfo>& geometryList, int x, int z, TileCacheData*);
-        /// Off-mesh connections to be rebuilt in the mesh processor.
-        PODVector<OffMeshConnection*> CollectOffMeshConnections(const BoundingBox& bounds);
-        /// Release the navigation mesh, query, and tile cache.
-        virtual void ReleaseNavigationMesh();
-
-    private:
-        /// Free the tile cache.
-        void ReleaseTileCache();
-
-        /// Detour tile cache instance that works with the nav mesh.
-        dtTileCache* tileCache_;
-        /// Used by dtTileCache to allocate blocks of memory.
-        dtTileCacheAlloc* allocator_;
-        /// Used by dtTileCache to compress the original tiles to use when reconstructing for changes.
-        dtTileCacheCompressor* compressor_;
-        /// Mesh processer used by Detour, in this case a 'pass-through' processor.
-        dtTileCacheMeshProcess* meshProcessor_;
-        /// Maximum number of obstacle objects allowed.
-        unsigned maxObstacles_;
-    };
+class OffMeshConnection;
+class Obstacle;
+
+class URHO3D_API DynamicNavigationMesh : public NavigationMesh
+{
+    OBJECT(DynamicNavigationMesh)
+    friend class Obstacle;
+    friend struct MeshProcess;
+
+public:
+    /// Constructor.
+    DynamicNavigationMesh(Context*);
+    /// Destructor.
+    virtual ~DynamicNavigationMesh();
+
+    /// Register with engine context.
+    static void RegisterObject(Context*);
+
+    /// Build/rebuild the entire navigation mesh.
+    virtual bool Build();
+    /// Build/rebuild a portion of the navigation mesh.
+    virtual bool Build(const BoundingBox& boundingBox);
+    /// Visualize the component as debug geometry.
+    virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
+
+    /// Set navigation data attribute.
+    virtual void SetNavigationDataAttr(const PODVector<unsigned char>& value);
+    /// Return navigation data attribute.
+    virtual PODVector<unsigned char> GetNavigationDataAttr() const;
+
+protected:
+    struct TileCacheData;
+
+    /// Subscribe to events when assigned to a node.
+    virtual void OnNodeSet(Node*);
+    /// Trigger the tile cache to make updates to the nav mesh if necessary.
+    void HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData);
+
+    /// Used by Obstacle class to add itself to the tile cache, if 'silent' an event will not be raised.
+    void AddObstacle(Obstacle* obstacle, bool silent = false);
+    /// Used by Obstacle class to update itself.
+    void ObstacleChanged(Obstacle* obstacle);
+    /// Used by Obstacle class to remove itself from the tile cache, if 'silent' an event will not be raised.
+    void RemoveObstacle(Obstacle*, bool silent = false);
+
+    /// Build one tile of the navigation mesh. Return true if successful.
+    int BuildTile(Vector<NavigationGeometryInfo>& geometryList, int x, int z, TileCacheData*);
+    /// Off-mesh connections to be rebuilt in the mesh processor.
+    PODVector<OffMeshConnection*> CollectOffMeshConnections(const BoundingBox& bounds);
+    /// Release the navigation mesh, query, and tile cache.
+    virtual void ReleaseNavigationMesh();
+
+private:
+    /// Free the tile cache.
+    void ReleaseTileCache();
+
+    /// Detour tile cache instance that works with the nav mesh.
+    dtTileCache* tileCache_;
+    /// Used by dtTileCache to allocate blocks of memory.
+    dtTileCacheAlloc* allocator_;
+    /// Used by dtTileCache to compress the original tiles to use when reconstructing for changes.
+    dtTileCacheCompressor* compressor_;
+    /// Mesh processer used by Detour, in this case a 'pass-through' processor.
+    dtTileCacheMeshProcess* meshProcessor_;
+    /// Maximum number of obstacle objects allowed.
+    unsigned maxObstacles_;
+};
 
 }

+ 13 - 7
Source/Urho3D/Navigation/NavArea.cpp

@@ -20,23 +20,27 @@
 // THE SOFTWARE.
 //
 
+#include "../Scene/Component.h"
 #include "../Core/Context.h"
 #include "../Graphics/DebugRenderer.h"
-#include "../Scene/Component.h"
-#include "../Scene/Node.h"
+#include "../IO/Log.h"
 #include "../Navigation/NavArea.h"
+#include "../Scene/Node.h"
+#include "../Container/Str.h"
 
 namespace Urho3D
 {
+    static const unsigned MAX_NAV_AREA_ID = 255;
     static const Vector3 DEFAULT_BOUNDING_BOX_MIN(-10.0f, -10.0f, -10.0f);
     static const Vector3 DEFAULT_BOUNDING_BOX_MAX(10.0f, 10.0f, 10.0f);
-    static const unsigned DEFAULT_AREA = 0;
+    static const unsigned DEFAULT_MASK_FLAG = 0;
+    static const unsigned DEFAULT_AREA_ID = 0;
 
     extern const char* NAVIGATION_CATEGORY;
 
     NavArea::NavArea(Context* context) :
         Component(context),
-        areaType_(0),
+        areaID_(DEFAULT_AREA_ID),
         boundingBox_(DEFAULT_BOUNDING_BOX_MIN, DEFAULT_BOUNDING_BOX_MAX)
     {
     }
@@ -52,12 +56,14 @@ namespace Urho3D
         COPY_BASE_ATTRIBUTES(Component);
         ATTRIBUTE("Bounding Box Min", Vector3, boundingBox_.min_, DEFAULT_BOUNDING_BOX_MIN, AM_DEFAULT);
         ATTRIBUTE("Bounding Box Max", Vector3, boundingBox_.max_, DEFAULT_BOUNDING_BOX_MAX, AM_DEFAULT);
-        ACCESSOR_ATTRIBUTE("Area Type", GetAreaType, SetAreaType, unsigned, DEFAULT_AREA, AM_DEFAULT);
+        ACCESSOR_ATTRIBUTE("Area ID", GetAreaID, SetAreaID, unsigned, DEFAULT_AREA_ID, AM_DEFAULT);
     }
 
-    void NavArea::SetAreaType(unsigned newType)
+    void NavArea::SetAreaID(unsigned newID)
     {
-        areaType_ = newType;
+        if (newID > MAX_NAV_AREA_ID)
+            LOGERRORF("NavArea Area ID %u exceeds maximum value of %u", newID, MAX_NAV_AREA_ID);
+        areaID_ = (unsigned char)newID;
         MarkNetworkUpdate();
     }
 

+ 3 - 3
Source/Urho3D/Navigation/NavArea.h

@@ -43,9 +43,9 @@ namespace Urho3D
         virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
 
         /// Get the area id for this volume.
-        unsigned GetAreaType() const { return areaType_; }
+        unsigned GetAreaID() const { return (unsigned)areaID_; }
         /// Set the area id for this volume.
-        void SetAreaType(unsigned);
+        void SetAreaID(unsigned newID);
 
         /// Get the bounding box of this navigation area, in local space.
         BoundingBox GetBoundingBox() const { return boundingBox_; }
@@ -61,6 +61,6 @@ namespace Urho3D
         /// Flags to assign to the marked area of the navigation map.
         unsigned flags_;
         /// Area id to assign to the marked area.
-        unsigned areaType_;
+        unsigned char areaID_;
     };
 }

+ 22 - 2
Source/Urho3D/Navigation/NavigationEvents.h

@@ -37,6 +37,8 @@ EVENT(E_NAVIGATION_MESH_REBUILT, NavigationMeshRebuilt)
 /// Partial bounding box rebuild of navigation mesh.
 EVENT(E_NAVIGATION_AREA_REBUILT, NavigationAreaRebuilt)
 {
+    PARAM(P_NODE, Node); // Node pointer
+    PARAM(P_MESH, Mesh); // NavigationMesh pointer
     PARAM(P_BOUNDSMIN, BoundsMin); // Vector3
     PARAM(P_BOUNDSMAX, BoundsMax); // Vector3
 }
@@ -44,22 +46,39 @@ EVENT(E_NAVIGATION_AREA_REBUILT, NavigationAreaRebuilt)
 /// Crowd agent has been repositioned.
 EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
 {
+    PARAM(P_NODE, Node); // Node pointer
+    PARAM(P_CROWD_AGENT, CrowdAgent); // CrowdAgent pointer
+    PARAM(P_POSITION, Position); // Vector3
+    PARAM(P_VELOCITY, Velocity); // Vector3
+}
+
+/// Crowd agent's internal state has become invalidated.
+EVENT(E_CROWD_AGENT_FAILURE, CrowdAgentFailure)
+{
+    PARAM(P_NODE, Node); // Node pointer
+    PARAM(P_CROWD_AGENT, CrowdAgent); // CrowdAgent pointer
     PARAM(P_POSITION, Position); // Vector3
     PARAM(P_VELOCITY, Velocity); // Vector3
+    PARAM(P_CROWD_AGENT_STATE, CrowdAgentState); // int
+    PARAM(P_CROWD_TARGET_STATE, CrowdTargetState); // int
 }
 
-/// Crowd agent's state has been changed, reached goal.
+/// Crowd agent's state has been changed.
 EVENT(E_CROWD_AGENT_STATE_CHANGED, CrowdAgentStateChanged)
 {
+    PARAM(P_NODE, Node); // Node pointer
+    PARAM(P_CROWD_AGENT, CrowdAgent); // CrowdAgent pointer
     PARAM(P_POSITION, Position); // Vector3
     PARAM(P_VELOCITY, Velocity); // Vector3
-    PARAM(P_STATE, State); // int
+    PARAM(P_CROWD_AGENT_STATE, CrowdAgentState); // int
+    PARAM(P_CROWD_TARGET_STATE, CrowdTargetState); // int
 }
 
 /// Addition of obstacle to dynamic navigation mesh.
 EVENT(E_NAVIGATION_OBSTACLE_ADDED, NavigationObstacleAdded)
 {
     PARAM(P_NODE, Node); // Node pointer
+    PARAM(P_OBSTACLE, Obstacle); // Obstacle pointer
     PARAM(P_POSITION, Position); // Vector3
     PARAM(P_RADIUS, Radius); // float
     PARAM(P_HEIGHT, Height); // float
@@ -69,6 +88,7 @@ EVENT(E_NAVIGATION_OBSTACLE_ADDED, NavigationObstacleAdded)
 EVENT(E_NAVIGATION_OBSTACLE_REMOVED, NavigationObstacleRemoved)
 {
     PARAM(P_NODE, Node); // Node pointer
+    PARAM(P_OBSTACLE, Obstacle); // Obstacle pointer
     PARAM(P_POSITION, Position); // Vector3
     PARAM(P_RADIUS, Radius); // float
     PARAM(P_HEIGHT, Height); // float

+ 29 - 18
Source/Urho3D/Navigation/NavigationMesh.cpp

@@ -172,21 +172,24 @@ void NavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
     {
         for (int x = 0; x < numTilesX_; ++x)
         {
-            const dtMeshTile* tile = navMesh->getTileAt(x, z, 0);
-            if (!tile)
-                continue;
-
-            for (int i = 0; i < tile->header->polyCount; ++i)
+            for (int i = 0; i < 128; ++i)
             {
-                dtPoly* poly = tile->polys + i;
-                for (unsigned j = 0; j < poly->vertCount; ++j)
+                const dtMeshTile* tile = navMesh->getTileAt(x, z, i);
+                if (!tile)
+                    continue;
+
+                for (int i = 0; i < tile->header->polyCount; ++i)
                 {
-                    debug->AddLine(
-                        worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[j] * 3]),
-                        worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[(j + 1) % poly->vertCount] * 3]),
-                        Color::YELLOW,
-                        depthTest
-                    );
+                    dtPoly* poly = tile->polys + i;
+                    for (unsigned j = 0; j < poly->vertCount; ++j)
+                    {
+                        debug->AddLine(
+                            worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[j] * 3]),
+                            worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[(j + 1) % poly->vertCount] * 3]),
+                            Color::YELLOW,
+                            depthTest
+                            );
+                    }
                 }
             }
         }
@@ -621,10 +624,10 @@ void NavigationMesh::DrawDebugGeometry(bool depthTest)
     }
 }
 
-void NavigationMesh::SetAreaTypeCost(unsigned areaType, float cost)
+void NavigationMesh::SetAreaCost(unsigned areaID, float cost)
 {
     if (queryFilter_)
-        queryFilter_->setAreaCost((int)areaType, cost);
+        queryFilter_->setAreaCost((int)areaID, cost);
 }
 
 BoundingBox NavigationMesh::GetWorldBoundingBox() const
@@ -632,6 +635,13 @@ BoundingBox NavigationMesh::GetWorldBoundingBox() const
     return node_ ? boundingBox_.Transformed(node_->GetWorldTransform()) : boundingBox_;
 }
 
+float NavigationMesh::GetAreaCost(unsigned areaID) const
+{
+    if (queryFilter_)
+        return queryFilter_->getAreaCost((int)areaID);
+    return 1.0f;
+}
+
 void NavigationMesh::SetNavigationDataAttr(const PODVector<unsigned char>& value)
 {
     ReleaseNavigationMesh();
@@ -777,7 +787,7 @@ void NavigationMesh::CollectGeometries(Vector<NavigationGeometryInfo>& geometryL
     {
         NavArea* area = navAreas[i];
         // Ignore disabled AND any areas that have no meaningful settings
-        if (area->IsEnabledEffective() && area->GetAreaType() != 0)
+        if (area->IsEnabledEffective() && area->GetAreaID() != 0)
         {
             NavigationGeometryInfo info;
             info.component_ = area;
@@ -889,8 +899,7 @@ void NavigationMesh::GetTileGeometry(NavBuildData* build, Vector<NavigationGeome
             {
                 NavArea* area = static_cast<NavArea*>(geometryList[i].component_);
                 NavAreaStub stub;
-                //\todo is there an alternative to casting down? Attributes restricts area ID/type to being an unsigned int
-                stub.areaID_ = (unsigned char)area->GetAreaType();
+                stub.areaID_ = (unsigned char)area->GetAreaID();
                 stub.bounds_ = area->GetWorldBoundingBox();
                 build->navAreas_.Push(stub);
                 continue;
@@ -1259,6 +1268,8 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
     {
         using namespace NavigationAreaRebuilt;
         VariantMap& eventData = GetContext()->GetEventDataMap();
+        eventData[P_NODE] = GetNode();
+        eventData[P_MESH] = this;
         eventData[P_BOUNDSMIN] = Variant(tileBoundingBox.min_);
         eventData[P_BOUNDSMAX] = Variant(tileBoundingBox.max_);
         SendEvent(E_NAVIGATION_AREA_REBUILT, eventData);

+ 4 - 3
Source/Urho3D/Navigation/NavigationMesh.h

@@ -72,7 +72,6 @@ class URHO3D_API NavigationMesh : public Component
 {
     OBJECT(NavigationMesh);
     friend class DetourCrowdManager;
-    friend class AnnotationBuilder;
 
 public:
     /// Construct.
@@ -113,6 +112,8 @@ public:
     void SetDetailSampleMaxError(float error);
     /// Set padding of the navigation mesh bounding box. Having enough padding allows to add geometry on the extremities of the navigation mesh when doing partial rebuilds.
     void SetPadding(const Vector3& padding);
+    /// Set the cost of an area.
+    void SetAreaCost(unsigned areaID, float cost);
     /// Rebuild the navigation mesh. Return true if successful.
     virtual bool Build();
     /// Rebuild part of the navigation mesh contained by the world-space bounding box. Return true if successful.
@@ -133,8 +134,6 @@ public:
     Vector3 Raycast(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE);
     /// Add debug geometry to the debug renderer.
     void DrawDebugGeometry(bool depthTest);
-    /// Set the cost of an area.
-    void SetAreaTypeCost(unsigned areaType, float cost);
 
     /// Return the given name of this navigation mesh.
     String GetMeshName() const { return meshName_; }
@@ -168,6 +167,8 @@ public:
     float GetDetailSampleMaxError() const { return detailSampleMaxError_; }
     /// Return navigation mesh bounding box padding.
     const Vector3& GetPadding() const { return padding_; }
+    /// Get the current cost of an area
+    float GetAreaCost(unsigned areaID) const;
     /// Return whether has been initialized with valid navigation data.
     bool IsInitialized() const { return navMesh_ != 0; }
     /// Return local space bounding box of the navigation mesh.

+ 9 - 8
Source/Urho3D/Script/NavigationAPI.cpp

@@ -65,7 +65,8 @@ template<class T> static void RegisterNavMeshBase(asIScriptEngine* engine, const
 {
     engine->RegisterObjectMethod(name, "bool Build()", asMETHODPR(T, Build, (void), bool), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "bool Build(const BoundingBox&in)", asMETHODPR(T, Build, (const BoundingBox&), bool), asCALL_THISCALL);
-    engine->RegisterObjectMethod(name, "void SetAreaTypeCost(uint, float)", asMETHOD(T, SetAreaTypeCost), asCALL_THISCALL);
+    engine->RegisterObjectMethod(name, "void SetAreaCost(uint, float)", asMETHOD(T, SetAreaCost), asCALL_THISCALL);
+    engine->RegisterObjectMethod(name, "float GetAreaCost(uint) const", asMETHOD(T, GetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "Vector3 FindNearestPoint(const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(T, FindNearestPoint), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "Vector3 MoveAlongSurface(const Vector3&in, const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0), uint = 3)", asMETHOD(T, MoveAlongSurface), asCALL_THISCALL);
     //engine->RegisterObjectMethod(name, "Array<Vector3>@ FindPath(const Vector3&in, const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asFUNCTION(NavigationMeshFindPath), asCALL_CDECL_OBJLAST);
@@ -150,8 +151,8 @@ void RegisterNavArea(asIScriptEngine* engine)
     RegisterComponent<NavArea>(engine, "NavArea");
     engine->RegisterObjectMethod("NavArea", "BoundingBox get_boundingBox() const", asMETHOD(NavArea, GetBoundingBox), asCALL_THISCALL);
     engine->RegisterObjectMethod("NavArea", "void set_boundingBox(const BoundingBox&in)", asMETHOD(NavArea, SetBoundingBox), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavArea", "uint get_areaType() const", asMETHOD(NavArea, GetAreaType), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavArea", "void set_areaType(uint)", asMETHOD(NavArea, SetAreaType), asCALL_THISCALL);
+    engine->RegisterObjectMethod("NavArea", "uint get_areaID() const", asMETHOD(NavArea, GetAreaID), asCALL_THISCALL);
+    engine->RegisterObjectMethod("NavArea", "void set_areaID(uint)", asMETHOD(NavArea, SetAreaID), asCALL_THISCALL);
     engine->RegisterObjectMethod("NavArea", "BoundingBox get_worldBoundingBox() const", asMETHOD(NavArea, GetWorldBoundingBox), asCALL_THISCALL);
 }
 
@@ -164,8 +165,8 @@ void RegisterDetourCrowdManager(asIScriptEngine* engine)
     engine->RegisterObjectMethod("DetourCrowdManager", "int get_maxAgents() const", asMETHOD(DetourCrowdManager, GetMaxAgents), asCALL_THISCALL);
     engine->RegisterObjectMethod("DetourCrowdManager", "void set_maxAgents(int)", asMETHOD(DetourCrowdManager, SetMaxAgents), asCALL_THISCALL);
     engine->RegisterObjectMethod("DetourCrowdManager", "Array<CrowdAgent@>@ GetActiveAgents()", asFUNCTION(DetourCrowdManagerGetActiveAgents), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("DetourCrowdManager", "void SetAreaTypeCost(uint, uint, float)", asMETHOD(DetourCrowdManager, SetAreaTypeCost), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DetourCrowdManager", "float GetAreaTypeCost(uint, uint)", asMETHOD(DetourCrowdManager, GetAreaTypeCost), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DetourCrowdManager", "void SetAreaCost(uint, uint, float)", asMETHOD(DetourCrowdManager, SetAreaCost), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DetourCrowdManager", "float GetAreaCost(uint, uint)", asMETHOD(DetourCrowdManager, GetAreaCost), asCALL_THISCALL);
 }
 
 void RegisterCrowdAgent(asIScriptEngine* engine)
@@ -181,9 +182,9 @@ void RegisterCrowdAgent(asIScriptEngine* engine)
     engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_ARRIVED", CROWD_AGENT_TARGET_ARRIVED);
 
     engine->RegisterEnum("CrowdAgentState");
-    engine->RegisterEnumValue("CrowdAgentState", "NAV_AGENT_INVALID", CROWD_AGENT_INVALID);
-    engine->RegisterEnumValue("CrowdAgentState", "NAV_AGENT_READY", CROWD_AGENT_READY);
-    engine->RegisterEnumValue("CrowdAgentState", "NAV_AGENT_TRAVERSINGLINK", CROWD_AGENT_TRAVERSINGLINK);
+    engine->RegisterEnumValue("CrowdAgentState", "CROWD_AGENT_INVALID", CROWD_AGENT_INVALID);
+    engine->RegisterEnumValue("CrowdAgentState", "CROWD_AGENT_READY", CROWD_AGENT_READY);
+    engine->RegisterEnumValue("CrowdAgentState", "CROWD_AGENT_TRAVERSINGLINK", CROWD_AGENT_TRAVERSINGLINK);
 
     engine->RegisterEnum("NavigationAvoidanceQuality");
     engine->RegisterEnumValue("NavigationAvoidanceQuality", "NAVIGATIONQUALITY_LOW", NAVIGATIONQUALITY_LOW);

+ 25 - 1
bin/Data/LuaScripts/39_CrowdNavigation.lua

@@ -85,6 +85,8 @@ function CreateScene()
 
     -- Create a DynamicNavigationMesh component to the scene root
     local navMesh = scene_:CreateComponent("DynamicNavigationMesh")
+    -- 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
     -- Create a Navigable component to the scene root. This tags all of the geometry in the scene as being part of the
@@ -157,6 +159,10 @@ function SubscribeToEvents()
     -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
     -- debug geometry
     SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+    
+    -- Subscribe HandleCrowdAgentFailure() function for resolving invalidation issues with agents, during which we
+    -- use a larger extents for finding a point on the navmesh to fix the agent's position
+    SubscribeToEvent("CrowdAgentFailure", "HandleCrowdAgentFailure")
 end
 
 function MoveCamera(timeStep)
@@ -235,7 +241,7 @@ function SetPathPoint()
                         agt:SetMoveTarget(pathPos)
                     else
                         -- Keep the random point on the navigation mesh
-                        local targetPos = navMesh:FindNearestPoint(pathPos + Vector3(Random(7.0), 0, Random(7.0)), Vector3.ONE)
+                        local targetPos = navMesh:FindNearestPoint(pathPos + Vector3(Random(-4.5, 4.5), 0, Random(-4.5, 4.5)), Vector3.ONE)
                         agt:SetMoveTarget(targetPos)
                     end
                 end
@@ -280,6 +286,7 @@ function SpawnJack(pos)
     modelObject.material = cache:GetResource("Material", "Materials/Jack.xml")
     modelObject.castShadows = true
     local agent = jackNode:CreateComponent("CrowdAgent")
+    agent.height = 2.0
     agent.enabled = false
     table.insert(jackNodes, jackNode)
 end
@@ -366,6 +373,23 @@ function HandlePostRenderUpdate(eventType, eventData)
     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))
+        -- Set the new node position, CrowdAgent component will automatically reset the state of the agent
+        node:SetWorldPosition(newPos)
+    end
+
+end
+
 -- Create XML patch instructions for screen joystick layout specific to this sample app
 function GetScreenJoystickPatchString()
     return

+ 28 - 7
bin/Data/Scripts/39_CrowdNavigation.as

@@ -69,12 +69,11 @@ void CreateScene()
 
     // Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
     // rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
-    const uint NUM_BOXES = 15;
-    Random(3.0f);
+    const uint NUM_BOXES = 20;
     for (uint i = 0; i < NUM_BOXES; ++i)
     {
         Node@ boxNode = scene_.CreateChild("Box");
-        float size = 1.0f + Random(5.0f);
+        float size = 1.0f + Random(10.0f);
         boxNode.position = Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f);
         boxNode.SetScale(size);
         StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
@@ -87,6 +86,8 @@ void CreateScene()
 
     // Create a DynamicNavigationMesh component to the scene root
     DynamicNavigationMesh@ navMesh = scene_.CreateComponent("DynamicNavigationMesh");
+    // Set the agent height large enough to exclude the layers under boxes
+    navMesh.agentHeight = 10;
     navMesh.tileSize = 64;
     // 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
@@ -109,7 +110,7 @@ void CreateScene()
     
     // Because the navigation mesh is a cache the mushrooms can be created after building
     // Create some mushrooms
-    const uint NUM_MUSHROOMS = 70;
+    const uint NUM_MUSHROOMS = 100;
     for (uint i = 0; i < NUM_MUSHROOMS; ++i)
         CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
     //navMesh.ForceUpdate();
@@ -167,6 +168,10 @@ void SubscribeToEvents()
     // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
     // debug geometry
     SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+    
+    // Subscribe HandleCrowdAgentFailure() function for resolving invalidation issues with agents, during which we
+    // use a larger extents for finding a point on the navmesh to fix the agent's position
+    SubscribeToEvent("CrowdAgentFailure", "HandleCrowdAgentFailure");
 }
 
 void MoveCamera(float timeStep)
@@ -231,7 +236,7 @@ void SetPathPoint()
         if (input.qualifierDown[QUAL_SHIFT])
         {
             // Spawn a jack
-            SpawnJack(Vector3(pathPos.x, 0, pathPos.z));
+            SpawnJack(pathPos);
         }
         else
         {
@@ -249,11 +254,10 @@ void SetPathPoint()
                 else
                 {
                     // Keep the random point on the navigation mesh
-                    Vector3 targetPos = navMesh.FindNearestPoint(endPos + Vector3(Random(7.0f), 0.0f, Random(7.0f)), Vector3(1.0f, 1.0f, 1.0f));
+                    Vector3 targetPos = navMesh.FindNearestPoint(endPos + Vector3(Random(-4.5f,4.5f), 0.0f, Random(-4.5, 4.5f)), Vector3(1.0f, 1.0f, 1.0f));
                     agent.SetMoveTarget(targetPos);
                 }
             }
-            //currentPath = navMesh.FindPath(jackNode.position, endPos);
         }
     }
 }
@@ -313,6 +317,7 @@ Node@ SpawnJack(const Vector3& pos)
     modelObject.material = cache.GetResource("Material", "Materials/Jack.xml");
     modelObject.castShadows = true;
     CrowdAgent@ navAgent = jackNode.CreateComponent("CrowdAgent");
+    navAgent.height = 2.0f;
     navAgent.enabled = false;
     return jackNode;
 }
@@ -404,6 +409,22 @@ void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
     }
 }
 
+void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData)
+{
+    Node@ node = eventData["Node"].GetPtr();
+    int state = eventData["CrowdAgentState"].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 (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
+        node.position = newPos;
+    }
+}
+
 // Create XML patch instructions for screen joystick layout specific to this sample app
 String patchInstructions =
         "<patch>" +