TrinityCore
PathGenerator.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "PathGenerator.h"
19#include "Creature.h"
20#include "DetourCommon.h"
21#include "DetourNavMeshQuery.h"
22#include "DisableMgr.h"
23#include "Log.h"
24#include "MMapFactory.h"
25#include "MMapManager.h"
26#include "Map.h"
27#include "Metric.h"
28#include "PhasingHandler.h"
29
32 _polyLength(0), _type(PATHFIND_BLANK), _useStraightPath(false),
33 _forceDestination(false), _pointPathLimit(MAX_POINT_PATH_LENGTH), _useRaycast(false),
34 _endPosition(G3D::Vector3::zero()), _source(owner), _navMesh(nullptr),
35 _navMeshQuery(nullptr)
36{
37 memset(_pathPolyRefs, 0, sizeof(_pathPolyRefs));
38
39 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::PathGenerator for {}", _source->GetGUID().ToString());
40
43 {
46 _navMesh = _navMeshQuery ? _navMeshQuery->getAttachedNavMesh() : mmap->GetNavMesh(mapId);
47 }
48
50}
51
53{
54 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::~PathGenerator() for {}", _source->GetGUID().ToString());
55}
56
57bool PathGenerator::CalculatePath(float destX, float destY, float destZ, bool forceDest)
58{
59 float x, y, z;
60 _source->GetPosition(x, y, z);
61
62 if (!Trinity::IsValidMapCoord(destX, destY, destZ) || !Trinity::IsValidMapCoord(x, y, z))
63 return false;
64
65 TC_METRIC_DETAILED_EVENT("mmap_events", "CalculatePath", "");
66
67 G3D::Vector3 dest(destX, destY, destZ);
68 SetEndPosition(dest);
69
70 G3D::Vector3 start(x, y, z);
71 SetStartPosition(start);
72
73 _forceDestination = forceDest;
74
75 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::CalculatePath() for {}", _source->GetGUID().ToString());
76
77 // make sure navMesh works - we can run on map w/o mmap
78 // check if the start and end point have a .mmtile loaded (can we pass via not loaded tile on the way?)
79 Unit const* _sourceUnit = _source->ToUnit();
80 if (!_navMesh || !_navMeshQuery || (_sourceUnit && _sourceUnit->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING)) ||
81 !HaveTile(start) || !HaveTile(dest))
82 {
85 return true;
86 }
87
89
90 BuildPolyPath(start, dest);
91 return true;
92}
93
94dtPolyRef PathGenerator::GetPathPolyByPosition(dtPolyRef const* polyPath, uint32 polyPathSize, float const* point, float* distance) const
95{
96 if (!polyPath || !polyPathSize)
97 return INVALID_POLYREF;
98
99 dtPolyRef nearestPoly = INVALID_POLYREF;
100 float minDist = FLT_MAX;
101
102 for (uint32 i = 0; i < polyPathSize; ++i)
103 {
104 float closestPoint[VERTEX_SIZE];
105 if (dtStatusFailed(_navMeshQuery->closestPointOnPoly(polyPath[i], point, closestPoint, nullptr)))
106 continue;
107
108 float d = dtVdistSqr(point, closestPoint);
109 if (d < minDist)
110 {
111 minDist = d;
112 nearestPoly = polyPath[i];
113 }
114
115 if (minDist < 1.0f) // shortcut out - close enough for us
116 break;
117 }
118
119 if (distance)
120 *distance = dtMathSqrtf(minDist);
121
122 return (minDist < 3.0f) ? nearestPoly : INVALID_POLYREF;
123}
124
125dtPolyRef PathGenerator::GetPolyByLocation(float const* point, float* distance) const
126{
127 // first we check the current path
128 // if the current path doesn't contain the current poly,
129 // we need to use the expensive navMesh.findNearestPoly
130 dtPolyRef polyRef = GetPathPolyByPosition(_pathPolyRefs, _polyLength, point, distance);
131 if (polyRef != INVALID_POLYREF)
132 return polyRef;
133
134 // we don't have it in our old path
135 // try to get it by findNearestPoly()
136 // first try with low search box
137 float extents[VERTEX_SIZE] = {3.0f, 5.0f, 3.0f}; // bounds of poly search area
138 float closestPoint[VERTEX_SIZE] = {0.0f, 0.0f, 0.0f};
139 if (dtStatusSucceed(_navMeshQuery->findNearestPoly(point, extents, &_filter, &polyRef, closestPoint)) && polyRef != INVALID_POLYREF)
140 {
141 *distance = dtVdist(closestPoint, point);
142 return polyRef;
143 }
144
145 // still nothing ..
146 // try with bigger search box
147 // Note that the extent should not overlap more than 128 polygons in the navmesh (see dtNavMeshQuery::findNearestPoly)
148 extents[1] = 50.0f;
149
150 if (dtStatusSucceed(_navMeshQuery->findNearestPoly(point, extents, &_filter, &polyRef, closestPoint)) && polyRef != INVALID_POLYREF)
151 {
152 *distance = dtVdist(closestPoint, point);
153 return polyRef;
154 }
155
156 *distance = FLT_MAX;
157 return INVALID_POLYREF;
158}
159
160void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos)
161{
162 // *** getting start/end poly logic ***
163
164 float distToStartPoly, distToEndPoly;
165 float startPoint[VERTEX_SIZE] = {startPos.y, startPos.z, startPos.x};
166 float endPoint[VERTEX_SIZE] = {endPos.y, endPos.z, endPos.x};
167
168 dtPolyRef startPoly = GetPolyByLocation(startPoint, &distToStartPoly);
169 dtPolyRef endPoly = GetPolyByLocation(endPoint, &distToEndPoly);
170
172
173 // we have a hole in our mesh
174 // make shortcut path and mark it as NOPATH ( with flying and swimming exception )
175 // its up to caller how he will use this info
176 if (startPoly == INVALID_POLYREF || endPoly == INVALID_POLYREF)
177 {
178 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: (startPoly == 0 || endPoly == 0)");
180 bool path = _source->GetTypeId() == TYPEID_UNIT && _source->ToCreature()->CanFly();
181
182 bool waterPath = _source->GetTypeId() == TYPEID_UNIT && _source->ToCreature()->CanEnterWater();
183 if (waterPath)
184 {
185 // Check both start and end points, if they're both in water, then we can *safely* let the creature move
186 for (uint32 i = 0; i < _pathPoints.size(); ++i)
187 {
189 // One of the points is not in the water, cancel movement.
190 if (status == LIQUID_MAP_NO_WATER)
191 {
192 waterPath = false;
193 break;
194 }
195 }
196 }
197
198 if (path || waterPath)
199 {
201 return;
202 }
203
204 // raycast doesn't need endPoly to be valid
205 if (!_useRaycast)
206 {
208 return;
209 }
210 }
211
212 // we may need a better number here
213 bool startFarFromPoly = distToStartPoly > 7.0f;
214 bool endFarFromPoly = distToEndPoly > 7.0f;
215 if (startFarFromPoly || endFarFromPoly)
216 {
217 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: farFromPoly distToStartPoly={:.3f} distToEndPoly={:.3f}", distToStartPoly, distToEndPoly);
218
219 bool buildShotrcut = false;
220
221 G3D::Vector3 const& p = (distToStartPoly > 7.0f) ? startPos : endPos;
222 if (_source->GetMap()->IsUnderWater(_source->GetPhaseShift(), p.x, p.y, p.z))
223 {
224 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: underWater case");
225 if (Unit const* _sourceUnit = _source->ToUnit())
226 if (_sourceUnit->CanSwim())
227 buildShotrcut = true;
228 }
229 else
230 {
231 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: flying case");
232 if (Unit const* _sourceUnit = _source->ToUnit())
233 {
234 if (_sourceUnit->CanFly())
235 buildShotrcut = true;
236 // Allow to build a shortcut if the unit is falling and it's trying to move downwards towards a target (i.e. charging)
237 else if (_sourceUnit->IsFalling() && endPos.z < startPos.z)
238 buildShotrcut = true;
239 }
240 }
241
242 if (buildShotrcut)
243 {
246
247 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
248
249 return;
250 }
251 else
252 {
253 float closestPoint[VERTEX_SIZE];
254 // we may want to use closestPointOnPolyBoundary instead
255 if (dtStatusSucceed(_navMeshQuery->closestPointOnPoly(endPoly, endPoint, closestPoint, nullptr)))
256 {
257 dtVcopy(endPoint, closestPoint);
258 SetActualEndPosition(G3D::Vector3(endPoint[2], endPoint[0], endPoint[1]));
259 }
260
262
263 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
264 }
265 }
266
267 // *** poly path generating logic ***
268
269 // start and end are on same polygon
270 // handle this case as if they were 2 different polygons, building a line path split in some few points
271 if (startPoly == endPoly && !_useRaycast)
272 {
273 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: (startPoly == endPoly)");
274
275 _pathPolyRefs[0] = startPoly;
276 _polyLength = 1;
277
278 if (startFarFromPoly || endFarFromPoly)
279 {
281
282 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
283 }
284 else
286
287 BuildPointPath(startPoint, endPoint);
288 return;
289 }
290
291 // look for startPoly/endPoly in current path
293 bool startPolyFound = false;
294 bool endPolyFound = false;
295 uint32 pathStartIndex = 0;
296 uint32 pathEndIndex = 0;
297
298 if (_polyLength)
299 {
300 for (; pathStartIndex < _polyLength; ++pathStartIndex)
301 {
302 // here to catch few bugs
303 if (_pathPolyRefs[pathStartIndex] == INVALID_POLYREF)
304 {
305 TC_LOG_ERROR("maps.mmaps", "Invalid poly ref in BuildPolyPath. _polyLength: {}, pathStartIndex: {},"
306 " startPos: {}, endPos: {}, mapid: {}",
307 _polyLength, pathStartIndex, startPos.toString(), endPos.toString(),
308 _source->GetMapId());
309
310 break;
311 }
312
313 if (_pathPolyRefs[pathStartIndex] == startPoly)
314 {
315 startPolyFound = true;
316 break;
317 }
318 }
319
320 for (pathEndIndex = _polyLength-1; pathEndIndex > pathStartIndex; --pathEndIndex)
321 if (_pathPolyRefs[pathEndIndex] == endPoly)
322 {
323 endPolyFound = true;
324 break;
325 }
326 }
327
328 if (startPolyFound && endPolyFound)
329 {
330 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: (startPolyFound && endPolyFound)");
331
332 // we moved along the path and the target did not move out of our old poly-path
333 // our path is a simple subpath case, we have all the data we need
334 // just "cut" it out
335
336 _polyLength = pathEndIndex - pathStartIndex + 1;
337 memmove(_pathPolyRefs, _pathPolyRefs + pathStartIndex, _polyLength * sizeof(dtPolyRef));
338 }
339 else if (startPolyFound && !endPolyFound)
340 {
341 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: (startPolyFound && !endPolyFound)");
342
343 // we are moving on the old path but target moved out
344 // so we have atleast part of poly-path ready
345
346 _polyLength -= pathStartIndex;
347
348 // try to adjust the suffix of the path instead of recalculating entire length
349 // at given interval the target cannot get too far from its last location
350 // thus we have less poly to cover
351 // sub-path of optimal path is optimal
352
353 // take ~80% of the original length
355 uint32 prefixPolyLength = uint32(_polyLength * 0.8f + 0.5f);
356 memmove(_pathPolyRefs, _pathPolyRefs+pathStartIndex, prefixPolyLength * sizeof(dtPolyRef));
357
358 dtPolyRef suffixStartPoly = _pathPolyRefs[prefixPolyLength-1];
359
360 // we need any point on our suffix start poly to generate poly-path, so we need last poly in prefix data
361 float suffixEndPoint[VERTEX_SIZE];
362 if (dtStatusFailed(_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, nullptr)))
363 {
364 // we can hit offmesh connection as last poly - closestPointOnPoly() don't like that
365 // try to recover by using prev polyref
366 --prefixPolyLength;
367 suffixStartPoly = _pathPolyRefs[prefixPolyLength-1];
368 if (dtStatusFailed(_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, nullptr)))
369 {
370 // suffixStartPoly is still invalid, error state
373 return;
374 }
375 }
376
377 // generate suffix
378 uint32 suffixPolyLength = 0;
379
380 dtStatus dtResult;
381 if (_useRaycast)
382 {
383 TC_LOG_ERROR("maps.mmaps", "PathGenerator::BuildPolyPath() called with _useRaycast with a previous path for unit {}", _source->GetGUID().ToString());
386 return;
387 }
388 else
389 {
390 dtResult = _navMeshQuery->findPath(
391 suffixStartPoly, // start polygon
392 endPoly, // end polygon
393 suffixEndPoint, // start position
394 endPoint, // end position
395 &_filter, // polygon search filter
396 _pathPolyRefs + prefixPolyLength - 1, // [out] path
397 (int*)&suffixPolyLength,
398 MAX_PATH_LENGTH - prefixPolyLength); // max number of polygons in output path
399 }
400
401 if (!suffixPolyLength || dtStatusFailed(dtResult))
402 {
403 // this is probably an error state, but we'll leave it
404 // and hopefully recover on the next Update
405 // we still need to copy our preffix
406 TC_LOG_ERROR("maps.mmaps", "Path Build failed\n{}", _source->GetDebugInfo());
407 }
408
409 TC_LOG_DEBUG("maps.mmaps", "++ m_polyLength={} prefixPolyLength={} suffixPolyLength={}", _polyLength, prefixPolyLength, suffixPolyLength);
410
411 // new path = prefix + suffix - overlap
412 _polyLength = prefixPolyLength + suffixPolyLength - 1;
413 }
414 else
415 {
416 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: (!startPolyFound && !endPolyFound)");
417
418 // either we have no path at all -> first run
419 // or something went really wrong -> we aren't moving along the path to the target
420 // just generate new path
421
422 // free and invalidate old path data
423 Clear();
424
425 dtStatus dtResult;
426 if (_useRaycast)
427 {
428 float hit = 0;
429 float hitNormal[3];
430 memset(hitNormal, 0, sizeof(hitNormal));
431
432 dtResult = _navMeshQuery->raycast(
433 startPoly,
434 startPoint,
435 endPoint,
436 &_filter,
437 &hit,
438 hitNormal,
440 (int*)&_polyLength,
442
443 if (!_polyLength || dtStatusFailed(dtResult))
444 {
447 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
448 return;
449 }
450
451 // raycast() sets hit to FLT_MAX if there is a ray between start and end
452 if (hit != FLT_MAX)
453 {
454 float hitPos[3];
455
456 // Walk back a bit from the hit point to make sure it's in the mesh (sometimes the point is actually outside of the polygons due to float precision issues)
457 hit *= 0.99f;
458 dtVlerp(hitPos, startPoint, endPoint, hit);
459
460 // if it fails again, clamp to poly boundary
461 if (dtStatusFailed(_navMeshQuery->getPolyHeight(_pathPolyRefs[_polyLength - 1], hitPos, &hitPos[1])))
462 _navMeshQuery->closestPointOnPolyBoundary(_pathPolyRefs[_polyLength - 1], hitPos, hitPos);
463
464 _pathPoints.resize(2);
466 _pathPoints[1] = G3D::Vector3(hitPos[2], hitPos[0], hitPos[1]);
467
470 AddFarFromPolyFlags(startFarFromPoly, false);
471 return;
472 }
473 else
474 {
475 // clamp to poly boundary if we fail to get the height
476 if (dtStatusFailed(_navMeshQuery->getPolyHeight(_pathPolyRefs[_polyLength - 1], endPoint, &endPoint[1])))
477 _navMeshQuery->closestPointOnPolyBoundary(_pathPolyRefs[_polyLength - 1], endPoint, endPoint);
478
479 _pathPoints.resize(2);
481 _pathPoints[1] = G3D::Vector3(endPoint[2], endPoint[0], endPoint[1]);
482
484 if (startFarFromPoly || endFarFromPoly)
485 {
487
488 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
489 }
490 else
492 return;
493 }
494 }
495 else
496 {
497 dtResult = _navMeshQuery->findPath(
498 startPoly, // start polygon
499 endPoly, // end polygon
500 startPoint, // start position
501 endPoint, // end position
502 &_filter, // polygon search filter
503 _pathPolyRefs, // [out] path
504 (int*)&_polyLength,
505 MAX_PATH_LENGTH); // max number of polygons in output path
506 }
507
508 if (!_polyLength || dtStatusFailed(dtResult))
509 {
510 // only happens if we passed bad data to findPath(), or navmesh is messed up
511 TC_LOG_ERROR("maps.mmaps", "{} Path Build failed: 0 length path", _source->GetGUID().ToString());
514 return;
515 }
516 }
517
518 // by now we know what type of path we can get
519 if (_pathPolyRefs[_polyLength - 1] == endPoly && !(_type & PATHFIND_INCOMPLETE))
521 else
523
524 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
525
526 // generate the point-path out of our up-to-date poly-path
527 BuildPointPath(startPoint, endPoint);
528}
529
530void PathGenerator::BuildPointPath(const float *startPoint, const float *endPoint)
531{
532 float pathPoints[MAX_POINT_PATH_LENGTH*VERTEX_SIZE];
533 uint32 pointCount = 0;
534 dtStatus dtResult = DT_FAILURE;
535 if (_useRaycast)
536 {
537 // _straightLine uses raycast and it currently doesn't support building a point path, only a 2-point path with start and hitpoint/end is returned
538 TC_LOG_ERROR("maps.mmaps", "PathGenerator::BuildPointPath() called with _useRaycast for unit {}", _source->GetGUID().ToString());
541 return;
542 }
543 else if (_useStraightPath)
544 {
545 dtResult = _navMeshQuery->findStraightPath(
546 startPoint, // start position
547 endPoint, // end position
548 _pathPolyRefs, // current path
549 _polyLength, // lenth of current path
550 pathPoints, // [out] path corner points
551 nullptr, // [out] flags
552 nullptr, // [out] shortened path
553 (int*)&pointCount,
554 _pointPathLimit); // maximum number of points/polygons to use
555 }
556 else
557 {
558 dtResult = FindSmoothPath(
559 startPoint, // start position
560 endPoint, // end position
561 _pathPolyRefs, // current path
562 _polyLength, // length of current path
563 pathPoints, // [out] path corner points
564 (int*)&pointCount,
565 _pointPathLimit); // maximum number of points
566 }
567
568 // Special case with start and end positions very close to each other
569 if (_polyLength == 1 && pointCount == 1)
570 {
571 // First point is start position, append end position
572 dtVcopy(&pathPoints[1 * VERTEX_SIZE], endPoint);
573 pointCount++;
574 }
575 else if ( pointCount < 2 || dtStatusFailed(dtResult))
576 {
577 // only happens if pass bad data to findStraightPath or navmesh is broken
578 // single point paths can be generated here
580 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::BuildPointPath FAILED! path sized {} returned\n", pointCount);
583 return;
584 }
585 else if (pointCount >= _pointPathLimit)
586 {
587 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::BuildPointPath FAILED! path sized {} returned, lower than limit set to {}", pointCount, _pointPathLimit);
590 return;
591 }
592
593 _pathPoints.resize(pointCount);
594 for (uint32 i = 0; i < pointCount; ++i)
595 _pathPoints[i] = G3D::Vector3(pathPoints[i*VERTEX_SIZE+2], pathPoints[i*VERTEX_SIZE], pathPoints[i*VERTEX_SIZE+1]);
596
598
599 // first point is always our current location - we need the next one
600 SetActualEndPosition(_pathPoints[pointCount-1]);
601
602 // force the given destination, if needed
603 if (_forceDestination &&
605 {
606 // we may want to keep partial subpath
608 {
611 }
612 else
613 {
616 }
617
619 }
620
621 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::BuildPointPath path type {} size {} poly-size {}", _type, pointCount, _polyLength);
622}
623
625{
626 for (uint32 i = 0; i < _pathPoints.size(); ++i)
628}
629
631{
632 TC_LOG_DEBUG("maps.mmaps", "++ BuildShortcut :: making shortcut");
633
634 Clear();
635
636 // make two point path, our curr pos is the start, and dest is the end
637 _pathPoints.resize(2);
638
639 // set start and a default next position
642
644
646}
647
649{
650 uint16 includeFlags = 0;
651 uint16 excludeFlags = 0;
652
653 if (_source->GetTypeId() == TYPEID_UNIT)
654 {
655 Creature* creature = (Creature*)_source;
656 if (!creature->IsAquatic())
657 includeFlags |= NAV_GROUND; // walk
658
659 // creatures don't take environmental damage
660 if (creature->CanEnterWater())
661 includeFlags |= (NAV_WATER | NAV_MAGMA_SLIME); // swim
662 }
663 else // assume Player
664 {
665 // perfect support not possible, just stay 'safe'
666 includeFlags |= (NAV_GROUND | NAV_WATER | NAV_MAGMA_SLIME);
667 }
668
669 _filter.setIncludeFlags(includeFlags);
670 _filter.setExcludeFlags(excludeFlags);
671
672 UpdateFilter();
673}
674
676{
677 _filter.setIncludeFlags(_filter.getIncludeFlags() | _source->GetMap()->GetForceEnabledNavMeshFilterFlags());
678 _filter.setExcludeFlags(_filter.getExcludeFlags() | _source->GetMap()->GetForceDisabledNavMeshFilterFlags());
679
680 // allow creatures to cheat and use different movement types if they are moved
681 // forcefully into terrain they can't normally move in
682 if (Unit const* _sourceUnit = _source->ToUnit())
683 {
684 if (_sourceUnit->IsInWater() || _sourceUnit->IsUnderWater())
685 {
686 uint16 includedFlags = _filter.getIncludeFlags();
687 includedFlags |= GetNavTerrain(_source->GetPositionX(),
690
691 _filter.setIncludeFlags(includedFlags);
692 }
693
694 if (Creature const* _sourceCreature = _source->ToCreature())
695 if (_sourceCreature->IsInCombat() || _sourceCreature->IsInEvadeMode())
696 _filter.setIncludeFlags(_filter.getIncludeFlags() | NAV_GROUND_STEEP);
697 }
698}
699
700NavTerrainFlag PathGenerator::GetNavTerrain(float x, float y, float z) const
701{
702 LiquidData data;
703 ZLiquidStatus liquidStatus = _source->GetMap()->GetLiquidStatus(_source->GetPhaseShift(), x, y, z, {}, &data, _source->GetCollisionHeight());
704 if (liquidStatus == LIQUID_MAP_NO_WATER)
705 return NAV_GROUND;
706
708 return NAV_WATER;
709
711 return NAV_MAGMA_SLIME;
712
713 return NAV_GROUND;
714}
715
716bool PathGenerator::HaveTile(const G3D::Vector3& p) const
717{
718 int tx = -1, ty = -1;
719 float point[VERTEX_SIZE] = {p.y, p.z, p.x};
720
721 _navMesh->calcTileLoc(point, &tx, &ty);
722
726 if (tx < 0 || ty < 0)
727 return false;
728
729 return (_navMesh->getTileAt(tx, ty, 0) != nullptr);
730}
731
732uint32 PathGenerator::FixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPath, dtPolyRef const* visited, uint32 nvisited)
733{
734 int32 furthestPath = -1;
735 int32 furthestVisited = -1;
736
737 // Find furthest common polygon.
738 for (int32 i = npath-1; i >= 0; --i)
739 {
740 bool found = false;
741 for (int32 j = nvisited-1; j >= 0; --j)
742 {
743 if (path[i] == visited[j])
744 {
745 furthestPath = i;
746 furthestVisited = j;
747 found = true;
748 }
749 }
750 if (found)
751 break;
752 }
753
754 // If no intersection found just return current path.
755 if (furthestPath == -1 || furthestVisited == -1)
756 return npath;
757
758 // Concatenate paths.
759
760 // Adjust beginning of the buffer to include the visited.
761 uint32 req = nvisited - furthestVisited;
762 uint32 orig = uint32(furthestPath + 1) < npath ? furthestPath + 1 : npath;
763 uint32 size = npath > orig ? npath - orig : 0;
764 if (req + size > maxPath)
765 size = maxPath-req;
766
767 if (size)
768 memmove(path + req, path + orig, size * sizeof(dtPolyRef));
769
770 // Store visited
771 for (uint32 i = 0; i < req; ++i)
772 path[i] = visited[(nvisited - 1) - i];
773
774 return req+size;
775}
776
777bool PathGenerator::GetSteerTarget(float const* startPos, float const* endPos,
778 float minTargetDist, dtPolyRef const* path, uint32 pathSize,
779 float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef)
780{
781 // Find steer target.
782 static const uint32 MAX_STEER_POINTS = 3;
783 float steerPath[MAX_STEER_POINTS*VERTEX_SIZE];
784 unsigned char steerPathFlags[MAX_STEER_POINTS];
785 dtPolyRef steerPathPolys[MAX_STEER_POINTS];
786 uint32 nsteerPath = 0;
787 dtStatus dtResult = _navMeshQuery->findStraightPath(startPos, endPos, path, pathSize,
788 steerPath, steerPathFlags, steerPathPolys, (int*)&nsteerPath, MAX_STEER_POINTS);
789 if (!nsteerPath || dtStatusFailed(dtResult))
790 return false;
791
792 // Find vertex far enough to steer to.
793 uint32 ns = 0;
794 while (ns < nsteerPath)
795 {
796 // Stop at Off-Mesh link or when point is further than slop away.
797 if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) ||
798 !InRangeYZX(&steerPath[ns*VERTEX_SIZE], startPos, minTargetDist, 1000.0f))
799 break;
800 ns++;
801 }
802 // Failed to find good point to steer to.
803 if (ns >= nsteerPath)
804 return false;
805
806 dtVcopy(steerPos, &steerPath[ns*VERTEX_SIZE]);
807 steerPos[1] = startPos[1]; // keep Z value
808 steerPosFlag = steerPathFlags[ns];
809 steerPosRef = steerPathPolys[ns];
810
811 return true;
812}
813
814dtStatus PathGenerator::FindSmoothPath(float const* startPos, float const* endPos,
815 dtPolyRef const* polyPath, uint32 polyPathSize,
816 float* smoothPath, int* smoothPathSize, uint32 maxSmoothPathSize)
817{
818 *smoothPathSize = 0;
819 uint32 nsmoothPath = 0;
820
821 dtPolyRef polys[MAX_PATH_LENGTH];
822 memcpy(polys, polyPath, sizeof(dtPolyRef)*polyPathSize);
823 uint32 npolys = polyPathSize;
824
825 float iterPos[VERTEX_SIZE], targetPos[VERTEX_SIZE];
826
827 if (polyPathSize > 1)
828 {
829 // Pick the closest points on poly border
830 if (dtStatusFailed(_navMeshQuery->closestPointOnPolyBoundary(polys[0], startPos, iterPos)))
831 return DT_FAILURE;
832
833 if (dtStatusFailed(_navMeshQuery->closestPointOnPolyBoundary(polys[npolys - 1], endPos, targetPos)))
834 return DT_FAILURE;
835 }
836 else
837 {
838 // Case where the path is on the same poly
839 dtVcopy(iterPos, startPos);
840 dtVcopy(targetPos, endPos);
841 }
842
843 dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], iterPos);
844 nsmoothPath++;
845
846 // Move towards target a small advancement at a time until target reached or
847 // when ran out of memory to store the path.
848 while (npolys && nsmoothPath < maxSmoothPathSize)
849 {
850 // Find location to steer towards.
851 float steerPos[VERTEX_SIZE];
852 unsigned char steerPosFlag;
853 dtPolyRef steerPosRef = INVALID_POLYREF;
854
855 if (!GetSteerTarget(iterPos, targetPos, SMOOTH_PATH_SLOP, polys, npolys, steerPos, steerPosFlag, steerPosRef))
856 break;
857
858 bool endOfPath = (steerPosFlag & DT_STRAIGHTPATH_END) != 0;
859 bool offMeshConnection = (steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0;
860
861 // Find movement delta.
862 float delta[VERTEX_SIZE];
863 dtVsub(delta, steerPos, iterPos);
864 float len = dtMathSqrtf(dtVdot(delta, delta));
865 // If the steer target is end of path or off-mesh link, do not move past the location.
866 if ((endOfPath || offMeshConnection) && len < SMOOTH_PATH_STEP_SIZE)
867 len = 1.0f;
868 else
869 len = SMOOTH_PATH_STEP_SIZE / len;
870
871 float moveTgt[VERTEX_SIZE];
872 dtVmad(moveTgt, iterPos, delta, len);
873
874 // Move
875 float result[VERTEX_SIZE];
876 const static uint32 MAX_VISIT_POLY = 16;
877 dtPolyRef visited[MAX_VISIT_POLY];
878
879 uint32 nvisited = 0;
880 if (dtStatusFailed(_navMeshQuery->moveAlongSurface(polys[0], iterPos, moveTgt, &_filter, result, visited, (int*)&nvisited, MAX_VISIT_POLY)))
881 return DT_FAILURE;
882 npolys = FixupCorridor(polys, npolys, MAX_PATH_LENGTH, visited, nvisited);
883
884 if (dtStatusFailed(_navMeshQuery->getPolyHeight(polys[0], result, &result[1])))
885 TC_LOG_DEBUG("maps.mmaps", "Cannot find height at position X: {} Y: {} Z: {} for {}", result[2], result[0], result[1], _source->GetDebugInfo());
886 result[1] += 0.5f;
887 dtVcopy(iterPos, result);
888
889 // Handle end of path and off-mesh links when close enough.
890 if (endOfPath && InRangeYZX(iterPos, steerPos, SMOOTH_PATH_SLOP, 1.0f))
891 {
892 // Reached end of path.
893 dtVcopy(iterPos, targetPos);
894 if (nsmoothPath < maxSmoothPathSize)
895 {
896 dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], iterPos);
897 nsmoothPath++;
898 }
899 break;
900 }
901 else if (offMeshConnection && InRangeYZX(iterPos, steerPos, SMOOTH_PATH_SLOP, 1.0f))
902 {
903 // Advance the path up to and over the off-mesh connection.
904 dtPolyRef prevRef = INVALID_POLYREF;
905 dtPolyRef polyRef = polys[0];
906 uint32 npos = 0;
907 while (npos < npolys && polyRef != steerPosRef)
908 {
909 prevRef = polyRef;
910 polyRef = polys[npos];
911 npos++;
912 }
913
914 for (uint32 i = npos; i < npolys; ++i)
915 polys[i-npos] = polys[i];
916
917 npolys -= npos;
918
919 // Handle the connection.
920 float connectionStartPos[VERTEX_SIZE], connectionEndPos[VERTEX_SIZE];
921 if (dtStatusSucceed(_navMesh->getOffMeshConnectionPolyEndPoints(prevRef, polyRef, connectionStartPos, connectionEndPos)))
922 {
923 if (nsmoothPath < maxSmoothPathSize)
924 {
925 dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], connectionStartPos);
926 nsmoothPath++;
927 }
928 // Move position at the other side of the off-mesh link.
929 dtVcopy(iterPos, connectionEndPos);
930 if (dtStatusFailed(_navMeshQuery->getPolyHeight(polys[0], iterPos, &iterPos[1])))
931 return DT_FAILURE;
932 iterPos[1] += 0.5f;
933 }
934 }
935
936 // Store results.
937 if (nsmoothPath < maxSmoothPathSize)
938 {
939 dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], iterPos);
940 nsmoothPath++;
941 }
942 }
943
944 *smoothPathSize = nsmoothPath;
945
946 // this is most likely a loop
947 return nsmoothPath < MAX_POINT_PATH_LENGTH ? DT_SUCCESS : DT_FAILURE;
948}
949
950bool PathGenerator::InRangeYZX(float const* v1, float const* v2, float r, float h) const
951{
952 const float dx = v2[0] - v1[0];
953 const float dy = v2[1] - v1[1]; // elevation
954 const float dz = v2[2] - v1[2];
955 return (dx * dx + dz * dz) < r * r && fabsf(dy) < h;
956}
957
958bool PathGenerator::InRange(G3D::Vector3 const& p1, G3D::Vector3 const& p2, float r, float h) const
959{
960 G3D::Vector3 d = p1 - p2;
961 return (d.x * d.x + d.y * d.y) < r * r && fabsf(d.z) < h;
962}
963
964float PathGenerator::Dist3DSqr(G3D::Vector3 const& p1, G3D::Vector3 const& p2) const
965{
966 return (p1 - p2).squaredLength();
967}
968
970{
971 float length = 0.0f;
972 for (std::size_t i = 0; i < _pathPoints.size() - 1; ++i)
973 length += (_pathPoints[i + 1] - _pathPoints[i]).length();
974
975 return length;
976}
977
978void PathGenerator::ShortenPathUntilDist(G3D::Vector3 const& target, float dist)
979{
980 if (GetPathType() == PATHFIND_BLANK || _pathPoints.size() < 2)
981 {
982 TC_LOG_ERROR("maps.mmaps", "PathGenerator::ReducePathLengthByDist called before path was successfully built");
983 return;
984 }
985
986 float const distSq = dist * dist;
987
988 // the first point of the path must be outside the specified range
989 // (this should have really been checked by the caller...)
990 if ((_pathPoints[0] - target).squaredLength() < distSq)
991 return;
992
993 // check if we even need to do anything
994 if ((*_pathPoints.rbegin() - target).squaredLength() >= distSq)
995 return;
996
997 size_t i = _pathPoints.size()-1;
998 float x, y, z, collisionHeight = _source->GetCollisionHeight();
999 // find the first i s.t.:
1000 // - _pathPoints[i] is still too close
1001 // - _pathPoints[i-1] is too far away
1002 // => the end point is somewhere on the line between the two
1003 while (1)
1004 {
1005 // we know that pathPoints[i] is too close already (from the previous iteration)
1006 if ((_pathPoints[i-1] - target).squaredLength() >= distSq)
1007 break; // bingo!
1008
1009 // check if the shortened path is still in LoS with the target
1010 _source->GetHitSpherePointFor({ _pathPoints[i - 1].x, _pathPoints[i - 1].y, _pathPoints[i - 1].z + collisionHeight }, x, y, z);
1012 {
1013 // whenver we find a point that is not in LoS anymore, simply use last valid path
1014 _pathPoints.resize(i + 1);
1015 return;
1016 }
1017
1018 if (!--i)
1019 {
1020 // no point found that fulfills the condition
1021 _pathPoints[0] = _pathPoints[1];
1022 _pathPoints.resize(2);
1023 return;
1024 }
1025 }
1026
1027 // ok, _pathPoints[i] is too close, _pathPoints[i-1] is not, so our target point is somewhere between the two...
1028 // ... settle for a guesstimate since i'm not confident in doing trig on every chase motion tick...
1029 // (@todo review this)
1030 _pathPoints[i] += (_pathPoints[i - 1] - _pathPoints[i]).direction() * (dist - (_pathPoints[i] - target).length());
1031 _pathPoints.resize(i+1);
1032}
1033
1035{
1036 return (target->GetPositionZ() - GetActualEndPosition().z) > 5.0f;
1037}
1038
1039void PathGenerator::AddFarFromPolyFlags(bool startFarFromPoly, bool endFarFromPoly)
1040{
1041 if (startFarFromPoly)
1043 if (endFarFromPoly)
1045}
int32_t int32
Definition: Define.h:138
uint16_t uint16
Definition: Define.h:143
uint32_t uint32
Definition: Define.h:142
#define TC_LOG_DEBUG(filterType__,...)
Definition: Log.h:156
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
NavTerrainFlag
Definition: MMapDefines.h:64
@ NAV_GROUND_STEEP
Definition: MMapDefines.h:67
@ NAV_GROUND
Definition: MMapDefines.h:66
@ NAV_WATER
Definition: MMapDefines.h:68
@ NAV_MAGMA_SLIME
Definition: MMapDefines.h:69
ZLiquidStatus
Definition: MapDefines.h:125
@ LIQUID_MAP_NO_WATER
Definition: MapDefines.h:126
#define TC_METRIC_DETAILED_EVENT(category, title, description)
Definition: Metric.h:240
@ TYPEID_UNIT
Definition: ObjectGuid.h:40
#define VERTEX_SIZE
Definition: PathGenerator.h:38
#define INVALID_POLYREF
Definition: PathGenerator.h:39
#define SMOOTH_PATH_SLOP
Definition: PathGenerator.h:36
#define MAX_PATH_LENGTH
Definition: PathGenerator.h:32
#define SMOOTH_PATH_STEP_SIZE
Definition: PathGenerator.h:35
#define MAX_POINT_PATH_LENGTH
Definition: PathGenerator.h:33
PathType
Definition: PathGenerator.h:42
@ PATHFIND_FARFROMPOLY_END
Definition: PathGenerator.h:51
@ PATHFIND_NOT_USING_PATH
Definition: PathGenerator.h:48
@ PATHFIND_NORMAL
Definition: PathGenerator.h:44
@ PATHFIND_NOPATH
Definition: PathGenerator.h:47
@ PATHFIND_FARFROMPOLY_START
Definition: PathGenerator.h:50
@ PATHFIND_SHORT
Definition: PathGenerator.h:49
@ PATHFIND_SHORTCUT
Definition: PathGenerator.h:45
@ PATHFIND_BLANK
Definition: PathGenerator.h:43
@ PATHFIND_INCOMPLETE
Definition: PathGenerator.h:46
@ LINEOFSIGHT_ALL_CHECKS
@ UNIT_STATE_IGNORE_PATHFINDING
Definition: Unit.h:283
bool CanFly() const override
Definition: Creature.h:147
bool CanEnterWater() const override
Definition: Creature.h:146
bool IsAquatic() const
Definition: Creature.h:122
constexpr bool HasFlag(T flag) const
Definition: EnumFlag.h:106
static MMapManager * createOrGetMMapManager()
Definition: MMapFactory.cpp:26
dtNavMesh const * GetNavMesh(uint32 mapId)
dtNavMeshQuery const * GetNavMeshQuery(uint32 meshMapId, uint32 instanceMapId, uint32 instanceId)
ZLiquidStatus GetLiquidStatus(PhaseShift const &phaseShift, float x, float y, float z, Optional< map_liquidHeaderTypeFlags > ReqLiquidType={}, LiquidData *data=nullptr, float collisionHeight=2.03128f)
Definition: Map.cpp:1726
bool IsUnderWater(PhaseShift const &phaseShift, float x, float y, float z)
Definition: Map.cpp:1772
uint16 GetForceDisabledNavMeshFilterFlags() const
Definition: Map.h:272
bool isInLineOfSight(PhaseShift const &phaseShift, float x1, float y1, float z1, float x2, float y2, float z2, LineOfSightChecks checks, VMAP::ModelIgnoreFlags ignoreFlags) const
Definition: Map.cpp:1782
uint16 GetForceEnabledNavMeshFilterFlags() const
Definition: Map.h:268
TerrainInfo * GetTerrain() const
Definition: Map.h:263
std::string ToString() const
Definition: ObjectGuid.cpp:554
static Creature * ToCreature(Object *o)
Definition: Object.h:219
static Unit * ToUnit(Object *o)
Definition: Object.h:225
TypeID GetTypeId() const
Definition: Object.h:173
static ObjectGuid GetGUID(Object const *o)
Definition: Object.h:159
PathType _type
Definition: PathGenerator.h:95
bool HaveTile(G3D::Vector3 const &p) const
void SetActualEndPosition(G3D::Vector3 const &point)
dtPolyRef GetPathPolyByPosition(dtPolyRef const *polyPath, uint32 polyPathSize, float const *Point, float *Distance=nullptr) const
G3D::Vector3 const & GetStartPosition() const
Definition: PathGenerator.h:77
void ShortenPathUntilDist(G3D::Vector3 const &point, float dist)
dtQueryFilter _filter
float Dist3DSqr(G3D::Vector3 const &p1, G3D::Vector3 const &p2) const
float GetPathLength() const
PathType GetPathType() const
Definition: PathGenerator.h:84
uint32 _pointPathLimit
Definition: PathGenerator.h:99
void AddFarFromPolyFlags(bool startFarFromPoly, bool endFarFromPoly)
dtStatus FindSmoothPath(float const *startPos, float const *endPos, dtPolyRef const *polyPath, uint32 polyPathSize, float *smoothPath, int *smoothPathSize, uint32 smoothPathMaxSize)
bool _useStraightPath
Definition: PathGenerator.h:97
dtNavMeshQuery const * _navMeshQuery
WorldObject const *const _source
G3D::Vector3 const & GetEndPosition() const
Definition: PathGenerator.h:78
uint32 _polyLength
Definition: PathGenerator.h:92
bool _forceDestination
Definition: PathGenerator.h:98
dtPolyRef GetPolyByLocation(float const *Point, float *Distance) const
PathGenerator(WorldObject const *owner)
void BuildPolyPath(G3D::Vector3 const &startPos, G3D::Vector3 const &endPos)
bool InRangeYZX(float const *v1, float const *v2, float r, float h) const
void SetStartPosition(G3D::Vector3 const &point)
bool IsInvalidDestinationZ(WorldObject const *target) const
uint32 FixupCorridor(dtPolyRef *path, uint32 npath, uint32 maxPath, dtPolyRef const *visited, uint32 nvisited)
dtPolyRef _pathPolyRefs[MAX_PATH_LENGTH]
Definition: PathGenerator.h:91
Movement::PointsArray _pathPoints
Definition: PathGenerator.h:94
bool InRange(G3D::Vector3 const &p1, G3D::Vector3 const &p2, float r, float h) const
void BuildPointPath(float const *startPoint, float const *endPoint)
dtNavMesh const * _navMesh
bool CalculatePath(float destX, float destY, float destZ, bool forceDest=false)
bool GetSteerTarget(float const *startPos, float const *endPos, float minTargetDist, dtPolyRef const *path, uint32 pathSize, float *steerPos, unsigned char &steerPosFlag, dtPolyRef &steerPosRef)
void SetEndPosition(G3D::Vector3 const &point)
NavTerrainFlag GetNavTerrain(float x, float y, float z) const
G3D::Vector3 const & GetActualEndPosition() const
Definition: PathGenerator.h:79
static uint32 GetTerrainMapId(PhaseShift const &phaseShift, uint32 mapId, TerrainInfo const *terrain, float x, float y)
Definition: Unit.h:627
bool HasUnitState(const uint32 f) const
Definition: Unit.h:732
constexpr uint32 GetMapId() const
Definition: Position.h:201
Map * GetMap() const
Definition: Object.h:624
void UpdateAllowedPositionZ(float x, float y, float &z, float *groundZ=nullptr) const
Definition: Object.cpp:1371
virtual float GetCollisionHeight() const
Definition: Object.h:769
Position GetHitSpherePointFor(Position const &dest) const
Definition: Object.cpp:1152
std::string GetDebugInfo() const override
Definition: Object.cpp:3785
PhaseShift & GetPhaseShift()
Definition: Object.h:523
uint32 GetInstanceId() const
Definition: Object.h:521
bool IsPathfindingEnabled(uint32 mapId)
Definition: DisableMgr.cpp:401
bool IsValidMapCoord(float c)
Definition: GridDefines.h:231
constexpr std::size_t size()
Definition: UpdateField.h:796
EnumFlag< map_liquidHeaderTypeFlags > type_flags
Definition: MapDefines.h:139
constexpr float GetPositionX() const
Definition: Position.h:76
constexpr float GetPositionY() const
Definition: Position.h:77
constexpr void GetPosition(float &x, float &y) const
Definition: Position.h:81
constexpr float GetPositionZ() const
Definition: Position.h:78