1937 lines
68 KiB
C++
1937 lines
68 KiB
C++
![]() |
#include "PathServer\PathServer.h"
|
||
|
|
||
|
// Rage headers
|
||
|
#include "atl\inmap.h"
|
||
|
#include "system/xtl.h"
|
||
|
|
||
|
// Framework headers
|
||
|
#include "ai/navmesh/priqueue.h"
|
||
|
#include "fwmaths\Vector.h"
|
||
|
#include "fwmaths/random.h"
|
||
|
|
||
|
|
||
|
NAVMESH_OPTIMISATIONS()
|
||
|
|
||
|
#define MAX_NUM_TESSELLATED_FRAGMENTS 768 // was 512 // was 256 // was 64
|
||
|
|
||
|
atArray<TNavMeshPoly*> startPolys(0,MAX_NUM_TESSELLATED_FRAGMENTS);
|
||
|
atArray<TNavMeshPoly*> tessellatedStartPolys(0,MAX_NUM_TESSELLATED_FRAGMENTS);
|
||
|
|
||
|
// The distances from adjacent waypoints towards the corner-point, for vCtrlPt2 and vCtrlPt3
|
||
|
//const float g_fPathSplineDists[PATHSPLINE_NUM_TEST_DISTANCES] = { 0.0f, 0.25f, 0.5f, 0.9f };
|
||
|
const float g_fPathSplineDists[PATHSPLINE_NUM_TEST_DISTANCES] = { 0.1f, 0.25f, 0.5f, 0.9f };
|
||
|
// The magnitude that the outer control pts are pulled back (scales the length of the line-seg)
|
||
|
const float g_fOuterCtrlPtDists[PATHSPLINE_NUM_TEST_DISTANCES] = { 1.0f, 0.75f, 0.5f, 0.25f };
|
||
|
|
||
|
namespace
|
||
|
{
|
||
|
bool CalcCornerPushDir(Vector3& vPushDir, const Vector3& vPrevPt, const Vector3& vMidPt, const Vector3& vNextPt, float fPushThreshold)
|
||
|
{
|
||
|
Vector3 vEntry = vMidPt - vPrevPt;
|
||
|
vEntry.z = 0.f;
|
||
|
vEntry.Normalize();
|
||
|
|
||
|
Vector3 vExit = vMidPt - vNextPt;
|
||
|
vExit.z = 0.f;
|
||
|
vExit.Normalize();
|
||
|
|
||
|
if (abs(vEntry.Dot(vExit)) < fPushThreshold)
|
||
|
{
|
||
|
vPushDir = vEntry + vExit;
|
||
|
vPushDir.Normalize();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* Not sued in the code base. Orbis complains about it
|
||
|
TNavMeshPoly* DigOutPolyBelowPoint(const Vector3& vNewPathPos, aiNavDomain domain)
|
||
|
{
|
||
|
const u32 iStartNavMesh = CPathServer::GetNavMeshIndexFromPosition(vNewPathPos, domain);
|
||
|
if (iStartNavMesh == NAVMESH_NAVMESH_INDEX_NONE)
|
||
|
return NULL;
|
||
|
|
||
|
CNavMesh * pStartNavMesh = CPathServer::GetNavMeshFromIndex(iStartNavMesh, domain);
|
||
|
if (!pStartNavMesh)
|
||
|
return NULL;
|
||
|
|
||
|
Vector3 vIntersectPoint;
|
||
|
const u32 iStartPolyIndex = pStartNavMesh->GetPolyBelowPoint(vNewPathPos + ZAXIS, vIntersectPoint, 5.0f);
|
||
|
if (iStartPolyIndex == NAVMESH_POLY_INDEX_NONE)
|
||
|
return NULL;
|
||
|
|
||
|
return pStartNavMesh->GetPoly(iStartPolyIndex);
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
bool IsPointsMakingAQuirk(const Vector3& vPrevPrevPt, const Vector3& vPrevPt, const Vector3& vThisPt, const Vector3& vNextPt, float UNUSED_PARAM(fTolerance))
|
||
|
{
|
||
|
Vector3 vToPrev = vPrevPt - vPrevPrevPt;
|
||
|
vToPrev.z = 0.f;
|
||
|
vToPrev.Normalize();
|
||
|
|
||
|
Vector3 vToThis = vThisPt - vPrevPt;
|
||
|
vToThis.z = 0.f;
|
||
|
vToThis.Normalize();
|
||
|
|
||
|
Vector3 vToNext = vNextPt - vThisPt;
|
||
|
vToNext.z = 0.f;
|
||
|
vToNext.Normalize();
|
||
|
|
||
|
// if (vToPrev.Dot(vToThis) < fTolerance && vToThis.Dot(vToNext) < fTolerance)
|
||
|
{
|
||
|
// Change of direction?
|
||
|
if ((int)Sign(-CrossProduct(vToThis, vToNext).z) == (int)Sign(-CrossProduct(vToPrev, vToThis).z))
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//*************************************************************************************************
|
||
|
// This function tessellates polys around the path start, and adds them to the priority queue
|
||
|
// ready for the pathfinding algorithm to use. It only does this if there are any dynamic
|
||
|
// objects in the vicinity of the start position. The original (untessellated) start poly is
|
||
|
// already in the priority queue at this time, and if it becomes tessellated here it will be
|
||
|
// marked as "replaced by tessellation" & subsequently ignored by the pathfinder when encountered.
|
||
|
//*************************************************************************************************
|
||
|
|
||
|
bool CPathServerThread::MaybeTessellateAndMarkPolysSurroundingPathStart(CNavMesh * pNavMesh, CPathRequest * pPathRequest, const float fInitialCost)
|
||
|
{
|
||
|
dev_float fNearbyPolyRadius = (pPathRequest->m_bExpandStartEndPolyTessellationRadius ? 2.5f : 0.25f);
|
||
|
static const float fStartPosRadius = 0.25f;
|
||
|
static const float fZSize = 5.0f;
|
||
|
|
||
|
// This tesselation stuff is only for the normal navigation domain for now.
|
||
|
const aiNavDomain domain = kNavDomainRegular;
|
||
|
|
||
|
const bool bUseMultipleStartPolys = DoesMinMaxIntersectAnyDynamicObjectsApproximate(m_Vars.m_pStartPoly->m_MinMax);
|
||
|
|
||
|
if(bUseMultipleStartPolys)
|
||
|
{
|
||
|
TShortMinMax startPosMinMax;
|
||
|
startPosMinMax.SetFloat(
|
||
|
pPathRequest->m_vPathStart.x - fStartPosRadius, pPathRequest->m_vPathStart.y - fStartPosRadius, pPathRequest->m_vPathStart.z - fZSize,
|
||
|
pPathRequest->m_vPathStart.x + fStartPosRadius, pPathRequest->m_vPathStart.y + fStartPosRadius, pPathRequest->m_vPathStart.z + fZSize
|
||
|
);
|
||
|
|
||
|
startPolys.clear();
|
||
|
startPolys.Append()=m_Vars.m_pStartPoly;
|
||
|
|
||
|
const float fAlternativeStartPenalty = 50.0f;
|
||
|
|
||
|
// Get a list of polys which are very close to the start position.
|
||
|
GetMultipleNavMeshPolysForRouteEndPoints(pPathRequest, m_Vars.m_pStartPoly, pNavMesh, pPathRequest->m_vPathStart, fNearbyPolyRadius, startPolys, domain);
|
||
|
|
||
|
for(int u=0; u<startPolys.GetCount(); u++)
|
||
|
{
|
||
|
TNavMeshPoly * pNavMeshPoly = startPolys[u];
|
||
|
|
||
|
CPathServerThread::OnFirstVisitingPoly(pNavMeshPoly);
|
||
|
|
||
|
if(pNavMeshPoly->TestFlags(NAVMESHPOLY_ALTERNATIVE_STARTING_POLY) ||
|
||
|
pNavMeshPoly->GetReplacedByTessellation())
|
||
|
continue;
|
||
|
|
||
|
s32 tp;
|
||
|
tessellatedStartPolys.clear();
|
||
|
tessellatedStartPolys.Append() = pNavMeshPoly;
|
||
|
|
||
|
for(tp=0; tp<tessellatedStartPolys.GetCount(); tp++)
|
||
|
{
|
||
|
TNavMeshPoly * pTessPoly = tessellatedStartPolys[tp];
|
||
|
|
||
|
// This should never return FALSE, as we will never reach max visited polys at this early stage
|
||
|
OnFirstVisitingPoly(pTessPoly);
|
||
|
|
||
|
Assert(!(pTessPoly->TestFlags(NAVMESHPOLY_ALTERNATIVE_STARTING_POLY)));
|
||
|
|
||
|
Assert(!pTessPoly->GetReplacedByTessellation());
|
||
|
CNavMesh * pTessNavMesh = CPathServer::GetNavMeshFromIndex(pTessPoly->GetNavMeshIndex(), domain);
|
||
|
|
||
|
if(fwPathServer::CanTessellateThisPoly(pTessPoly) &&
|
||
|
fwPathServer::IsRoomToTessellateThisPoly(pTessPoly))
|
||
|
{
|
||
|
CPathServerThread::ms_dynamicObjectsIntersectingPolygons.clear();
|
||
|
CDynamicObjectsContainer::GetObjectsIntersectingRegion(pTessPoly->m_MinMax, CPathServerThread::ms_dynamicObjectsIntersectingPolygons, TDynamicObject::FLAG_IGNORE_OPENABLE|TDynamicObject::FLAG_IGNORE_NOT_OBSTACLE);
|
||
|
|
||
|
if(CPathServerThread::ms_dynamicObjectsIntersectingPolygons.GetCount())
|
||
|
{
|
||
|
Assert(!(pTessPoly->TestFlags(NAVMESHPOLY_ALTERNATIVE_STARTING_POLY)));
|
||
|
|
||
|
const bool bOk = TessellateNavMeshPolyAndCreateDegenerateConnections(
|
||
|
pTessNavMesh, pTessPoly,
|
||
|
pTessNavMesh->GetPolyIndex(pTessPoly),
|
||
|
(pTessPoly == m_Vars.m_pStartPoly), // don't bother updating m_pStartPoly when we're using multiple starting polys
|
||
|
(pTessPoly == m_Vars.m_pEndPoly), // but we do want to update the m_pEndPoly
|
||
|
NULL, NAVMESH_NAVMESH_INDEX_NONE, NAVMESH_POLY_INDEX_NONE,
|
||
|
&tessellatedStartPolys
|
||
|
);
|
||
|
|
||
|
NAVMESH_OPTIMISATIONS_OFF_ONLY( Assert(bOk); );
|
||
|
|
||
|
if(!bOk)
|
||
|
{
|
||
|
if(!m_PathSearchPriorityQueue->GetNodeCount())
|
||
|
{
|
||
|
Assert(m_Vars.m_pStartPoly);
|
||
|
if(!m_Vars.m_pStartPoly)
|
||
|
return false;
|
||
|
|
||
|
m_PathSearchPriorityQueue->Insert(fInitialCost, 0.0f, m_Vars.m_pStartPoly, pPathRequest->m_vPathStart, m_Vars.m_vDirFromPrevious, 0, NAVMESH_POLY_INDEX_NONE);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// This should never return FALSE, as we will never reach max visited polys at this early stage
|
||
|
OnFirstVisitingPoly(pTessPoly);
|
||
|
tessellatedStartPolys.Delete(tp);
|
||
|
tp--;
|
||
|
|
||
|
if(!m_Vars.m_pStartPoly)
|
||
|
{
|
||
|
float fClosestEdgeDistSqr = FLT_MAX;
|
||
|
TNavMeshPoly * pClosestPoly = NULL;
|
||
|
Vector3 vClosestPt;
|
||
|
for(tp=0; tp<tessellatedStartPolys.GetCount(); tp++)
|
||
|
{
|
||
|
pTessPoly = tessellatedStartPolys[tp];
|
||
|
if(pTessPoly->GetIsTessellatedFragment())
|
||
|
{
|
||
|
const s32 iEdge = fwPathServer::GetTessellationNavMesh()->GetPolyClosestEdgeMidToPoint(
|
||
|
pPathRequest->m_vPathStart, pTessPoly, &vClosestPt);
|
||
|
if(iEdge >= 0)
|
||
|
{
|
||
|
const float fDistSqr = (vClosestPt-pPathRequest->m_vPathStart).XYMag2();
|
||
|
if(fDistSqr < fClosestEdgeDistSqr)
|
||
|
{
|
||
|
fClosestEdgeDistSqr = fDistSqr;
|
||
|
pClosestPoly = pTessPoly;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// There's a chance this still isn't found (eg. if all polygons were removed);
|
||
|
m_Vars.m_pStartPoly = pClosestPoly;
|
||
|
Assertf(m_Vars.m_pStartPoly, "Starting poly must have been clipped to nothing");
|
||
|
if(!m_Vars.m_pStartPoly)
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for(tp=0; tp<tessellatedStartPolys.GetCount(); tp++)
|
||
|
{
|
||
|
TNavMeshPoly * pTessPoly = tessellatedStartPolys[tp];
|
||
|
|
||
|
if(pTessPoly == m_Vars.m_pStartPoly)
|
||
|
continue;
|
||
|
|
||
|
if(!pTessPoly->m_MinMax.Intersects(startPosMinMax))
|
||
|
continue;
|
||
|
|
||
|
// This should never return FALSE, as we will never reach max visited polys at this early stage
|
||
|
OnFirstVisitingPoly(pTessPoly);
|
||
|
|
||
|
Assert(!(pTessPoly->TestFlags(NAVMESHPOLY_ALTERNATIVE_STARTING_POLY)));
|
||
|
|
||
|
// We are about to see whether this poly could be a valid alternative starting poly for the pathsearch.
|
||
|
// If so then will be added to the binheap, and thus when the pathfind algorithm traces back the
|
||
|
// route to the start, it could end up at this polygon.
|
||
|
// Therefore its important that we chose a point-enum for this poly from which the path startpos can be
|
||
|
// seen in an unobstructed line.
|
||
|
|
||
|
pTessPoly->m_PathParentPoly = NULL;
|
||
|
|
||
|
CNavMesh * pTessNavMesh = CPathServer::GetNavMeshFromIndex(pTessPoly->GetNavMeshIndex(), domain);
|
||
|
|
||
|
u32 v;
|
||
|
for(v=0; v<pTessPoly->GetNumVertices(); v++)
|
||
|
{
|
||
|
pTessNavMesh->GetVertex( pTessNavMesh->GetPolyVertexIndex(pTessPoly, v), m_Vars.m_vPolyPts[v] );
|
||
|
}
|
||
|
|
||
|
const u32 iNumCandidatePts = CreatePointsInPoly(
|
||
|
pTessNavMesh,
|
||
|
pTessPoly,
|
||
|
m_Vars.m_vPolyPts,
|
||
|
ShouldUseMorePointsForPoly(*pTessPoly, pPathRequest) ? POINTSINPOLY_EXTRA : POINTSINPOLY_NORMAL,
|
||
|
m_Vars.g_vClosestPtsInPoly
|
||
|
);
|
||
|
|
||
|
for(v=0; v<iNumCandidatePts; v++)
|
||
|
{
|
||
|
if(TestDynamicObjectLOS(pPathRequest->m_vPathStart, m_Vars.g_vClosestPtsInPoly[v]))
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// If we have found a point enum value which can see the start position, then we can safely use this poly
|
||
|
// and point-enum as an alternative starting point for the pathsearch - thus increasing the chance of the
|
||
|
// pathsearch completing successfully.
|
||
|
if(v != iNumCandidatePts)
|
||
|
{
|
||
|
pTessPoly->OrFlags(NAVMESHPOLY_ALTERNATIVE_STARTING_POLY);
|
||
|
pTessPoly->SetPointEnum(v);
|
||
|
|
||
|
float fDist = (m_Vars.g_vClosestPtsInPoly[v] - pPathRequest->m_vPathStart).Mag();
|
||
|
|
||
|
const float fCost = fInitialCost + fAlternativeStartPenalty;
|
||
|
|
||
|
m_PathSearchPriorityQueue->Insert(fCost, fDist, pTessPoly, m_Vars.g_vClosestPtsInPoly[v], m_Vars.m_vDirFromPrevious, 0, NAVMESH_POLY_INDEX_NONE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If the m_pStartPoly didn't get added added to the binheap, then make sure it is - we don't want to end up with an empty binheap!
|
||
|
|
||
|
Assert(!(m_Vars.m_pStartPoly->TestFlags(NAVMESHPOLY_ALTERNATIVE_STARTING_POLY)));
|
||
|
|
||
|
m_PathSearchPriorityQueue->Insert(fInitialCost, 0.0f, m_Vars.m_pStartPoly, pPathRequest->m_vPathStart, m_Vars.m_vDirFromPrevious, 0, NAVMESH_POLY_INDEX_NONE);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_Vars.m_pStartPoly->m_PathParentPoly = NULL;
|
||
|
|
||
|
m_PathSearchPriorityQueue->Insert(fInitialCost, 0.0f, m_Vars.m_pStartPoly, pPathRequest->m_vPathStart, m_Vars.m_vDirFromPrevious, 0, NAVMESH_POLY_INDEX_NONE);
|
||
|
}
|
||
|
|
||
|
// Finally make sure that the actual m_pStartPoly itself doesn't have the NAVMESHPOLY_ALTERNATIVE_STARTING_POLY flag set.
|
||
|
// This is important, because if our route started on a polygon marked with this flag - then we will automatically
|
||
|
// prepend the m_pStartPoly itself.
|
||
|
m_Vars.m_pStartPoly->AndFlags(~NAVMESHPOLY_ALTERNATIVE_STARTING_POLY);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
CPathServerThread::MaybeTessellatePolysSurroundingPathEnd(CNavMesh * pNavMesh, CPathRequest * pPathRequest)
|
||
|
{
|
||
|
Assert(m_Vars.m_pEndPoly);
|
||
|
|
||
|
const float fOriginalEdgeThreshold = CNavMesh::ms_fSmallestTessellatedEdgeThresholdSqr;
|
||
|
static dev_float fSmallEdgeThreshold = 2.0f * 2.0f;
|
||
|
|
||
|
dev_float fNearbyPolyRadius = (pPathRequest->m_bExpandStartEndPolyTessellationRadius ? 2.5f : 0.25f);
|
||
|
const bool bUseMultipleEndPolys = DoesMinMaxIntersectAnyDynamicObjectsApproximate(m_Vars.m_pEndPoly->m_MinMax);
|
||
|
|
||
|
// This tesselation stuff is only for the normal navigation domain for now.
|
||
|
const aiNavDomain domain = kNavDomainRegular;
|
||
|
|
||
|
if(bUseMultipleEndPolys)
|
||
|
{
|
||
|
startPolys.clear();
|
||
|
startPolys.Append() = m_Vars.m_pEndPoly;
|
||
|
|
||
|
// Get a list of polys which are very close to the end position.
|
||
|
GetMultipleNavMeshPolysForRouteEndPoints(pPathRequest, m_Vars.m_pEndPoly, pNavMesh, pPathRequest->m_vPathEnd, fNearbyPolyRadius, startPolys, domain);
|
||
|
|
||
|
s32 u;
|
||
|
for(u=0; u<startPolys.GetCount(); u++)
|
||
|
{
|
||
|
TNavMeshPoly * pNavMeshPoly = startPolys[u];
|
||
|
|
||
|
// This should never return FALSE, as we will never reach max visited polys at this early stage
|
||
|
OnFirstVisitingPoly(pNavMeshPoly);
|
||
|
|
||
|
if(pNavMeshPoly->TestFlags(NAVMESHPOLY_ALTERNATIVE_STARTING_POLY) ||
|
||
|
pNavMeshPoly->GetReplacedByTessellation() ||
|
||
|
pNavMeshPoly == m_Vars.m_pStartPoly)
|
||
|
continue;
|
||
|
|
||
|
s32 tp;
|
||
|
tessellatedStartPolys.clear();
|
||
|
tessellatedStartPolys.Append() = pNavMeshPoly;
|
||
|
|
||
|
for(tp=0; tp<tessellatedStartPolys.GetCount(); tp++)
|
||
|
{
|
||
|
TNavMeshPoly * pTessPoly = tessellatedStartPolys[tp];
|
||
|
|
||
|
// This should never return FALSE, as we will never reach max visited polys at this early stage
|
||
|
OnFirstVisitingPoly(pTessPoly);
|
||
|
|
||
|
Assert(!pTessPoly->GetReplacedByTessellation());
|
||
|
CNavMesh * pTessNavMesh = CPathServer::GetNavMeshFromIndex(pTessPoly->GetNavMeshIndex(), domain);
|
||
|
|
||
|
if(pTessPoly == m_Vars.m_pEndPoly)
|
||
|
{
|
||
|
CNavMesh::ms_fSmallestTessellatedEdgeThresholdSqr = fSmallEdgeThreshold;
|
||
|
}
|
||
|
|
||
|
if(fwPathServer::CanTessellateThisPoly(pTessPoly) &&
|
||
|
fwPathServer::IsRoomToTessellateThisPoly(pTessPoly))
|
||
|
{
|
||
|
CPathServerThread::ms_dynamicObjectsIntersectingPolygons.clear();
|
||
|
CDynamicObjectsContainer::GetObjectsIntersectingRegion(pTessPoly->m_MinMax, CPathServerThread::ms_dynamicObjectsIntersectingPolygons, TDynamicObject::FLAG_IGNORE_OPENABLE|TDynamicObject::FLAG_IGNORE_NOT_OBSTACLE);
|
||
|
|
||
|
if(CPathServerThread::ms_dynamicObjectsIntersectingPolygons.GetCount())
|
||
|
{
|
||
|
bool bOk = TessellateNavMeshPolyAndCreateDegenerateConnections(
|
||
|
pTessNavMesh, pTessPoly,
|
||
|
pTessNavMesh->GetPolyIndex(pTessPoly),
|
||
|
false, // don't bother updating m_pStartPoly when we're using multiple starting polys
|
||
|
(pTessPoly == m_Vars.m_pEndPoly), // but we do want to update the m_pEndPoly
|
||
|
NULL, NAVMESH_NAVMESH_INDEX_NONE, NAVMESH_POLY_INDEX_NONE,
|
||
|
&tessellatedStartPolys
|
||
|
);
|
||
|
Assert(bOk);
|
||
|
if(bOk)
|
||
|
{
|
||
|
// This should never return FALSE, as we will never reach max visited polys at this early stage
|
||
|
OnFirstVisitingPoly(pTessPoly);
|
||
|
tessellatedStartPolys.Delete(tp);
|
||
|
tp--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CNavMesh::ms_fSmallestTessellatedEdgeThresholdSqr = fOriginalEdgeThreshold;
|
||
|
}
|
||
|
|
||
|
float fClosestEdgeDistSqr = FLT_MAX;
|
||
|
TNavMeshPoly * pClosestPoly = NULL;
|
||
|
Vector3 vClosestPt;
|
||
|
|
||
|
for(tp=0; tp<tessellatedStartPolys.GetCount(); tp++)
|
||
|
{
|
||
|
TNavMeshPoly * pTessPoly = tessellatedStartPolys[tp];
|
||
|
OnFirstVisitingPoly(pTessPoly);
|
||
|
pTessPoly->m_PathParentPoly = NULL;
|
||
|
|
||
|
// If no replacement end poly was found underneath the end pos,
|
||
|
// select the one whose edge is closest to the end pos
|
||
|
if(!m_Vars.m_pEndPoly && pTessPoly->GetIsTessellatedFragment())
|
||
|
{
|
||
|
const s32 iEdge = fwPathServer::GetTessellationNavMesh()->GetPolyClosestEdgeMidToPoint(
|
||
|
pPathRequest->m_vPathEnd, pTessPoly, &vClosestPt);
|
||
|
if(iEdge >= 0)
|
||
|
{
|
||
|
const float fDistSqr = (vClosestPt-pPathRequest->m_vPathEnd).XYMag2();
|
||
|
if(fDistSqr < fClosestEdgeDistSqr)
|
||
|
{
|
||
|
fClosestEdgeDistSqr = fDistSqr;
|
||
|
pClosestPoly = pTessPoly;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// There's a chance this still isn't found (eg. if all polygons were removed);
|
||
|
if(!m_Vars.m_pEndPoly)
|
||
|
m_Vars.m_pEndPoly = pClosestPoly;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CNavMesh::ms_fSmallestTessellatedEdgeThresholdSqr = fOriginalEdgeThreshold;
|
||
|
}
|
||
|
|
||
|
|
||
|
//******************************************************************************************************************
|
||
|
// AddWaypointsToPreserveHeightChanges
|
||
|
// After finding & refining (string-pulling) the path, we are left with a set of straight line segments. The
|
||
|
// string-pulling operates in 2d & ignores height changes on the polys underneath. If we need to have waypoints
|
||
|
// placed at significant changes in *slope* in the navmesh underneath the path - then use this function to
|
||
|
// traverse path inserting waypoints wherever the slope changes.
|
||
|
//
|
||
|
//******************************************************************************************************************
|
||
|
|
||
|
void
|
||
|
CPathServerThread::AddWaypointsToPreserveHeightChanges(CPathRequest * pPathRequest)
|
||
|
{
|
||
|
#if !__FINAL
|
||
|
m_PerfTimer->Reset();
|
||
|
m_PerfTimer->Start();
|
||
|
#endif
|
||
|
|
||
|
#if __ASSERT
|
||
|
if( pPathRequest->m_iNumPoints < 2 )
|
||
|
{
|
||
|
pPathRequest->m_pSavePathFileName = "x:\\gta5\\build\\dev\\807928.prob";
|
||
|
|
||
|
Displayf( "Less than 2 points in path..\n");
|
||
|
Displayf( "m_vPathStart (%.2f, %.2f, %.2f)\n", pPathRequest->m_vPathStart.x, pPathRequest->m_vPathStart.y, pPathRequest->m_vPathStart.z);
|
||
|
Displayf( "m_vPathEnd (%.2f, %.2f, %.2f)\n", pPathRequest->m_vPathEnd.x, pPathRequest->m_vPathEnd.y, pPathRequest->m_vPathEnd.z);
|
||
|
Displayf( "m_iNumPoints %i \n", pPathRequest->m_iNumPoints);
|
||
|
Displayf( "m_PathPolys[0] 0x%p", pPathRequest->m_PathPolys[0]);
|
||
|
Displayf( "m_PathPolys[1] 0x%p", pPathRequest->m_PathPolys[1]);
|
||
|
Displayf( "m_iNumInitiallyFoundPolys %i", pPathRequest->m_iNumInitiallyFoundPolys);
|
||
|
Displayf( "m_WaypointFlags[0] %u", pPathRequest->m_WaypointFlags[0].GetSpecialActionFlags());
|
||
|
Displayf( "m_WaypointFlags[1] %u", pPathRequest->m_WaypointFlags[1].GetSpecialActionFlags());
|
||
|
|
||
|
Assertf(pPathRequest->m_iNumPoints >= 2, "see (url:bugstar:807928). Please include the TTY as it will have some more details, and attach the file \"%s\" after closing this dialog box.", pPathRequest->m_pSavePathFileName);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
TNavMeshPoly * pHeightChangePoly = NULL;
|
||
|
Vector3 vHeightChangePos;
|
||
|
|
||
|
const Vector3 vEpsilon(0.25f, 0.25f, 0.5f);
|
||
|
|
||
|
static Vector3 vFinalPoints[PRESERVE_HEIGHT_CHANGE_MAX_NUM_PTS];
|
||
|
static TNavMeshPoly * pFinalPolys[PRESERVE_HEIGHT_CHANGE_MAX_NUM_PTS];
|
||
|
static TNavMeshWaypointFlag iFinalWaypointFlags[PRESERVE_HEIGHT_CHANGE_MAX_NUM_PTS];
|
||
|
|
||
|
vFinalPoints[0] = pPathRequest->m_PathPoints[0];
|
||
|
pFinalPolys[0] = pPathRequest->m_PathPolys[0];
|
||
|
iFinalWaypointFlags[0] = pPathRequest->m_WaypointFlags[0];
|
||
|
u32 iNumFinalPoints=1;
|
||
|
|
||
|
s32 i;
|
||
|
|
||
|
const aiNavDomain domain = pPathRequest->GetMeshDataSet();
|
||
|
|
||
|
for(i=0; i<pPathRequest->m_iNumPoints-1; i++)
|
||
|
{
|
||
|
Vector3 & vVert = pPathRequest->m_PathPoints[i];
|
||
|
const Vector3 & vNextVert = pPathRequest->m_PathPoints[i+1];
|
||
|
|
||
|
TNavMeshPoly * pPoly = pPathRequest->m_PathPolys[i];
|
||
|
TNavMeshPoly * pNextPoly = pPathRequest->m_PathPolys[i+1];
|
||
|
|
||
|
if(pPoly && pNextPoly)
|
||
|
{
|
||
|
// If any of these polygons are tessellated, then replace with the original polygons
|
||
|
// for the purpose of our tests. This is in line with our strategy for navmesh LOS
|
||
|
// queries: it results in considerably less processing, and less complicated code than
|
||
|
// having to deal with connection polygons, etc.
|
||
|
|
||
|
if(pPoly->GetNavMeshIndex() == NAVMESH_INDEX_TESSELLATION)
|
||
|
{
|
||
|
const u32 iPoly = CPathServer::GetTessellationNavMesh()->GetPolyIndex(pPoly);
|
||
|
TTessInfo * pTess = CPathServer::GetTessInfo(iPoly);
|
||
|
Assert(pTess);
|
||
|
CNavMesh * pOriginalNavMesh = CPathServer::GetNavMeshFromIndex(pTess->m_iNavMeshIndex, kNavDomainRegular);
|
||
|
NAVMESH_OPTIMISATIONS_OFF_ONLY( Assert(pOriginalNavMesh); )
|
||
|
if(pOriginalNavMesh)
|
||
|
pPoly = pOriginalNavMesh->GetPoly(pTess->m_iPolyIndex);
|
||
|
else
|
||
|
pPoly = NULL;
|
||
|
}
|
||
|
if(pNextPoly->GetNavMeshIndex() == NAVMESH_INDEX_TESSELLATION)
|
||
|
{
|
||
|
const u32 iPoly = CPathServer::GetTessellationNavMesh()->GetPolyIndex(pNextPoly);
|
||
|
TTessInfo * pTess = CPathServer::GetTessInfo(iPoly);
|
||
|
Assert(pTess);
|
||
|
CNavMesh * pOriginalNavMesh = CPathServer::GetNavMeshFromIndex(pTess->m_iNavMeshIndex, kNavDomainRegular);
|
||
|
NAVMESH_OPTIMISATIONS_OFF_ONLY( Assert(pOriginalNavMesh); )
|
||
|
if(pOriginalNavMesh)
|
||
|
pNextPoly = pOriginalNavMesh->GetPoly(pTess->m_iPolyIndex);
|
||
|
else
|
||
|
pNextPoly = NULL;
|
||
|
}
|
||
|
|
||
|
if(pPoly && pNextPoly)
|
||
|
{
|
||
|
Vector3 vVecToNext = vNextVert - vVert;
|
||
|
vVecToNext.Normalize();
|
||
|
|
||
|
IncTimeStamp();
|
||
|
|
||
|
bool bHasHeightChange = FindHeightChangePosAlongLine(vVert, vNextVert, pNextPoly, pPoly, pHeightChangePoly, vHeightChangePos, false, domain);
|
||
|
|
||
|
while(bHasHeightChange)
|
||
|
{
|
||
|
Assert(pHeightChangePoly && pHeightChangePoly->GetNavMeshIndex()!=NAVMESH_INDEX_TESSELLATION);
|
||
|
|
||
|
const bool bAlmostSamePositionAsVert1 = vHeightChangePos.IsClose(vVert, vEpsilon);
|
||
|
const bool bAlmostSamePositionAsVert2 = vHeightChangePos.IsClose(vNextVert, vEpsilon);
|
||
|
|
||
|
if(!bAlmostSamePositionAsVert1 && !bAlmostSamePositionAsVert2)
|
||
|
{
|
||
|
vFinalPoints[iNumFinalPoints] = vHeightChangePos;
|
||
|
pFinalPolys[iNumFinalPoints] = pHeightChangePoly;
|
||
|
iFinalWaypointFlags[iNumFinalPoints].Clear();
|
||
|
|
||
|
//**************************
|
||
|
// Preserve certain flags
|
||
|
|
||
|
if(((pPathRequest->m_WaypointFlags[i].m_iSpecialActionFlags) & WAYPOINT_FLAG_IS_INTERIOR) &&
|
||
|
((pPathRequest->m_WaypointFlags[i+1].m_iSpecialActionFlags) & WAYPOINT_FLAG_IS_INTERIOR))
|
||
|
iFinalWaypointFlags[iNumFinalPoints].m_iSpecialActionFlags |= WAYPOINT_FLAG_IS_INTERIOR;
|
||
|
|
||
|
if(((pPathRequest->m_WaypointFlags[i].m_iSpecialActionFlags) & WAYPOINT_FLAG_IS_ON_WATER_SURFACE) &&
|
||
|
((pPathRequest->m_WaypointFlags[i+1].m_iSpecialActionFlags) & WAYPOINT_FLAG_IS_ON_WATER_SURFACE))
|
||
|
iFinalWaypointFlags[iNumFinalPoints].m_iSpecialActionFlags |= WAYPOINT_FLAG_IS_ON_WATER_SURFACE;
|
||
|
|
||
|
iNumFinalPoints++;
|
||
|
|
||
|
if(iNumFinalPoints==PRESERVE_HEIGHT_CHANGE_MAX_NUM_PTS-1)
|
||
|
goto __BreakOut;
|
||
|
}
|
||
|
|
||
|
pPoly = pHeightChangePoly;
|
||
|
vVert = vHeightChangePos;
|
||
|
|
||
|
vVecToNext = vNextVert - vVert;
|
||
|
vVecToNext.Normalize();
|
||
|
|
||
|
bHasHeightChange = FindHeightChangePosAlongLine(vVert, vNextVert, pNextPoly, pPoly, pHeightChangePoly, vHeightChangePos, false, domain);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
vFinalPoints[iNumFinalPoints] = vNextVert;
|
||
|
pFinalPolys[iNumFinalPoints] = pNextPoly;
|
||
|
iFinalWaypointFlags[iNumFinalPoints] = pPathRequest->m_WaypointFlags[i+1];
|
||
|
iNumFinalPoints++;
|
||
|
|
||
|
if(iNumFinalPoints==PRESERVE_HEIGHT_CHANGE_MAX_NUM_PTS-1)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
__BreakOut:
|
||
|
|
||
|
pPathRequest->m_PathPoints[0] = vFinalPoints[0];
|
||
|
pPathRequest->m_PathPolys[0] = pFinalPolys[0];
|
||
|
pPathRequest->m_WaypointFlags[0] = iFinalWaypointFlags[0];
|
||
|
|
||
|
vFinalPoints[iNumFinalPoints] = pPathRequest->m_PathPoints[pPathRequest->m_iNumPoints-1];
|
||
|
pFinalPolys[iNumFinalPoints] = pPathRequest->m_PathPolys[pPathRequest->m_iNumPoints-1];
|
||
|
iFinalWaypointFlags[iNumFinalPoints] = pPathRequest->m_WaypointFlags[pPathRequest->m_iNumPoints-1];
|
||
|
iNumFinalPoints++;
|
||
|
|
||
|
pPathRequest->m_iNumPoints = 1;
|
||
|
|
||
|
static const float fMaxDot = rage::Cosf(( DtoR * 1.0f));
|
||
|
|
||
|
// Now go through points, and remove any between which there is no significant gradient change
|
||
|
u32 p;
|
||
|
for(p=1; p<iNumFinalPoints-1; p++)
|
||
|
{
|
||
|
const Vector3 & vLastPt = vFinalPoints[p-1];
|
||
|
const Vector3 & vPt = vFinalPoints[p];
|
||
|
const Vector3 & vNextPt = vFinalPoints[p+1];
|
||
|
|
||
|
Vector3 vFromLast = vPt - vLastPt;
|
||
|
Vector3 vToNext = vNextPt - vPt;
|
||
|
vFromLast.Normalize();
|
||
|
vToNext.Normalize();
|
||
|
|
||
|
const float fDot = DotProduct(vFromLast, vToNext);
|
||
|
|
||
|
// Gradients are similar, so central point can be removed
|
||
|
if(fDot > fMaxDot && (iFinalWaypointFlags[p-1].GetSpecialAction() == iFinalWaypointFlags[p].GetSpecialAction()
|
||
|
&& iFinalWaypointFlags[p].GetSpecialAction() == iFinalWaypointFlags[p+1].GetSpecialAction()))
|
||
|
{
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pPathRequest->m_PathPoints[pPathRequest->m_iNumPoints] = vFinalPoints[p];
|
||
|
pPathRequest->m_PathPolys[pPathRequest->m_iNumPoints] = pFinalPolys[p];
|
||
|
pPathRequest->m_WaypointFlags[pPathRequest->m_iNumPoints] = iFinalWaypointFlags[p];
|
||
|
pPathRequest->m_iNumPoints++;
|
||
|
}
|
||
|
|
||
|
if(pPathRequest->m_iNumPoints == MAX_NUM_PATH_POINTS-1)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
#if __ASSERT
|
||
|
for(s32 t=0; t<pPathRequest->m_iNumPoints; t++)
|
||
|
{
|
||
|
Assert( rage::FPIsFinite(pPathRequest->m_PathPoints[t].x) && rage::FPIsFinite(pPathRequest->m_PathPoints[t].y) && rage::FPIsFinite(pPathRequest->m_PathPoints[t].z) );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if !__FINAL
|
||
|
m_PerfTimer->Stop();
|
||
|
pPathRequest->m_fMillisecsToPostProcessZHeights = (float) m_PerfTimer->GetTimeMS();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
//********************************************************************
|
||
|
// MinimisePathDistance
|
||
|
// This function minimises the distance of the path by examining
|
||
|
// each set of 3 nodes, and moving the center one within its
|
||
|
// polygon to find a position where the total length of the two
|
||
|
// connected line segments are least - and the LOS is maintained
|
||
|
// to each of the adjoined points.
|
||
|
//********************************************************************
|
||
|
|
||
|
#define MINIMISE_PATH_RANDDIST_SQR 36.0f
|
||
|
|
||
|
void CPathServerThread::MinimisePathDistance(CPathRequest * pPathRequest)
|
||
|
{
|
||
|
if(pPathRequest->m_iNumPoints <= 2)
|
||
|
return;
|
||
|
|
||
|
Assert(pPathRequest->m_iNumPoints > 2); // Try to catch weird num-points bug
|
||
|
|
||
|
float fRandErrorRangeSqr = 0.0f;
|
||
|
if(pPathRequest->m_bRandomisePoints)
|
||
|
{
|
||
|
m_RandomNumGen.Reset(pPathRequest->m_iPedRandomSeed);
|
||
|
}
|
||
|
|
||
|
#if !__FINAL
|
||
|
m_PerfTimer->Reset();
|
||
|
m_PerfTimer->Start();
|
||
|
#endif
|
||
|
|
||
|
static Vector3 vPolyPts[NAVMESHPOLY_MAX_NUM_VERTICES];
|
||
|
|
||
|
s32 p;
|
||
|
u32 v;
|
||
|
u32 n;
|
||
|
Vector3 * pPrevPt, * pCurrentPt, * pNextPt;
|
||
|
TNavMeshPoly * pPrevPolyPt, * pNextPolyPt;
|
||
|
CNavMesh * pNavMesh;
|
||
|
TNavMeshPoly * pPoly;
|
||
|
|
||
|
float fPrevToCurrDist;
|
||
|
float fCurrToNextDist;
|
||
|
float fPrevToCurrDistSqr;
|
||
|
float fCurrToNextDistSqr;
|
||
|
|
||
|
float fTrialPrevToCurrDist;
|
||
|
float fTrialCurrToNextDist;
|
||
|
float fTrialPrevToCurrDistSqr;
|
||
|
float fTrialCurrToNextDistSqr;
|
||
|
|
||
|
static const float fSmallDistance = 0.1f;
|
||
|
static const float fSmallDistanceSqr = 0.1f * 0.1f;
|
||
|
|
||
|
float fBestTotalDiff, fDiff;
|
||
|
|
||
|
s32 iNumPoints = pPathRequest->m_iNumPoints;
|
||
|
Assert( ((iNumPoints-1) > 0) && ((iNumPoints-1) < (s32)pPathRequest->m_iNumPoints)); // Try to catch weird num-points bug
|
||
|
|
||
|
const aiNavDomain domain = pPathRequest->GetMeshDataSet();
|
||
|
|
||
|
for(p=1; p<iNumPoints-1; p++)
|
||
|
{
|
||
|
Assert(iNumPoints > 2); // Try to catch weird num-points bug
|
||
|
|
||
|
pPoly = pPathRequest->m_PathPolys[p];
|
||
|
|
||
|
// If there was no poly set for this point, it means it was either the start/end point (but not in this case)
|
||
|
// or was a point generated along the edge between 2 polys for which there was no LOS between.
|
||
|
// For now, we shall skip the processing of these cases - hoping that the generated point is okay.
|
||
|
if(!pPoly)
|
||
|
continue;
|
||
|
|
||
|
const u32 iPointEnum = pPoly->GetPointEnum();
|
||
|
// Assert(iPointEnum != NAVMESHPOLY_POINTENUM_SPECIAL_LINK_ENDPOS);
|
||
|
|
||
|
if((iPointEnum == NAVMESHPOLY_POINTENUM_CENTROID /*|| iPointEnum == NAVMESHPOLY_POINTENUM_UNUSED*/ )
|
||
|
&& !pPathRequest->m_bRandomisePoints)
|
||
|
continue;
|
||
|
|
||
|
if(pPathRequest->m_bRandomisePoints)
|
||
|
fRandErrorRangeSqr = m_RandomNumGen.GetVaried(MINIMISE_PATH_RANDDIST_SQR);
|
||
|
|
||
|
pNavMesh = CPathServer::GetNavMeshFromIndex(pPoly->GetNavMeshIndex(), domain);
|
||
|
if(!pNavMesh)
|
||
|
continue;
|
||
|
|
||
|
Assert(p+1 < iNumPoints);
|
||
|
|
||
|
pPrevPt = &pPathRequest->m_PathPoints[p-1];
|
||
|
pCurrentPt = &pPathRequest->m_PathPoints[p];
|
||
|
pNextPt = &pPathRequest->m_PathPoints[p+1];
|
||
|
|
||
|
Assert( rage::FPIsFinite(pPrevPt->x) && rage::FPIsFinite(pPrevPt->y) && rage::FPIsFinite(pPrevPt->z) );
|
||
|
Assert( rage::FPIsFinite(pCurrentPt->x) && rage::FPIsFinite(pCurrentPt->y) && rage::FPIsFinite(pCurrentPt->z) );
|
||
|
Assert( rage::FPIsFinite(pNextPt->x) && rage::FPIsFinite(pNextPt->y) && rage::FPIsFinite(pNextPt->z) );
|
||
|
|
||
|
pPrevPolyPt = pPathRequest->m_PathPolys[p-1];
|
||
|
pNextPolyPt = pPathRequest->m_PathPolys[p+1];
|
||
|
|
||
|
fPrevToCurrDistSqr = (*pCurrentPt - *pPrevPt).Mag2() + fRandErrorRangeSqr;
|
||
|
fCurrToNextDistSqr = (*pNextPt - *pCurrentPt).Mag2() + fRandErrorRangeSqr;
|
||
|
|
||
|
// Calc the actual distances from the squared distances
|
||
|
if(fPrevToCurrDistSqr > fSmallDistanceSqr)
|
||
|
{
|
||
|
Assert(fPrevToCurrDistSqr > 0.0f);
|
||
|
fPrevToCurrDist = 1.0f / invsqrtf_fast(fPrevToCurrDistSqr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fPrevToCurrDist = fSmallDistance;
|
||
|
}
|
||
|
|
||
|
if(fCurrToNextDistSqr > fSmallDistanceSqr)
|
||
|
{
|
||
|
Assert(fCurrToNextDistSqr > 0.0f);
|
||
|
fCurrToNextDist = 1.0f / invsqrtf_fast(fCurrToNextDistSqr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fCurrToNextDist = fSmallDistance;
|
||
|
}
|
||
|
|
||
|
for(v=0; v<pPoly->GetNumVertices(); v++)
|
||
|
{
|
||
|
pNavMesh->GetVertex( pNavMesh->GetPolyVertexIndex(pPoly, v), vPolyPts[v]);
|
||
|
}
|
||
|
|
||
|
const u32 iNumClosePts = CreatePointsInPoly(
|
||
|
pNavMesh,
|
||
|
pPoly,
|
||
|
vPolyPts,
|
||
|
ShouldUseMorePointsForPoly(*pPoly, pPathRequest) ? POINTSINPOLY_EXTRA : POINTSINPOLY_NORMAL,
|
||
|
m_Vars.g_vClosestPtsInPoly);
|
||
|
|
||
|
Assert(iNumClosePts < NUM_CLOSE_PTS_IN_POLY);
|
||
|
|
||
|
if(pPathRequest->m_bRandomisePoints && iNumClosePts > 1)
|
||
|
{
|
||
|
for(n=0; n<iNumClosePts; n++)
|
||
|
{
|
||
|
if(n != iPointEnum)
|
||
|
{
|
||
|
m_Vars.g_vClosestPtsInPoly[n] = CreateRandomPointInPoly(pPoly, vPolyPts);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// To start with we'll set our best choice as out existing (current) point
|
||
|
fBestTotalDiff = 0.0f;
|
||
|
|
||
|
for(n=0; n<iNumClosePts; n++)
|
||
|
{
|
||
|
// Don't bother processing for the point which we already have
|
||
|
if(n == pPoly->GetPointEnum())
|
||
|
continue;
|
||
|
|
||
|
if(pPathRequest->m_bRandomisePoints)
|
||
|
fRandErrorRangeSqr = m_RandomNumGen.GetVaried(MINIMISE_PATH_RANDDIST_SQR);
|
||
|
|
||
|
fTrialPrevToCurrDistSqr = (m_Vars.g_vClosestPtsInPoly[n] - *pPrevPt).Mag2() + fRandErrorRangeSqr;
|
||
|
fTrialCurrToNextDistSqr = (*pNextPt - m_Vars.g_vClosestPtsInPoly[n]).Mag2() + fRandErrorRangeSqr;
|
||
|
|
||
|
// We can early-out by comparing both the squared distances
|
||
|
if(fTrialPrevToCurrDistSqr > fPrevToCurrDistSqr && fTrialCurrToNextDistSqr > fCurrToNextDistSqr)
|
||
|
continue;
|
||
|
|
||
|
// Calc the actual distances from the squared distances
|
||
|
if(fTrialPrevToCurrDistSqr > fSmallDistanceSqr)
|
||
|
{
|
||
|
Assert(fTrialPrevToCurrDistSqr > 0.0f);
|
||
|
fTrialPrevToCurrDist = 1.0f / invsqrtf_fast(fTrialPrevToCurrDistSqr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fTrialPrevToCurrDist = fSmallDistance;
|
||
|
}
|
||
|
|
||
|
if(fTrialCurrToNextDistSqr > fSmallDistanceSqr)
|
||
|
{
|
||
|
Assert(fTrialCurrToNextDistSqr > 0.0f);
|
||
|
fTrialCurrToNextDist = 1.0f / invsqrtf_fast(fTrialCurrToNextDistSqr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fTrialCurrToNextDist = fSmallDistance;
|
||
|
}
|
||
|
|
||
|
// If this is a better path, fDiff will be a positive value
|
||
|
fDiff = (fPrevToCurrDist + fCurrToNextDist) - (fTrialPrevToCurrDist + fTrialCurrToNextDist);
|
||
|
|
||
|
// If this point placement is better than our current best effort, see if the LOS is clear
|
||
|
if(fDiff > fBestTotalDiff)
|
||
|
{
|
||
|
// If there was no previous poly, it indicates that the prev point was a waypoint
|
||
|
// inserted on the polygon edge in order to see round a corner - so it is guaranteed
|
||
|
// to be visible from anywhere in this poly
|
||
|
if(pPrevPolyPt)
|
||
|
{
|
||
|
// Test LOS from current point to last point
|
||
|
IncTimeStamp();
|
||
|
|
||
|
if(!TestNavMeshLOS(m_Vars.g_vClosestPtsInPoly[n], *pPrevPt, pPrevPolyPt, pPoly, NULL, m_Vars.m_iLosFlags, domain))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If there is no next poly, it indicates that the next point was a waypoint
|
||
|
// inserted on the polygon edge in order to see round a corner - so it is guaranteed
|
||
|
// to be visible from anywhere in this poly
|
||
|
if(pNextPolyPt)
|
||
|
{
|
||
|
// Test LOS from current point to next point
|
||
|
IncTimeStamp();
|
||
|
|
||
|
if(!TestNavMeshLOS(m_Vars.g_vClosestPtsInPoly[n], *pNextPt, pNextPolyPt, pPoly, NULL, m_Vars.m_iLosFlags, domain))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we're considering dynamic objects, then test those as well
|
||
|
if(!TestDynamicObjectLOS(m_Vars.g_vClosestPtsInPoly[n], *pPrevPt))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
// If we're considering dynamic objects, then test those as well
|
||
|
if(!TestDynamicObjectLOS(m_Vars.g_vClosestPtsInPoly[n], *pNextPt))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// The distance is shorter, and all the LOS tests have passed - so this is a better point for the route.
|
||
|
fBestTotalDiff = fDiff;
|
||
|
|
||
|
Assert(p < MAX_NUM_PATH_POINTS+1);
|
||
|
Assert(n < NUM_CLOSE_PTS_IN_POLY);
|
||
|
|
||
|
pPathRequest->m_PathPoints[p] = m_Vars.g_vClosestPtsInPoly[n];
|
||
|
pPoly->SetPointEnum(n);
|
||
|
|
||
|
fPrevToCurrDistSqr = fTrialPrevToCurrDistSqr;
|
||
|
fCurrToNextDistSqr = fTrialCurrToNextDistSqr;
|
||
|
fPrevToCurrDist = fTrialPrevToCurrDist;
|
||
|
fCurrToNextDist = fTrialCurrToNextDist;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
#ifdef GTA_ENGINE
|
||
|
#if AI_OPTIMISATIONS_OFF || NAVMESH_OPTIMISATIONS_OFF
|
||
|
Assertf(pPathRequest->m_iNumPoints > 2, "path handle:%p", pPathRequest->m_hHandle); // Try to catch weird num-points bug
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#if !__FINAL
|
||
|
m_PerfTimer->Stop();
|
||
|
pPathRequest->m_fMillisecsToMinimisePathLength = (float) m_PerfTimer->GetTimeMS();
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
void CPathServerThread::PullPathOutFromEdges(CPathRequest * pPathRequest, bool bDoStringPull)
|
||
|
{
|
||
|
if (pPathRequest->m_iNumPoints <= 2 && pPathRequest->m_iNumPoints < MAX_NUM_PATH_POINTS)
|
||
|
return;
|
||
|
|
||
|
#if !__FINAL
|
||
|
m_PerfTimer->Reset();
|
||
|
m_PerfTimer->Start();
|
||
|
#endif
|
||
|
|
||
|
const float fPushDist = (pPathRequest->m_bPullFromEdgeExtra ? CPathServerThread::ms_fPullOutFromEdgesDistanceExtra : CPathServerThread::ms_fPullOutFromEdgesDistance);
|
||
|
static const float fPushThreshold = 0.999f;
|
||
|
Vector3 vCacheVec;
|
||
|
|
||
|
const aiNavDomain domain = pPathRequest->GetMeshDataSet();
|
||
|
|
||
|
Vector3* pCachePushDir = NULL;
|
||
|
int nPoints = pPathRequest->m_iNumPoints - 1; // We check one node ahead
|
||
|
for (int i = 1; i < nPoints; ++i) // and one behind
|
||
|
{
|
||
|
Vector3 vPushDir;
|
||
|
const Vector3 vPrevPt = pPathRequest->m_PathPoints[i-1];
|
||
|
const Vector3 vThisPt = pPathRequest->m_PathPoints[i];
|
||
|
const Vector3 vNextPt = pPathRequest->m_PathPoints[i+1];
|
||
|
|
||
|
bool bSkip = false;
|
||
|
bool bFirstFrame = (i == 1);
|
||
|
if (pCachePushDir)
|
||
|
vPushDir = *pCachePushDir;
|
||
|
else if (!bFirstFrame)
|
||
|
bSkip = true;
|
||
|
|
||
|
//Scan ahead 2 nodes and cache that direction to push since if we push without caching
|
||
|
//we will be pushing in the wrong direction since the last push will then move the "prev vector"
|
||
|
if (i < nPoints - 1)
|
||
|
{
|
||
|
const Vector3 vNextNextPt = pPathRequest->m_PathPoints[i+2];
|
||
|
if (CalcCornerPushDir(vCacheVec, vThisPt, vNextPt, vNextNextPt, fPushThreshold))
|
||
|
pCachePushDir = &vCacheVec;
|
||
|
else
|
||
|
pCachePushDir = NULL;
|
||
|
}
|
||
|
|
||
|
if (bFirstFrame && !CalcCornerPushDir(vPushDir, vPrevPt, vThisPt, vNextPt, fPushThreshold))
|
||
|
continue;
|
||
|
else if (bSkip)
|
||
|
continue; // The cache was invalid (straight)
|
||
|
|
||
|
// Not having valid polys might cause a crash?
|
||
|
TNavMeshPoly* pPrevPoly = pPathRequest->m_PathPolys[i-1];
|
||
|
TNavMeshPoly* pThisPoly = pPathRequest->m_PathPolys[i];
|
||
|
TNavMeshPoly* pNextPoly = pPathRequest->m_PathPolys[i+1];
|
||
|
if (!pPrevPoly || !pThisPoly || !pNextPoly)
|
||
|
continue;
|
||
|
|
||
|
// Don't pull and stuff if we got any special action near, such as climbs
|
||
|
if (pPathRequest->m_WaypointFlags[i-1].GetSpecialAction() ||
|
||
|
pPathRequest->m_WaypointFlags[i].GetSpecialAction() ||
|
||
|
pPathRequest->m_WaypointFlags[i+1].GetSpecialAction())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//// If we pull points too close to each other and one of them fail the angle between them will be quite bad
|
||
|
//if (vPrevPt.Dist2(vThisPt) < rage::square(fPushDist * 2.f) ||
|
||
|
// vNextPt.Dist2(vThisPt) < rage::square(fPushDist * 2.f))
|
||
|
//{
|
||
|
// continue;
|
||
|
//}
|
||
|
|
||
|
// Do those LOS tests...
|
||
|
// Consider a table like this: [7,6,5,4 (middle) 3,2,1,0], where middle is our current position
|
||
|
// We loop through and test from middle and see how far we got LOS on each side
|
||
|
// The left in the table represent pulling the corner point out, the right is pulling it in
|
||
|
// We do consider LOS distance on each side, and try to find a good position to pull or push our position to
|
||
|
// We skew this algorithm slightly towards pushing out from corners as that should be better
|
||
|
Vector3 vIntersectPoint;
|
||
|
const Vector2 vLosDir = Vector2(vPushDir * fPushDist, Vector2::kXY);
|
||
|
|
||
|
int nIn, nOut;
|
||
|
float fIn = 0.f;
|
||
|
float fOut = 0.f;
|
||
|
const float fPushTableScale[5] = {1.0f, 0.75f, 0.5f, 0.25f, 0.0f};
|
||
|
|
||
|
// We first check LOS distance how close we are to the corner
|
||
|
for (nIn = 0; nIn < 4; ++nIn, fIn += 1.f)
|
||
|
{
|
||
|
IncTimeStamp();
|
||
|
if (TestNavMeshLOS(vThisPt, -vLosDir * fPushTableScale[nIn], &vIntersectPoint, pThisPoly, m_Vars.m_iLosFlags, domain) &&
|
||
|
TestDynamicObjectLOS(vThisPt, vThisPt - vPushDir * fPushDist * fPushTableScale[nIn]))
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// And how far we can push out from that corner
|
||
|
for (nOut = 0; nOut < 4; ++nOut, fOut += 1.f)
|
||
|
{
|
||
|
IncTimeStamp();
|
||
|
if (TestNavMeshLOS(vThisPt, vLosDir * fPushTableScale[nOut], &vIntersectPoint, pThisPoly, m_Vars.m_iLosFlags, domain) &&
|
||
|
TestDynamicObjectLOS(vThisPt, vThisPt + vPushDir * fPushDist * fPushTableScale[nOut]))
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Full LOS to either side, no need to move this path position around
|
||
|
if (nIn == 0 && nOut ==0)
|
||
|
continue;
|
||
|
|
||
|
// Map the values to match the table [8,7,6,5 (4) 3,2,1,0], this should be skewed by adding the middle as a value, and round up
|
||
|
const int iAvg = (int)ceil(((fIn + (8.f - fOut)) * 0.5f) + 0.5f);
|
||
|
|
||
|
if (nIn > 0)
|
||
|
pPathRequest->m_WaypointFlags[i].m_iSpecialActionFlags |= WAYPOINT_FLAG_IS_NARROW_IN;
|
||
|
if (nOut > 0)
|
||
|
pPathRequest->m_WaypointFlags[i].m_iSpecialActionFlags |= WAYPOINT_FLAG_IS_NARROW_OUT;
|
||
|
|
||
|
// Map the values to match the table [7,6,5,4 (middle) 3,2,1,0], should not be skewed or we won't favor pushing the corner out
|
||
|
const bool bIsPullingOut = iAvg > 4;
|
||
|
const int iPushIndex = (bIsPullingOut ? 7 - iAvg : iAvg);
|
||
|
|
||
|
//
|
||
|
const Vector3 vNewPathPos = vThisPt + vPushDir * fPushDist * fPushTableScale[iPushIndex] * (bIsPullingOut ? 1.f : -1.f);
|
||
|
|
||
|
IncTimeStamp();
|
||
|
const Vector2 vLosDirFinal = Vector2(vNewPathPos - vThisPt, Vector2::kXY);
|
||
|
TNavMeshPoly* pNewPoly = TestNavMeshLOS(vThisPt, vLosDirFinal, &vIntersectPoint, pThisPoly, m_Vars.m_iLosFlags, domain);
|
||
|
if (pNewPoly)
|
||
|
{
|
||
|
// And then we just test to see if this pull is a valid position to reach in the path
|
||
|
IncTimeStamp();
|
||
|
if (!TestNavMeshLOS(vNewPathPos, vPrevPt, pPrevPoly, pNewPoly, NULL, m_Vars.m_iLosFlags, domain))
|
||
|
continue;
|
||
|
|
||
|
if (!TestDynamicObjectLOS(vNewPathPos, vPrevPt))
|
||
|
continue;
|
||
|
|
||
|
IncTimeStamp();
|
||
|
if (!TestNavMeshLOS(vNewPathPos, vNextPt, pNextPoly, pNewPoly, NULL, m_Vars.m_iLosFlags, domain))
|
||
|
continue;
|
||
|
|
||
|
if (!TestDynamicObjectLOS(vNewPathPos, vNextPt))
|
||
|
continue;
|
||
|
|
||
|
pPathRequest->m_PathPoints[i] = vNewPathPos;
|
||
|
pPathRequest->m_PathPolys[i] = pNewPoly;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We do another pass of string pull, in case we can get rid of smaller quirky points hidden behind corners
|
||
|
// This might defeat our purpose slightly though, we should only string pull quirks so we might need to
|
||
|
// improve this part. Maybe it is better to avoid the quirk in the pull out instead though.
|
||
|
if (bDoStringPull)
|
||
|
{
|
||
|
for (int i = 2; i < nPoints; ++i)
|
||
|
{
|
||
|
const Vector3 vPrevPrevPt = pPathRequest->m_PathPoints[i-2];
|
||
|
const Vector3 vPrevPt = pPathRequest->m_PathPoints[i-1];
|
||
|
const Vector3 vThisPt = pPathRequest->m_PathPoints[i];
|
||
|
const Vector3 vNextPt = pPathRequest->m_PathPoints[i+1];
|
||
|
// const Vector3 vNextNextPt = pPathRequest->m_PathPoints[i+2];
|
||
|
|
||
|
// Not having valid polys might cause a crash?
|
||
|
TNavMeshPoly* pPrevPrevPoly = pPathRequest->m_PathPolys[i-2];
|
||
|
TNavMeshPoly* pPrevPoly = pPathRequest->m_PathPolys[i-1];
|
||
|
TNavMeshPoly* pThisPoly = pPathRequest->m_PathPolys[i];
|
||
|
TNavMeshPoly* pNextPoly = pPathRequest->m_PathPolys[i+1];
|
||
|
if (!pPrevPoly || !pNextPoly)
|
||
|
continue;
|
||
|
|
||
|
// Don't pull and stuff if we got any special action near, such as climbs
|
||
|
if (pPathRequest->m_WaypointFlags[i-1].GetSpecialAction() || pPathRequest->m_WaypointFlags[i+1].GetSpecialAction())
|
||
|
continue;
|
||
|
|
||
|
// Pull out should not cause greater dist than this
|
||
|
const float fMinDistSqr = rage::square(fPushDist * 2.0f);
|
||
|
|
||
|
// Don't stringpull when points are not close as that could defeat our purpose
|
||
|
if (vThisPt.Dist2(vPrevPt) > fMinDistSqr && vThisPt.Dist2(vNextPt) > fMinDistSqr)
|
||
|
continue;
|
||
|
|
||
|
// And the angle has to make a sharp corner "quirk"
|
||
|
static const float fTolerance = 0.15f; // Almost 90deg
|
||
|
if (!IsPointsMakingAQuirk(vPrevPrevPt, vPrevPt, vThisPt, vNextPt, fTolerance) && vThisPt.Dist2(vPrevPt) > rage::square(0.1f))
|
||
|
continue;
|
||
|
|
||
|
int iPointToRemove = i;
|
||
|
|
||
|
// Do those LOS tests...
|
||
|
IncTimeStamp();
|
||
|
if (!TestNavMeshLOS(vPrevPt, vNextPt, pPrevPoly, pNextPoly, NULL, m_Vars.m_iLosFlags, domain) || !TestDynamicObjectLOS(vPrevPt, vNextPt))
|
||
|
{
|
||
|
// Not having valid polys might cause a crash
|
||
|
if (!pPrevPrevPoly || !pThisPoly)
|
||
|
continue;
|
||
|
|
||
|
// Shit ok, let's try with the previous one instead
|
||
|
// Don't pull and stuff if we got any special action near, such as climbs
|
||
|
if (pPathRequest->m_WaypointFlags[i-2].GetSpecialAction() || pPathRequest->m_WaypointFlags[i].GetSpecialAction())
|
||
|
continue;
|
||
|
|
||
|
// Don't stringpull when points are not close as that could defeat our purpose
|
||
|
if (vThisPt.Dist2(vPrevPt) > fMinDistSqr)
|
||
|
continue;
|
||
|
|
||
|
// Do those LOS tests...
|
||
|
IncTimeStamp();
|
||
|
if (!TestNavMeshLOS(vPrevPrevPt, vThisPt, pPrevPrevPoly, pThisPoly, NULL, m_Vars.m_iLosFlags, domain) || !TestDynamicObjectLOS(vPrevPrevPt, vThisPt))
|
||
|
continue;
|
||
|
|
||
|
// So remove the one behind us instead!
|
||
|
iPointToRemove = i - 1;
|
||
|
}
|
||
|
|
||
|
for (int j = iPointToRemove; j < nPoints; ++j)
|
||
|
{
|
||
|
pPathRequest->m_PathPoints[j] = pPathRequest->m_PathPoints[j+1];
|
||
|
pPathRequest->m_PathPolys[j] = pPathRequest->m_PathPolys[j+1];
|
||
|
pPathRequest->m_WaypointFlags[j] = pPathRequest->m_WaypointFlags[j+1];
|
||
|
}
|
||
|
|
||
|
--i;
|
||
|
--nPoints;
|
||
|
--pPathRequest->m_iNumPoints;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if __ASSERT
|
||
|
for(s32 t=0; t<pPathRequest->m_iNumPoints; t++)
|
||
|
{
|
||
|
Assert( rage::FPIsFinite(pPathRequest->m_PathPoints[t].x) && rage::FPIsFinite(pPathRequest->m_PathPoints[t].y) && rage::FPIsFinite(pPathRequest->m_PathPoints[t].z) );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if !__FINAL
|
||
|
m_PerfTimer->Stop();
|
||
|
pPathRequest->m_fMillisecsToPullOutFromEdges = (float) m_PerfTimer->GetTimeMS();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
//****************************************************************************
|
||
|
// CalcPathControlPoints
|
||
|
// This is a new approach to the problem which SmoothPath() tries to fix.
|
||
|
// At each sharp bend in the path, a bezier curve is created to try and
|
||
|
// cut off as much of the sharp corner as possible. The bezier is evaluated
|
||
|
// to a fixed resolution, and a number of line-segments are tested against
|
||
|
// the world in an attempt to determine whether the new route is clear of
|
||
|
// obstacles. If so, then some information is stored with the path about
|
||
|
// the control points which were chosen. If obstacles do exist, then we can
|
||
|
// attempt again with a different set of control points - or abort the
|
||
|
// operation for this corner in the path.
|
||
|
|
||
|
void
|
||
|
CPathServerThread::CalcPathBezierControlPoints(CPathRequest * pPathRequest)
|
||
|
{
|
||
|
// If this is a straight line, then there's nothing to do..
|
||
|
if(pPathRequest->m_iNumPoints < 3)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#if !__FINAL
|
||
|
m_PerfTimer->Reset();
|
||
|
m_PerfTimer->Start();
|
||
|
#endif
|
||
|
|
||
|
static const float fMinSmoothDist = 1.0f;
|
||
|
static const float fSplineStep = 1.0f / ((float)PATHSPLINE_NUM_TEST_SUBDIVISIONS);
|
||
|
|
||
|
// Use this variable to prevent spline overlaps
|
||
|
float fDistPreviousPt3ToNext = FLT_MAX;
|
||
|
|
||
|
const aiNavDomain domain = pPathRequest->GetMeshDataSet();
|
||
|
|
||
|
for(s32 iCurrent=1; iCurrent<pPathRequest->m_iNumPoints-1; iCurrent++)
|
||
|
{
|
||
|
const int iLast = iCurrent-1;
|
||
|
const int iNext = iCurrent+1;
|
||
|
const Vector3 & vLast = pPathRequest->m_PathPoints[iLast];
|
||
|
const Vector3 & vCurrent = pPathRequest->m_PathPoints[iCurrent];
|
||
|
const Vector3 & vNext = pPathRequest->m_PathPoints[iNext];
|
||
|
|
||
|
const Vector3 vLastToCurrent = vCurrent - vLast;
|
||
|
const Vector3 vCurrentToNext = vNext - vCurrent;
|
||
|
const float fDistLastToCurrent = vLastToCurrent.Mag();
|
||
|
const float fDistCurrentToNext = vCurrentToNext.Mag();
|
||
|
|
||
|
// If the current waypoint is a climb/drop/etc - then don't use splines here
|
||
|
const u32 iWpt = pPathRequest->m_WaypointFlags[iCurrent].GetSpecialActionFlags();
|
||
|
if(IsSpecialActionClimbOrDrop(iWpt))
|
||
|
{
|
||
|
fDistPreviousPt3ToNext = FLT_MAX;
|
||
|
continue;
|
||
|
}
|
||
|
if(fDistLastToCurrent < fMinSmoothDist || fDistCurrentToNext < fMinSmoothDist)
|
||
|
{
|
||
|
fDistPreviousPt3ToNext = FLT_MAX;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// If the angle between the two waypoints is shallow, then don't bother with using splines
|
||
|
Vector3 vLastToCurrentNormalized = vLastToCurrent;
|
||
|
Vector3 vCurrentToNextNormalized = vCurrentToNext;
|
||
|
NormalizeAndMag(vLastToCurrentNormalized);
|
||
|
vCurrentToNextNormalized.Normalize();
|
||
|
static const float fDeadAhead = rage::Cosf(( DtoR * 8.0f));
|
||
|
if(DotProduct(vLastToCurrentNormalized, vCurrentToNextNormalized) > fDeadAhead)
|
||
|
{
|
||
|
fDistPreviousPt3ToNext = FLT_MAX;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
Vector3 vCtrlPt1, vCtrlPt2, vCtrlPt3, vCtrlPt4;
|
||
|
|
||
|
for(int s=0; s<PATHSPLINE_NUM_TEST_DISTANCES; s++)
|
||
|
{
|
||
|
// If the distance from vCtrlPt2->vCurrent is MORE than the previous vCtrlPt3->vNext
|
||
|
// Then we CANNOT use this enumerant value of 's' since it will OVERLAP the last spline,
|
||
|
// and cause awkward situations when following the path (such as doubling-back)
|
||
|
vCtrlPt2 = vLast + (vLastToCurrent * g_fPathSplineDists[s]);
|
||
|
|
||
|
const float fDistPt2ToCurrent = (vCurrent-vCtrlPt2).Mag();
|
||
|
if(fDistPt2ToCurrent > fDistPreviousPt3ToNext)
|
||
|
{
|
||
|
pPathRequest->m_PathResultInfo.m_fPathCtrlPt2TVals[iCurrent] = 1.0f - (fDistPreviousPt3ToNext/fDistLastToCurrent);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pPathRequest->m_PathResultInfo.m_fPathCtrlPt2TVals[iCurrent] = 0.0f;
|
||
|
}
|
||
|
|
||
|
CalcPathSplineCtrlPts(s, pPathRequest->m_PathResultInfo.m_fPathCtrlPt2TVals[iCurrent], vLast, vNext, vLastToCurrent, vCurrentToNext, vCtrlPt1, vCtrlPt2, vCtrlPt3, vCtrlPt4);
|
||
|
|
||
|
// Now create PATHSPLINE_NUM_TEST_SUBDIVISIONS points along this spline.
|
||
|
Vector3 vSplinePtA = vCtrlPt2;
|
||
|
Vector3 vEndPos;
|
||
|
float u;
|
||
|
bool bSplineOk = true;
|
||
|
TNavMeshPoly * pEndingPoly = NULL;
|
||
|
|
||
|
// Efficiently find the initial pEndingPoly.
|
||
|
// We already know which polygon vCurrent is upon, so we can do a navmesh LOS
|
||
|
// to vCtrlPt2 (the starting point for this spline curve) to obtain the polygon
|
||
|
// directly under that point. This should be hugely more efficient than doing
|
||
|
// a full polygon query in the navmesh.
|
||
|
TNavMeshPoly * pPolyUnderCurrent = pPathRequest->m_PathPolys[iCurrent];
|
||
|
// pPolyUnderCurrent may be NULL if this point was added before/after a non-standard adjacency
|
||
|
// such as a climb or drop-down, etc.
|
||
|
if(pPolyUnderCurrent)
|
||
|
{
|
||
|
IncTimeStamp();
|
||
|
const Vector2 vDirToCtrlPt2(vCtrlPt2.x - vCurrent.x, vCtrlPt2.y - vCurrent.y);
|
||
|
pEndingPoly = TestNavMeshLOS(vCurrent, vDirToCtrlPt2, &vEndPos, pPolyUnderCurrent, m_Vars.m_iLosFlags, domain);
|
||
|
|
||
|
if(pEndingPoly)
|
||
|
{
|
||
|
for(u=fSplineStep; u<=1.0f; u+=fSplineStep)
|
||
|
{
|
||
|
const Vector3 vSplinePtB = PathSpline(vCtrlPt1, vCtrlPt2, vCtrlPt3, vCtrlPt4, u);
|
||
|
const Vector2 vDir(vSplinePtB.x - vSplinePtA.x, vSplinePtB.y - vSplinePtA.y);
|
||
|
|
||
|
IncTimeStamp();
|
||
|
pEndingPoly = TestNavMeshLOS(vSplinePtA, vDir, &vEndPos, pEndingPoly, m_Vars.m_iLosFlags, domain);
|
||
|
|
||
|
if(pEndingPoly && !TestDynamicObjectLOS(vSplinePtA, vSplinePtB))
|
||
|
pEndingPoly = NULL;
|
||
|
|
||
|
if(!pEndingPoly)
|
||
|
{
|
||
|
// Fail!
|
||
|
bSplineOk = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
vSplinePtA = vSplinePtB;
|
||
|
}
|
||
|
// If this spline worked ok (ie. all the test segments had a clear LOS, then we'll use this)
|
||
|
// Otherwise the algorithm will test another spline for this corner, using different ctrl pts.
|
||
|
if(bSplineOk)
|
||
|
{
|
||
|
pPathRequest->m_PathResultInfo.m_iPathSplineDistances[iCurrent] = s;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// We have a spline at this corner, so calculate this distance to use in the overlap test at the next corner
|
||
|
if(pPathRequest->m_PathResultInfo.m_iPathSplineDistances[iCurrent] != -1)
|
||
|
{
|
||
|
fDistPreviousPt3ToNext = (vNext - vCtrlPt3).Mag(); // xy?
|
||
|
}
|
||
|
// No spline at this corner, so reset this value
|
||
|
else
|
||
|
{
|
||
|
fDistPreviousPt3ToNext = FLT_MAX;
|
||
|
pPathRequest->m_PathResultInfo.m_fPathCtrlPt2TVals[iCurrent] = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if __ASSERT
|
||
|
for(s32 t=0; t<pPathRequest->m_iNumPoints; t++)
|
||
|
{
|
||
|
Assert( rage::FPIsFinite(pPathRequest->m_PathPoints[t].x) && rage::FPIsFinite(pPathRequest->m_PathPoints[t].y) && rage::FPIsFinite(pPathRequest->m_PathPoints[t].z) );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if !__FINAL
|
||
|
m_PerfTimer->Stop();
|
||
|
pPathRequest->m_fMillisecsToSmoothPath = (float) m_PerfTimer->GetTimeMS();
|
||
|
#endif // !__FINAL
|
||
|
}
|
||
|
|
||
|
//******************************************************************************
|
||
|
// SmoothPath
|
||
|
// Smooth the path by identifying sharp corners, and cutting the corners off.
|
||
|
// We do this by moving one vertex & inserting another.
|
||
|
// If this creates a path which is larger than 'iMaxNumPoints' then we will
|
||
|
// have to truncate the path up to where we ended processing it..
|
||
|
//******************************************************************************
|
||
|
|
||
|
// Add all the path points into a list
|
||
|
atArray<Vector3> newPoints(0, 32);
|
||
|
atArray<TNavMeshWaypointFlag> newWayPoints(0, 32);
|
||
|
|
||
|
void
|
||
|
CPathServerThread::SmoothPath(CPathRequest * pPathRequest)
|
||
|
{
|
||
|
// If this is a straight line, then there's nothing to do..
|
||
|
if(pPathRequest->m_iNumPoints < 3)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#if !__FINAL
|
||
|
m_PerfTimer->Reset();
|
||
|
m_PerfTimer->Start();
|
||
|
#endif
|
||
|
|
||
|
static const float fCosSharpAngle = rage::Cosf(( DtoR * 67.5f));
|
||
|
static const float fSmallDistanceSqr = 0.35f * 0.35f;
|
||
|
|
||
|
static const int NUM_TESTS = 5;
|
||
|
float fLastToCurrTestDists[NUM_TESTS];
|
||
|
float fCurrToNextTestDists[NUM_TESTS];
|
||
|
Vector3 vTmp;
|
||
|
|
||
|
newPoints.clear();
|
||
|
newWayPoints.clear();
|
||
|
|
||
|
s32 p;
|
||
|
for(p=0; p<pPathRequest->m_iNumPoints; p++)
|
||
|
{
|
||
|
newPoints.Append() = pPathRequest->m_PathPoints[p];
|
||
|
newWayPoints.Append() = pPathRequest->m_WaypointFlags[p];
|
||
|
}
|
||
|
|
||
|
bool bMadeChanges = false;
|
||
|
int iCurrentPt = 1;
|
||
|
|
||
|
const aiNavDomain domain = pPathRequest->GetMeshDataSet();
|
||
|
|
||
|
while(iCurrentPt < newPoints.GetCount()-1 && iCurrentPt < MAX_NUM_PATH_POINTS)
|
||
|
{
|
||
|
// Can only cut corners which aren't special waypoints..
|
||
|
if(newWayPoints[iCurrentPt].m_iSpecialActionFlags != 0)
|
||
|
{
|
||
|
iCurrentPt++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const Vector3 & vLastPt = newPoints[iCurrentPt-1];
|
||
|
const Vector3 & vCurrentPt = newPoints[iCurrentPt];
|
||
|
const Vector3 & vNextPt = newPoints[iCurrentPt+1];
|
||
|
|
||
|
Vector3 vLastToCurr = vCurrentPt - vLastPt;
|
||
|
Vector3 vCurrToNext = vNextPt - vCurrentPt;
|
||
|
|
||
|
float fLastToCurrDist2 = vLastToCurr.Mag2();
|
||
|
float fCurrToNextDist2 = vCurrToNext.Mag2();
|
||
|
|
||
|
if(fLastToCurrDist2 < fSmallDistanceSqr || fCurrToNextDist2 < fSmallDistanceSqr)
|
||
|
{
|
||
|
iCurrentPt++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
vLastToCurr.NormalizeFast();
|
||
|
vCurrToNext.NormalizeFast();
|
||
|
|
||
|
const float fDot = Dot(vLastToCurr, vCurrToNext);
|
||
|
if(fDot < fCosSharpAngle)
|
||
|
{
|
||
|
bool bGotLos=false;
|
||
|
Vector3 vMidPointFromLast, vMidPointToNext;
|
||
|
|
||
|
float fLastToCurrDist = Sqrtf(fLastToCurrDist2);
|
||
|
float fCurrToNextDist = Sqrtf(fCurrToNextDist2);
|
||
|
|
||
|
// Work out a number of test distances for each vector
|
||
|
fLastToCurrTestDists[0] = fLastToCurrDist * 0.5f;
|
||
|
fLastToCurrTestDists[1] = fLastToCurrDist * 0.75f;
|
||
|
fLastToCurrTestDists[2] = fLastToCurrDist * 0.85f;
|
||
|
fLastToCurrTestDists[3] = Max(fLastToCurrDist - 1.0f, 1.0f);
|
||
|
fLastToCurrTestDists[4] = Max(fLastToCurrDist - 0.5f, 1.0f);
|
||
|
|
||
|
fCurrToNextTestDists[0] = fCurrToNextDist * 0.5f;
|
||
|
fCurrToNextTestDists[1] = fCurrToNextDist * 0.25f;
|
||
|
fCurrToNextTestDists[2] = fCurrToNextDist * 0.15f;
|
||
|
fCurrToNextTestDists[3] = Min(1.0f, fCurrToNextDist);
|
||
|
fCurrToNextTestDists[4] = Min(0.5f, fCurrToNextDist);
|
||
|
|
||
|
for(int iTest=0; iTest<NUM_TESTS; iTest++)
|
||
|
{
|
||
|
vMidPointFromLast = vLastPt + (vLastToCurr * fLastToCurrTestDists[iTest]);
|
||
|
vMidPointToNext = vCurrentPt + (vCurrToNext * fCurrToNextTestDists[iTest]);
|
||
|
|
||
|
// Is there a clear navmesh LOS between these new midpoints?
|
||
|
|
||
|
u32 iStartNavMesh = CPathServer::GetNavMeshIndexFromPosition(vMidPointFromLast, domain);
|
||
|
if(iStartNavMesh == NAVMESH_NAVMESH_INDEX_NONE)
|
||
|
continue;
|
||
|
CNavMesh * pStartNavMesh = CPathServer::GetNavMeshFromIndex(iStartNavMesh, domain);
|
||
|
if(!pStartNavMesh)
|
||
|
continue;
|
||
|
u32 iStartPolyIndex = pStartNavMesh->GetPolyBelowPoint(vMidPointFromLast + Vector3(0, 0, 1.0f), vTmp, 5.0f);
|
||
|
if(iStartPolyIndex == NAVMESH_POLY_INDEX_NONE)
|
||
|
continue;
|
||
|
TNavMeshPoly * pStartPoly = pStartNavMesh->GetPoly(iStartPolyIndex);
|
||
|
Assert(pStartPoly);
|
||
|
|
||
|
u32 iEndNavMesh = CPathServer::GetNavMeshIndexFromPosition(vMidPointToNext, domain);
|
||
|
if(iEndNavMesh == NAVMESH_NAVMESH_INDEX_NONE)
|
||
|
continue;
|
||
|
CNavMesh * pEndNavMesh = CPathServer::GetNavMeshFromIndex(iEndNavMesh, domain);
|
||
|
if(!pEndNavMesh)
|
||
|
continue;
|
||
|
u32 iEndPolyIndex = pEndNavMesh->GetPolyBelowPoint(vMidPointToNext + Vector3(0, 0, 1.0f), vTmp, 5.0f);
|
||
|
if(iEndPolyIndex == NAVMESH_POLY_INDEX_NONE)
|
||
|
continue;
|
||
|
TNavMeshPoly * pEndPoly = pEndNavMesh->GetPoly(iEndPolyIndex);
|
||
|
Assert(pEndPoly);
|
||
|
|
||
|
IncTimeStamp();
|
||
|
bGotLos = TestNavMeshLOS(vMidPointFromLast, vMidPointToNext, pEndPoly, pStartPoly, NULL, m_Vars.m_iLosFlags, domain);
|
||
|
if(bGotLos)
|
||
|
{
|
||
|
bGotLos = TestDynamicObjectLOS(vMidPointFromLast, vMidPointToNext);
|
||
|
if(bGotLos)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(bGotLos)
|
||
|
{
|
||
|
newPoints[iCurrentPt] = vMidPointFromLast;
|
||
|
newWayPoints[iCurrentPt].Clear();
|
||
|
|
||
|
newPoints.Insert(iCurrentPt+1) = vMidPointToNext;
|
||
|
newWayPoints.Insert(iCurrentPt+1).Clear();
|
||
|
|
||
|
bMadeChanges = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
iCurrentPt++;
|
||
|
}
|
||
|
|
||
|
// Now copy the new points back into the path request
|
||
|
if(bMadeChanges)
|
||
|
{
|
||
|
s32 iNum = Min(newPoints.GetCount(), MAX_NUM_PATH_POINTS);
|
||
|
for(p=0; p<iNum; p++)
|
||
|
{
|
||
|
pPathRequest->m_PathPoints[p] = newPoints[p];
|
||
|
pPathRequest->m_WaypointFlags[p] = newWayPoints[p];
|
||
|
pPathRequest->m_PathPolys[p] = NULL;
|
||
|
}
|
||
|
pPathRequest->m_iNumPoints = iNum;
|
||
|
}
|
||
|
|
||
|
#if !__FINAL
|
||
|
m_PerfTimer->Stop();
|
||
|
pPathRequest->m_fMillisecsToSmoothPath = (float) m_PerfTimer->GetTimeMS();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
bool
|
||
|
CPathServerThread::GetSharedEdgeEndPoints(TNavMeshPoly * pPoly1, TNavMeshPoly * pPoly2, Vector3 & vPoint1, Vector3 & vPoint2, aiNavDomain domain)
|
||
|
{
|
||
|
CNavMesh * pPoly1NavMesh = CPathServer::GetNavMeshFromIndex(pPoly1->GetNavMeshIndex(), domain);
|
||
|
if(!pPoly1NavMesh)
|
||
|
return false;
|
||
|
|
||
|
CNavMesh * pPoly2NavMesh = CPathServer::GetNavMeshFromIndex(pPoly2->GetNavMeshIndex(), domain);
|
||
|
if(!pPoly2NavMesh)
|
||
|
return false;
|
||
|
|
||
|
// Go through pPoly1's edges
|
||
|
for(u32 v=0; v<pPoly1->GetNumVertices(); v++)
|
||
|
{
|
||
|
const TAdjPoly & adjPoly = pPoly1NavMesh->GetAdjacentPoly(pPoly1->GetFirstVertexIndex()+v);
|
||
|
|
||
|
// Adjacent poly is in same NavMesh as pPoly2
|
||
|
if(adjPoly.GetAdjacencyType()==ADJACENCY_TYPE_NORMAL && adjPoly.GetNavMeshIndex(pPoly1NavMesh->GetAdjacentMeshes()) == pPoly2->GetNavMeshIndex())
|
||
|
{
|
||
|
// Is this pPoly2 ?
|
||
|
if(pPoly2NavMesh->GetPoly(adjPoly.GetPolyIndex()) == pPoly2)
|
||
|
{
|
||
|
// Okay, so we've found the shared edge between the 2 polys.
|
||
|
// Now take the line defined by "vBestPointInPoly1 to vBestPointInPoly2" and test
|
||
|
// both endpoints of the shared edge against it. Boint endpoints should be
|
||
|
// to the same side of the line.
|
||
|
// The vertex result is to be the endpoint closest to the line.
|
||
|
|
||
|
const int nextv = (v+1 < pPoly1->GetNumVertices()) ? v+1 : 0;
|
||
|
|
||
|
pPoly1NavMesh->GetVertex( pPoly1NavMesh->GetPolyVertexIndex(pPoly1, v), vPoint1 );
|
||
|
pPoly1NavMesh->GetVertex( pPoly1NavMesh->GetPolyVertexIndex(pPoly1, nextv), vPoint2 );
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
// NAME : ObtainEntryEdgeViaConnectionPoly
|
||
|
// PURPOSE : Given pParentPoly and pChildPoly, which are separated by any number of connection polys -
|
||
|
// we wish to find which edge pChildPoly was entered by.
|
||
|
bool CPathServerThread::ObtainEntryEdgeViaConnectionPoly(
|
||
|
TNavMeshPoly * pStartPoly, CNavMesh * pStartNavMesh,
|
||
|
TNavMeshPoly * pTargetPoly, CNavMesh * pTargetNavMesh,
|
||
|
TNavMeshPoly * pRealOrConnectionPoly, CNavMesh * pNavMesh,
|
||
|
TNavMeshPoly * pParentPoly, CNavMesh * pParentNavMesh,
|
||
|
TNavMeshAndPoly & outPrevNavMeshAndPoly,
|
||
|
const Vector3 & vStartExitEdge, const Vector3 & vStartExitEdgeV1, const Vector3 & vStartExitEdgeV2, const aiNavDomain domain ) const
|
||
|
{
|
||
|
// Avoid possibility of infinite recursion by timestamping polygons we visit
|
||
|
pRealOrConnectionPoly->m_TimeStamp = m_iNavMeshPolyTimeStamp;
|
||
|
|
||
|
// When we recurse down to a real polygon, we see if this is the pChildPoly
|
||
|
if( !pRealOrConnectionPoly->GetIsDegenerateConnectionPoly())
|
||
|
{
|
||
|
if(pRealOrConnectionPoly == pTargetPoly)
|
||
|
{
|
||
|
Assert(pParentNavMesh && pParentPoly);
|
||
|
if(pParentNavMesh && pParentPoly)
|
||
|
{
|
||
|
outPrevNavMeshAndPoly.m_iNavMeshIndex = pParentNavMesh->GetIndexOfMesh();
|
||
|
outPrevNavMeshAndPoly.m_iPolyIndex = pParentNavMesh->GetPolyIndex(pParentPoly);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
// For connection polygons, we find all edges pointing in opposite direction to the exit edge
|
||
|
else
|
||
|
{
|
||
|
s32 lasta = pRealOrConnectionPoly->GetNumVertices()-1;
|
||
|
Vector3 vLast, vVert, vAdjacentEdge;
|
||
|
|
||
|
s32 lastvi = pNavMesh->GetPolyVertexIndex(pRealOrConnectionPoly, lasta);
|
||
|
pNavMesh->GetVertex( lastvi, vLast);
|
||
|
|
||
|
for( s32 a=0; a<pRealOrConnectionPoly->GetNumVertices(); a++ )
|
||
|
{
|
||
|
const s32 vi = pNavMesh->GetPolyVertexIndex(pRealOrConnectionPoly, a);
|
||
|
|
||
|
const TAdjPoly & adjacentPoly = pNavMesh->GetAdjacentPoly(lastvi);
|
||
|
const TNavMeshIndex iNavMesh = adjacentPoly.GetNavMeshIndex( pNavMesh->GetAdjacentMeshes() );
|
||
|
if( iNavMesh != NAVMESH_NAVMESH_INDEX_NONE && adjacentPoly.GetAdjacencyType()==ADJACENCY_TYPE_NORMAL )
|
||
|
{
|
||
|
pNavMesh->GetVertex( vi, vVert);
|
||
|
vAdjacentEdge = vVert - vLast;
|
||
|
|
||
|
NAVMESH_OPTIMISATIONS_OFF_ONLY( Assert(vAdjacentEdge.Mag2() > SMALL_FLOAT); )
|
||
|
|
||
|
if(vAdjacentEdge.Mag2() > SMALL_FLOAT)
|
||
|
{
|
||
|
vAdjacentEdge.NormalizeFast();
|
||
|
|
||
|
const float fDot = (vAdjacentEdge.x * vStartExitEdge.x) + (vAdjacentEdge.y * vStartExitEdge.y);
|
||
|
|
||
|
NAVMESH_OPTIMISATIONS_OFF_ONLY( Assert(Abs(fDot) > 0.707f); )
|
||
|
|
||
|
if( fDot > 0.0f )
|
||
|
{
|
||
|
// Test for interval overlap
|
||
|
float fExitEdgeMin = vAdjacentEdge.Dot( vStartExitEdgeV1 );
|
||
|
float fExitEdgeMax = vAdjacentEdge.Dot( vStartExitEdgeV2 );
|
||
|
if(fExitEdgeMin > fExitEdgeMax)
|
||
|
SwapEm(fExitEdgeMin, fExitEdgeMax);
|
||
|
|
||
|
float fAdjacentEdgeMin = vAdjacentEdge.Dot( vLast );
|
||
|
float fAdjacentEdgeMax = vAdjacentEdge.Dot( vVert );
|
||
|
if(fAdjacentEdgeMin > fAdjacentEdgeMax)
|
||
|
SwapEm(fAdjacentEdgeMin, fAdjacentEdgeMax);
|
||
|
|
||
|
const float fIntervalDistance = (fExitEdgeMin < fAdjacentEdgeMin) ?
|
||
|
fAdjacentEdgeMin - fExitEdgeMax : fExitEdgeMin - fAdjacentEdgeMax;
|
||
|
|
||
|
// Overlap exists, so this is an edge leaving the connection poly on the opposite side to the
|
||
|
// exit edge of the previous polyg) and with overlapping extents : ie. a candidate to visit
|
||
|
if(fIntervalDistance < 0.0f)
|
||
|
{
|
||
|
CNavMesh * pAdjNavMesh = CPathServer::GetNavMeshFromIndex(iNavMesh, domain);
|
||
|
if(pAdjNavMesh)
|
||
|
{
|
||
|
TNavMeshPoly * pNextPoly = pAdjNavMesh->GetPoly( pNavMesh->GetAdjacentPoly(lastvi).GetPolyIndex() );
|
||
|
// Only visit a polygon we've not already visited
|
||
|
if( pNextPoly->m_TimeStamp != m_iNavMeshPolyTimeStamp )
|
||
|
{
|
||
|
if( ObtainEntryEdgeViaConnectionPoly( pStartPoly, pStartNavMesh, pTargetPoly, pTargetNavMesh, pNextPoly, pAdjNavMesh, pRealOrConnectionPoly, pNavMesh, outPrevNavMeshAndPoly, vStartExitEdge, vStartExitEdgeV1, vStartExitEdgeV2, domain ) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
vLast = vVert;
|
||
|
lasta = a;
|
||
|
lastvi = vi;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const float g_fEdgeOffsetDist = 0.1f;
|
||
|
|
||
|
bool CPathServerThread::GetPointAlongSharedEdge(
|
||
|
const Vector3 & vBestPointInParentPoly,
|
||
|
const Vector3 & vBestPointInChildPoly,
|
||
|
TNavMeshPoly * pParentPoly,
|
||
|
TNavMeshPoly * pChildPoly,
|
||
|
float fEntityRadius,
|
||
|
Vector3 & vPoint,
|
||
|
bool & bOut_LineActuallyIntersectsEdge,
|
||
|
aiNavDomain domain)
|
||
|
{
|
||
|
if(pChildPoly->m_PathParentPoly==NULL)
|
||
|
{
|
||
|
Assert(pChildPoly->TestFlags(NAVMESHPOLY_ALTERNATIVE_STARTING_POLY));
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
CNavMesh * pParentNavMesh = CPathServer::GetNavMeshFromIndex(pParentPoly->GetNavMeshIndex(), domain);
|
||
|
Assert(pParentNavMesh);
|
||
|
CNavMesh * pChildNavMesh = CPathServer::GetNavMeshFromIndex(pChildPoly->GetNavMeshIndex(), domain);
|
||
|
Assert(pChildNavMesh);
|
||
|
|
||
|
// Find out if we reached the pChildPoly via a connection polygon.
|
||
|
// If so we will have to perform some more complex logic, if not then we
|
||
|
// can simply traverse the child's edges to find the parent.
|
||
|
|
||
|
const s32 iParentEdge = pChildPoly->m_Struct4.m_iParentExitEdge;
|
||
|
const TAdjPoly adjPoly = pParentNavMesh->GetAdjacentPoly( pParentPoly->GetFirstVertexIndex()+iParentEdge );
|
||
|
|
||
|
const u32 iAdjNavMesh = adjPoly.GetNavMeshIndex( pParentNavMesh->GetAdjacentMeshes() );
|
||
|
if(iAdjNavMesh==NAVMESH_NAVMESH_INDEX_NONE)
|
||
|
return false;
|
||
|
|
||
|
CNavMesh * pAdjNavMesh = CPathServer::GetNavMeshFromIndex(iAdjNavMesh, domain);
|
||
|
if(!pAdjNavMesh)
|
||
|
return false;
|
||
|
|
||
|
TNavMeshPoly * pAdjPoly = pAdjNavMesh->GetPoly(adjPoly.GetPolyIndex());
|
||
|
|
||
|
Vector3 vert1, vert2;
|
||
|
|
||
|
if(pAdjPoly->GetIsDegenerateConnectionPoly())
|
||
|
{
|
||
|
TNavMeshAndPoly prevNavMeshAndPoly;
|
||
|
prevNavMeshAndPoly.Reset();
|
||
|
|
||
|
Vector3 vParentV1, vParentV2, vParentEdge;
|
||
|
pParentNavMesh->GetVertex( pParentNavMesh->GetPolyVertexIndex( pParentPoly, iParentEdge ), vParentV1 );
|
||
|
pParentNavMesh->GetVertex( pParentNavMesh->GetPolyVertexIndex( pParentPoly, (iParentEdge+1)%pParentPoly->GetNumVertices() ), vParentV2 );
|
||
|
vParentEdge = vParentV2 - vParentV1;
|
||
|
vParentEdge.Normalize();
|
||
|
|
||
|
IncTimeStamp();
|
||
|
if( !ObtainEntryEdgeViaConnectionPoly( pParentPoly, pParentNavMesh, pChildPoly, pChildNavMesh, pAdjPoly, pAdjNavMesh, NULL, NULL, prevNavMeshAndPoly, vParentEdge, vParentV1, vParentV2, domain ) )
|
||
|
{
|
||
|
#if NAVMESH_OPTIMISATIONS_OFF
|
||
|
//Assert(false);
|
||
|
#endif
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if(prevNavMeshAndPoly.m_iNavMeshIndex == NAVMESH_NAVMESH_INDEX_NONE)
|
||
|
return false;
|
||
|
|
||
|
CNavMesh * pPriorNavMesh = CPathServer::GetNavMeshFromIndex(prevNavMeshAndPoly.m_iNavMeshIndex, domain);
|
||
|
Assert(pPriorNavMesh && pPriorNavMesh->GetIndexOfMesh()==NAVMESH_INDEX_TESSELLATION);
|
||
|
TNavMeshPoly * pPriorPoly = pPriorNavMesh->GetPoly(prevNavMeshAndPoly.m_iPolyIndex);
|
||
|
|
||
|
if(!GetSharedEdgeEndPoints(pPriorPoly, pChildPoly, vert1, vert2, domain))
|
||
|
return false;
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(!GetSharedEdgeEndPoints(pParentPoly, pChildPoly, vert1, vert2, domain))
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Vector3 vEdge = vert2 - vert1;
|
||
|
|
||
|
if(vEdge.Mag2() > SMALL_FLOAT)
|
||
|
{
|
||
|
Assert(fEntityRadius >= PATHSERVER_PED_RADIUS);
|
||
|
|
||
|
fEntityRadius -= PATHSERVER_PED_RADIUS;
|
||
|
|
||
|
// Test to see if the line segments actually intersect.
|
||
|
// If so then we'll just return the intersection point.
|
||
|
if(CNavMesh::LineSegsIntersect2D(vBestPointInParentPoly, vBestPointInChildPoly, vert1, vert2, &vPoint)==SEGMENTS_INTERSECT)
|
||
|
{
|
||
|
bOut_LineActuallyIntersectsEdge = true;
|
||
|
|
||
|
// If we have a non-standard entity radius, we may have to offset this point along the edge
|
||
|
// to account for the difference between the entity's radius & the PATHSERVER_PED_RADIUS.
|
||
|
if(fEntityRadius > 0.0f)
|
||
|
{
|
||
|
vEdge.NormalizeFast();
|
||
|
Vector3 vVert1ToPoint = vPoint - vert1;
|
||
|
Vector3 vVert2ToPoint = vPoint - vert2;
|
||
|
const float fV1PLength = NormalizeAndMag(vVert1ToPoint);
|
||
|
const float fV2PLength = NormalizeAndMag(vVert2ToPoint);
|
||
|
|
||
|
if(fV1PLength < fEntityRadius)
|
||
|
vPoint += vEdge * (fEntityRadius-fV1PLength);
|
||
|
else if(fV2PLength < fEntityRadius)
|
||
|
vPoint -= vEdge * (fEntityRadius-fV2PLength);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bOut_LineActuallyIntersectsEdge = false;
|
||
|
|
||
|
// This vector is used to nudge the chosen point away from the poly edge a bit
|
||
|
Vector3 vPolyEdgeVec = vert2 - vert1;
|
||
|
vPolyEdgeVec.Normalize();
|
||
|
|
||
|
Vector3 vLineVec = vBestPointInChildPoly - vBestPointInParentPoly;
|
||
|
vLineVec.Normalize();
|
||
|
|
||
|
Vector3 vEdgeNormal;
|
||
|
vEdgeNormal.Cross(vLineVec, Vector3(0, 0, 1.0f));
|
||
|
float fEdgePlaneDist = - Dot(vEdgeNormal, vBestPointInParentPoly);
|
||
|
|
||
|
float fVert1Dist = Dot(vEdgeNormal, vert1) + fEdgePlaneDist;
|
||
|
float fVert2Dist = Dot(vEdgeNormal, vert2) + fEdgePlaneDist;
|
||
|
|
||
|
if(rage::Abs(fVert1Dist) < rage::Abs(fVert2Dist))
|
||
|
{
|
||
|
vPoint = vert1;
|
||
|
vPoint += vPolyEdgeVec * (fEntityRadius + g_fEdgeOffsetDist);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vPoint = vert2;
|
||
|
vPoint -= vPolyEdgeVec * (fEntityRadius + g_fEdgeOffsetDist);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool CPathServerThread::GetPointWithDynamicObjectVisiblityAlongSharedEdge(const Vector3 & vBestPointInPoly1, const Vector3 & vBestPointInPoly2, TNavMeshPoly * pPoly1, TNavMeshPoly * pPoly2, float fEntityRadius, Vector3 & vPoint, aiNavDomain domain)
|
||
|
{
|
||
|
if(pPoly2->m_PathParentPoly==NULL)
|
||
|
{
|
||
|
NAVMESH_OPTIMISATIONS_OFF_ONLY( Assert(pPoly2->TestFlags(NAVMESHPOLY_ALTERNATIVE_STARTING_POLY)); )
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
CNavMesh * pPoly1NavMesh = CPathServer::GetNavMeshFromIndex(pPoly1->GetNavMeshIndex(), domain);
|
||
|
Assert(pPoly1NavMesh);
|
||
|
CNavMesh * pPoly2NavMesh = CPathServer::GetNavMeshFromIndex(pPoly2->GetNavMeshIndex(), domain);
|
||
|
Assert(pPoly2NavMesh);
|
||
|
|
||
|
// Find out if we reached the pChildPoly via a connection polygon.
|
||
|
// If so we will have to perform some more complex logic, if not then we
|
||
|
// can simply traverse the child's edges to find the parent.
|
||
|
|
||
|
const s32 iParentEdge = pPoly2->m_Struct4.m_iParentExitEdge;
|
||
|
const TAdjPoly adjPoly = pPoly1NavMesh->GetAdjacentPoly( pPoly1->GetFirstVertexIndex()+iParentEdge );
|
||
|
|
||
|
const u32 iAdjNavMesh = adjPoly.GetNavMeshIndex( pPoly1NavMesh->GetAdjacentMeshes() );
|
||
|
if(iAdjNavMesh == NAVMESH_NAVMESH_INDEX_NONE)
|
||
|
return false;
|
||
|
|
||
|
CNavMesh * pAdjNavMesh = CPathServer::GetNavMeshFromIndex(iAdjNavMesh, domain);
|
||
|
if(!pAdjNavMesh)
|
||
|
return false;
|
||
|
|
||
|
TNavMeshPoly * pAdjPoly = pAdjNavMesh->GetPoly(adjPoly.GetPolyIndex());
|
||
|
|
||
|
Vector3 vEdgeVert1, vEdgeVert2;
|
||
|
|
||
|
if(pAdjPoly->GetIsDegenerateConnectionPoly())
|
||
|
{
|
||
|
TNavMeshAndPoly prevNavMeshAndPoly;
|
||
|
prevNavMeshAndPoly.Reset();
|
||
|
|
||
|
Vector3 vParentV1, vParentV2, vParentEdge;
|
||
|
pPoly1NavMesh->GetVertex( pPoly1NavMesh->GetPolyVertexIndex( pPoly1, iParentEdge ), vParentV1 );
|
||
|
pPoly1NavMesh->GetVertex( pPoly1NavMesh->GetPolyVertexIndex( pPoly1, (iParentEdge+1)%pPoly1->GetNumVertices() ), vParentV2 );
|
||
|
vParentEdge = vParentV2 - vParentV1;
|
||
|
vParentEdge.Normalize();
|
||
|
|
||
|
IncTimeStamp();
|
||
|
if( !ObtainEntryEdgeViaConnectionPoly( pPoly1, pPoly1NavMesh, pPoly2, pPoly2NavMesh, pAdjPoly, pAdjNavMesh, NULL, NULL, prevNavMeshAndPoly, vParentEdge, vParentV1, vParentV2, domain ) )
|
||
|
{
|
||
|
#if NAVMESH_OPTIMISATIONS_OFF
|
||
|
//Assert(false);
|
||
|
#endif
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if(prevNavMeshAndPoly.m_iNavMeshIndex == NAVMESH_NAVMESH_INDEX_NONE)
|
||
|
return false;
|
||
|
|
||
|
CNavMesh * pPriorNavMesh = CPathServer::GetNavMeshFromIndex(prevNavMeshAndPoly.m_iNavMeshIndex, domain);
|
||
|
Assert(pPriorNavMesh && pPriorNavMesh->GetIndexOfMesh()==NAVMESH_INDEX_TESSELLATION);
|
||
|
TNavMeshPoly * pPriorPoly = pPriorNavMesh->GetPoly(prevNavMeshAndPoly.m_iPolyIndex);
|
||
|
|
||
|
if(!GetSharedEdgeEndPoints(pPriorPoly, pPoly2, vEdgeVert1, vEdgeVert2, domain))
|
||
|
return false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(!GetSharedEdgeEndPoints(pPoly1, pPoly2, vEdgeVert1, vEdgeVert2, domain))
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Vector3 vEdge = vEdgeVert2 - vEdgeVert1;
|
||
|
|
||
|
// If we have a non-standard actor radius then we must move edge points in by the
|
||
|
// difference between the radius and the default radius which is baked into the navmesh
|
||
|
if(fEntityRadius != PATHSERVER_PED_RADIUS)
|
||
|
{
|
||
|
Assert(fEntityRadius > PATHSERVER_PED_RADIUS);
|
||
|
// Subtract the PATHSERVER_PED_RADIUS, which is baked into the navmesh
|
||
|
fEntityRadius -= PATHSERVER_PED_RADIUS;
|
||
|
|
||
|
// If this edge is too small for us to offset points by radius - they just give up on this
|
||
|
if(vEdge.Mag2() < PATHSERVER_PED_RADIUS*PATHSERVER_PED_RADIUS)
|
||
|
return false;
|
||
|
vEdge.NormalizeFast();
|
||
|
vEdge *= fEntityRadius;
|
||
|
vEdgeVert1 += vEdge;
|
||
|
vEdgeVert2 -= vEdge;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vEdge.NormalizeFast();
|
||
|
}
|
||
|
|
||
|
Vector3 vPointsToTry[3] = { vEdgeVert1 + (vEdge * g_fEdgeOffsetDist), vEdgeVert2 - (vEdge * g_fEdgeOffsetDist), (vEdgeVert1+vEdgeVert2)*0.5f };
|
||
|
float fDists[3] =
|
||
|
{
|
||
|
((vPointsToTry[0] - vBestPointInPoly1).Mag() + (vPointsToTry[0] - vBestPointInPoly2).Mag()),
|
||
|
((vPointsToTry[1] - vBestPointInPoly1).Mag() + (vPointsToTry[1] - vBestPointInPoly2).Mag()),
|
||
|
((vPointsToTry[2] - vBestPointInPoly1).Mag() + (vPointsToTry[2] - vBestPointInPoly2).Mag())
|
||
|
};
|
||
|
|
||
|
// Cheesy bubble-sort by total distance.
|
||
|
// NB: This is actually flawed, need to take a look...
|
||
|
if(fDists[2] < fDists[1])
|
||
|
{
|
||
|
SwapEm(fDists[1], fDists[2]);
|
||
|
SwapEm(vPointsToTry[1], vPointsToTry[2]);
|
||
|
}
|
||
|
if(fDists[1] < fDists[0])
|
||
|
{
|
||
|
SwapEm(fDists[0], fDists[1]);
|
||
|
SwapEm(vPointsToTry[0], vPointsToTry[1]);
|
||
|
}
|
||
|
|
||
|
for(int t=0; t<3; t++)
|
||
|
{
|
||
|
if(TestDynamicObjectLOS(vBestPointInPoly1, vPointsToTry[t]) && TestDynamicObjectLOS(vPointsToTry[t], vBestPointInPoly2))
|
||
|
{
|
||
|
vPoint = vPointsToTry[t];
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// None of the vPointsToTry had visibility.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CPathServer::ModifyMovementCosts(CPathFindMovementCosts* pMovementCosts, bool bWander, bool bFlee)
|
||
|
{
|
||
|
if(bWander)
|
||
|
{
|
||
|
pMovementCosts->m_fClimbHighPenalty *= CPathServerThread::ms_fClimbMultiplier_Wander;
|
||
|
pMovementCosts->m_fClimbLowPenalty *= CPathServerThread::ms_fClimbMultiplier_Wander;
|
||
|
}
|
||
|
else if(bFlee)
|
||
|
{
|
||
|
pMovementCosts->m_fClimbHighPenalty *= CPathServerThread::ms_fClimbMultiplier_Flee;
|
||
|
pMovementCosts->m_fClimbLowPenalty *= CPathServerThread::ms_fClimbMultiplier_Flee;
|
||
|
pMovementCosts->m_fEnterWaterPenalty *= CPathServerThread::ms_fWaterMultiplier_Flee;
|
||
|
pMovementCosts->m_fLeaveWaterPenalty *= CPathServerThread::ms_fWaterMultiplier_Flee;
|
||
|
pMovementCosts->m_fBeInWaterPenalty *= CPathServerThread::ms_fWaterMultiplier_Flee;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pMovementCosts->m_fClimbHighPenalty *= CPathServerThread::ms_fClimbMultiplier_ShortestPath;
|
||
|
pMovementCosts->m_fClimbLowPenalty *= CPathServerThread::ms_fClimbMultiplier_ShortestPath;
|
||
|
}
|
||
|
}
|
||
|
|