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