From e3bab35d5e9e3af0de5a63368c7492ccbe43b5a0 Mon Sep 17 00:00:00 2001 From: Giacomo Pozzoni Date: Fri, 15 May 2020 18:43:12 +0000 Subject: [PATCH] Core/MMAPs: Adjust walkable climb and fix a lot of mmap raycast issues (#24539) * Core/MMAPs: Adjust walkable climb Adjust walkable climb as recast using walkableClimb also to find the poly from a position, giving priority to polys that are below the position but closer than walkableClimb. * Temporarily disable static collision (the whole check should be removed) * Core/Spells: removed deprecated mmap path check for TARGET_DEST_CASTER_FRONT_LEAP * Core/Objects: allow flying units to use the helper as well (flying units casting radius based spells) * Code refactor * Handle raycasts that end in a point with no height in the mmap mesh as PATHFIND_NOPATH * Walk back a bit from raycast hitpoints as sometime the 2D result point is outside of the polygons due to floating point errors. * Remove whitespace * Revert 4a197ba22af4eed01be632ea2dd7d103a963f043 as a raycast point path should have the Z retrieved with getPolyHeight(). Raycast will only return a 2-point path with Start and Hitpoint/End * Cleanup PathGenerator raycast case * Fix PathGenerator raycast broken if start and end are on same poly. Fix PathGenerator raycast broken if no wall is hit. Remove unused case of using raycast with an existing previous path (can be added back properly if needed). Remove forcing poly length to 2 when we actually have already the right number. * Use closestPointOnPolyBoundary on the second try of finding a point on poly for raycast. Note that in this case the mesh height is not used which might cause issues. The poly boundary height will be used instead. * Handle cases where getPolyHeight() fails because the point is on polygon border (and caused by floating point imprecision) * Add far from poly flags * Set PATHFIND_INCOMPLETE in raycast case if startFarFromPoly or endFarFromPoly * Fix blink close to walls with no valid polygon behind the wall * Require to re-extract mmaps Co-authored-by: Ovah # Conflicts: # src/server/game/Entities/Object/Object.cpp --- src/common/Collision/Maps/MapDefines.h | 2 +- src/server/game/Entities/Object/Object.cpp | 7 +- src/server/game/Movement/PathGenerator.cpp | 174 ++++++++++----------- src/server/game/Movement/PathGenerator.h | 7 +- src/server/game/Spells/Spell.cpp | 2 +- src/server/scripts/Commands/cs_mmaps.cpp | 11 +- src/tools/mmaps_generator/MapBuilder.cpp | 2 +- 7 files changed, 101 insertions(+), 104 deletions(-) diff --git a/src/common/Collision/Maps/MapDefines.h b/src/common/Collision/Maps/MapDefines.h index 4935e99125..db0035c05f 100644 --- a/src/common/Collision/Maps/MapDefines.h +++ b/src/common/Collision/Maps/MapDefines.h @@ -5,7 +5,7 @@ #include "DetourNavMesh.h" const uint32 MMAP_MAGIC = 0x4d4d4150; // 'MMAP' -#define MMAP_VERSION 9 +#define MMAP_VERSION 10 struct MmapTileHeader { diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index a62d6e713c..2fe57bff74 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2517,7 +2517,8 @@ void WorldObject::MovePositionToFirstCollision(Position &pos, float dist, float // Use a detour raycast to get our first collision point PathGenerator path(this); - path.CalculatePath(destx, desty, destz, false, true); + path.SetUseRaycast(true); + path.CalculatePath(destx, desty, destz, false); // We have a invalid path result. Skip further processing. if (path.GetPathType() & ~(PATHFIND_NORMAL | PATHFIND_SHORTCUT | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY_END | PATHFIND_NOT_USING_PATH)) @@ -2530,7 +2531,8 @@ void WorldObject::MovePositionToFirstCollision(Position &pos, float dist, float // Object is using a shortcut. Check static LOS float halfHeight = GetCollisionHeight() * 0.5f; - + bool col; + /* uint32 terrainMapId = PhasingHandler::GetTerrainMapId(GetPhaseShift(), GetMap(), pos.m_positionX, pos.m_positionY); bool col = VMAP::VMapFactory::createOrGetVMapManager()->getObjectHitPos(terrainMapId, pos.m_positionX, pos.m_positionY, pos.m_positionZ + halfHeight, @@ -2546,6 +2548,7 @@ void WorldObject::MovePositionToFirstCollision(Position &pos, float dist, float desty -= CONTACT_DISTANCE * std::sin(angle); dist = std::sqrt((pos.m_positionX - destx) * (pos.m_positionX - destx) + (pos.m_positionY - desty) * (pos.m_positionY - desty)); } + */ // check dynamic collision col = GetMap()->getObjectHitPos(GetPhaseShift(), diff --git a/src/server/game/Movement/PathGenerator.cpp b/src/server/game/Movement/PathGenerator.cpp index 798a77f49c..33000f7144 100644 --- a/src/server/game/Movement/PathGenerator.cpp +++ b/src/server/game/Movement/PathGenerator.cpp @@ -31,7 +31,7 @@ ////////////////// PathGenerator ////////////////// PathGenerator::PathGenerator(WorldObject const* owner) : _polyLength(0), _type(PATHFIND_BLANK), _useStraightPath(false), - _forceDestination(false), _pointPathLimit(MAX_POINT_PATH_LENGTH), _straightLine(false), + _forceDestination(false), _pointPathLimit(MAX_POINT_PATH_LENGTH), _useRaycast(false), _endPosition(G3D::Vector3::zero()), _source(owner), _navMesh(nullptr), _navMeshQuery(nullptr) { @@ -55,7 +55,7 @@ PathGenerator::~PathGenerator() TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::~PathGenerator() for %u", _source->GetGUID().GetCounter()); } -bool PathGenerator::CalculatePath(float destX, float destY, float destZ, bool forceDest, bool straightLine) +bool PathGenerator::CalculatePath(float destX, float destY, float destZ, bool forceDest) { float x, y, z; _source->GetPosition(x, y, z); @@ -72,7 +72,6 @@ bool PathGenerator::CalculatePath(float destX, float destY, float destZ, bool fo SetStartPosition(start); _forceDestination = forceDest; - _straightLine = straightLine; TC_LOG_DEBUG("maps.mmaps", "++ PathGenerator::CalculatePath() for %u", _source->GetGUID().GetCounter()); @@ -157,6 +156,7 @@ dtPolyRef PathGenerator::GetPolyByLocation(float const* point, float* distance) return polyRef; } + *distance = FLT_MAX; return INVALID_POLYREF; } @@ -171,6 +171,8 @@ void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 con dtPolyRef startPoly = GetPolyByLocation(startPoint, &distToStartPoly); dtPolyRef endPoly = GetPolyByLocation(endPoint, &distToEndPoly); + _type = PathType(PATHFIND_NORMAL); + // we have a hole in our mesh // make shortcut path and mark it as NOPATH ( with flying and swimming exception ) // its up to caller how he will use this info @@ -196,8 +198,18 @@ void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 con } } - _type = (path || waterPath) ? PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH) : PATHFIND_NOPATH; - return; + if (path || waterPath) + { + _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); + return; + } + + // raycast doesn't need endPoly to be valid + if (!_useRaycast) + { + _type = PATHFIND_NOPATH; + return; + } } // we may need a better number here @@ -235,10 +247,7 @@ void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 con BuildShortcut(); _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); - if (startFarFromPoly) - _type = PathType(_type | PATHFIND_FARFROMPOLY_START); - if (endFarFromPoly) - _type = PathType(_type | PATHFIND_FARFROMPOLY_END); + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); return; } @@ -254,10 +263,7 @@ void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 con _type = PathType(PATHFIND_INCOMPLETE); - if (startFarFromPoly) - _type = PathType(_type | PATHFIND_FARFROMPOLY_START); - if (endFarFromPoly) - _type = PathType(_type | PATHFIND_FARFROMPOLY_END); + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); } } @@ -265,7 +271,7 @@ void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 con // start and end are on same polygon // handle this case as if they were 2 different polygons, building a line path split in some few points - if (startPoly == endPoly) + if (startPoly == endPoly && !_useRaycast) { TC_LOG_DEBUG("maps.mmaps", "++ BuildPolyPath :: (startPoly == endPoly)"); @@ -276,10 +282,7 @@ void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 con { _type = PathType(PATHFIND_INCOMPLETE); - if (startFarFromPoly) - _type = PathType(_type | PATHFIND_FARFROMPOLY_START); - if (endFarFromPoly) - _type = PathType(_type | PATHFIND_FARFROMPOLY_END); + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); } else _type = PATHFIND_NORMAL; @@ -378,39 +381,12 @@ void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 con uint32 suffixPolyLength = 0; dtStatus dtResult; - if (_straightLine) + if (_useRaycast) { - float hit = 0; - float hitNormal[3]; - memset(hitNormal, 0, sizeof(hitNormal)); - - dtResult = _navMeshQuery->raycast( - suffixStartPoly, - suffixEndPoint, - endPoint, - &_filter, - &hit, - hitNormal, - _pathPolyRefs + prefixPolyLength - 1, - (int*)&suffixPolyLength, - MAX_PATH_LENGTH - prefixPolyLength); - - // raycast() sets hit to FLT_MAX if there is a ray between start and end - if (hit != FLT_MAX) - { - // the ray hit something, return no path instead of the incomplete one - Clear(); - _polyLength = 2; - _pathPoints.resize(2); - _pathPoints[0] = GetStartPosition(); - float hitPos[3]; - dtVlerp(hitPos, startPoint, endPoint, hit); - _pathPoints[1] = G3D::Vector3(hitPos[2], hitPos[0], hitPos[1]); - - NormalizePath(); - _type = PATHFIND_INCOMPLETE; - return; - } + TC_LOG_ERROR("maps.mmaps", "PathGenerator::BuildPolyPath() called with _useRaycast with a previous path for unit %s", _source->GetGUID().ToString().c_str()); + BuildShortcut(); + _type = PATHFIND_NOPATH; + return; } else { @@ -450,7 +426,7 @@ void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 con Clear(); dtStatus dtResult; - if (_straightLine) + if (_useRaycast) { float hit = 0; float hitNormal[3]; @@ -467,24 +443,57 @@ void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 con (int*)&_polyLength, MAX_PATH_LENGTH); + if (!_polyLength || dtStatusFailed(dtResult)) + { + BuildShortcut(); + _type = PATHFIND_NOPATH; + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); + return; + } + // raycast() sets hit to FLT_MAX if there is a ray between start and end if (hit != FLT_MAX) { - // the ray hit something, return no path instead of the incomplete one - Clear(); - _polyLength = 2; + float hitPos[3]; + + // 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) + hit *= 0.99f; + dtVlerp(hitPos, startPoint, endPoint, hit); + + // if it fails again, clamp to poly boundary + if (dtStatusFailed(_navMeshQuery->getPolyHeight(_pathPolyRefs[_polyLength - 1], hitPos, &hitPos[1]))) + _navMeshQuery->closestPointOnPolyBoundary(_pathPolyRefs[_polyLength - 1], hitPos, hitPos); + _pathPoints.resize(2); _pathPoints[0] = GetStartPosition(); - float hitPos[3]; - dtVlerp(hitPos, startPoint, endPoint, hit); _pathPoints[1] = G3D::Vector3(hitPos[2], hitPos[0], hitPos[1]); NormalizePath(); _type = PATHFIND_INCOMPLETE; + AddFarFromPolyFlags(startFarFromPoly, false); return; } else - _navMeshQuery->getPolyHeight(_pathPolyRefs[_polyLength - 1], endPoint, &endPoint[1]); + { + // clamp to poly boundary if we fail to get the height + if (dtStatusFailed(_navMeshQuery->getPolyHeight(_pathPolyRefs[_polyLength - 1], endPoint, &endPoint[1]))) + _navMeshQuery->closestPointOnPolyBoundary(_pathPolyRefs[_polyLength - 1], endPoint, endPoint); + + _pathPoints.resize(2); + _pathPoints[0] = GetStartPosition(); + _pathPoints[1] = G3D::Vector3(endPoint[2], endPoint[0], endPoint[1]); + + NormalizePath(); + if (startFarFromPoly || endFarFromPoly) + { + _type = PathType(PATHFIND_INCOMPLETE); + + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); + } + else + _type = PATHFIND_NORMAL; + return; + } } else { @@ -515,10 +524,7 @@ void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 con else _type = PATHFIND_INCOMPLETE; - if (startFarFromPoly) - _type = PathType(_type | PATHFIND_FARFROMPOLY_START); - if (endFarFromPoly) - _type = PathType(_type | PATHFIND_FARFROMPOLY_END); + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); // generate the point-path out of our up-to-date poly-path BuildPointPath(startPoint, endPoint); @@ -529,37 +535,13 @@ void PathGenerator::BuildPointPath(const float *startPoint, const float *endPoin float pathPoints[MAX_POINT_PATH_LENGTH*VERTEX_SIZE]; uint32 pointCount = 0; dtStatus dtResult = DT_FAILURE; - if (_straightLine) + if (_useRaycast) { - dtResult = DT_SUCCESS; - pointCount = 1; - memcpy(&pathPoints[VERTEX_SIZE * 0], startPoint, sizeof(float)* 3); // first point - - // path has to be split into polygons with dist SMOOTH_PATH_STEP_SIZE between them - G3D::Vector3 startVec = G3D::Vector3(startPoint[0], startPoint[1], startPoint[2]); - G3D::Vector3 endVec = G3D::Vector3(endPoint[0], endPoint[1], endPoint[2]); - G3D::Vector3 diffVec = (endVec - startVec); - G3D::Vector3 prevVec = startVec; - float len = diffVec.length(); - diffVec *= SMOOTH_PATH_STEP_SIZE / len; - - // If the path is short PATHFIND_SHORT will be set as type - while (len > SMOOTH_PATH_STEP_SIZE && pointCount < MAX_POINT_PATH_LENGTH) - { - len -= SMOOTH_PATH_STEP_SIZE; - prevVec += diffVec; - pathPoints[VERTEX_SIZE * pointCount + 0] = prevVec.x; - pathPoints[VERTEX_SIZE * pointCount + 1] = prevVec.y; - pathPoints[VERTEX_SIZE * pointCount + 2] = prevVec.z; - ++pointCount; - } - - // If the path is short PATHFIND_SHORT will be set as type - if (pointCount < MAX_POINT_PATH_LENGTH) - { - memcpy(&pathPoints[VERTEX_SIZE * pointCount], endPoint, sizeof(float) * 3); // last point - ++pointCount; - } + // _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 + TC_LOG_ERROR("maps.mmaps", "PathGenerator::BuildPointPath() called with _useRaycast for unit %s", _source->GetGUID().ToString().c_str()); + BuildShortcut(); + _type = PATHFIND_NOPATH; + return; } else if (_useStraightPath) { @@ -842,7 +824,7 @@ dtStatus PathGenerator::FindSmoothPath(float const* startPos, float const* endPo if (polyPathSize > 1) { - // Pick the closest poitns on poly border + // Pick the closest points on poly border if (dtStatusFailed(_navMeshQuery->closestPointOnPolyBoundary(polys[0], startPos, iterPos))) return DT_FAILURE; @@ -1042,3 +1024,11 @@ bool PathGenerator::IsInvalidDestinationZ(Unit const* target) const { return (target->GetPositionZ() - GetActualEndPosition().z) > 5.0f; } + +void PathGenerator::AddFarFromPolyFlags(bool startFarFromPoly, bool endFarFromPoly) +{ + if (startFarFromPoly) + _type = PathType(_type | PATHFIND_FARFROMPOLY_START); + if (endFarFromPoly) + _type = PathType(_type | PATHFIND_FARFROMPOLY_END); +} diff --git a/src/server/game/Movement/PathGenerator.h b/src/server/game/Movement/PathGenerator.h index cbdfa17cec..b785c2739d 100644 --- a/src/server/game/Movement/PathGenerator.h +++ b/src/server/game/Movement/PathGenerator.h @@ -61,12 +61,13 @@ class TC_GAME_API PathGenerator // Calculate the path from owner to given destination // return: true if new path was calculated, false otherwise (no change needed) - bool CalculatePath(float destX, float destY, float destZ, bool forceDest = false, bool straightLine = false); + bool CalculatePath(float destX, float destY, float destZ, bool forceDest = false); bool IsInvalidDestinationZ(Unit const* target) const; // option setters - use optional void SetUseStraightPath(bool useStraightPath) { _useStraightPath = useStraightPath; } void SetPathLengthLimit(float distance) { _pointPathLimit = std::min(uint32(distance/SMOOTH_PATH_STEP_SIZE), MAX_POINT_PATH_LENGTH); } + void SetUseRaycast(bool useRaycast) { _useRaycast = useRaycast; } // result getters G3D::Vector3 const& GetStartPosition() const { return _startPosition; } @@ -91,7 +92,7 @@ class TC_GAME_API PathGenerator bool _useStraightPath; // type of path will be generated bool _forceDestination; // when set, we will always arrive at given point uint32 _pointPathLimit; // limit point path size; min(this, MAX_POINT_PATH_LENGTH) - bool _straightLine; // use raycast if true for a straight line path + bool _useRaycast; // use raycast if true for a straight line path G3D::Vector3 _startPosition; // {x, y, z} of current location G3D::Vector3 _endPosition; // {x, y, z} of the destination @@ -137,6 +138,8 @@ class TC_GAME_API PathGenerator dtStatus FindSmoothPath(float const* startPos, float const* endPos, dtPolyRef const* polyPath, uint32 polyPathSize, float* smoothPath, int* smoothPathSize, uint32 smoothPathMaxSize); + + void AddFarFromPolyFlags(bool startFarFromPoly, bool endFarFromPoly); }; #endif diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 8211f7a681..4e9381d0a8 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -5804,7 +5804,7 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint m_preGeneratedPath->SetPathLengthLimit(range); // first try with raycast, if it fails fall back to normal path - bool result = m_preGeneratedPath->CalculatePath(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), false, false); + bool result = m_preGeneratedPath->CalculatePath(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), false); if (m_preGeneratedPath->GetPathType() & PATHFIND_SHORT) return SPELL_FAILED_NOPATH; else if (!result || m_preGeneratedPath->GetPathType() & (PATHFIND_NOPATH | PATHFIND_INCOMPLETE)) diff --git a/src/server/scripts/Commands/cs_mmaps.cpp b/src/server/scripts/Commands/cs_mmaps.cpp index c092b7badd..dcd6bb0b71 100644 --- a/src/server/scripts/Commands/cs_mmaps.cpp +++ b/src/server/scripts/Commands/cs_mmaps.cpp @@ -86,9 +86,9 @@ public: if (para && strcmp(para, "true") == 0) useStraightPath = true; - bool useStraightLine = false; - if (para && strcmp(para, "line") == 0) - useStraightLine = true; + bool useRaycast = false; + if (para && (strcmp(para, "line") == 0 || strcmp(para, "ray") == 0 || strcmp(para, "raycast") == 0)) + useRaycast = true; // unit locations float x, y, z; @@ -97,11 +97,12 @@ public: // path PathGenerator path(target); path.SetUseStraightPath(useStraightPath); - bool result = path.CalculatePath(x, y, z, false, useStraightLine); + path.SetUseRaycast(useRaycast); + bool result = path.CalculatePath(x, y, z, false); Movement::PointsArray const& pointPath = path.GetPath(); handler->PSendSysMessage("%s's path to %s:", target->GetName().c_str(), player->GetName().c_str()); - handler->PSendSysMessage("Building: %s", useStraightPath ? "StraightPath" : useStraightLine ? "Raycast" : "SmoothPath"); + handler->PSendSysMessage("Building: %s", useStraightPath ? "StraightPath" : useRaycast ? "Raycast" : "SmoothPath"); handler->PSendSysMessage("Result: %s - Length: %zu - Type: %u", (result ? "true" : "false"), pointPath.size(), path.GetPathType()); G3D::Vector3 const& start = path.GetStartPosition(); diff --git a/src/tools/mmaps_generator/MapBuilder.cpp b/src/tools/mmaps_generator/MapBuilder.cpp index d5a74180e1..0ab7932872 100644 --- a/src/tools/mmaps_generator/MapBuilder.cpp +++ b/src/tools/mmaps_generator/MapBuilder.cpp @@ -1041,7 +1041,7 @@ namespace MMAP config.walkableHeight = m_bigBaseUnit ? 3 : 6; // a value >= 3|6 allows npcs to walk over some fences // a value >= 4|8 allows npcs to walk over all fences - config.walkableClimb = m_bigBaseUnit ? 4 : 8; + config.walkableClimb = m_bigBaseUnit ? 3 : 6; config.minRegionArea = rcSqr(60); config.mergeRegionArea = rcSqr(50); config.maxSimplificationError = 1.8f; // eliminates most jagged edges (tiny polygons)