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 "Map.h"
20#include "Creature.h"
21#include "MMapFactory.h"
22#include "MMapManager.h"
23#include "Log.h"
24#include "DisableMgr.h"
25#include "DetourCommon.h"
26#include "DetourNavMeshQuery.h"
27#include "Metric.h"
28
31 _polyLength(0), _type(PATHFIND_BLANK), _useStraightPath(false),
32 _forceDestination(false), _pointPathLimit(MAX_POINT_PATH_LENGTH), _useRaycast(false),
33 _endPosition(G3D::Vector3::zero()), _source(owner), _navMesh(nullptr),
34 _navMeshQuery(nullptr)
35{
36 memset(_pathPolyRefs, 0, sizeof(_pathPolyRefs));
37
38 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::PathGenerator for {}", _source->GetGUID().ToString());
39
40 uint32 mapId = _source->GetMapId();
42 {
45 _navMesh = _navMeshQuery ? _navMeshQuery->getAttachedNavMesh() : mmap->GetNavMesh(mapId);
46 }
47
49}
50
52{
53 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::~PathGenerator() for {}", _source->GetGUID().ToString());
54}
55
56bool PathGenerator::CalculatePath(float destX, float destY, float destZ, bool forceDest)
57{
58 float x, y, z;
59 _source->GetPosition(x, y, z);
60
61 if (!Trinity::IsValidMapCoord(destX, destY, destZ) || !Trinity::IsValidMapCoord(x, y, z))
62 return false;
63
64 TC_METRIC_DETAILED_EVENT("mmap_events", "CalculatePath", "");
65
66 G3D::Vector3 dest(destX, destY, destZ);
67 SetEndPosition(dest);
68
69 G3D::Vector3 start(x, y, z);
70 SetStartPosition(start);
71
72 _forceDestination = forceDest;
73
74 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::CalculatePath() for {}", _source->GetGUID().ToString());
75
76 // make sure navMesh works - we can run on map w/o mmap
77 // check if the start and end point have a .mmtile loaded (can we pass via not loaded tile on the way?)
78 Unit const* _sourceUnit = _source->ToUnit();
79 if (!_navMesh || !_navMeshQuery || (_sourceUnit && _sourceUnit->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING)) ||
80 !HaveTile(start) || !HaveTile(dest))
81 {
84 return true;
85 }
86
88
89 BuildPolyPath(start, dest);
90 return true;
91}
92
93dtPolyRef PathGenerator::GetPathPolyByPosition(dtPolyRef const* polyPath, uint32 polyPathSize, float const* point, float* distance) const
94{
95 if (!polyPath || !polyPathSize)
96 return INVALID_POLYREF;
97
98 dtPolyRef nearestPoly = INVALID_POLYREF;
99 float minDist = FLT_MAX;
100
101 for (uint32 i = 0; i < polyPathSize; ++i)
102 {
103 float closestPoint[VERTEX_SIZE];
104 if (dtStatusFailed(_navMeshQuery->closestPointOnPoly(polyPath[i], point, closestPoint, nullptr)))
105 continue;
106
107 float d = dtVdistSqr(point, closestPoint);
108 if (d < minDist)
109 {
110 minDist = d;
111 nearestPoly = polyPath[i];
112 }
113
114 if (minDist < 1.0f) // shortcut out - close enough for us
115 break;
116 }
117
118 if (distance)
119 *distance = dtMathSqrtf(minDist);
120
121 return (minDist < 3.0f) ? nearestPoly : INVALID_POLYREF;
122}
123
124dtPolyRef PathGenerator::GetPolyByLocation(float const* point, float* distance) const
125{
126 // first we check the current path
127 // if the current path doesn't contain the current poly,
128 // we need to use the expensive navMesh.findNearestPoly
129 dtPolyRef polyRef = GetPathPolyByPosition(_pathPolyRefs, _polyLength, point, distance);
130 if (polyRef != INVALID_POLYREF)
131 return polyRef;
132
133 // we don't have it in our old path
134 // try to get it by findNearestPoly()
135 // first try with low search box
136 float extents[VERTEX_SIZE] = {3.0f, 5.0f, 3.0f}; // bounds of poly search area
137 float closestPoint[VERTEX_SIZE] = {0.0f, 0.0f, 0.0f};
138 if (dtStatusSucceed(_navMeshQuery->findNearestPoly(point, extents, &_filter, &polyRef, closestPoint)) && polyRef != INVALID_POLYREF)
139 {
140 *distance = dtVdist(closestPoint, point);
141 return polyRef;
142 }
143
144 // still nothing ..
145 // try with bigger search box
146 // Note that the extent should not overlap more than 128 polygons in the navmesh (see dtNavMeshQuery::findNearestPoly)
147 extents[1] = 50.0f;
148
149 if (dtStatusSucceed(_navMeshQuery->findNearestPoly(point, extents, &_filter, &polyRef, closestPoint)) && polyRef != INVALID_POLYREF)
150 {
151 *distance = dtVdist(closestPoint, point);
152 return polyRef;
153 }
154
155 *distance = FLT_MAX;
156 return INVALID_POLYREF;
157}
158
159void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos)
160{
161 // *** getting start/end poly logic ***
162
163 float distToStartPoly, distToEndPoly;
164 float startPoint[VERTEX_SIZE] = {startPos.y, startPos.z, startPos.x};
165 float endPoint[VERTEX_SIZE] = {endPos.y, endPos.z, endPos.x};
166
167 dtPolyRef startPoly = GetPolyByLocation(startPoint, &distToStartPoly);
168 dtPolyRef endPoly = GetPolyByLocation(endPoint, &distToEndPoly);
169
171
172 // we have a hole in our mesh
173 // make shortcut path and mark it as NOPATH ( with flying and swimming exception )
174 // its up to caller how he will use this info
175 if (startPoly == INVALID_POLYREF || endPoly == INVALID_POLYREF)
176 {
177 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: (startPoly == 0 || endPoly == 0)");
179 bool path = _source->GetTypeId() == TYPEID_UNIT && _source->ToCreature()->CanFly();
180
181 bool waterPath = _source->GetTypeId() == TYPEID_UNIT && _source->ToCreature()->CanSwim();
182 if (waterPath)
183 {
184 // Check both start and end points, if they're both in water, then we can *safely* let the creature move
185 for (uint32 i = 0; i < _pathPoints.size(); ++i)
186 {
188 // One of the points is not in the water, cancel movement.
189 if (status == LIQUID_MAP_NO_WATER)
190 {
191 waterPath = false;
192 break;
193 }
194 }
195 }
196
197 if (path || waterPath)
198 {
200 return;
201 }
202
203 // raycast doesn't need endPoly to be valid
204 if (!_useRaycast)
205 {
207 return;
208 }
209 }
210
211 // we may need a better number here
212 bool startFarFromPoly = distToStartPoly > 7.0f;
213 bool endFarFromPoly = distToEndPoly > 7.0f;
214 if (startFarFromPoly || endFarFromPoly)
215 {
216 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: farFromPoly distToStartPoly={:.3f} distToEndPoly={:.3f}", distToStartPoly, distToEndPoly);
217
218 bool buildShotrcut = false;
219
220 G3D::Vector3 const& p = (distToStartPoly > 7.0f) ? startPos : endPos;
221 if (_source->GetMap()->IsUnderWater(_source->GetPhaseMask(), p.x, p.y, p.z))
222 {
223 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: underWater case");
224 if (Unit const* _sourceUnit = _source->ToUnit())
225 if (_sourceUnit->CanSwim())
226 buildShotrcut = true;
227 }
228 else
229 {
230 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: flying case");
231 if (Unit const* _sourceUnit = _source->ToUnit())
232 {
233 if (_sourceUnit->CanFly())
234 buildShotrcut = true;
235 // Allow to build a shortcut if the unit is falling and it's trying to move downwards towards a target (i.e. charging)
236 else if (_sourceUnit->IsFalling() && endPos.z < startPos.z)
237 buildShotrcut = true;
238 }
239 }
240
241 if (buildShotrcut)
242 {
245
246 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
247
248 return;
249 }
250 else
251 {
252 float closestPoint[VERTEX_SIZE];
253 // we may want to use closestPointOnPolyBoundary instead
254 if (dtStatusSucceed(_navMeshQuery->closestPointOnPoly(endPoly, endPoint, closestPoint, nullptr)))
255 {
256 dtVcopy(endPoint, closestPoint);
257 SetActualEndPosition(G3D::Vector3(endPoint[2], endPoint[0], endPoint[1]));
258 }
259
261
262 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
263 }
264 }
265
266 // *** poly path generating logic ***
267
268 // start and end are on same polygon
269 // handle this case as if they were 2 different polygons, building a line path split in some few points
270 if (startPoly == endPoly && !_useRaycast)
271 {
272 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: (startPoly == endPoly)");
273
274 _pathPolyRefs[0] = startPoly;
275 _polyLength = 1;
276
277 if (startFarFromPoly || endFarFromPoly)
278 {
280
281 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
282 }
283 else
285
286 BuildPointPath(startPoint, endPoint);
287 return;
288 }
289
290 // look for startPoly/endPoly in current path
292 bool startPolyFound = false;
293 bool endPolyFound = false;
294 uint32 pathStartIndex = 0;
295 uint32 pathEndIndex = 0;
296
297 if (_polyLength)
298 {
299 for (; pathStartIndex < _polyLength; ++pathStartIndex)
300 {
301 // here to catch few bugs
302 if (_pathPolyRefs[pathStartIndex] == INVALID_POLYREF)
303 {
304 TC_LOG_ERROR("maps.mmaps", "Invalid poly ref in BuildPolyPath. _polyLength: {}, pathStartIndex: {},"
305 " startPos: {}, endPos: {}, mapid: {}",
306 _polyLength, pathStartIndex, startPos.toString(), endPos.toString(),
307 _source->GetMapId());
308
309 break;
310 }
311
312 if (_pathPolyRefs[pathStartIndex] == startPoly)
313 {
314 startPolyFound = true;
315 break;
316 }
317 }
318
319 for (pathEndIndex = _polyLength-1; pathEndIndex > pathStartIndex; --pathEndIndex)
320 if (_pathPolyRefs[pathEndIndex] == endPoly)
321 {
322 endPolyFound = true;
323 break;
324 }
325 }
326
327 if (startPolyFound && endPolyFound)
328 {
329 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: (startPolyFound && endPolyFound)");
330
331 // we moved along the path and the target did not move out of our old poly-path
332 // our path is a simple subpath case, we have all the data we need
333 // just "cut" it out
334
335 _polyLength = pathEndIndex - pathStartIndex + 1;
336 memmove(_pathPolyRefs, _pathPolyRefs + pathStartIndex, _polyLength * sizeof(dtPolyRef));
337 }
338 else if (startPolyFound && !endPolyFound)
339 {
340 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: (startPolyFound && !endPolyFound)");
341
342 // we are moving on the old path but target moved out
343 // so we have atleast part of poly-path ready
344
345 _polyLength -= pathStartIndex;
346
347 // try to adjust the suffix of the path instead of recalculating entire length
348 // at given interval the target cannot get too far from its last location
349 // thus we have less poly to cover
350 // sub-path of optimal path is optimal
351
352 // take ~80% of the original length
354 uint32 prefixPolyLength = uint32(_polyLength * 0.8f + 0.5f);
355 memmove(_pathPolyRefs, _pathPolyRefs+pathStartIndex, prefixPolyLength * sizeof(dtPolyRef));
356
357 dtPolyRef suffixStartPoly = _pathPolyRefs[prefixPolyLength-1];
358
359 // we need any point on our suffix start poly to generate poly-path, so we need last poly in prefix data
360 float suffixEndPoint[VERTEX_SIZE];
361 if (dtStatusFailed(_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, nullptr)))
362 {
363 // we can hit offmesh connection as last poly - closestPointOnPoly() don't like that
364 // try to recover by using prev polyref
365 --prefixPolyLength;
366 suffixStartPoly = _pathPolyRefs[prefixPolyLength-1];
367 if (dtStatusFailed(_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, nullptr)))
368 {
369 // suffixStartPoly is still invalid, error state
372 return;
373 }
374 }
375
376 // generate suffix
377 uint32 suffixPolyLength = 0;
378
379 dtStatus dtResult;
380 if (_useRaycast)
381 {
382 TC_LOG_ERROR("maps.mmaps", "PathGenerator::BuildPolyPath() called with _useRaycast with a previous path for unit {}", _source->GetGUID().ToString());
385 return;
386 }
387 else
388 {
389 dtResult = _navMeshQuery->findPath(
390 suffixStartPoly, // start polygon
391 endPoly, // end polygon
392 suffixEndPoint, // start position
393 endPoint, // end position
394 &_filter, // polygon search filter
395 _pathPolyRefs + prefixPolyLength - 1, // [out] path
396 (int*)&suffixPolyLength,
397 MAX_PATH_LENGTH - prefixPolyLength); // max number of polygons in output path
398 }
399
400 if (!suffixPolyLength || dtStatusFailed(dtResult))
401 {
402 // this is probably an error state, but we'll leave it
403 // and hopefully recover on the next Update
404 // we still need to copy our preffix
405 TC_LOG_ERROR("maps.mmaps", "Path Build failed\n{}", _source->GetDebugInfo());
406 }
407
408 TC_LOG_DEBUG("maps.mmaps", "++ m_polyLength={} prefixPolyLength={} suffixPolyLength={}", _polyLength, prefixPolyLength, suffixPolyLength);
409
410 // new path = prefix + suffix - overlap
411 _polyLength = prefixPolyLength + suffixPolyLength - 1;
412 }
413 else
414 {
415 TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: (!startPolyFound && !endPolyFound)");
416
417 // either we have no path at all -> first run
418 // or something went really wrong -> we aren't moving along the path to the target
419 // just generate new path
420
421 // free and invalidate old path data
422 Clear();
423
424 dtStatus dtResult;
425 if (_useRaycast)
426 {
427 float hit = 0;
428 float hitNormal[3];
429 memset(hitNormal, 0, sizeof(hitNormal));
430
431 dtResult = _navMeshQuery->raycast(
432 startPoly,
433 startPoint,
434 endPoint,
435 &_filter,
436 &hit,
437 hitNormal,
439 (int*)&_polyLength,
441
442 if (!_polyLength || dtStatusFailed(dtResult))
443 {
446 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
447 return;
448 }
449
450 // raycast() sets hit to FLT_MAX if there is a ray between start and end
451 if (hit != FLT_MAX)
452 {
453 float hitPos[3];
454
455 // 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)
456 hit *= 0.99f;
457 dtVlerp(hitPos, startPoint, endPoint, hit);
458
459 // if it fails again, clamp to poly boundary
460 if (dtStatusFailed(_navMeshQuery->getPolyHeight(_pathPolyRefs[_polyLength - 1], hitPos, &hitPos[1])))
461 _navMeshQuery->closestPointOnPolyBoundary(_pathPolyRefs[_polyLength - 1], hitPos, hitPos);
462
463 _pathPoints.resize(2);
465 _pathPoints[1] = G3D::Vector3(hitPos[2], hitPos[0], hitPos[1]);
466
469 AddFarFromPolyFlags(startFarFromPoly, false);
470 return;
471 }
472 else
473 {
474 // clamp to poly boundary if we fail to get the height
475 if (dtStatusFailed(_navMeshQuery->getPolyHeight(_pathPolyRefs[_polyLength - 1], endPoint, &endPoint[1])))
476 _navMeshQuery->closestPointOnPolyBoundary(_pathPolyRefs[_polyLength - 1], endPoint, endPoint);
477
478 _pathPoints.resize(2);
480 _pathPoints[1] = G3D::Vector3(endPoint[2], endPoint[0], endPoint[1]);
481
483 if (startFarFromPoly || endFarFromPoly)
484 {
486
487 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
488 }
489 else
491 return;
492 }
493 }
494 else
495 {
496 dtResult = _navMeshQuery->findPath(
497 startPoly, // start polygon
498 endPoly, // end polygon
499 startPoint, // start position
500 endPoint, // end position
501 &_filter, // polygon search filter
502 _pathPolyRefs, // [out] path
503 (int*)&_polyLength,
504 MAX_PATH_LENGTH); // max number of polygons in output path
505 }
506
507 if (!_polyLength || dtStatusFailed(dtResult))
508 {
509 // only happens if we passed bad data to findPath(), or navmesh is messed up
510 TC_LOG_ERROR("maps.mmaps", "{} Path Build failed: 0 length path", _source->GetGUID().ToString());
513 return;
514 }
515 }
516
517 // by now we know what type of path we can get
518 if (_pathPolyRefs[_polyLength - 1] == endPoly && !(_type & PATHFIND_INCOMPLETE))
520 else
522
523 AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly);
524
525 // generate the point-path out of our up-to-date poly-path
526 BuildPointPath(startPoint, endPoint);
527}
528
529void PathGenerator::BuildPointPath(const float *startPoint, const float *endPoint)
530{
531 float pathPoints[MAX_POINT_PATH_LENGTH*VERTEX_SIZE];
532 uint32 pointCount = 0;
533 dtStatus dtResult = DT_FAILURE;
534 if (_useRaycast)
535 {
536 // _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
537 TC_LOG_ERROR("maps.mmaps", "PathGenerator::BuildPointPath() called with _useRaycast for unit {}", _source->GetGUID().ToString());
540 return;
541 }
542 else if (_useStraightPath)
543 {
544 dtResult = _navMeshQuery->findStraightPath(
545 startPoint, // start position
546 endPoint, // end position
547 _pathPolyRefs, // current path
548 _polyLength, // lenth of current path
549 pathPoints, // [out] path corner points
550 nullptr, // [out] flags
551 nullptr, // [out] shortened path
552 (int*)&pointCount,
553 _pointPathLimit); // maximum number of points/polygons to use
554 }
555 else
556 {
557 dtResult = FindSmoothPath(
558 startPoint, // start position
559 endPoint, // end position
560 _pathPolyRefs, // current path
561 _polyLength, // length of current path
562 pathPoints, // [out] path corner points
563 (int*)&pointCount,
564 _pointPathLimit); // maximum number of points
565 }
566
567 // Special case with start and end positions very close to each other
568 if (_polyLength == 1 && pointCount == 1)
569 {
570 // First point is start position, append end position
571 dtVcopy(&pathPoints[1 * VERTEX_SIZE], endPoint);
572 pointCount++;
573 }
574 else if ( pointCount < 2 || dtStatusFailed(dtResult))
575 {
576 // only happens if pass bad data to findStraightPath or navmesh is broken
577 // single point paths can be generated here
579 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::BuildPointPath FAILED! path sized {} returned\n", pointCount);
582 return;
583 }
584 else if (pointCount >= _pointPathLimit)
585 {
586 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::BuildPointPath FAILED! path sized {} returned, lower than limit set to {}", pointCount, _pointPathLimit);
589 return;
590 }
591
592 _pathPoints.resize(pointCount);
593 for (uint32 i = 0; i < pointCount; ++i)
594 _pathPoints[i] = G3D::Vector3(pathPoints[i*VERTEX_SIZE+2], pathPoints[i*VERTEX_SIZE], pathPoints[i*VERTEX_SIZE+1]);
595
597
598 // first point is always our current location - we need the next one
599 SetActualEndPosition(_pathPoints[pointCount-1]);
600
601 // force the given destination, if needed
602 if (_forceDestination &&
604 {
605 // we may want to keep partial subpath
607 {
610 }
611 else
612 {
615 }
616
618 }
619
620 TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::BuildPointPath path type {} size {} poly-size {}", _type, pointCount, _polyLength);
621}
622
624{
625 for (uint32 i = 0; i < _pathPoints.size(); ++i)
627}
628
630{
631 TC_LOG_DEBUG("maps.mmaps", "++ BuildShortcut :: making shortcut");
632
633 Clear();
634
635 // make two point path, our curr pos is the start, and dest is the end
636 _pathPoints.resize(2);
637
638 // set start and a default next position
641
643
645}
646
648{
649 uint16 includeFlags = 0;
650 uint16 excludeFlags = 0;
651
652 if (_source->GetTypeId() == TYPEID_UNIT)
653 {
654 Creature* creature = (Creature*)_source;
655 if (creature->CanWalk())
656 includeFlags |= NAV_GROUND; // walk
657
658 // creatures don't take environmental damage
659 if (creature->CanEnterWater())
660 includeFlags |= (NAV_WATER | NAV_MAGMA_SLIME); // swim
661 }
662 else // assume Player
663 {
664 // perfect support not possible, just stay 'safe'
665 includeFlags |= (NAV_GROUND | NAV_WATER | NAV_MAGMA_SLIME);
666 }
667
668 _filter.setIncludeFlags(includeFlags);
669 _filter.setExcludeFlags(excludeFlags);
670
671 UpdateFilter();
672}
673
675{
676 // allow creatures to cheat and use different movement types if they are moved
677 // forcefully into terrain they can't normally move in
678 if (Unit const* _sourceUnit = _source->ToUnit())
679 {
680 if (_sourceUnit->IsInWater() || _sourceUnit->IsUnderWater())
681 {
682 uint16 includedFlags = _filter.getIncludeFlags();
683 includedFlags |= GetNavTerrain(_source->GetPositionX(),
686
687 _filter.setIncludeFlags(includedFlags);
688 }
689
690 if (Creature const* _sourceCreature = _source->ToCreature())
691 if (_sourceCreature->IsInCombat() || _sourceCreature->IsInEvadeMode())
692 _filter.setIncludeFlags(_filter.getIncludeFlags() | NAV_GROUND_STEEP);
693 }
694}
695
697{
698 LiquidData data;
699 ZLiquidStatus liquidStatus = _source->GetMap()->GetLiquidStatus(_source->GetPhaseMask(), x, y, z, {}, &data, _source->GetCollisionHeight());
700 if (liquidStatus == LIQUID_MAP_NO_WATER)
701 return NAV_GROUND;
702
703 switch (data.type_flags)
704 {
707 return NAV_WATER;
710 return NAV_MAGMA_SLIME;
711 default:
712 return NAV_GROUND;
713 }
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
969void PathGenerator::ShortenPathUntilDist(G3D::Vector3 const& target, float dist)
970{
971 if (GetPathType() == PATHFIND_BLANK || _pathPoints.size() < 2)
972 {
973 TC_LOG_ERROR("maps.mmaps", "PathGenerator::ReducePathLengthByDist called before path was successfully built");
974 return;
975 }
976
977 float const distSq = dist * dist;
978
979 // the first point of the path must be outside the specified range
980 // (this should have really been checked by the caller...)
981 if ((_pathPoints[0] - target).squaredLength() < distSq)
982 return;
983
984 // check if we even need to do anything
985 if ((*_pathPoints.rbegin() - target).squaredLength() >= distSq)
986 return;
987
988 size_t i = _pathPoints.size()-1;
989 float x, y, z, collisionHeight = _source->GetCollisionHeight();
990 // find the first i s.t.:
991 // - _pathPoints[i] is still too close
992 // - _pathPoints[i-1] is too far away
993 // => the end point is somewhere on the line between the two
994 while (true)
995 {
996 // we know that pathPoints[i] is too close already (from the previous iteration)
997 if ((_pathPoints[i-1] - target).squaredLength() >= distSq)
998 break; // bingo!
999
1000 // check if the shortened path is still in LoS with the target
1001 _source->GetHitSpherePointFor({ _pathPoints[i - 1].x, _pathPoints[i - 1].y, _pathPoints[i - 1].z + collisionHeight }, x, y, z);
1003 {
1004 // whenver we find a point that is not in LoS anymore, simply use last valid path
1005 _pathPoints.resize(i + 1);
1006 return;
1007 }
1008
1009 if (!--i)
1010 {
1011 // no point found that fulfills the condition
1012 _pathPoints[0] = _pathPoints[1];
1013 _pathPoints.resize(2);
1014 return;
1015 }
1016 }
1017
1018 // ok, _pathPoints[i] is too close, _pathPoints[i-1] is not, so our target point is somewhere between the two...
1019 // ... settle for a guesstimate since i'm not confident in doing trig on every chase motion tick...
1020 // (@todo review this)
1021 _pathPoints[i] += (_pathPoints[i - 1] - _pathPoints[i]).direction() * (dist - (_pathPoints[i] - target).length());
1022 _pathPoints.resize(i+1);
1023}
1024
1026{
1027 return (target->GetPositionZ() - GetActualEndPosition().z) > 5.0f;
1028}
1029
1030void PathGenerator::AddFarFromPolyFlags(bool startFarFromPoly, bool endFarFromPoly)
1031{
1032 if (startFarFromPoly)
1034 if (endFarFromPoly)
1036}
int32_t int32
Definition Define.h:129
uint16_t uint16
Definition Define.h:134
uint32_t uint32
Definition Define.h:133
#define TC_LOG_DEBUG(filterType__,...)
Definition Log.h:156
#define TC_LOG_ERROR(filterType__,...)
Definition Log.h:165
NavTerrainFlag
Definition MapDefines.h:65
@ NAV_GROUND_STEEP
Definition MapDefines.h:68
@ NAV_GROUND
Definition MapDefines.h:67
@ NAV_WATER
Definition MapDefines.h:69
@ NAV_MAGMA_SLIME
Definition MapDefines.h:70
ZLiquidStatus
Definition MapDefines.h:74
@ LIQUID_MAP_NO_WATER
Definition MapDefines.h:75
#define MAP_LIQUID_TYPE_MAGMA
Definition Map.h:147
#define MAP_LIQUID_TYPE_WATER
Definition Map.h:145
#define MAP_LIQUID_TYPE_OCEAN
Definition Map.h:146
#define MAP_LIQUID_TYPE_SLIME
Definition Map.h:148
#define TC_METRIC_DETAILED_EVENT(category, title, description)
Definition Metric.h:229
@ TYPEID_UNIT
Definition ObjectGuid.h:38
#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:248
uint32 const pathSize
bool CanSwim() const override
bool CanWalk() const
Definition Creature.h:105
bool CanFly() const override
Definition Creature.h:108
bool CanEnterWater() const override
static MMapManager * createOrGetMMapManager()
dtNavMeshQuery const * GetNavMeshQuery(uint32 mapId, uint32 instanceId)
dtNavMesh const * GetNavMesh(uint32 mapId)
bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask, LineOfSightChecks checks, VMAP::ModelIgnoreFlags ignoreFlags) const
Definition Map.cpp:2870
ZLiquidStatus GetLiquidStatus(uint32 phaseMask, float x, float y, float z, Optional< uint8 > ReqLiquidType, LiquidData *data=nullptr, float collisionHeight=2.03128f) const
Definition Map.cpp:2635
bool IsUnderWater(uint32 phaseMask, float x, float y, float z) const
Definition Map.cpp:2902
std::string ToString() const
static Creature * ToCreature(Object *o)
Definition Object.h:186
static Unit * ToUnit(Object *o)
Definition Object.h:192
TypeID GetTypeId() const
Definition Object.h:93
static ObjectGuid GetGUID(Object const *o)
Definition Object.h:78
NavTerrainFlag GetNavTerrain(float x, float y, float z)
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
void ShortenPathUntilDist(G3D::Vector3 const &point, float dist)
dtQueryFilter _filter
float Dist3DSqr(G3D::Vector3 const &p1, G3D::Vector3 const &p2) const
PathType GetPathType() const
uint32 _pointPathLimit
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)
dtNavMeshQuery const * _navMeshQuery
WorldObject const *const _source
G3D::Vector3 const & GetEndPosition() const
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(Unit 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 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)
G3D::Vector3 const & GetActualEndPosition() const
Definition Unit.h:769
bool HasUnitState(const uint32 f) const
Definition Unit.h:876
uint32 GetMapId() const
Definition Position.h:193
uint32 GetPhaseMask() const
Definition Object.h:368
Map * GetMap() const
Definition Object.h:449
void UpdateAllowedPositionZ(float x, float y, float &z, float *groundZ=nullptr) const
Definition Object.cpp:1416
virtual float GetCollisionHeight() const
Definition Object.h:583
Position GetHitSpherePointFor(Position const &dest) const
Definition Object.cpp:1197
std::string GetDebugInfo() const override
Definition Object.cpp:3614
uint32 GetInstanceId() const
Definition Object.h:365
bool IsPathfindingEnabled(uint32 mapId)
bool IsValidMapCoord(float c)
uint32 type_flags
Definition MapDefines.h:87
float GetPositionZ() const
Definition Position.h:81
float GetPositionX() const
Definition Position.h:79
void GetPosition(float &x, float &y) const
Definition Position.h:84
float GetPositionY() const
Definition Position.h:80