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 "../IO/MemoryBuffer.h"
30 #include "../Navigation/NavigationEvents.h"
31 #include "../Navigation/NavigationMesh.h"
32 #include "../Navigation/CrowdAgent.h"
33 #include "../Scene/Node.h"
34 #include "../Scene/Scene.h"
35 
36 #include <Detour/DetourCommon.h>
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 CrowdAgentRequestedTarget DEFAULT_AGENT_REQUEST_TARGET_TYPE = CA_REQUESTEDTARGET_NONE;
47 static const float DEFAULT_AGENT_MAX_SPEED = 0.f;
48 static const float DEFAULT_AGENT_MAX_ACCEL = 0.f;
49 static const unsigned DEFAULT_AGENT_QUERY_FILTER_TYPE = 0;
50 static const unsigned DEFAULT_AGENT_OBSTACLE_AVOIDANCE_TYPE = 0;
51 static const NavigationQuality DEFAULT_AGENT_AVOIDANCE_QUALITY = NAVIGATIONQUALITY_HIGH;
52 static const NavigationPushiness DEFAULT_AGENT_NAVIGATION_PUSHINESS = NAVIGATIONPUSHINESS_MEDIUM;
53 
54 static const unsigned SCOPE_NAVIGATION_QUALITY_PARAMS = 1;
55 static const unsigned SCOPE_NAVIGATION_PUSHINESS_PARAMS = 2;
56 static const unsigned SCOPE_BASE_PARAMS = M_MAX_UNSIGNED & ~SCOPE_NAVIGATION_QUALITY_PARAMS & ~SCOPE_NAVIGATION_PUSHINESS_PARAMS;
57 
58 static const char* crowdAgentRequestedTargetTypeNames[] = {
59     "None",
60     "Position",
61     "Velocity",
62     0
63 };
64 
65 static const char* crowdAgentAvoidanceQualityNames[] = {
66     "Low",
67     "Medium",
68     "High",
69     0
70 };
71 
72 static const char* crowdAgentPushinessNames[] = {
73     "Low",
74     "Medium",
75     "High",
76     "None",
77     0
78 };
79 
CrowdAgent(Context * context)80 CrowdAgent::CrowdAgent(Context* context) :
81     Component(context),
82     agentCrowdId_(-1),
83     requestedTargetType_(DEFAULT_AGENT_REQUEST_TARGET_TYPE),
84     updateNodePosition_(true),
85     maxAccel_(DEFAULT_AGENT_MAX_ACCEL),
86     maxSpeed_(DEFAULT_AGENT_MAX_SPEED),
87     radius_(0.0f),
88     height_(0.0f),
89     queryFilterType_(DEFAULT_AGENT_QUERY_FILTER_TYPE),
90     obstacleAvoidanceType_(DEFAULT_AGENT_OBSTACLE_AVOIDANCE_TYPE),
91     navQuality_(DEFAULT_AGENT_AVOIDANCE_QUALITY),
92     navPushiness_(DEFAULT_AGENT_NAVIGATION_PUSHINESS),
93     previousTargetState_(CA_TARGET_NONE),
94     previousAgentState_(CA_STATE_WALKING),
95     ignoreTransformChanges_(false)
96 {
97     SubscribeToEvent(E_NAVIGATION_TILE_ADDED, URHO3D_HANDLER(CrowdAgent, HandleNavigationTileAdded));
98 }
99 
~CrowdAgent()100 CrowdAgent::~CrowdAgent()
101 {
102     RemoveAgentFromCrowd();
103 }
104 
RegisterObject(Context * context)105 void CrowdAgent::RegisterObject(Context* context)
106 {
107     context->RegisterFactory<CrowdAgent>(NAVIGATION_CATEGORY);
108 
109     URHO3D_ATTRIBUTE("Target Position", Vector3, targetPosition_, Vector3::ZERO, AM_DEFAULT);
110     URHO3D_ATTRIBUTE("Target Velocity", Vector3, targetVelocity_, Vector3::ZERO, AM_DEFAULT);
111     URHO3D_ENUM_ATTRIBUTE("Requested Target Type", requestedTargetType_, crowdAgentRequestedTargetTypeNames,
112         DEFAULT_AGENT_REQUEST_TARGET_TYPE, AM_DEFAULT);
113     URHO3D_ACCESSOR_ATTRIBUTE("Update Node Position", GetUpdateNodePosition, SetUpdateNodePosition, bool, true, AM_DEFAULT);
114     URHO3D_ATTRIBUTE("Max Accel", float, maxAccel_, DEFAULT_AGENT_MAX_ACCEL, AM_DEFAULT);
115     URHO3D_ATTRIBUTE("Max Speed", float, maxSpeed_, DEFAULT_AGENT_MAX_SPEED, AM_DEFAULT);
116     URHO3D_ATTRIBUTE("Radius", float, radius_, 0.0f, AM_DEFAULT);
117     URHO3D_ATTRIBUTE("Height", float, height_, 0.0f, AM_DEFAULT);
118     URHO3D_ATTRIBUTE("Query Filter Type", unsigned, queryFilterType_, DEFAULT_AGENT_QUERY_FILTER_TYPE, AM_DEFAULT);
119     URHO3D_ATTRIBUTE("Obstacle Avoidance Type", unsigned, obstacleAvoidanceType_, DEFAULT_AGENT_OBSTACLE_AVOIDANCE_TYPE, AM_DEFAULT);
120     URHO3D_ENUM_ATTRIBUTE("Navigation Pushiness", navPushiness_, crowdAgentPushinessNames, DEFAULT_AGENT_NAVIGATION_PUSHINESS, AM_DEFAULT);
121     URHO3D_ENUM_ATTRIBUTE("Navigation Quality", navQuality_, crowdAgentAvoidanceQualityNames, DEFAULT_AGENT_AVOIDANCE_QUALITY, AM_DEFAULT);
122 }
123 
ApplyAttributes()124 void CrowdAgent::ApplyAttributes()
125 {
126     // Values from Editor, saved-file, or network must be checked before applying
127     maxAccel_ = Max(0.f, maxAccel_);
128     maxSpeed_ = Max(0.f, maxSpeed_);
129     radius_ = Max(0.f, radius_);
130     height_ = Max(0.f, height_);
131     queryFilterType_ = Min(queryFilterType_, (unsigned)DT_CROWD_MAX_QUERY_FILTER_TYPE - 1);
132     obstacleAvoidanceType_ = Min(obstacleAvoidanceType_, (unsigned)DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS - 1);
133 
134     UpdateParameters();
135 
136     // Set or reset target after we have attributes applied to the agent's parameters.
137     CrowdAgentRequestedTarget requestedTargetType = requestedTargetType_;
138     if (CA_REQUESTEDTARGET_NONE != requestedTargetType_)
139     {
140         // Assign a dummy value such that the value check in the setter method passes
141         requestedTargetType_ = CA_REQUESTEDTARGET_NONE;
142         if (requestedTargetType == CA_REQUESTEDTARGET_POSITION)
143             SetTargetPosition(targetPosition_);
144         else
145             SetTargetVelocity(targetVelocity_);
146     }
147     else
148     {
149         requestedTargetType_ = CA_REQUESTEDTARGET_POSITION;
150         ResetTarget();
151     }
152 }
153 
OnSetEnabled()154 void CrowdAgent::OnSetEnabled()
155 {
156     bool enabled = IsEnabledEffective();
157 
158     if (enabled && !IsInCrowd())
159         AddAgentToCrowd();
160     else if (!enabled && IsInCrowd())
161         RemoveAgentFromCrowd();
162 }
163 
DrawDebugGeometry(bool depthTest)164 void CrowdAgent::DrawDebugGeometry(bool depthTest)
165 {
166     Scene* scene = GetScene();
167     if (scene)
168     {
169         DebugRenderer* debug = scene->GetComponent<DebugRenderer>();
170         if (debug)
171             DrawDebugGeometry(debug, depthTest);
172     }
173 }
174 
DrawDebugGeometry(DebugRenderer * debug,bool depthTest)175 void CrowdAgent::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
176 {
177     if (node_)
178     {
179         const Vector3 pos = GetPosition();
180         const Vector3 vel = GetActualVelocity();
181         const Vector3 desiredVel = GetDesiredVelocity();
182         const Vector3 agentHeightVec(0, height_, 0);
183 
184         debug->AddLine(pos + 0.5f * agentHeightVec, pos + vel + 0.5f * agentHeightVec, Color::GREEN, depthTest);
185         debug->AddLine(pos + 0.25f * agentHeightVec, pos + desiredVel + 0.25f * agentHeightVec, Color::RED, depthTest);
186         debug->AddCylinder(pos, radius_, height_, HasArrived() ? Color::GREEN : Color::WHITE, depthTest);
187     }
188 }
189 
UpdateParameters(unsigned scope)190 void CrowdAgent::UpdateParameters(unsigned scope)
191 {
192     const dtCrowdAgent* agent = GetDetourCrowdAgent();
193     if (agent)
194     {
195         dtCrowdAgentParams params = agent->params;
196 
197         if (scope & SCOPE_NAVIGATION_QUALITY_PARAMS)
198         {
199             switch (navQuality_)
200             {
201             case NAVIGATIONQUALITY_LOW:
202                 params.updateFlags = 0
203                                      | DT_CROWD_OPTIMIZE_VIS
204                                      | DT_CROWD_ANTICIPATE_TURNS;
205                 break;
206 
207             case NAVIGATIONQUALITY_MEDIUM:
208                 params.updateFlags = 0
209                                      | DT_CROWD_OPTIMIZE_TOPO
210                                      | DT_CROWD_OPTIMIZE_VIS
211                                      | DT_CROWD_ANTICIPATE_TURNS
212                                      | DT_CROWD_SEPARATION;
213                 break;
214 
215             case NAVIGATIONQUALITY_HIGH:
216                 params.updateFlags = 0
217                                      // Path finding
218                                      | DT_CROWD_OPTIMIZE_TOPO
219                                      | DT_CROWD_OPTIMIZE_VIS
220                                      // Steering
221                                      | DT_CROWD_ANTICIPATE_TURNS
222                                      | DT_CROWD_SEPARATION
223                                      // Velocity planning
224                                      | DT_CROWD_OBSTACLE_AVOIDANCE;
225                 break;
226             }
227         }
228 
229         if (scope & SCOPE_NAVIGATION_PUSHINESS_PARAMS)
230         {
231             switch (navPushiness_)
232             {
233             case NAVIGATIONPUSHINESS_LOW:
234                 params.separationWeight = 4.0f;
235                 params.collisionQueryRange = radius_ * 16.0f;
236                 break;
237 
238             case NAVIGATIONPUSHINESS_MEDIUM:
239                 params.separationWeight = 2.0f;
240                 params.collisionQueryRange = radius_ * 8.0f;
241                 break;
242 
243             case NAVIGATIONPUSHINESS_HIGH:
244                 params.separationWeight = 0.5f;
245                 params.collisionQueryRange = radius_ * 1.0f;
246                 break;
247 
248             case NAVIGATIONPUSHINESS_NONE:
249                 params.separationWeight = 0.0f;
250                 params.collisionQueryRange = radius_ * 1.0f;
251                 break;
252             }
253         }
254 
255         if (scope & SCOPE_BASE_PARAMS)
256         {
257             params.radius = radius_;
258             params.height = height_;
259             params.maxAcceleration = maxAccel_;
260             params.maxSpeed = maxSpeed_;
261             params.pathOptimizationRange = radius_ * 30.0f;
262             params.queryFilterType = (unsigned char)queryFilterType_;
263             params.obstacleAvoidanceType = (unsigned char)obstacleAvoidanceType_;
264         }
265 
266         crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
267     }
268 }
269 
AddAgentToCrowd(bool force)270 int CrowdAgent::AddAgentToCrowd(bool force)
271 {
272     if (!node_ || !crowdManager_ || !crowdManager_->crowd_)
273         return -1;
274 
275     if (force || !IsInCrowd())
276     {
277         URHO3D_PROFILE(AddAgentToCrowd);
278 
279         agentCrowdId_ = crowdManager_->AddAgent(this, node_->GetWorldPosition());
280         if (agentCrowdId_ == -1)
281             return -1;
282 
283         ApplyAttributes();
284 
285         previousAgentState_ = GetAgentState();
286         previousTargetState_ = GetTargetState();
287 
288         // Agent created, but initial state is invalid and needs to be addressed
289         if (previousAgentState_ == CA_STATE_INVALID)
290         {
291             using namespace CrowdAgentFailure;
292 
293             VariantMap& map = GetEventDataMap();
294             map[P_NODE] = GetNode();
295             map[P_CROWD_AGENT] = this;
296             map[P_CROWD_TARGET_STATE] = previousTargetState_;
297             map[P_CROWD_AGENT_STATE] = previousAgentState_;
298             map[P_POSITION] = GetPosition();
299             map[P_VELOCITY] = GetActualVelocity();
300             crowdManager_->SendEvent(E_CROWD_AGENT_FAILURE, map);
301             Node* node = GetNode();
302             if (node)
303                 node->SendEvent(E_CROWD_AGENT_NODE_FAILURE, map);
304 
305             // Reevaluate states as handling of event may have resulted in changes
306             previousAgentState_ = GetAgentState();
307             previousTargetState_ = GetTargetState();
308         }
309 
310         // Save the initial position to prevent CrowdAgentReposition event being triggered unnecessarily
311         previousPosition_ = GetPosition();
312     }
313 
314     return agentCrowdId_;
315 }
316 
RemoveAgentFromCrowd()317 void CrowdAgent::RemoveAgentFromCrowd()
318 {
319     if (IsInCrowd())
320     {
321         crowdManager_->RemoveAgent(this);
322         agentCrowdId_ = -1;
323     }
324 }
325 
SetTargetPosition(const Vector3 & position)326 void CrowdAgent::SetTargetPosition(const Vector3& position)
327 {
328     if (position != targetPosition_ || CA_REQUESTEDTARGET_POSITION != requestedTargetType_)
329     {
330         targetPosition_ = position;
331         requestedTargetType_ = CA_REQUESTEDTARGET_POSITION;
332         MarkNetworkUpdate();
333 
334         if (!IsInCrowd())
335             AddAgentToCrowd();
336         if (IsInCrowd())   // Make sure the previous method call is successful
337         {
338             dtPolyRef nearestRef;
339             Vector3 nearestPos = crowdManager_->FindNearestPoint(position, queryFilterType_, &nearestRef);
340             crowdManager_->GetCrowd()->requestMoveTarget(agentCrowdId_, nearestRef, nearestPos.Data());
341         }
342     }
343 }
344 
SetTargetVelocity(const Vector3 & velocity)345 void CrowdAgent::SetTargetVelocity(const Vector3& velocity)
346 {
347     if (velocity != targetVelocity_ || CA_REQUESTEDTARGET_VELOCITY != requestedTargetType_)
348     {
349         targetVelocity_ = velocity;
350         requestedTargetType_ = CA_REQUESTEDTARGET_VELOCITY;
351         MarkNetworkUpdate();
352 
353         if (IsInCrowd())
354             crowdManager_->GetCrowd()->requestMoveVelocity(agentCrowdId_, velocity.Data());
355     }
356 }
357 
ResetTarget()358 void CrowdAgent::ResetTarget()
359 {
360     if (CA_REQUESTEDTARGET_NONE != requestedTargetType_)
361     {
362         requestedTargetType_ = CA_REQUESTEDTARGET_NONE;
363         MarkNetworkUpdate();
364 
365         if (IsInCrowd())
366             crowdManager_->GetCrowd()->resetMoveTarget(agentCrowdId_);
367     }
368 }
369 
SetUpdateNodePosition(bool unodepos)370 void CrowdAgent::SetUpdateNodePosition(bool unodepos)
371 {
372     if (unodepos != updateNodePosition_)
373     {
374         updateNodePosition_ = unodepos;
375         MarkNetworkUpdate();
376     }
377 }
378 
SetMaxAccel(float maxAccel)379 void CrowdAgent::SetMaxAccel(float maxAccel)
380 {
381     if (maxAccel != maxAccel_ && maxAccel >= 0.f)
382     {
383         maxAccel_ = maxAccel;
384         UpdateParameters(SCOPE_BASE_PARAMS);
385         MarkNetworkUpdate();
386     }
387 }
388 
SetMaxSpeed(float maxSpeed)389 void CrowdAgent::SetMaxSpeed(float maxSpeed)
390 {
391     if (maxSpeed != maxSpeed_ && maxSpeed >= 0.f)
392     {
393         maxSpeed_ = maxSpeed;
394         UpdateParameters(SCOPE_BASE_PARAMS);
395         MarkNetworkUpdate();
396     }
397 }
398 
SetRadius(float radius)399 void CrowdAgent::SetRadius(float radius)
400 {
401     if (radius != radius_ && radius > 0.f)
402     {
403         radius_ = radius;
404         UpdateParameters(SCOPE_BASE_PARAMS | SCOPE_NAVIGATION_PUSHINESS_PARAMS);
405         MarkNetworkUpdate();
406     }
407 }
408 
SetHeight(float height)409 void CrowdAgent::SetHeight(float height)
410 {
411     if (height != height_ && height > 0.f)
412     {
413         height_ = height;
414         UpdateParameters(SCOPE_BASE_PARAMS);
415         MarkNetworkUpdate();
416     }
417 }
418 
SetQueryFilterType(unsigned queryFilterType)419 void CrowdAgent::SetQueryFilterType(unsigned queryFilterType)
420 {
421     if (queryFilterType != queryFilterType_)
422     {
423         if (queryFilterType >= DT_CROWD_MAX_QUERY_FILTER_TYPE)
424         {
425             URHO3D_LOGERRORF("The specified filter type index (%d) exceeds the maximum allowed value (%d)", queryFilterType,
426                 DT_CROWD_MAX_QUERY_FILTER_TYPE);
427             return;
428         }
429 
430         queryFilterType_ = queryFilterType;
431         UpdateParameters(SCOPE_BASE_PARAMS);
432         MarkNetworkUpdate();
433     }
434 }
435 
SetObstacleAvoidanceType(unsigned obstacleAvoidanceType)436 void CrowdAgent::SetObstacleAvoidanceType(unsigned obstacleAvoidanceType)
437 {
438     if (obstacleAvoidanceType != obstacleAvoidanceType_)
439     {
440         if (obstacleAvoidanceType >= DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
441         {
442             URHO3D_LOGERRORF("The specified obstacle avoidance type index (%d) exceeds the maximum allowed value (%d)",
443                 obstacleAvoidanceType, DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS);
444             return;
445         }
446 
447         obstacleAvoidanceType_ = obstacleAvoidanceType;
448         UpdateParameters(SCOPE_BASE_PARAMS);
449         MarkNetworkUpdate();
450     }
451 }
452 
SetNavigationQuality(NavigationQuality val)453 void CrowdAgent::SetNavigationQuality(NavigationQuality val)
454 {
455     if (val != navQuality_)
456     {
457         navQuality_ = val;
458         UpdateParameters(SCOPE_NAVIGATION_QUALITY_PARAMS);
459         MarkNetworkUpdate();
460     }
461 }
462 
SetNavigationPushiness(NavigationPushiness val)463 void CrowdAgent::SetNavigationPushiness(NavigationPushiness val)
464 {
465     if (val != navPushiness_)
466     {
467         navPushiness_ = val;
468         UpdateParameters(SCOPE_NAVIGATION_PUSHINESS_PARAMS);
469         MarkNetworkUpdate();
470     }
471 }
472 
GetPosition() const473 Vector3 CrowdAgent::GetPosition() const
474 {
475     const dtCrowdAgent* agent = GetDetourCrowdAgent();
476     return agent ? Vector3(agent->npos) : node_->GetWorldPosition();
477 }
478 
GetDesiredVelocity() const479 Vector3 CrowdAgent::GetDesiredVelocity() const
480 {
481     const dtCrowdAgent* agent = GetDetourCrowdAgent();
482     return agent ? Vector3(agent->dvel) : Vector3::ZERO;
483 }
484 
GetActualVelocity() const485 Vector3 CrowdAgent::GetActualVelocity() const
486 {
487     const dtCrowdAgent* agent = GetDetourCrowdAgent();
488     return agent ? Vector3(agent->vel) : Vector3::ZERO;
489 }
490 
GetAgentState() const491 CrowdAgentState CrowdAgent::GetAgentState() const
492 {
493     const dtCrowdAgent* agent = GetDetourCrowdAgent();
494     return agent ? (CrowdAgentState)agent->state : CA_STATE_INVALID;
495 }
496 
GetTargetState() const497 CrowdAgentTargetState CrowdAgent::GetTargetState() const
498 {
499     const dtCrowdAgent* agent = GetDetourCrowdAgent();
500     return agent ? (CrowdAgentTargetState)agent->targetState : CA_TARGET_NONE;
501 }
502 
HasArrived() const503 bool CrowdAgent::HasArrived() const
504 {
505     // Is the agent at or near the end of its path and within its own radius of the goal?
506     const dtCrowdAgent* agent = GetDetourCrowdAgent();
507     return agent && (!agent->ncorners || (agent->cornerFlags[agent->ncorners - 1] & DT_STRAIGHTPATH_END &&
508                                           dtVdist2D(agent->npos, &agent->cornerVerts[(agent->ncorners - 1) * 3]) <=
509                                           agent->params.radius));
510 }
511 
IsInCrowd() const512 bool CrowdAgent::IsInCrowd() const
513 {
514     return crowdManager_ && agentCrowdId_ != -1;
515 }
516 
OnCrowdUpdate(dtCrowdAgent * ag,float dt)517 void CrowdAgent::OnCrowdUpdate(dtCrowdAgent* ag, float dt)
518 {
519     assert (ag);
520     if (node_)
521     {
522         // Use pointer to self to check for destruction after sending events
523         WeakPtr<CrowdAgent> self(this);
524 
525         Vector3 newPos(ag->npos);
526         Vector3 newVel(ag->vel);
527 
528         // Notify parent node of the reposition
529         if (newPos != previousPosition_)
530         {
531             previousPosition_ = newPos;
532 
533             if (updateNodePosition_)
534             {
535                 ignoreTransformChanges_ = true;
536                 node_->SetWorldPosition(newPos);
537                 ignoreTransformChanges_ = false;
538             }
539 
540             using namespace CrowdAgentReposition;
541 
542             VariantMap& map = GetEventDataMap();
543             map[P_NODE] = node_;
544             map[P_CROWD_AGENT] = this;
545             map[P_POSITION] = newPos;
546             map[P_VELOCITY] = newVel;
547             map[P_ARRIVED] = HasArrived();
548             map[P_TIMESTEP] = dt;
549             crowdManager_->SendEvent(E_CROWD_AGENT_REPOSITION, map);
550             if (self.Expired())
551                 return;
552             node_->SendEvent(E_CROWD_AGENT_NODE_REPOSITION, map);
553             if (self.Expired())
554                 return;
555         }
556 
557         // Send a notification event if we've reached the destination
558         CrowdAgentTargetState newTargetState = GetTargetState();
559         CrowdAgentState newAgentState = GetAgentState();
560         if (newAgentState != previousAgentState_ || newTargetState != previousTargetState_)
561         {
562             using namespace CrowdAgentStateChanged;
563 
564             VariantMap& map = GetEventDataMap();
565             map[P_NODE] = node_;
566             map[P_CROWD_AGENT] = this;
567             map[P_CROWD_TARGET_STATE] = newTargetState;
568             map[P_CROWD_AGENT_STATE] = newAgentState;
569             map[P_POSITION] = newPos;
570             map[P_VELOCITY] = newVel;
571             crowdManager_->SendEvent(E_CROWD_AGENT_STATE_CHANGED, map);
572             if (self.Expired())
573                 return;
574             node_->SendEvent(E_CROWD_AGENT_NODE_STATE_CHANGED, map);
575             if (self.Expired())
576                 return;
577 
578             // Send a failure event if either state is a failed status
579             if (newAgentState == CA_STATE_INVALID || newTargetState == CA_TARGET_FAILED)
580             {
581                 VariantMap& map = GetEventDataMap();
582                 map[P_NODE] = node_;
583                 map[P_CROWD_AGENT] = this;
584                 map[P_CROWD_TARGET_STATE] = newTargetState;
585                 map[P_CROWD_AGENT_STATE] = newAgentState;
586                 map[P_POSITION] = newPos;
587                 map[P_VELOCITY] = newVel;
588                 crowdManager_->SendEvent(E_CROWD_AGENT_FAILURE, map);
589                 if (self.Expired())
590                     return;
591                 node_->SendEvent(E_CROWD_AGENT_NODE_FAILURE, map);
592                 if (self.Expired())
593                     return;
594             }
595 
596             // State may have been altered during the handling of the event
597             previousAgentState_ = GetAgentState();
598             previousTargetState_ = GetTargetState();
599         }
600     }
601 }
602 
OnNodeSet(Node * node)603 void CrowdAgent::OnNodeSet(Node* node)
604 {
605     if (node)
606         node->AddListener(this);
607 }
608 
OnSceneSet(Scene * scene)609 void CrowdAgent::OnSceneSet(Scene* scene)
610 {
611     if (scene)
612     {
613         if (scene == node_)
614             URHO3D_LOGERROR(GetTypeName() + " should not be created to the root scene node");
615         crowdManager_ = scene->GetOrCreateComponent<CrowdManager>();
616         AddAgentToCrowd();
617     }
618     else
619         RemoveAgentFromCrowd();
620 }
621 
OnMarkedDirty(Node * node)622 void CrowdAgent::OnMarkedDirty(Node* node)
623 {
624     if (!ignoreTransformChanges_ && IsEnabledEffective())
625     {
626         dtCrowdAgent* agent = const_cast<dtCrowdAgent*>(GetDetourCrowdAgent());
627         if (agent)
628         {
629             Vector3& agentPos = reinterpret_cast<Vector3&>(agent->npos);
630             Vector3 nodePos = node->GetWorldPosition();
631 
632             // Only reset position / state if actually changed
633             if (nodePos != agentPos)
634             {
635                 agentPos = nodePos;
636 
637                 // If the node has been externally altered, provide the opportunity for DetourCrowd to reevaluate the crowd agent
638                 if (agent->state == CA_STATE_INVALID)
639                     agent->state = CA_STATE_WALKING;
640             }
641         }
642     }
643 }
644 
GetDetourCrowdAgent() const645 const dtCrowdAgent* CrowdAgent::GetDetourCrowdAgent() const
646 {
647     return IsInCrowd() ? crowdManager_->GetDetourCrowdAgent(agentCrowdId_) : 0;
648 }
649 
HandleNavigationTileAdded(StringHash eventType,VariantMap & eventData)650 void CrowdAgent::HandleNavigationTileAdded(StringHash eventType, VariantMap& eventData)
651 {
652     if (!crowdManager_)
653         return;
654 
655     NavigationMesh* mesh = static_cast<NavigationMesh*>(eventData[NavigationTileAdded::P_MESH].GetPtr());
656     if (crowdManager_->GetNavigationMesh() != mesh)
657         return;
658 
659     const IntVector2 tile = eventData[NavigationTileRemoved::P_TILE].GetIntVector2();
660     const IntVector2 agentTile = mesh->GetTileIndex(node_->GetWorldPosition());
661     const BoundingBox boundingBox = mesh->GetTileBoudningBox(agentTile);
662     if (tile == agentTile && IsInCrowd())
663     {
664         RemoveAgentFromCrowd();
665         AddAgentToCrowd();
666     }
667 }
668 
669 }
670