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_, ¶ms);
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