1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../Precompiled.h"
24 
25 #include "../Core/Context.h"
26 #include "../Core/Profiler.h"
27 #include "../Graphics/DebugRenderer.h"
28 #include "../IO/Log.h"
29 #include "../Navigation/CrowdAgent.h"
30 #include "../Navigation/CrowdManager.h"
31 #include "../Navigation/DynamicNavigationMesh.h"
32 #include "../Navigation/NavigationEvents.h"
33 #include "../Scene/Node.h"
34 #include "../Scene/Scene.h"
35 #include "../Scene/SceneEvents.h"
36 
37 #include <DetourCrowd/DetourCrowd.h>
38 
39 #include "../DebugNew.h"
40 
41 namespace Urho3D
42 {
43 
44 extern const char* NAVIGATION_CATEGORY;
45 
46 static const unsigned DEFAULT_MAX_AGENTS = 512;
47 static const float DEFAULT_MAX_AGENT_RADIUS = 0.f;
48 
49 const char* filterTypesStructureElementNames[] =
50 {
51     "Query Filter Type Count",
52     "   Include Flags",
53     "   Exclude Flags",
54     "   >AreaCost",
55     0
56 };
57 
58 const char* obstacleAvoidanceTypesStructureElementNames[] =
59 {
60     "Obstacle Avoid. Type Count",
61     "   Velocity Bias",
62     "   Desired Velocity Weight",
63     "   Current Velocity Weight",
64     "   Side Bias Weight",
65     "   Time of Impact Weight",
66     "   Time Horizon",
67     "   Grid Size",
68     "   Adaptive Divs",
69     "   Adaptive Rings",
70     "   Adaptive Depth",
71     0
72 };
73 
CrowdAgentUpdateCallback(dtCrowdAgent * ag,float dt)74 void CrowdAgentUpdateCallback(dtCrowdAgent* ag, float dt)
75 {
76     static_cast<CrowdAgent*>(ag->params.userData)->OnCrowdUpdate(ag, dt);
77 }
78 
CrowdManager(Context * context)79 CrowdManager::CrowdManager(Context* context) :
80     Component(context),
81     crowd_(0),
82     navigationMeshId_(0),
83     maxAgents_(DEFAULT_MAX_AGENTS),
84     maxAgentRadius_(DEFAULT_MAX_AGENT_RADIUS),
85     numQueryFilterTypes_(0),
86     numObstacleAvoidanceTypes_(0)
87 {
88     // The actual buffer is allocated inside dtCrowd, we only track the number of "slots" being configured explicitly
89     numAreas_.Reserve(DT_CROWD_MAX_QUERY_FILTER_TYPE);
90     for (unsigned i = 0; i < DT_CROWD_MAX_QUERY_FILTER_TYPE; ++i)
91         numAreas_.Push(0);
92 }
93 
~CrowdManager()94 CrowdManager::~CrowdManager()
95 {
96     dtFreeCrowd(crowd_);
97     crowd_ = 0;
98 }
99 
RegisterObject(Context * context)100 void CrowdManager::RegisterObject(Context* context)
101 {
102     context->RegisterFactory<CrowdManager>(NAVIGATION_CATEGORY);
103 
104     URHO3D_ATTRIBUTE("Max Agents", unsigned, maxAgents_, DEFAULT_MAX_AGENTS, AM_DEFAULT);
105     URHO3D_ATTRIBUTE("Max Agent Radius", float, maxAgentRadius_, DEFAULT_MAX_AGENT_RADIUS, AM_DEFAULT);
106     URHO3D_ATTRIBUTE("Navigation Mesh", unsigned, navigationMeshId_, 0, AM_DEFAULT | AM_COMPONENTID);
107     URHO3D_MIXED_ACCESSOR_VARIANT_VECTOR_STRUCTURE_ATTRIBUTE("Filter Types", GetQueryFilterTypesAttr, SetQueryFilterTypesAttr,
108                                                              VariantVector, Variant::emptyVariantVector,
109                                                              filterTypesStructureElementNames, AM_DEFAULT);
110     URHO3D_MIXED_ACCESSOR_VARIANT_VECTOR_STRUCTURE_ATTRIBUTE("Obstacle Avoidance Types", GetObstacleAvoidanceTypesAttr, SetObstacleAvoidanceTypesAttr,
111                                                              VariantVector, Variant::emptyVariantVector,
112                                                              obstacleAvoidanceTypesStructureElementNames, AM_DEFAULT);
113 }
114 
ApplyAttributes()115 void CrowdManager::ApplyAttributes()
116 {
117     // Values from Editor, saved-file, or network must be checked before applying
118     maxAgents_ = Max(1U, maxAgents_);
119     maxAgentRadius_ = Max(0.f, maxAgentRadius_);
120 
121     bool navMeshChange = false;
122     Scene* scene = GetScene();
123     if (scene && navigationMeshId_)
124     {
125         NavigationMesh* navMesh = dynamic_cast<NavigationMesh*>(scene->GetComponent(navigationMeshId_));
126         if (navMesh && navMesh != navigationMesh_)
127         {
128             SetNavigationMesh(navMesh); // This will also CreateCrowd(), so the rest of the function is unnecessary
129             return;
130         }
131     }
132     // In case of receiving an invalid component id, revert it back to the existing navmesh component id (if any)
133     navigationMeshId_ = navigationMesh_ ? navigationMesh_->GetID() : 0;
134 
135     // If the Detour crowd initialization parameters have changed then recreate it
136     if (crowd_ && (navMeshChange || crowd_->getAgentCount() != maxAgents_ || crowd_->getMaxAgentRadius() != maxAgentRadius_))
137         CreateCrowd();
138 }
139 
DrawDebugGeometry(DebugRenderer * debug,bool depthTest)140 void CrowdManager::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
141 {
142     if (debug && crowd_)
143     {
144         // Current position-to-target line
145         for (int i = 0; i < crowd_->getAgentCount(); i++)
146         {
147             const dtCrowdAgent* ag = crowd_->getAgent(i);
148             if (!ag->active)
149                 continue;
150 
151             // Draw CrowdAgent shape (from its radius & height)
152             CrowdAgent* crowdAgent = static_cast<CrowdAgent*>(ag->params.userData);
153             crowdAgent->DrawDebugGeometry(debug, depthTest);
154 
155             // Draw move target if any
156             if (crowdAgent->GetTargetState() == CA_TARGET_NONE || crowdAgent->GetTargetState() == CA_TARGET_VELOCITY)
157                 continue;
158 
159             Color color(0.6f, 0.2f, 0.2f, 1.0f);
160 
161             // Draw line to target
162             Vector3 pos1(ag->npos[0], ag->npos[1], ag->npos[2]);
163             Vector3 pos2;
164             for (int i = 0; i < ag->ncorners; ++i)
165             {
166                 pos2.x_ = ag->cornerVerts[i * 3];
167                 pos2.y_ = ag->cornerVerts[i * 3 + 1];
168                 pos2.z_ = ag->cornerVerts[i * 3 + 2];
169                 debug->AddLine(pos1, pos2, color, depthTest);
170                 pos1 = pos2;
171             }
172             pos2.x_ = ag->targetPos[0];
173             pos2.y_ = ag->targetPos[1];
174             pos2.z_ = ag->targetPos[2];
175             debug->AddLine(pos1, pos2, color, depthTest);
176 
177             // Draw target circle
178             debug->AddSphere(Sphere(pos2, 0.5f), color, depthTest);
179         }
180     }
181 }
182 
DrawDebugGeometry(bool depthTest)183 void CrowdManager::DrawDebugGeometry(bool depthTest)
184 {
185     Scene* scene = GetScene();
186     if (scene)
187     {
188         DebugRenderer* debug = scene->GetComponent<DebugRenderer>();
189         if (debug)
190             DrawDebugGeometry(debug, depthTest);
191     }
192 }
193 
SetCrowdTarget(const Vector3 & position,Node * node)194 void CrowdManager::SetCrowdTarget(const Vector3& position, Node* node)
195 {
196     if (!crowd_)
197         return;
198 
199     PODVector<CrowdAgent*> agents = GetAgents(node, false);     // Get all crowd agent components
200     Vector3 moveTarget(position);
201     for (unsigned i = 0; i < agents.Size(); ++i)
202     {
203         // Give application a chance to determine the desired crowd formation when they reach the target position
204         CrowdAgent* agent = agents[i];
205 
206         using namespace CrowdAgentFormation;
207 
208         VariantMap& map = GetEventDataMap();
209         map[P_NODE] = agent->GetNode();
210         map[P_CROWD_AGENT] = agent;
211         map[P_INDEX] = i;
212         map[P_SIZE] = agents.Size();
213         map[P_POSITION] = moveTarget;   // Expect the event handler will modify this position accordingly
214 
215         SendEvent(E_CROWD_AGENT_FORMATION, map);
216 
217         moveTarget = map[P_POSITION].GetVector3();
218         agent->SetTargetPosition(moveTarget);
219     }
220 }
221 
SetCrowdVelocity(const Vector3 & velocity,Node * node)222 void CrowdManager::SetCrowdVelocity(const Vector3& velocity, Node* node)
223 {
224     if (!crowd_)
225         return;
226 
227     PODVector<CrowdAgent*> agents = GetAgents(node, true);      // Get only crowd agent components already in the crowd
228     for (unsigned i = 0; i < agents.Size(); ++i)
229         agents[i]->SetTargetVelocity(velocity);
230 }
231 
ResetCrowdTarget(Node * node)232 void CrowdManager::ResetCrowdTarget(Node* node)
233 {
234     if (!crowd_)
235         return;
236 
237     PODVector<CrowdAgent*> agents = GetAgents(node, true);
238     for (unsigned i = 0; i < agents.Size(); ++i)
239         agents[i]->ResetTarget();
240 }
241 
SetMaxAgents(unsigned maxAgents)242 void CrowdManager::SetMaxAgents(unsigned maxAgents)
243 {
244     if (maxAgents != maxAgents_ && maxAgents > 0)
245     {
246         maxAgents_ = maxAgents;
247         CreateCrowd();
248         MarkNetworkUpdate();
249     }
250 }
251 
SetMaxAgentRadius(float maxAgentRadius)252 void CrowdManager::SetMaxAgentRadius(float maxAgentRadius)
253 {
254     if (maxAgentRadius != maxAgentRadius_ && maxAgentRadius > 0.f)
255     {
256         maxAgentRadius_ = maxAgentRadius;
257         CreateCrowd();
258         MarkNetworkUpdate();
259     }
260 }
261 
SetNavigationMesh(NavigationMesh * navMesh)262 void CrowdManager::SetNavigationMesh(NavigationMesh* navMesh)
263 {
264     UnsubscribeFromEvent(E_COMPONENTADDED);
265     UnsubscribeFromEvent(E_NAVIGATION_MESH_REBUILT);
266     UnsubscribeFromEvent(E_COMPONENTREMOVED);
267 
268     if (navMesh != navigationMesh_)     // It is possible to reset navmesh pointer back to 0
269     {
270         Scene* scene = GetScene();
271 
272         navigationMesh_ = navMesh;
273         navigationMeshId_ = navMesh ? navMesh->GetID() : 0;
274 
275         if (navMesh)
276         {
277             SubscribeToEvent(navMesh, E_NAVIGATION_MESH_REBUILT, URHO3D_HANDLER(CrowdManager, HandleNavMeshChanged));
278             SubscribeToEvent(scene, E_COMPONENTREMOVED, URHO3D_HANDLER(CrowdManager, HandleNavMeshChanged));
279         }
280 
281         CreateCrowd();
282         MarkNetworkUpdate();
283     }
284 }
285 
SetQueryFilterTypesAttr(const VariantVector & value)286 void CrowdManager::SetQueryFilterTypesAttr(const VariantVector& value)
287 {
288     if (!crowd_)
289         return;
290 
291     unsigned index = 0;
292     unsigned queryFilterType = 0;
293     numQueryFilterTypes_ = index < value.Size() ? Min(value[index++].GetUInt(), (unsigned)DT_CROWD_MAX_QUERY_FILTER_TYPE) : 0;
294 
295     while (queryFilterType < numQueryFilterTypes_)
296     {
297         if (index + 3 <= value.Size())
298         {
299             dtQueryFilter* filter = crowd_->getEditableFilter(queryFilterType);
300             assert(filter);
301             filter->setIncludeFlags((unsigned short)value[index++].GetUInt());
302             filter->setExcludeFlags((unsigned short)value[index++].GetUInt());
303             unsigned prevNumAreas = numAreas_[queryFilterType];
304             numAreas_[queryFilterType] = Min(value[index++].GetUInt(), (unsigned)DT_MAX_AREAS);
305 
306             // Must loop through based on previous number of areas, the new area cost (if any) can only be set in the next attribute get/set iteration
307             if (index + prevNumAreas <= value.Size())
308             {
309                 for (unsigned i = 0; i < prevNumAreas; ++i)
310                     filter->setAreaCost(i, value[index++].GetFloat());
311             }
312         }
313         ++queryFilterType;
314     }
315 }
316 
SetIncludeFlags(unsigned queryFilterType,unsigned short flags)317 void CrowdManager::SetIncludeFlags(unsigned queryFilterType, unsigned short flags)
318 {
319     dtQueryFilter* filter = const_cast<dtQueryFilter*>(GetDetourQueryFilter(queryFilterType));
320     if (filter)
321     {
322         filter->setIncludeFlags(flags);
323         if (numQueryFilterTypes_ < queryFilterType + 1)
324             numQueryFilterTypes_ = queryFilterType + 1;
325         MarkNetworkUpdate();
326     }
327 }
328 
SetExcludeFlags(unsigned queryFilterType,unsigned short flags)329 void CrowdManager::SetExcludeFlags(unsigned queryFilterType, unsigned short flags)
330 {
331     dtQueryFilter* filter = const_cast<dtQueryFilter*>(GetDetourQueryFilter(queryFilterType));
332     if (filter)
333     {
334         filter->setExcludeFlags(flags);
335         if (numQueryFilterTypes_ < queryFilterType + 1)
336             numQueryFilterTypes_ = queryFilterType + 1;
337         MarkNetworkUpdate();
338     }
339 }
340 
SetAreaCost(unsigned queryFilterType,unsigned areaID,float cost)341 void CrowdManager::SetAreaCost(unsigned queryFilterType, unsigned areaID, float cost)
342 {
343     dtQueryFilter* filter = const_cast<dtQueryFilter*>(GetDetourQueryFilter(queryFilterType));
344     if (filter && areaID < DT_MAX_AREAS)
345     {
346         filter->setAreaCost((int)areaID, cost);
347         if (numQueryFilterTypes_ < queryFilterType + 1)
348             numQueryFilterTypes_ = queryFilterType + 1;
349         if (numAreas_[queryFilterType] < areaID + 1)
350             numAreas_[queryFilterType] = areaID + 1;
351         MarkNetworkUpdate();
352     }
353 }
354 
SetObstacleAvoidanceTypesAttr(const VariantVector & value)355 void CrowdManager::SetObstacleAvoidanceTypesAttr(const VariantVector& value)
356 {
357     if (!crowd_)
358         return;
359 
360     unsigned index = 0;
361     unsigned obstacleAvoidanceType = 0;
362     numObstacleAvoidanceTypes_ = index < value.Size() ? Min(value[index++].GetUInt(), (unsigned)DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS) : 0;
363 
364     while (obstacleAvoidanceType < numObstacleAvoidanceTypes_)
365     {
366         if (index + 10 <= value.Size())
367         {
368             dtObstacleAvoidanceParams params;
369             params.velBias = value[index++].GetFloat();
370             params.weightDesVel = value[index++].GetFloat();
371             params.weightCurVel = value[index++].GetFloat();
372             params.weightSide = value[index++].GetFloat();
373             params.weightToi = value[index++].GetFloat();
374             params.horizTime = value[index++].GetFloat();
375             params.gridSize = (unsigned char)value[index++].GetUInt();
376             params.adaptiveDivs = (unsigned char)value[index++].GetUInt();
377             params.adaptiveRings = (unsigned char)value[index++].GetUInt();
378             params.adaptiveDepth = (unsigned char)value[index++].GetUInt();
379             crowd_->setObstacleAvoidanceParams(obstacleAvoidanceType, &params);
380         }
381         ++obstacleAvoidanceType;
382     }
383 }
384 
SetObstacleAvoidanceParams(unsigned obstacleAvoidanceType,const CrowdObstacleAvoidanceParams & params)385 void CrowdManager::SetObstacleAvoidanceParams(unsigned obstacleAvoidanceType, const CrowdObstacleAvoidanceParams& params)
386 {
387     if (crowd_ && obstacleAvoidanceType < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
388     {
389         crowd_->setObstacleAvoidanceParams(obstacleAvoidanceType, reinterpret_cast<const dtObstacleAvoidanceParams*>(&params));
390         if (numObstacleAvoidanceTypes_ < obstacleAvoidanceType + 1)
391             numObstacleAvoidanceTypes_ = obstacleAvoidanceType + 1;
392         MarkNetworkUpdate();
393     }
394 }
395 
FindNearestPoint(const Vector3 & point,int queryFilterType,dtPolyRef * nearestRef)396 Vector3 CrowdManager::FindNearestPoint(const Vector3& point, int queryFilterType, dtPolyRef* nearestRef)
397 {
398     if (nearestRef)
399         *nearestRef = 0;
400     return crowd_ && navigationMesh_ ?
401         navigationMesh_->FindNearestPoint(point, Vector3(crowd_->getQueryExtents()), crowd_->getFilter(queryFilterType), nearestRef) : point;
402 }
403 
MoveAlongSurface(const Vector3 & start,const Vector3 & end,int queryFilterType,int maxVisited)404 Vector3 CrowdManager::MoveAlongSurface(const Vector3& start, const Vector3& end, int queryFilterType, int maxVisited)
405 {
406     return crowd_ && navigationMesh_ ?
407         navigationMesh_->MoveAlongSurface(start, end, Vector3(crowd_->getQueryExtents()), maxVisited, crowd_->getFilter(queryFilterType)) :
408         end;
409 }
410 
FindPath(PODVector<Vector3> & dest,const Vector3 & start,const Vector3 & end,int queryFilterType)411 void CrowdManager::FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, int queryFilterType)
412 {
413     if (crowd_ && navigationMesh_)
414         navigationMesh_->FindPath(dest, start, end, Vector3(crowd_->getQueryExtents()), crowd_->getFilter(queryFilterType));
415 }
416 
GetRandomPoint(int queryFilterType,dtPolyRef * randomRef)417 Vector3 CrowdManager::GetRandomPoint(int queryFilterType, dtPolyRef* randomRef)
418 {
419     if (randomRef)
420         *randomRef = 0;
421     return crowd_ && navigationMesh_ ? navigationMesh_->GetRandomPoint(crowd_->getFilter(queryFilterType), randomRef) :
422         Vector3::ZERO;
423 }
424 
GetRandomPointInCircle(const Vector3 & center,float radius,int queryFilterType,dtPolyRef * randomRef)425 Vector3 CrowdManager::GetRandomPointInCircle(const Vector3& center, float radius, int queryFilterType, dtPolyRef* randomRef)
426 {
427     if (randomRef)
428         *randomRef = 0;
429     return crowd_ && navigationMesh_ ?
430         navigationMesh_->GetRandomPointInCircle(center, radius, Vector3(crowd_->getQueryExtents()),
431             crowd_->getFilter(queryFilterType), randomRef) : center;
432 }
433 
GetDistanceToWall(const Vector3 & point,float radius,int queryFilterType,Vector3 * hitPos,Vector3 * hitNormal)434 float CrowdManager::GetDistanceToWall(const Vector3& point, float radius, int queryFilterType, Vector3* hitPos, Vector3* hitNormal)
435 {
436     if (hitPos)
437         *hitPos = Vector3::ZERO;
438     if (hitNormal)
439         *hitNormal = Vector3::DOWN;
440     return crowd_ && navigationMesh_ ?
441         navigationMesh_->GetDistanceToWall(point, radius, Vector3(crowd_->getQueryExtents()), crowd_->getFilter(queryFilterType),
442             hitPos, hitNormal) : radius;
443 }
444 
Raycast(const Vector3 & start,const Vector3 & end,int queryFilterType,Vector3 * hitNormal)445 Vector3 CrowdManager::Raycast(const Vector3& start, const Vector3& end, int queryFilterType, Vector3* hitNormal)
446 {
447     if (hitNormal)
448         *hitNormal = Vector3::DOWN;
449     return crowd_ && navigationMesh_ ?
450         navigationMesh_->Raycast(start, end, Vector3(crowd_->getQueryExtents()), crowd_->getFilter(queryFilterType), hitNormal)
451             : end;
452 }
453 
GetNumAreas(unsigned queryFilterType) const454 unsigned CrowdManager::GetNumAreas(unsigned queryFilterType) const
455 {
456     return queryFilterType < numQueryFilterTypes_ ? numAreas_[queryFilterType] : 0;
457 }
458 
GetQueryFilterTypesAttr() const459 VariantVector CrowdManager::GetQueryFilterTypesAttr() const
460 {
461     VariantVector ret;
462     if (crowd_)
463     {
464         unsigned totalNumAreas = 0;
465         for (unsigned i = 0; i < numQueryFilterTypes_; ++i)
466             totalNumAreas += numAreas_[i];
467 
468         ret.Reserve(numQueryFilterTypes_ * 3 + totalNumAreas + 1);
469         ret.Push(numQueryFilterTypes_);
470 
471         for (unsigned i = 0; i < numQueryFilterTypes_; ++i)
472         {
473             const dtQueryFilter* filter = crowd_->getFilter(i);
474             assert(filter);
475             ret.Push(filter->getIncludeFlags());
476             ret.Push(filter->getExcludeFlags());
477             ret.Push(numAreas_[i]);
478 
479             for (unsigned j = 0; j < numAreas_[i]; ++j)
480                 ret.Push(filter->getAreaCost(j));
481         }
482     }
483     else
484         ret.Push(0);
485 
486     return ret;
487 }
488 
GetIncludeFlags(unsigned queryFilterType) const489 unsigned short CrowdManager::GetIncludeFlags(unsigned queryFilterType) const
490 {
491     if (queryFilterType >= numQueryFilterTypes_)
492         URHO3D_LOGWARNINGF("Query filter type %d is not configured yet, returning the default include flags initialized by dtCrowd",
493             queryFilterType);
494     const dtQueryFilter* filter = GetDetourQueryFilter(queryFilterType);
495     return (unsigned short)(filter ? filter->getIncludeFlags() : 0xffff);
496 }
497 
GetExcludeFlags(unsigned queryFilterType) const498 unsigned short CrowdManager::GetExcludeFlags(unsigned queryFilterType) const
499 {
500     if (queryFilterType >= numQueryFilterTypes_)
501         URHO3D_LOGWARNINGF("Query filter type %d is not configured yet, returning the default exclude flags initialized by dtCrowd",
502             queryFilterType);
503     const dtQueryFilter* filter = GetDetourQueryFilter(queryFilterType);
504     return (unsigned short)(filter ? filter->getExcludeFlags() : 0);
505 }
506 
GetAreaCost(unsigned queryFilterType,unsigned areaID) const507 float CrowdManager::GetAreaCost(unsigned queryFilterType, unsigned areaID) const
508 {
509     if (queryFilterType >= numQueryFilterTypes_ || areaID >= numAreas_[queryFilterType])
510         URHO3D_LOGWARNINGF(
511             "Query filter type %d and/or area id %d are not configured yet, returning the default area cost initialized by dtCrowd",
512             queryFilterType, areaID);
513     const dtQueryFilter* filter = GetDetourQueryFilter(queryFilterType);
514     return filter ? filter->getAreaCost((int)areaID) : 1.f;
515 }
516 
GetObstacleAvoidanceTypesAttr() const517 VariantVector CrowdManager::GetObstacleAvoidanceTypesAttr() const
518 {
519     VariantVector ret;
520     if (crowd_)
521     {
522         ret.Reserve(numObstacleAvoidanceTypes_ * 10 + 1);
523         ret.Push(numObstacleAvoidanceTypes_);
524 
525         for (unsigned i = 0; i < numObstacleAvoidanceTypes_; ++i)
526         {
527             const dtObstacleAvoidanceParams* params = crowd_->getObstacleAvoidanceParams(i);
528             assert(params);
529             ret.Push(params->velBias);
530             ret.Push(params->weightDesVel);
531             ret.Push(params->weightCurVel);
532             ret.Push(params->weightSide);
533             ret.Push(params->weightToi);
534             ret.Push(params->horizTime);
535             ret.Push(params->gridSize);
536             ret.Push(params->adaptiveDivs);
537             ret.Push(params->adaptiveRings);
538             ret.Push(params->adaptiveDepth);
539         }
540     }
541     else
542         ret.Push(0);
543 
544     return ret;
545 }
546 
GetObstacleAvoidanceParams(unsigned obstacleAvoidanceType) const547 const CrowdObstacleAvoidanceParams& CrowdManager::GetObstacleAvoidanceParams(unsigned obstacleAvoidanceType) const
548 {
549     static const CrowdObstacleAvoidanceParams EMPTY_PARAMS = CrowdObstacleAvoidanceParams();
550     const dtObstacleAvoidanceParams* params = crowd_ ? crowd_->getObstacleAvoidanceParams(obstacleAvoidanceType) : 0;
551     return params ? *reinterpret_cast<const CrowdObstacleAvoidanceParams*>(params) : EMPTY_PARAMS;
552 }
553 
GetAgents(Node * node,bool inCrowdFilter) const554 PODVector<CrowdAgent*> CrowdManager::GetAgents(Node* node, bool inCrowdFilter) const
555 {
556     if (!node)
557         node = GetScene();
558     PODVector<CrowdAgent*> agents;
559     node->GetComponents<CrowdAgent>(agents, true);
560     if (inCrowdFilter)
561     {
562         PODVector<CrowdAgent*>::Iterator i = agents.Begin();
563         while (i != agents.End())
564         {
565             if ((*i)->IsInCrowd())
566                 ++i;
567             else
568                 i = agents.Erase(i);
569         }
570     }
571     return agents;
572 }
573 
CreateCrowd()574 bool CrowdManager::CreateCrowd()
575 {
576     if (!navigationMesh_ || !navigationMesh_->InitializeQuery())
577         return false;
578 
579     // Preserve the existing crowd configuration before recreating it
580     VariantVector queryFilterTypeConfiguration, obstacleAvoidanceTypeConfiguration;
581     bool recreate = crowd_ != 0;
582     if (recreate)
583     {
584         queryFilterTypeConfiguration = GetQueryFilterTypesAttr();
585         obstacleAvoidanceTypeConfiguration = GetObstacleAvoidanceTypesAttr();
586         dtFreeCrowd(crowd_);
587     }
588     crowd_ = dtAllocCrowd();
589 
590     // Initialize the crowd
591     if (maxAgentRadius_ == 0.f)
592         maxAgentRadius_ = navigationMesh_->GetAgentRadius();
593     if (!crowd_->init(maxAgents_, maxAgentRadius_, navigationMesh_->navMesh_, CrowdAgentUpdateCallback))
594     {
595         URHO3D_LOGERROR("Could not initialize DetourCrowd");
596         return false;
597     }
598 
599     if (recreate)
600     {
601         // Reconfigure the newly initialized crowd
602         SetQueryFilterTypesAttr(queryFilterTypeConfiguration);
603         SetObstacleAvoidanceTypesAttr(obstacleAvoidanceTypeConfiguration);
604 
605         // Re-add the existing crowd agents
606         PODVector<CrowdAgent*> agents = GetAgents();
607         for (unsigned i = 0; i < agents.Size(); ++i)
608         {
609             // Keep adding until the crowd cannot take it anymore
610             if (agents[i]->AddAgentToCrowd(true) == -1)
611             {
612                 URHO3D_LOGWARNINGF("CrowdManager: %d crowd agents orphaned", agents.Size() - i);
613                 break;
614             }
615         }
616     }
617 
618     return true;
619 }
620 
AddAgent(CrowdAgent * agent,const Vector3 & pos)621 int CrowdManager::AddAgent(CrowdAgent* agent, const Vector3& pos)
622 {
623     if (!crowd_ || !navigationMesh_ || !agent)
624         return -1;
625     dtCrowdAgentParams params;
626     params.userData = agent;
627     if (agent->radius_ == 0.f)
628         agent->radius_ = navigationMesh_->GetAgentRadius();
629     if (agent->height_ == 0.f)
630         agent->height_ = navigationMesh_->GetAgentHeight();
631     // dtCrowd::addAgent() requires the query filter type to find the nearest position on navmesh as the initial agent's position
632     params.queryFilterType = (unsigned char)agent->GetQueryFilterType();
633     return crowd_->addAgent(pos.Data(), &params);
634 }
635 
RemoveAgent(CrowdAgent * agent)636 void CrowdManager::RemoveAgent(CrowdAgent* agent)
637 {
638     if (!crowd_ || !agent)
639         return;
640     dtCrowdAgent* agt = crowd_->getEditableAgent(agent->GetAgentCrowdId());
641     if (agt)
642         agt->params.userData = 0;
643     crowd_->removeAgent(agent->GetAgentCrowdId());
644 }
645 
OnSceneSet(Scene * scene)646 void CrowdManager::OnSceneSet(Scene* scene)
647 {
648     // Subscribe to the scene subsystem update, which will trigger the crowd update step, and grab a reference
649     // to the scene's NavigationMesh
650     if (scene)
651     {
652         if (scene != node_)
653         {
654             URHO3D_LOGERROR("CrowdManager is a scene component and should only be attached to the scene node");
655             return;
656         }
657 
658         SubscribeToEvent(scene, E_SCENESUBSYSTEMUPDATE, URHO3D_HANDLER(CrowdManager, HandleSceneSubsystemUpdate));
659 
660         // Attempt to auto discover a NavigationMesh component (or its derivative) under the scene node
661         if (navigationMeshId_ == 0)
662         {
663             NavigationMesh* navMesh = scene->GetDerivedComponent<NavigationMesh>(true);
664             if (navMesh)
665                 SetNavigationMesh(navMesh);
666             else
667             {
668                 // If not found, attempt to find in a delayed manner
669                 SubscribeToEvent(scene, E_COMPONENTADDED, URHO3D_HANDLER(CrowdManager, HandleComponentAdded));
670             }
671         }
672     }
673     else
674     {
675         UnsubscribeFromEvent(E_SCENESUBSYSTEMUPDATE);
676         UnsubscribeFromEvent(E_NAVIGATION_MESH_REBUILT);
677         UnsubscribeFromEvent(E_COMPONENTADDED);
678         UnsubscribeFromEvent(E_COMPONENTREMOVED);
679 
680         navigationMesh_ = 0;
681     }
682 }
683 
Update(float delta)684 void CrowdManager::Update(float delta)
685 {
686     assert(crowd_ && navigationMesh_);
687     URHO3D_PROFILE(UpdateCrowd);
688     crowd_->update(delta, 0);
689 }
690 
GetDetourCrowdAgent(int agent) const691 const dtCrowdAgent* CrowdManager::GetDetourCrowdAgent(int agent) const
692 {
693     return crowd_ ? crowd_->getAgent(agent) : 0;
694 }
695 
GetDetourQueryFilter(unsigned queryFilterType) const696 const dtQueryFilter* CrowdManager::GetDetourQueryFilter(unsigned queryFilterType) const
697 {
698     return crowd_ ? crowd_->getFilter(queryFilterType) : 0;
699 }
700 
HandleSceneSubsystemUpdate(StringHash eventType,VariantMap & eventData)701 void CrowdManager::HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData)
702 {
703     // Perform update tick as long as the crowd is initialized and the associated navmesh has not been removed
704     if (crowd_ && navigationMesh_)
705     {
706         using namespace SceneSubsystemUpdate;
707 
708         if (IsEnabledEffective())
709             Update(eventData[P_TIMESTEP].GetFloat());
710     }
711 }
712 
HandleNavMeshChanged(StringHash eventType,VariantMap & eventData)713 void CrowdManager::HandleNavMeshChanged(StringHash eventType, VariantMap& eventData)
714 {
715     NavigationMesh* navMesh;
716     if (eventType == E_NAVIGATION_MESH_REBUILT)
717     {
718         navMesh = static_cast<NavigationMesh*>(eventData[NavigationMeshRebuilt::P_MESH].GetPtr());
719         // Reset internal pointer so that the same navmesh can be reassigned and the crowd creation be reattempted
720         if (navMesh == navigationMesh_)
721             navigationMesh_.Reset();
722     }
723     else
724     {
725         // eventType == E_COMPONENTREMOVED
726         navMesh = static_cast<NavigationMesh*>(eventData[ComponentRemoved::P_COMPONENT].GetPtr());
727         // Only interested in navmesh component being used to initialized the crowd
728         if (navMesh != navigationMesh_)
729             return;
730         // Since this is a component removed event, reset our own navmesh pointer
731         navMesh = 0;
732     }
733 
734     SetNavigationMesh(navMesh);
735 }
736 
HandleComponentAdded(StringHash eventType,VariantMap & eventData)737 void CrowdManager::HandleComponentAdded(StringHash eventType, VariantMap& eventData)
738 {
739     Scene* scene = GetScene();
740     if (scene)
741     {
742         NavigationMesh* navMesh = scene->GetDerivedComponent<NavigationMesh>(true);
743         if (navMesh)
744             SetNavigationMesh(navMesh);
745     }
746 }
747 
748 }
749