/** * MaNGOS is a full featured server for World of Warcraft, supporting * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * * Copyright (C) 2005-2022 MaNGOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * World of Warcraft, and all World of Warcraft or Warcraft art, images, * and lore are copyrighted by Blizzard Entertainment, Inc. */ #include "../recastnavigation/Detour/Include/DetourCommon.h" #include "MoveMap.h" #include "GridMap.h" #include "Creature.h" #include "Map.h" #include "PathFinder.h" #include "Log.h" ////////////////// PathFinder ////////////////// PathFinder::PathFinder(const Unit* owner) : m_polyLength(0), m_type(PATHFIND_BLANK), m_useStraightPath(false), m_forceDestination(false), m_pointPathLimit(MAX_POINT_PATH_LENGTH), m_sourceUnit(owner), m_navMesh(NULL), m_navMeshQuery(NULL) { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::PathFinder for %s \n", m_sourceUnit->GetGuidStr().c_str()); memset(m_pathPolyRefs, 0, sizeof(m_pathPolyRefs)); uint32 mapId = m_sourceUnit->GetMapId(); if (MMAP::MMapFactory::IsPathfindingEnabled(mapId, owner)) { MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); m_navMesh = mmap->GetNavMesh(mapId); m_navMeshQuery = mmap->GetNavMeshQuery(mapId, m_sourceUnit->GetInstanceId()); } createFilter(); } PathFinder::~PathFinder() { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::~PathFinder() for %s \n", m_sourceUnit->GetGuidStr().c_str()); } bool PathFinder::calculate(float destX, float destY, float destZ, bool forceDest) { float x, y, z; m_sourceUnit->GetPosition(x, y, z); if (!MaNGOS::IsValidMapCoord(x,y,z) || !MaNGOS::IsValidMapCoord(destX, destY, destZ)) { return false; } Vector3 start(x, y, z); setStartPosition(start); Vector3 dest(destX, destY, destZ); setEndPosition(dest); m_forceDestination = forceDest; DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::calculate() for %s \n", m_sourceUnit->GetGuidStr().c_str()); // make sure navMesh works - we can run on map w/o mmap // check if the start and end point have a .mmtile loaded (can we pass via not loaded tile on the way?) if (!m_navMesh || !m_navMeshQuery || m_sourceUnit->hasUnitState(UNIT_STAT_IGNORE_PATHFINDING) || !HaveTile(start) || !HaveTile(dest)) { BuildShortcut(); m_type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); return true; } updateFilter(); BuildPolyPath(start, dest); return true; } dtPolyRef PathFinder::getPathPolyByPosition(const dtPolyRef* polyPath, uint32 polyPathSize, const float* point, float* distance) const { if (!polyPath || !polyPathSize) { return INVALID_POLYREF; } dtPolyRef nearestPoly = INVALID_POLYREF; float minDist2d = FLT_MAX; float minDist3d = 0.0f; for (uint32 i = 0; i < polyPathSize; ++i) { float closestPoint[VERTEX_SIZE]; if (dtStatusFailed(m_navMeshQuery->closestPointOnPoly(polyPath[i], point, closestPoint, NULL))) { continue; } float d = dtVdist2DSqr(point, closestPoint); if (d < minDist2d) { minDist2d = d; nearestPoly = polyPath[i]; minDist3d = dtVdistSqr(point, closestPoint); } if (minDist2d < 1.0f) // shortcut out - close enough for us { break; } } if (distance) { *distance = dtMathSqrtf(minDist3d); } return (minDist2d < 3.0f) ? nearestPoly : INVALID_POLYREF; } dtPolyRef PathFinder::getPolyByLocation(const float* point, float* distance) const { // first we check the current path // if the current path doesn't contain the current poly, // we need to use the expensive navMesh.findNearestPoly dtPolyRef polyRef = getPathPolyByPosition(m_pathPolyRefs, m_polyLength, point, distance); if (polyRef != INVALID_POLYREF) { return polyRef; } // we don't have it in our old path // try to get it by findNearestPoly() // first try with low search box float extents[VERTEX_SIZE] = {3.0f, 5.0f, 3.0f}; // bounds of poly search area float closestPoint[VERTEX_SIZE] = {0.0f, 0.0f, 0.0f}; if (dtStatusSucceed(m_navMeshQuery->findNearestPoly(point, extents, &m_filter, &polyRef, closestPoint)) && polyRef != INVALID_POLYREF) { *distance = dtVdist(closestPoint, point); return polyRef; } // still nothing .. // try with bigger search box extents[1] = 200.0f; if (dtStatusSucceed(m_navMeshQuery->findNearestPoly(point, extents, &m_filter, &polyRef, closestPoint)) && polyRef != INVALID_POLYREF) { *distance = dtVdist(closestPoint, point); return polyRef; } return INVALID_POLYREF; } void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) { // *** getting start/end poly logic *** float distToStartPoly, distToEndPoly; float startPoint[VERTEX_SIZE] = {startPos.y, startPos.z, startPos.x}; float endPoint[VERTEX_SIZE] = {endPos.y, endPos.z, endPos.x}; dtPolyRef startPoly = getPolyByLocation(startPoint, &distToStartPoly); dtPolyRef endPoly = getPolyByLocation(endPoint, &distToEndPoly); dtStatus dtResult; // we have a hole in our mesh // make shortcut path and mark it as NOPATH ( with flying exception ) // its up to caller how he will use this info if (startPoly == INVALID_POLYREF || endPoly == INVALID_POLYREF) { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: (startPoly == 0 || endPoly == 0) for %s\n", m_sourceUnit->GetGuidStr().c_str()); BuildShortcut(); if (m_sourceUnit->GetTypeId() == TYPEID_UNIT) { // Check for swimming or flying shortcut if ((startPoly == INVALID_POLYREF && m_sourceUnit->GetMap()->GetTerrain()->IsUnderWater(startPos.x, startPos.y, startPos.z)) || (endPoly == INVALID_POLYREF && m_sourceUnit->GetMap()->GetTerrain()->IsUnderWater(endPos.x, endPos.y, endPos.z))) { m_type = m_sourceUnit->ToCreature()->CanSwim() ? PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH) : PATHFIND_NOPATH; } else { m_type = m_sourceUnit->ToCreature()->CanFly() ? PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH) : PATHFIND_NOPATH; } } else { m_type = PATHFIND_NOPATH; } return; } // we may need a better number here bool farFromPoly = (distToStartPoly > 7.0f || distToEndPoly > 7.0f); if (farFromPoly) { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: farFromPoly distToStartPoly=%.3f distToEndPoly=%.3f for %s\n", distToStartPoly, distToEndPoly, m_sourceUnit->GetGuidStr().c_str()); bool buildShotrcut = false; if (m_sourceUnit->GetTypeId() == TYPEID_UNIT) { const Creature* owner = m_sourceUnit->ToCreature(); Vector3 p = (distToStartPoly > 7.0f) ? startPos : endPos; if (m_sourceUnit->GetMap()->GetTerrain()->IsUnderWater(p.x, p.y, p.z)) { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: underWater case for %s\n", m_sourceUnit->GetGuidStr().c_str()); if (owner->CanSwim()) { buildShotrcut = true; } } else { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: flying case for %s\n", m_sourceUnit->GetGuidStr().c_str()); if (owner->CanFly()) { buildShotrcut = true; } } } if (buildShotrcut) { BuildShortcut(); m_type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); return; } else { float closestPoint[VERTEX_SIZE]; // we may want to use closestPointOnPolyBoundary instead dtResult = m_navMeshQuery->closestPointOnPoly(endPoly, endPoint, closestPoint, NULL); if (dtStatusSucceed(dtResult)) { dtVcopy(endPoint, closestPoint); setActualEndPosition(Vector3(endPoint[2], endPoint[0], endPoint[1])); } m_type = PATHFIND_INCOMPLETE; } } // *** poly path generating logic *** // start and end are on same polygon // just need to move in straight line if (startPoly == endPoly) { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: (startPoly == endPoly) for %s\n", m_sourceUnit->GetGuidStr().c_str()); BuildShortcut(); m_pathPolyRefs[0] = startPoly; m_polyLength = 1; m_type = farFromPoly ? PATHFIND_INCOMPLETE : PATHFIND_NORMAL; DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: path type %d for %s\n", m_type, m_sourceUnit->GetGuidStr().c_str()); return; } // look for startPoly/endPoly in current path // TODO: we can merge it with getPathPolyByPosition() loop bool startPolyFound = false; bool endPolyFound = false; uint32 pathStartIndex, pathEndIndex; if (m_polyLength) { for (pathStartIndex = 0; pathStartIndex < m_polyLength; ++pathStartIndex) { // here to catch few bugs MANGOS_ASSERT(m_pathPolyRefs[pathStartIndex] != INVALID_POLYREF || m_sourceUnit->PrintEntryError("PathFinder::BuildPolyPath")); if (m_pathPolyRefs[pathStartIndex] == startPoly) { startPolyFound = true; break; } } for (pathEndIndex = m_polyLength - 1; pathEndIndex > pathStartIndex; --pathEndIndex) if (m_pathPolyRefs[pathEndIndex] == endPoly) { endPolyFound = true; break; } } if (startPolyFound && endPolyFound) { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: (startPolyFound && endPolyFound) for %s\n", m_sourceUnit->GetGuidStr().c_str()); // we moved along the path and the target did not move out of our old poly-path // our path is a simple subpath case, we have all the data we need // just "cut" it out m_polyLength = pathEndIndex - pathStartIndex + 1; memmove(m_pathPolyRefs, m_pathPolyRefs + pathStartIndex, m_polyLength * sizeof(dtPolyRef)); } else if (startPolyFound && !endPolyFound) { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: (startPolyFound && !endPolyFound) for %s\n", m_sourceUnit->GetGuidStr().c_str()); // we are moving on the old path but target moved out // so we have atleast part of poly-path ready m_polyLength -= pathStartIndex; // try to adjust the suffix of the path instead of recalculating entire length // at given interval the target can not get too far from its last location // thus we have less poly to cover // sub-path of optimal path is optimal // take ~80% of the original length // TODO : play with the values here uint32 prefixPolyLength = uint32(m_polyLength * 0.8f + 0.5f); memmove(m_pathPolyRefs, m_pathPolyRefs + pathStartIndex, prefixPolyLength * sizeof(dtPolyRef)); dtPolyRef suffixStartPoly = m_pathPolyRefs[prefixPolyLength - 1]; // we need any point on our suffix start poly to generate poly-path, so we need last poly in prefix data float suffixEndPoint[VERTEX_SIZE]; dtResult = m_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, NULL); if (dtStatusFailed(dtResult)) { // we can hit offmesh connection as last poly - closestPointOnPoly() don't like that // try to recover by using prev polyref --prefixPolyLength; suffixStartPoly = m_pathPolyRefs[prefixPolyLength - 1]; dtResult = m_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, NULL); if (dtStatusFailed(dtResult)) { // suffixStartPoly is still invalid, error state BuildShortcut(); m_type = PATHFIND_NOPATH; return; } } // generate suffix uint32 suffixPolyLength = 0; dtResult = m_navMeshQuery->findPath( suffixStartPoly, // start polygon endPoly, // end polygon suffixEndPoint, // start position endPoint, // end position &m_filter, // polygon search filter m_pathPolyRefs + prefixPolyLength - 1, // [out] path (int*)&suffixPolyLength, MAX_PATH_LENGTH - prefixPolyLength); // max number of polygons in output path if (!suffixPolyLength || dtStatusFailed(dtResult)) { // this is probably an error state, but we'll leave it // and hopefully recover on the next Update // we still need to copy our preffix sLog.outError("%u's Path Build failed: 0 length path", m_sourceUnit->GetGUIDLow()); } DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ m_polyLength=%u prefixPolyLength=%u suffixPolyLength=%u for %s\n", m_polyLength, prefixPolyLength, suffixPolyLength, m_sourceUnit->GetGuidStr().c_str()); // new path = prefix + suffix - overlap m_polyLength = prefixPolyLength + suffixPolyLength - 1; } else { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: (!startPolyFound && !endPolyFound) for %s\n", m_sourceUnit->GetGuidStr().c_str()); // either we have no path at all -> first run // or something went really wrong -> we aren't moving along the path to the target // just generate new path // free and invalidate old path data clear(); dtResult = m_navMeshQuery->findPath( startPoly, // start polygon endPoly, // end polygon startPoint, // start position endPoint, // end position &m_filter, // polygon search filter m_pathPolyRefs, // [out] path (int*)&m_polyLength, MAX_PATH_LENGTH); // max number of polygons in output path if (!m_polyLength || dtStatusFailed(dtResult)) { // only happens if we passed bad data to findPath(), or navmesh is messed up sLog.outError("Path Build failed: 0 length path for %s", m_sourceUnit->GetGuidStr().c_str()); BuildShortcut(); m_type = PATHFIND_NOPATH; return; } } // by now we know what type of path we can get if (m_pathPolyRefs[m_polyLength - 1] == endPoly && !(m_type & PATHFIND_INCOMPLETE)) { m_type = PATHFIND_NORMAL; } else { m_type = PATHFIND_INCOMPLETE; } // generate the point-path out of our up-to-date poly-path BuildPointPath(startPoint, endPoint); } void PathFinder::BuildPointPath(const float* startPoint, const float* endPoint) { float pathPoints[MAX_POINT_PATH_LENGTH * VERTEX_SIZE]; uint32 pointCount = 0; dtStatus dtResult; if (m_useStraightPath) { dtResult = m_navMeshQuery->findStraightPath( startPoint, // start position endPoint, // end position m_pathPolyRefs, // current path m_polyLength, // lenth of current path pathPoints, // [out] path corner points NULL, // [out] flags NULL, // [out] shortened path (int*)&pointCount, m_pointPathLimit); // maximum number of points/polygons to use } else { dtResult = findSmoothPath( startPoint, // start position endPoint, // end position m_pathPolyRefs, // current path m_polyLength, // length of current path pathPoints, // [out] path corner points (int*)&pointCount, m_pointPathLimit); // maximum number of points } if (pointCount < 2 || dtStatusFailed(dtResult)) { // only happens if pass bad data to findStraightPath or navmesh is broken // single point paths can be generated here // TODO : check the exact cases DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::BuildPointPath FAILED! path sized %d returned for %s\n", pointCount, m_sourceUnit->GetGuidStr().c_str()); BuildShortcut(); m_type = PATHFIND_NOPATH; return; } m_pathPoints.resize(pointCount); for (uint32 i = 0; i < pointCount; ++i) { m_pathPoints[i] = Vector3(pathPoints[i * VERTEX_SIZE + 2], pathPoints[i * VERTEX_SIZE], pathPoints[i * VERTEX_SIZE + 1]); } // first point is always our current location - we need the next one setActualEndPosition(m_pathPoints[pointCount - 1]); // force the given destination, if needed if (m_forceDestination && (!(m_type & PATHFIND_NORMAL) || !inRange(getEndPosition(), getActualEndPosition(), 1.0f, 1.0f))) { // we may want to keep partial subpath if (dist3DSqr(getActualEndPosition(), getEndPosition()) < 0.3f * dist3DSqr(getStartPosition(), getEndPosition())) { setActualEndPosition(getEndPosition()); m_pathPoints[m_pathPoints.size() - 1] = getEndPosition(); } else { setActualEndPosition(getEndPosition()); BuildShortcut(); } m_type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); } DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::BuildPointPath path type %d size %d poly-size %d for %s\n", m_type, pointCount, m_polyLength, m_sourceUnit->GetGuidStr().c_str()); } void PathFinder::BuildShortcut() { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::BuildShortcut :: making shortcut for %s\n", m_sourceUnit->GetGuidStr().c_str()); clear(); // make two point path, our curr pos is the start, and dest is the end uint32 size = 2; m_pathPoints.resize(size); // set start and a default next position m_pathPoints[0] = getStartPosition(); m_pathPoints[1] = getActualEndPosition(); m_type = PATHFIND_SHORTCUT; } void PathFinder::createFilter() { uint16 includeFlags = 0; uint16 excludeFlags = 0; if (m_sourceUnit->GetTypeId() == TYPEID_UNIT) { Creature* creature = (Creature*)m_sourceUnit; if (creature->CanWalk()) { includeFlags |= NAV_GROUND; // walk } // creatures don't take environmental damage if (creature->CanSwim()) { includeFlags |= (NAV_WATER | NAV_MAGMA | NAV_SLIME); // swim } } else if (m_sourceUnit->GetTypeId() == TYPEID_PLAYER) { // perfect support not possible, just stay 'safe' includeFlags |= (NAV_GROUND | NAV_WATER); } m_filter.setIncludeFlags(includeFlags); m_filter.setExcludeFlags(excludeFlags); updateFilter(); } void PathFinder::updateFilter() { // allow creatures to cheat and use different movement types if they are moved // forcefully into terrain they can't normally move in if (m_sourceUnit->IsInWater() || m_sourceUnit->IsUnderWater()) { uint16 includedFlags = m_filter.getIncludeFlags(); includedFlags |= getNavTerrain(m_sourceUnit->GetPositionX(), m_sourceUnit->GetPositionY(), m_sourceUnit->GetPositionZ()); m_filter.setIncludeFlags(includedFlags); } } NavTerrain PathFinder::getNavTerrain(float x, float y, float z) { GridMapLiquidData data; m_sourceUnit->GetMap()->GetTerrain()->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &data); switch (data.type_flags) { case MAP_LIQUID_TYPE_WATER: case MAP_LIQUID_TYPE_OCEAN: return NAV_WATER; case MAP_LIQUID_TYPE_MAGMA: return NAV_MAGMA; case MAP_LIQUID_TYPE_SLIME: return NAV_SLIME; default: return NAV_GROUND; } } bool PathFinder::HaveTile(const Vector3& p) const { int tx, ty; float point[VERTEX_SIZE] = {p.y, p.z, p.x}; m_navMesh->calcTileLoc(point, &tx, &ty); return (m_navMesh->getTileAt(tx, ty, 0) != NULL); } uint32 PathFinder::fixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPath, const dtPolyRef* visited, uint32 nvisited) { int32 furthestPath = -1; int32 furthestVisited = -1; // Find furthest common polygon. for (int32 i = npath - 1; i >= 0; --i) { bool found = false; for (int32 j = nvisited - 1; j >= 0; --j) { if (path[i] == visited[j]) { furthestPath = i; furthestVisited = j; found = true; } } if (found) { break; } } // If no intersection found just return current path. if (furthestPath == -1 || furthestVisited == -1) { return npath; } // Concatenate paths. // Adjust beginning of the buffer to include the visited. uint32 req = nvisited - furthestVisited; uint32 orig = uint32(furthestPath + 1) < npath ? furthestPath + 1 : npath; uint32 size = npath > orig ? npath - orig : 0; if (req + size > maxPath) { size = maxPath - req; } if (size) { memmove(path + req, path + orig, size * sizeof(dtPolyRef)); } // Store visited for (uint32 i = 0; i < req; ++i) { path[i] = visited[(nvisited - 1) - i]; } return req + size; } bool PathFinder::getSteerTarget(const float* startPos, const float* endPos, float minTargetDist, const dtPolyRef* path, uint32 pathSize, float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef) { // Find steer target. static const uint32 MAX_STEER_POINTS = 3; float steerPath[MAX_STEER_POINTS * VERTEX_SIZE]; unsigned char steerPathFlags[MAX_STEER_POINTS]; dtPolyRef steerPathPolys[MAX_STEER_POINTS]; uint32 nsteerPath = 0; dtStatus dtResult = m_navMeshQuery->findStraightPath(startPos, endPos, path, pathSize, steerPath, steerPathFlags, steerPathPolys, (int*)&nsteerPath, MAX_STEER_POINTS); if (!nsteerPath || dtStatusFailed(dtResult)) { return false; } // Find vertex far enough to steer to. uint32 ns = 0; while (ns < nsteerPath) { // Stop at Off-Mesh link or when point is further than slop away. if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) || !inRangeYZX(&steerPath[ns * VERTEX_SIZE], startPos, minTargetDist, 1000.0f)) { break; } ++ns; } // Failed to find good point to steer to. if (ns >= nsteerPath) { return false; } dtVcopy(steerPos, &steerPath[ns * VERTEX_SIZE]); steerPos[1] = startPos[1]; // keep Z value steerPosFlag = steerPathFlags[ns]; steerPosRef = steerPathPolys[ns]; return true; } dtStatus PathFinder::findSmoothPath(const float* startPos, const float* endPos, const dtPolyRef* polyPath, uint32 polyPathSize, float* smoothPath, int* smoothPathSize, uint32 maxSmoothPathSize) { *smoothPathSize = 0; uint32 nsmoothPath = 0; dtPolyRef polys[MAX_PATH_LENGTH]; memcpy(polys, polyPath, sizeof(dtPolyRef)*polyPathSize); uint32 npolys = polyPathSize; float iterPos[VERTEX_SIZE], targetPos[VERTEX_SIZE]; dtStatus dtResult = m_navMeshQuery->closestPointOnPolyBoundary(polys[0], startPos, iterPos); if (dtStatusFailed(dtResult)) { return DT_FAILURE; } dtResult = m_navMeshQuery->closestPointOnPolyBoundary(polys[npolys - 1], endPos, targetPos); if (dtStatusFailed(dtResult)) { return DT_FAILURE; } dtVcopy(&smoothPath[nsmoothPath * VERTEX_SIZE], iterPos); ++nsmoothPath; // Move towards target a small advancement at a time until target reached or // when ran out of memory to store the path. while (npolys && nsmoothPath < maxSmoothPathSize) { // Find location to steer towards. float steerPos[VERTEX_SIZE]; unsigned char steerPosFlag; dtPolyRef steerPosRef = INVALID_POLYREF; if (!getSteerTarget(iterPos, targetPos, SMOOTH_PATH_SLOP, polys, npolys, steerPos, steerPosFlag, steerPosRef)) { break; } bool endOfPath = (steerPosFlag & DT_STRAIGHTPATH_END); bool offMeshConnection = (steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION); // Find movement delta. float delta[VERTEX_SIZE]; dtVsub(delta, steerPos, iterPos); float len = dtMathSqrtf(dtVdot(delta, delta)); // If the steer target is end of path or off-mesh link, do not move past the location. if ((endOfPath || offMeshConnection) && len < SMOOTH_PATH_STEP_SIZE) { len = 1.0f; } else { len = SMOOTH_PATH_STEP_SIZE / len; } float moveTgt[VERTEX_SIZE]; dtVmad(moveTgt, iterPos, delta, len); // Move float result[VERTEX_SIZE]; const static uint32 MAX_VISIT_POLY = 16; dtPolyRef visited[MAX_VISIT_POLY]; uint32 nvisited = 0; m_navMeshQuery->moveAlongSurface(polys[0], iterPos, moveTgt, &m_filter, result, visited, (int*)&nvisited, MAX_VISIT_POLY); npolys = fixupCorridor(polys, npolys, MAX_PATH_LENGTH, visited, nvisited); m_navMeshQuery->getPolyHeight(polys[0], result, &result[1]); result[1] += 0.5f; dtVcopy(iterPos, result); // Handle end of path and off-mesh links when close enough. if (endOfPath && inRangeYZX(iterPos, steerPos, SMOOTH_PATH_SLOP, 1.0f)) { // Reached end of path. dtVcopy(iterPos, targetPos); if (nsmoothPath < maxSmoothPathSize) { dtVcopy(&smoothPath[nsmoothPath * VERTEX_SIZE], iterPos); ++nsmoothPath; } break; } else if (offMeshConnection && inRangeYZX(iterPos, steerPos, SMOOTH_PATH_SLOP, 1.0f)) { // Advance the path up to and over the off-mesh connection. dtPolyRef prevRef = INVALID_POLYREF; dtPolyRef polyRef = polys[0]; uint32 npos = 0; while (npos < npolys && polyRef != steerPosRef) { prevRef = polyRef; polyRef = polys[npos]; ++npos; } for (uint32 i = npos; i < npolys; ++i) { polys[i - npos] = polys[i]; } npolys -= npos; // Handle the connection. float newStartPos[VERTEX_SIZE], newEndPos[VERTEX_SIZE]; dtResult = m_navMesh->getOffMeshConnectionPolyEndPoints(prevRef, polyRef, newStartPos, newEndPos); if (dtStatusSucceed(dtResult)) { if (nsmoothPath < maxSmoothPathSize) { dtVcopy(&smoothPath[nsmoothPath * VERTEX_SIZE], newStartPos); ++nsmoothPath; } // Move position at the other side of the off-mesh link. dtVcopy(iterPos, newEndPos); m_navMeshQuery->getPolyHeight(polys[0], iterPos, &iterPos[1]); iterPos[1] += 0.5f; } } // Store results. if (nsmoothPath < maxSmoothPathSize) { dtVcopy(&smoothPath[nsmoothPath * VERTEX_SIZE], iterPos); ++nsmoothPath; } } *smoothPathSize = nsmoothPath; // this is most likely a loop return nsmoothPath < MAX_POINT_PATH_LENGTH ? DT_SUCCESS : DT_FAILURE; } bool PathFinder::inRangeYZX(const float* v1, const float* v2, float r, float h) const { const float dx = v2[0] - v1[0]; const float dy = v2[1] - v1[1]; // elevation const float dz = v2[2] - v1[2]; return (dx * dx + dz * dz) < r * r && fabsf(dy) < h; } bool PathFinder::inRange(const Vector3& p1, const Vector3& p2, float r, float h) const { Vector3 d = p1 - p2; return (d.x * d.x + d.y * d.y) < r * r && fabsf(d.z) < h; } float PathFinder::dist3DSqr(const Vector3& p1, const Vector3& p2) const { return (p1 - p2).squaredLength(); } void PathFinder::NormalizePath(uint32& size) { for (uint32 i = 0; i < m_pathPoints.size(); ++i) { m_sourceUnit->UpdateAllowedPositionZ(m_pathPoints[i].x, m_pathPoints[i].y, m_pathPoints[i].z); } // check if the Z difference between each point is higher than SMOOTH_PATH_HEIGHT. // add another point if that's the case and keep adding new midpoints till the Z difference is low enough for (uint32 i = 1; i < m_pathPoints.size(); ++i) { if ((m_pathPoints[i - 1].z - m_pathPoints[i].z) > SMOOTH_PATH_HEIGHT) { auto midPoint = m_pathPoints[i - 1] + (m_pathPoints[i] - m_pathPoints[i - 1]) / 2.f; m_sourceUnit->UpdateAllowedPositionZ(midPoint.x, midPoint.y, midPoint.z); m_pathPoints.insert(m_pathPoints.begin() + i, midPoint); --i; } } size = m_pathPoints.size(); }