#include "PathServer.h" //**************************************************************************** // PathServer_Objects // This file contains all the functions for dealing with dynamic objects. // That includes the functions concerned with tessellating or clipping the // navmesh polys around these objects. //**************************************************************************** #include "ai/navmesh/tessellation.h" #include "fragment/type.h" #ifdef GTA_ENGINE // These includes for using within the GTA engine #include "game/ModelIndices.h" #include "Objects\Door.h" #include "Objects\DoorTuning.h" #include "Objects\Object.h" #include "Vehicles\Vehicle.h" #include "ModelInfo\PedModelInfo.h" #include "Game\ModelIndices.h" #include "Peds\Ped.h" #include "Peds\PedGeometryAnalyser.h" #include "Peds\PedIntelligence.h" #include "Task\Movement\TaskNavMesh.h" #include "Task\Vehicle\TaskCarUtils.h" #include "Vfx\Misc\Fire.h" #else // GTA_ENGINE // This include for when developing the system outside of GTA #include "GTATypes.h" #endif #include "math/angmath.h" #include "atl/array.h" #include "fwmaths/random.h" #include "fwscene/world/worldlimits.h" // Define this to have the pathfinder ignore objects which have fallen flat. The "m_bIsCurrentlyAnObstacle" flag becomes reset if so. #define __DEACTIVATE_FLAT_OBJECTS 1 //0 #define __DEACTIVATE_UPROOTED_OBJECTS 0 //1 // This define makes the TestDynamicObjectLOS functions use an additional 2nd LOS a little higher than the first. // NB: This will help the cases where objects aren't placed exactly upon the ground. The first LOS is quite close // to the floor, and sometimes misses objects which are above this. #define __USE_SECOND_OBJECT_LOS_TEST 1 // If not defined then we will never allow paths to push closed vehicle doors (by default the optional flag is in the path request) #define __ALLOW_TO_PUSH_VEHICLE_DOORS_CLOSED 1 #define __TESSELLATE_USE_EXPANDED_CONNECTION_POLYS 0 #define __TESSELLATED_POLYS_USE_PARENTS_MINMAX 0 //1 #define __CONNECTION_POLYS_USE_PARENTS_MINMAX 1 #define __REUSE_TESSELLATION_POLYS 0 // Makes better use of tessellation pool, makes for very complex code (NB: not working) #ifdef GTA_ENGINE AI_NAVIGATION_OPTIMISATIONS() NAVMESH_OPTIMISATIONS() #endif const float CNavMeshTrackedObject::DEFAULT_RESCAN_PROBE_LENGTH = 3.0f; //----------------------------------------------------------------------------- #if __TRACK_PEDS_IN_NAVMESH #ifdef GTA_ENGINE const float CNavMeshTrackedObject::ms_fMaxTrackingPositionChangeSqr = 10.0f * 10.0f; const float CNavMeshTrackedObject::ms_fDefaultMaxErrorForPathSearches = 0.1f; const u32 CNavMeshTrackedObject::ms_iMaxRescanFreq = 1000; const float CNavMeshTrackedObject::ms_fOffMap = -99999.0f; #if __BANK && defined(DEBUG_DRAW) bank_bool CNavMeshTrackedObject::ms_bDrawPedTrackingProbes = false; #endif CNavMeshTrackedObject::CNavMeshTrackedObject() : m_vLastPosition(ms_fOffMap, ms_fOffMap, ms_fOffMap), m_vLastNavMeshIntersection(ms_fOffMap, ms_fOffMap, ms_fOffMap), m_iHitEdgeIndex(-1), m_bHasValidLastPositionOnNavMesh(false), m_bHitNavMeshEdge(false), m_bPerformAvoidanceLineTests(false), m_bAvoidanceLeftIsBlocked(0), m_bAvoidanceRightIsBlocked(0), m_bLosAheadIsBlocked(false), #if __BANK m_bPerformedRescanLastFrame(false), #endif // __BANK m_fZPosFromTask(-10000.0f), m_iLastRescanTime(0), m_bTeleported(true) { } CNavMeshTrackedObject::~CNavMeshTrackedObject() { } bool CNavMeshTrackedObject::IsLastNavMeshIntersectionValid() const { return (m_vLastNavMeshIntersection.x != ms_fOffMap && m_vLastNavMeshIntersection.y != ms_fOffMap && m_vLastNavMeshIntersection.z != ms_fOffMap); } bool CNavMeshTrackedObject::IsUpToDate(const Vector3 & vPosition, const float fEps) const { return (vPosition - m_vLastPosition).Mag2() < fEps*fEps; } void CNavMeshTrackedObject::SetInvalid() { m_vLastPosition = Vector3(ms_fOffMap, ms_fOffMap, ms_fOffMap); m_vPolyNormal.Zero(); m_NavMeshAndPoly.m_iNavMeshIndex = NAVMESH_NAVMESH_INDEX_NONE; m_NavMeshAndPoly.m_iPolyIndex = NAVMESH_POLY_INDEX_NONE; m_bPerformAvoidanceLineTests = false; for (int i = 0; i < m_NavMeshLosChecks.GetCount(); i++) { m_NavMeshLosChecks[i].Reset(); } } void CNavMeshTrackedObject::Teleport(const Vector3 & vNewPosition) { m_vLastPosition = vNewPosition; m_vPolyNormal.Zero(); m_bUpToDate = false; m_bTeleported = true; m_bHasValidLastPositionOnNavMesh = false; m_bHitNavMeshEdge = false; m_bAvoidanceLeftIsBlocked = 0; m_bAvoidanceRightIsBlocked = 0; m_iHitEdgeIndex = -1; m_NavMeshAndPoly.m_iNavMeshIndex = NAVMESH_NAVMESH_INDEX_NONE; m_NavMeshAndPoly.m_iPolyIndex = NAVMESH_POLY_INDEX_NONE; m_bPerformAvoidanceLineTests = false; for (int i = 0; i < m_NavMeshLosChecks.GetCount(); i++) { m_NavMeshLosChecks[i].Reset(); } } bool CNavMeshTrackedObject::QueryLosCheck(Vector3& o_Intersection, Vector3& o_Vertex1, Vector3& o_Vertex2, const Vector3& in_Position, const Vector3& in_EndPosition) const { for ( int i = 0; i < m_NavMeshLosChecks.GetCount(); i++ ) { o_Vertex1 = m_NavMeshLosChecks[i].m_Vertex1; o_Vertex2 = m_NavMeshLosChecks[i].m_Vertex2; // check to see if we intersect this segment if(CNavMesh::LineSegsIntersect2D(in_Position, in_EndPosition, o_Vertex1, o_Vertex2, &o_Intersection) == SEGMENTS_INTERSECT ) { // check we're not already on other side of object // note assume point ordering here Vector3 vSeg = o_Vertex2 - o_Vertex1; Vector3 vIntToPos = in_Position - o_Intersection; // Vec2V vIntersection = Vec2V(o_Intersection.x, o_Intersection.y); Vec2V vSegDir = Normalize(Vec2V(vSeg.x, vSeg.y)); Vec2V vSegRight = Vec2V(vSegDir.GetY(), -vSegDir.GetX()); Vec2V vIntersectionToPos = Normalize(Vec2V(vIntToPos.x, vIntToPos.y)); if ( (Dot(vSegRight, vIntersectionToPos ) >= ScalarV(0.0f) ).Getb() ) { return true; } } } return false; } void CNavMeshTrackedObject::RequestLosCheck(const Vector3& in_Vector) { // only supporting one right now if ( m_NavMeshLosRequests.GetAvailable() > 0 ) { m_NavMeshLosRequests.Push(in_Vector); } } void CNavMeshTrackedObject::Rescan(const Vector3 & vObjectPos, const float rescanProbeLength) { // The tracking is always done on the regular navmesh for now. const aiNavDomain domain = kNavDomainRegular; m_iLastRescanTime = fwTimer::GetTimeInMilliseconds(); CNavMesh * pNavMesh = CPathServer::GetNavMeshFromIndex( CPathServer::GetNavMeshIndexFromPosition(vObjectPos, domain), domain ); u32 iPolyIndex = NAVMESH_POLY_INDEX_NONE; if(pNavMesh) { const float fDistBelow = rescanProbeLength; Vector3 vIntersect; iPolyIndex = pNavMesh->GetPolyBelowPoint(vObjectPos, vIntersect, fDistBelow); if(iPolyIndex != NAVMESH_POLY_INDEX_NONE) { m_NavMeshAndPoly.m_iNavMeshIndex = pNavMesh->GetIndexOfMesh(); m_NavMeshAndPoly.m_iPolyIndex = iPolyIndex; m_bHasValidLastPositionOnNavMesh = true; m_vLastNavMeshIntersection = vIntersect; // Store off data etc from the TNavMeshPoly underfoot // Should we store the entire poly? (40 bytes) TNavMeshPoly * pPoly = pNavMesh->GetPoly(iPolyIndex); // TODO: Move this into CNavMeshTrackedObject or TNavPolyData m_NavPolyData.m_bPavement = pPoly->GetIsPavement(); m_NavPolyData.m_bSheltered = pPoly->GetIsSheltered(); m_NavPolyData.m_bIsolated = pPoly->GetIsIsolated(); m_NavPolyData.m_bNetworkSpawn = pPoly->GetIsNetworkSpawnCandidate(); m_NavPolyData.m_bIsRoad = pPoly->GetIsRoad(); m_NavPolyData.m_bInterior = pPoly->GetIsInterior(); m_NavPolyData.m_bIsWater = pPoly->GetIsWater(); m_NavPolyData.m_iAudioProperties = pPoly->GetAudioProperties(); m_NavPolyData.m_bTrainTracks = pPoly->GetIsTrainTrack(); } } if(!pNavMesh || iPolyIndex == NAVMESH_POLY_INDEX_NONE) { // If we got here, then it means we could not find a navmesh poly underneath the ped m_NavMeshAndPoly.m_iNavMeshIndex = NAVMESH_NAVMESH_INDEX_NONE; m_NavMeshAndPoly.m_iPolyIndex = NAVMESH_POLY_INDEX_NONE; } } void CPathServer::UpdateTrackedObject(CNavMeshTrackedObject & obj, const Vector3 & vObjectPos, const float rescanProbeLength, bool in_bNeedsToReScan) { Assert(m_iImmediateModeNumVisitedPolys==0); m_iImmediateModeNumVisitedPolys = 0; // The tracking is always done on the regular navmesh for now. const aiNavDomain domain = kNavDomainRegular; BANK_ONLY(obj.ResetPerformedRescanLastUpdate()); // If the position of the object has changed over some threshold we will rescan bool bNeedToReScan = in_bNeedsToReScan || TrackedObjectNeedsRescan(obj, vObjectPos); // If the navmesh:poly info is up-to-date, and the ped hasn't moved then there's no need to rescan if(obj.m_bUpToDate && !bNeedToReScan) return; if(bNeedToReScan) { const Vector3 vDiff = vObjectPos - obj.m_vLastPosition; // If the delta is sufficiently small, then we will try to track the object by simply // sliding it across the navmesh surface noting when we exit/enter polygons. If we // can successfully track the object, then update its navmesh:poly indices and set it's // m_bUpToDate flag. If not, we will have to search for the start/end points next // time we handle a path request. if(vDiff.Mag2() < CNavMeshTrackedObject::ms_fMaxTrackingPositionChangeSqr) { const Vector2 vDir(vDiff.x, vDiff.y); if(vDir.Mag2() > 0.0f) { if(obj.m_NavMeshAndPoly.m_iNavMeshIndex != NAVMESH_NAVMESH_INDEX_NONE && obj.m_NavMeshAndPoly.m_iPolyIndex != NAVMESH_POLY_INDEX_NONE) { CNavMesh * pNavMesh = GetNavMeshFromIndex(obj.m_NavMeshAndPoly.m_iNavMeshIndex, domain); if(pNavMesh) { Vector3 vIntersectPos; TNavMeshPoly * pStartPoly = pNavMesh->GetPoly(obj.m_NavMeshAndPoly.m_iPolyIndex); TNavMeshPoly * pEndPoly = CPathServer::TestShortLineOfSightImmediate(obj.m_vLastPosition, vDir, pStartPoly, &vIntersectPos, NULL, NULL, domain); Assert(m_iImmediateModeNumVisitedPolys == 0); if(pEndPoly) { Assert(pEndPoly->GetNavMeshIndex()!=NAVMESH_INDEX_TESSELLATION); CNavMesh * pEndNavMesh = CPathServer::GetNavMeshFromIndex(pEndPoly->GetNavMeshIndex(), domain); obj.m_NavMeshAndPoly.m_iNavMeshIndex = pEndPoly->GetNavMeshIndex(); obj.m_NavMeshAndPoly.m_iPolyIndex = pEndNavMesh->GetPolyIndex(pEndPoly); obj.m_bHasValidLastPositionOnNavMesh = true; obj.m_vLastNavMeshIntersection = vIntersectPos; // Store off data etc from the TNavMeshPoly underfoot // Should we store the entire poly? (40 bytes) // TODO: Move this into CNavMeshTrackedObject or TNavPolyData obj.m_NavPolyData.m_bPavement = pEndPoly->GetIsPavement(); obj.m_NavPolyData.m_bSheltered = pEndPoly->GetIsSheltered(); obj.m_NavPolyData.m_bIsolated = pEndPoly->GetIsIsolated(); obj.m_NavPolyData.m_bNetworkSpawn = pEndPoly->GetIsNetworkSpawnCandidate(); obj.m_NavPolyData.m_bIsRoad = pEndPoly->GetIsRoad(); obj.m_NavPolyData.m_bInterior = pEndPoly->GetIsInterior(); obj.m_NavPolyData.m_bIsWater = pEndPoly->GetIsWater(); obj.m_NavPolyData.m_iAudioProperties = pEndPoly->GetAudioProperties(); obj.m_NavPolyData.m_bTrainTracks = pEndPoly->GetIsTrainTrack(); bNeedToReScan = false; } } } } } // If we still need to rescan, then the delta update must have failed :-( if(bNeedToReScan) { obj.Rescan(vObjectPos, rescanProbeLength); BANK_ONLY(obj.SetPerformedRescanLastUpdate()); } Assert(m_iImmediateModeNumVisitedPolys==0); obj.m_bTeleported = false; obj.m_bUpToDate = true; obj.m_vLastPosition = vObjectPos; } } bool CPathServer::TrackedObjectNeedsRescan(const CNavMeshTrackedObject& obj, const Vector3& vObjectPos) { // check if the object has been teleported if( obj.m_bTeleported ) { // needs rescan return true; } // check if the object has been displaced static const float fEpsXY = 0.01f; static const float fEpsZ = 0.1f; if( (!rage::IsNearZero(vObjectPos.x - obj.m_vLastPosition.x, fEpsXY)) || (!rage::IsNearZero(vObjectPos.y - obj.m_vLastPosition.y, fEpsXY)) || (!rage::IsNearZero(vObjectPos.z - obj.m_vLastPosition.z, fEpsZ)) ) { // needs rescan return true; } // by default object does not need a rescan return false; } Vector3 CPathServer::UpdateTrackedObjectConstrainedToNavMesh(const Vector3 & vStartPos, const Vector3 & vMoveSpeed, CNavMeshTrackedObject & obj) { // The tracking is always done on the regular navmesh for now. const aiNavDomain domain = kNavDomainRegular; if(obj.GetNavMeshAndPoly().m_iNavMeshIndex == NAVMESH_NAVMESH_INDEX_NONE || obj.GetNavMeshAndPoly().m_iPolyIndex == NAVMESH_POLY_INDEX_NONE) { // Beware! We don't want a ped to get into a state of repeatedly rescanning, or it will spamn the pathfinder if(obj.GetLastRescanTime() + CNavMeshTrackedObject::ms_iMaxRescanFreq < fwTimer::GetTimeInMilliseconds()) { obj.Rescan(vStartPos); } else { return vStartPos; } } static const float fDistLeftEps = 0.0f; static const float fDistMovedEps = 0.001f; Vector2 vMoveDir(vMoveSpeed.x, vMoveSpeed.y); vMoveDir *= fwTimer::GetTimeStep(); float fDistLeft = vMoveDir.Mag(); if(fDistLeft <= fDistMovedEps) return vStartPos; CNavMesh * pNavMesh = CPathServer::GetNavMeshFromIndex(obj.GetNavMeshAndPoly().m_iNavMeshIndex, domain); if(!pNavMesh) { return vStartPos; } TNavMeshPoly * pPoly = pNavMesh->GetPoly(obj.GetNavMeshAndPoly().m_iPolyIndex); Vector3 vIntersectPos(0.0f,0.0f,0.0f); Vector3 vIntersectEdgeVec(0.0f, 0.0f, 0.0f); int iEdgeHitIndex = obj.GetHitEdgeIndex(); // get the initial state from last frame // vCurrentPos has to be on the navmesh. The input vec is the ped's position, which is 1m above. Vector3 vCurrentPos = Vector3(vStartPos.x, vStartPos.y, vStartPos.z - 1.0f); int iIterations = 10; while(fDistLeft > fDistLeftEps && iIterations-- > 0) { // If we're not on an edge, then try to move the position across the mesh if(iEdgeHitIndex==-1) { TNavMeshPoly * pEndPoly = CPathServer::TestShortLineOfSightImmediate(vCurrentPos, vMoveDir, pPoly, &vIntersectPos, &vIntersectEdgeVec, &iEdgeHitIndex, domain); if(!pEndPoly) { //************************************************************************************************ // Ensure that the start poly is actually underneath the start positiion. // If we're really unlucky the position could be exactly between polygons & might not be found.. const Vector3 vJitteredStart( vCurrentPos.x + fwRandom::GetRandomNumberInRange(-0.1f, 0.1f), vCurrentPos.y + fwRandom::GetRandomNumberInRange(-0.1f, 0.1f), vCurrentPos.z); const Vector3 vEnd = vJitteredStart + Vector3(vMoveDir.x, vMoveDir.y, 0.0f); pEndPoly = CPathServer::TestShortLineOfSightImmediate(vJitteredStart, vEnd, vIntersectPos, domain); Assert(pEndPoly); } if(!pEndPoly) { //************************************************************************************************ // Its possible that the vCurrentPos is on a navmesh poly edge, but the LOS test is not detecting // this fact. Iterate through the pPoly's edges and see if the position is exactly upon an edge. Vector3 vCurr, vLast; float fClosestEdgeDist = FLT_MAX; int iClosestEdge = -1; static dev_float fCloseEdgeEps = 0.05f; int v2=pPoly->GetNumVertices()-1; pNavMesh->GetVertex(pNavMesh->GetPolyVertexIndex(pPoly, v2), vLast); vLast.z = vCurrentPos.z; for(u32 v1=0; v1GetNumVertices(); v1++) { pNavMesh->GetVertex(pNavMesh->GetPolyVertexIndex(pPoly, v1), vCurr); vCurr.z = vCurrentPos.z; const TAdjPoly & closedEdge = pNavMesh->GetAdjacentPoly(pPoly->GetFirstVertexIndex() + v1); if(closedEdge.GetAdjacencyType()!=ADJACENCY_TYPE_NORMAL) { const float fEdgeDist = geomDistances::Distance2SegToPoint(vLast, vCurr-vLast, vCurrentPos); if(fEdgeDist > fCloseEdgeEps && fEdgeDist < fClosestEdgeDist) { fClosestEdgeDist = fEdgeDist; iClosestEdge = v1; } } vLast = vCurr; v2 = v1; } if(iClosestEdge == -1) { // Still not found break; } iEdgeHitIndex = iClosestEdge; } else { pPoly = pEndPoly; const float fDistMoved = (vIntersectPos - vCurrentPos).Mag(); vCurrentPos = vIntersectPos; if(fDistMoved < fDistMovedEps && iEdgeHitIndex==-1) break; if(fDistMoved > 0.0f) { vMoveDir.Scale(fDistMoved / fDistLeft); fDistLeft -= fDistMoved; } if(fDistLeft <= fDistLeftEps) break; } } // If we have hit an edge, then slide the position along the navmesh edges if(iEdgeHitIndex!=-1) { int iEdgeHitIndexBefore = iEdgeHitIndex; TNavMeshPoly * pPolyBefore = pPoly; TNavMeshPoly * pEndPoly = CPathServer::SlidePointAlongNavMeshEdgeImmediate(vCurrentPos, vMoveDir, pPoly, iEdgeHitIndex, vIntersectPos, domain); if(!pEndPoly) break; pPoly = pEndPoly; pNavMesh = CPathServer::GetNavMeshFromIndex(pPoly->GetNavMeshIndex(), domain); Assert(pNavMesh); const float fDistMoved = (vIntersectPos - vCurrentPos).Mag(); vCurrentPos = vIntersectPos; // Disengaged from edge? if(iEdgeHitIndex==-1) continue; if(pEndPoly==pPolyBefore && iEdgeHitIndex==iEdgeHitIndexBefore) { break; } vMoveDir.Scale(fDistMoved / fDistLeft); fDistLeft -= fDistMoved; if(fDistLeft <= fDistLeftEps) break; if(fDistMoved < fDistMovedEps) break; } } if(pPoly) { pNavMesh = CPathServer::GetNavMeshFromIndex(pPoly->GetNavMeshIndex(), domain); obj.m_bUpToDate = true; obj.m_NavMeshAndPoly.m_iNavMeshIndex = pNavMesh->GetIndexOfMesh(); obj.m_NavMeshAndPoly.m_iPolyIndex = pNavMesh->GetPolyIndex(pPoly); obj.m_vLastPosition = Vector3(vCurrentPos.x, vCurrentPos.y, vCurrentPos.z + 1.0f); obj.m_vLastNavMeshIntersection = vCurrentPos; obj.m_bHitNavMeshEdge = iEdgeHitIndex != -1; obj.m_iHitEdgeIndex = (s16)iEdgeHitIndex; obj.m_vNavMeshIntersectEdge = vIntersectEdgeVec; } else { obj.m_bUpToDate = true; // Leave the poly & navmesh indices unchanged // Place the ped back at the last good navmesh intersection position obj.m_vLastPosition = Vector3(obj.m_vLastNavMeshIntersection.x, obj.m_vLastNavMeshIntersection.y, obj.m_vLastNavMeshIntersection.z + 1.0f); obj.m_bHitNavMeshEdge = false; obj.m_iHitEdgeIndex = -1; obj.m_vNavMeshIntersectEdge.Zero(); } return obj.m_vLastPosition; } #endif // GTA_ENGINE #endif // __TRACK_PEDS_IN_NAVMESH //----------------------------------------------------------------------------------- // FUNCTION : TessellateNavMeshPolyAndCreateDegenerateConnections // PURPOSE : Tessellates the given navmesh into a number of fragments, and sets up // adjacencies between them. Creates zero area 'connection' polygons between the // fragments and the input polygon's neighbours - so as to bisect each edge 1:2 bool CPathServer::OnFirstVisitingPolyCallback(TNavMeshPoly * pPoly) { return m_PathServerThread.OnFirstVisitingPoly(pPoly); } //----------------------------------------------------------------------------------- // FUNCTION : TessellateNavMeshPolyAndCreateDegenerateConnections // PURPOSE : Tessellates the given navmesh into a number of fragments, and sets up // adjacencies between them. Creates zero area 'connection' polygons between the // fragments and the input polygon's neighbours - so as to bisect each edge 1:2 #if __TESSELLATE_USE_EXPANDED_CONNECTION_POLYS static const float fExpandWeighting = 0.2f; static const float fOtherVertWeighting = (1.0f - fExpandWeighting) * 0.5f; #endif bool CPathServerThread::TessellateNavMeshPolyAndCreateDegenerateConnections( CNavMesh * pNavMesh, TNavMeshPoly * pPoly, u32 iPolyIndex, bool bThisIsStartPoly, bool bThisIsEndPoly, TNavMeshPoly ** pOutStartingPoly, u32 iReturnStartingPoly_NavMeshIndex, u32 iReturnStartingPoly_PolyIndex, atArray * outputFragments, Vector3 * pMidPoint) { // This is only for the regular navmesh domain at this point. const aiNavDomain domain = kNavDomainRegular; #if !__FINAL m_PolyTessellationTimer->Reset(); m_PolyTessellationTimer->Start(); #endif Assert(!pPoly->GetIsDegenerateConnectionPoly()); //***************************************************************** // See if we have enough tessellation polys free for tessellating. // The number we'll use is iNumVerts*3. const u32 iSpaceAvailable = CNavMesh::ms_iNumPolysInTesselationNavMesh - CPathServer::m_iCurrentNumTessellationPolys; if(iSpaceAvailable < pPoly->GetNumVertices()*3) { #if !__FINAL m_PolyTessellationTimer->Stop(); m_pCurrentActivePathRequest->m_fMillisecsSpentInTessellation += (float) m_PolyTessellationTimer->GetTimeMS(); #endif Assertf(false, "WARNING : No space to tessellate this polygon, earlying out.\n"); return false; } static Vector3 vTempVerts[NAVMESHPOLY_MAX_NUM_VERTICES]; // I've doubled the number of tessellated polys created from each poly. // There will still be the fan-shaped tessellation scheme, but triangles will be // created with poly-edge midpoints as well as just the existing vertices. // In this manner we will bisect each edge and allow better navigation around // objects in cramped spaced. HOWEVER - this will (along with the connection polys) // TRIPLE the amount of tessellation polys required. If this proves a problem the // requirements could be reduced by allowing tessellation polys to be QUADS, but // this will increase the complexity of the code somewhat.. static u32 iNewPolyIndices[NAVMESHPOLY_MAX_NUM_VERTICES*2]; static TNavMeshPoly * pNewPolys[NAVMESHPOLY_MAX_NUM_VERTICES*2]; Vector3 vCentroid; u32 v, lastv, a; u32 iTessellatedPolyIndex1, iTessellatedPolyIndex2; CNavMesh * pTessellationNavMesh = CPathServer::m_pTessellationNavMesh; struct TSpecialLinkRetargetInfo { Vector3 vPosition; CSpecialLinkInfo * pLink; bool bRetargetFromLink; // if true we are retargetting the start position, otherwise its the end position }; static TSpecialLinkRetargetInfo specialLinkInfo[NAVMESHPOLY_MAX_SPECIAL_LINKS]; CNavMesh * pOriginalNavMesh = NULL; s32 iNumSpecialLinks = 0; if(pPoly->GetNumSpecialLinks() != 0) { if(pNavMesh->GetIndexOfMesh()==NAVMESH_INDEX_TESSELLATION) { const TTessInfo & tessInfo = CPathServer::m_PolysTessellatedFrom[iPolyIndex]; pOriginalNavMesh = CPathServer::GetNavMeshFromIndex(tessInfo.m_iNavMeshIndex, kNavDomainRegular); } else { pOriginalNavMesh = pNavMesh; } s32 iLinkLookupIndex = pPoly->GetSpecialLinksStartIndex(); for(s32 s=0; sGetNumSpecialLinks(); s++) { const u16 iLinkIndex = pOriginalNavMesh->GetSpecialLinkIndex(iLinkLookupIndex++); Assert(iLinkIndex < pOriginalNavMesh->GetNumSpecialLinks()); CSpecialLinkInfo * pLink = &pOriginalNavMesh->GetSpecialLinks()[iLinkIndex]; if(pLink->GetAStarTimeStamp() != m_iAStarTimeStamp) { pLink->Reset(); pLink->SetAStarTimeStamp(m_iAStarTimeStamp); } if(pLink->GetLinkFromNavMesh() == pNavMesh->GetIndexOfMesh() && pLink->GetLinkFromPoly() == iPolyIndex) { specialLinkInfo[iNumSpecialLinks].pLink = pLink; specialLinkInfo[iNumSpecialLinks].bRetargetFromLink = true; CNavMesh::DecompressVertex( specialLinkInfo[iNumSpecialLinks].vPosition, pLink->GetLinkFromPosX(), pLink->GetLinkFromPosY(), pLink->GetLinkFromPosZ(), pOriginalNavMesh->GetQuadTree()->m_Mins, pOriginalNavMesh->GetExtents() ); iNumSpecialLinks++; } else if(pLink->GetLinkToNavMesh() == pNavMesh->GetIndexOfMesh() && pLink->GetLinkToPoly() == iPolyIndex) { specialLinkInfo[iNumSpecialLinks].pLink = pLink; specialLinkInfo[iNumSpecialLinks].bRetargetFromLink = false; CNavMesh::DecompressVertex( specialLinkInfo[iNumSpecialLinks].vPosition, pLink->GetLinkToPosX(), pLink->GetLinkToPosY(), pLink->GetLinkToPosZ(), pOriginalNavMesh->GetQuadTree()->m_Mins, pOriginalNavMesh->GetExtents() ); iNumSpecialLinks++; } } } // Important note : during navmesh construction all polys with an area less than NAVMESH_SMALL_POLY_AREA have the NAVMESHPOLY_SMALL flag // set for them. No polys with this flag set are tessellated. By having a small value for NAVMESHPOLY_SMALL (currently 1.0f?) we can // increase the number of polys which get tessellated, and aid the pathfinding. When creating tessellated fragments here we may want to // use a slightly higher value when classifying the size of the fragments - otherwise we will risk bogging down the cpu with the // tessellation. In this way original navmesh polys are more likely to be tessellated than their fragments. if(pMidPoint) vCentroid = *pMidPoint; else pNavMesh->GetPolyCentroid(iPolyIndex, vCentroid); for(v=0; vGetNumVertices(); v++) { pNavMesh->GetVertex( pNavMesh->GetPolyVertexIndex(pPoly, v), vTempVerts[v] ); } //************************************* // 'Allocate' the tessellation polys lastv = pPoly->GetNumVertices()-1; for(v=0; vGetNumVertices(); v++) { #if __TESSELLATE_USE_EXPANDED_CONNECTION_POLYS const Vector3 vEdgeMidpoint = (vTempVerts[lastv]*fOtherVertWeighting) + (vTempVerts[v]*fOtherVertWeighting) + (vCentroid*fExpandWeighting); #else const Vector3 vEdgeMidpoint = (vTempVerts[lastv] + vTempVerts[v]) * 0.5f; #endif //**************************************************************************** // Set up the FIRST tessellation poly for this original poly edge. // This poly's external edge runs from vTempVerts[lastv] to the edge midpoint // NOTE : GetNextFreeTessellationPolyIndex() must be replaced by a function which uses a queue of free indices, // or something similar. It currently searches through a bitfield to find the first unset bit, which is slow.. iTessellatedPolyIndex1 = fwPathServer::GetNextFreeTessellationPolyIndex(); if(iTessellatedPolyIndex1 == NAVMESH_POLY_INDEX_NONE) { #if !__FINAL m_PolyTessellationTimer->Stop(); m_pCurrentActivePathRequest->m_fMillisecsSpentInTessellation += (float) m_PolyTessellationTimer->GetTimeMS(); #endif Assertf(false, "ERROR : There was no space for new tessellation poly #1 in navmesh.\n"); return false; } Assert(iTessellatedPolyIndex1 < CNavMesh::ms_iNumPolysInTesselationNavMesh); // If the poly we are tessellating is already a tessellated poly itself, then record an entry in the // m_PolysTessellatedFrom array, which points us back to the original untessellated poly. if(pNavMesh == pTessellationNavMesh) { TTessInfo * pTessInfo = CPathServer::GetTessInfo(iTessellatedPolyIndex1); TTessInfo * pParentFragmentTessInfo = CPathServer::GetTessInfo(iPolyIndex); pTessInfo->m_iNavMeshIndex = pParentFragmentTessInfo->m_iNavMeshIndex; pTessInfo->m_iPolyIndex = pParentFragmentTessInfo->m_iPolyIndex; pTessInfo->m_bInUse = true; } // The poly is untessellated, so set up a record in the m_PolysTessellatedFrom array. else { TTessInfo * pTessInfo = CPathServer::GetTessInfo(iTessellatedPolyIndex1); pTessInfo->m_iNavMeshIndex = (u16)pNavMesh->GetIndexOfMesh(); pTessInfo->m_iPolyIndex = (u16)iPolyIndex; pTessInfo->m_bInUse = true; } // Set up the new poly & vertices TNavMeshPoly * pTessPoly1 = pTessellationNavMesh->GetPoly(iTessellatedPolyIndex1); pTessPoly1->SetNumVertices(3); pTessPoly1->SetFirstVertexIndex(CPathServer::m_iCurrentNumTessellationPolys*3); CPathServer::m_iCurrentNumTessellationPolys++; pTessPoly1->m_TimeStamp = 0; pTessPoly1->m_AStarTimeStamp = 0; pTessPoly1->SetIsTessellatedFragment(true); pTessPoly1->SetReplacedByTessellation(false); pTessPoly1->SetIsDegenerateConnectionPoly(false); pTessellationNavMesh->GetVertexPool()[pTessPoly1->GetFirstVertexIndex()] = vCentroid; pTessellationNavMesh->GetVertexPool()[pTessPoly1->GetFirstVertexIndex()+1] = vTempVerts[lastv]; pTessellationNavMesh->GetVertexPool()[pTessPoly1->GetFirstVertexIndex()+2] = vEdgeMidpoint; #if __TESSELLATED_POLYS_USE_PARENTS_MINMAX pTessPoly1->m_MinMax = pPoly->m_MinMax; #else // Calc min/max pTessPoly1->m_MinMax.SetFloat( Min(vCentroid.x, Min(vEdgeMidpoint.x, vTempVerts[lastv].x)), Min(vCentroid.y, Min(vEdgeMidpoint.y, vTempVerts[lastv].y)), Min(vCentroid.z, Min(vEdgeMidpoint.z, vTempVerts[lastv].z)), Max(vCentroid.x, Max(vEdgeMidpoint.x, vTempVerts[lastv].x)), Max(vCentroid.y, Max(vEdgeMidpoint.y, vTempVerts[lastv].y)), Max(vCentroid.z, Max(vEdgeMidpoint.z, vTempVerts[lastv].z)) ); #endif if(!CPathServerThread::OnFirstVisitingPoly(pTessPoly1)) { #if !__FINAL m_PolyTessellationTimer->Stop(); m_pCurrentActivePathRequest->m_fMillisecsSpentInTessellation += (float) m_PolyTessellationTimer->GetTimeMS(); #endif Assert(0); return false; } Assert(pTessPoly1->GetNavMeshIndex() == NAVMESH_INDEX_TESSELLATION); iNewPolyIndices[v*2] = iTessellatedPolyIndex1; pNewPolys[v*2] = pTessPoly1; //**************************************************************************** // Set up the SECOND tessellation poly for this original poly edge. // This poly's external edge runs from vTempVerts[lastv] to the edge midpoint iTessellatedPolyIndex2 = fwPathServer::GetNextFreeTessellationPolyIndex(); if(iTessellatedPolyIndex2 == NAVMESH_POLY_INDEX_NONE) { #if !__FINAL m_PolyTessellationTimer->Stop(); m_pCurrentActivePathRequest->m_fMillisecsSpentInTessellation += (float) m_PolyTessellationTimer->GetTimeMS(); #endif Assertf(false, "ERROR : There was no space for new tessellation poly #2 in navmesh.\n"); return false; } Assert(iTessellatedPolyIndex2 < CNavMesh::ms_iNumPolysInTesselationNavMesh); // If the poly we are tessellating is already a tessellated poly itself, then record an entry in the // m_PolysTessellatedFrom array, which points us back to the original untessellated poly. if(pNavMesh == pTessellationNavMesh) { TTessInfo * pTessInfo = CPathServer::GetTessInfo(iTessellatedPolyIndex2); TTessInfo * pParentFragmentTessInfo = CPathServer::GetTessInfo(iPolyIndex); pTessInfo->m_iNavMeshIndex = pParentFragmentTessInfo->m_iNavMeshIndex; pTessInfo->m_iPolyIndex = pParentFragmentTessInfo->m_iPolyIndex; pTessInfo->m_bInUse = true; } // The poly is untessellated, so set up a record in the m_PolysTessellatedFrom array. else { TTessInfo * pTessInfo = CPathServer::GetTessInfo(iTessellatedPolyIndex2); pTessInfo->m_iNavMeshIndex = (u16)pNavMesh->GetIndexOfMesh(); pTessInfo->m_iPolyIndex = (u16)iPolyIndex; pTessInfo->m_bInUse = true; } // Set up the new poly & vertices TNavMeshPoly * pTessPoly2 = pTessellationNavMesh->GetPoly(iTessellatedPolyIndex2); pTessPoly2->SetNumVertices(3); pTessPoly2->SetFirstVertexIndex(CPathServer::m_iCurrentNumTessellationPolys*3); CPathServer::m_iCurrentNumTessellationPolys++; pTessPoly2->m_TimeStamp = 0; pTessPoly2->m_AStarTimeStamp = 0; pTessPoly2->SetIsTessellatedFragment(true); pTessPoly2->SetReplacedByTessellation(false); pTessPoly2->SetIsDegenerateConnectionPoly(false); pTessellationNavMesh->GetVertexPool()[pTessPoly2->GetFirstVertexIndex()] = vCentroid; pTessellationNavMesh->GetVertexPool()[pTessPoly2->GetFirstVertexIndex()+1] = vEdgeMidpoint; pTessellationNavMesh->GetVertexPool()[pTessPoly2->GetFirstVertexIndex()+2] = vTempVerts[v]; #if __TESSELLATED_POLYS_USE_PARENTS_MINMAX pTessPoly2->m_MinMax = pPoly->m_MinMax; #else // Calc min/max pTessPoly2->m_MinMax.SetFloat( Min(vCentroid.x, Min(vEdgeMidpoint.x, vTempVerts[v].x)), Min(vCentroid.y, Min(vEdgeMidpoint.y, vTempVerts[v].y)), Min(vCentroid.z, Min(vEdgeMidpoint.z, vTempVerts[v].z)), Max(vCentroid.x, Max(vEdgeMidpoint.x, vTempVerts[v].x)), Max(vCentroid.y, Max(vEdgeMidpoint.y, vTempVerts[v].y)), Max(vCentroid.z, Max(vEdgeMidpoint.z, vTempVerts[v].z)) ); #endif if(!CPathServerThread::OnFirstVisitingPoly(pTessPoly2)) { #if !__FINAL m_PolyTessellationTimer->Stop(); m_pCurrentActivePathRequest->m_fMillisecsSpentInTessellation += (float) m_PolyTessellationTimer->GetTimeMS(); #endif Assert(0); return false; } Assert(pTessPoly2->GetNavMeshIndex() == NAVMESH_INDEX_TESSELLATION); iNewPolyIndices[(v*2)+1] = iTessellatedPolyIndex2; pNewPolys[(v*2)+1] = pTessPoly2; lastv = v; } //***************************************************************************** // 'Allocate' the connection polys. Connect all the new polys together. // NB: For now just take these from the tessellation pool. // In time we may decide to have another store for these, since they could be // considerably more light-weight that the other polys. lastv = pPoly->GetNumVertices()-1; for(v=0; vGetNumVertices(); v++) { const u32 iDegenerateConnectionPolyIndex = fwPathServer::GetNextFreeTessellationPolyIndex(); if(iDegenerateConnectionPolyIndex == NAVMESH_POLY_INDEX_NONE) { #if !__FINAL m_PolyTessellationTimer->Stop(); m_pCurrentActivePathRequest->m_fMillisecsSpentInTessellation += (float) m_PolyTessellationTimer->GetTimeMS(); #endif Assertf(false, "ERROR : There was no space for new connection poly in navmesh.\n"); return false; } Assert(iDegenerateConnectionPolyIndex < CNavMesh::ms_iNumPolysInTesselationNavMesh); // Set up the new poly & vertices TNavMeshPoly * pConnectionPoly = pTessellationNavMesh->GetPoly(iDegenerateConnectionPolyIndex); pConnectionPoly->SetNumVertices(3); pConnectionPoly->SetFirstVertexIndex(CPathServer::m_iCurrentNumTessellationPolys*3); CPathServer::m_iCurrentNumTessellationPolys++; #if __TESSELLATE_USE_EXPANDED_CONNECTION_POLYS const Vector3 vEdgeMidpoint = (vTempVerts[lastv]*fExpandWeighting) + (vTempVerts[v]*fExpandWeighting) + (vCentroid*fOtherVertWeighting); #else const Vector3 vEdgeMidpoint = (vTempVerts[lastv] + vTempVerts[v]) * 0.5f; #endif // If the poly we are tessellating is already a tessellated poly itself, then record an entry in the // m_PolysTessellatedFrom array, which points us back to the original untessellated poly. if(pNavMesh == pTessellationNavMesh) { TTessInfo * pTessInfo = CPathServer::GetTessInfo(iDegenerateConnectionPolyIndex); TTessInfo * pParentFragmentTessInfo = CPathServer::GetTessInfo(iPolyIndex); pTessInfo->m_iNavMeshIndex = pParentFragmentTessInfo->m_iNavMeshIndex; pTessInfo->m_iPolyIndex = pParentFragmentTessInfo->m_iPolyIndex; pTessInfo->m_bInUse = true; } // The poly is untessellated, so set up a record in the m_PolysTessellatedFrom array. else { TTessInfo * pTessInfo = CPathServer::GetTessInfo(iDegenerateConnectionPolyIndex); pTessInfo->m_iNavMeshIndex = (u16)pNavMesh->GetIndexOfMesh(); pTessInfo->m_iPolyIndex = (u16)iPolyIndex; pTessInfo->m_bInUse = true; } pPoly->CopyDataIntoTessellatedPoly(*pConnectionPoly); pConnectionPoly->m_TimeStamp = 0; pConnectionPoly->m_AStarTimeStamp = 0; pConnectionPoly->SetIsTessellatedFragment(true); pConnectionPoly->SetReplacedByTessellation(false); pConnectionPoly->SetIsDegenerateConnectionPoly(true); pConnectionPoly->SetIsSmall(true); pConnectionPoly->SetIsLarge(false); pTessellationNavMesh->GetVertexPool()[pConnectionPoly->GetFirstVertexIndex()] = vTempVerts[lastv]; pTessellationNavMesh->GetVertexPool()[pConnectionPoly->GetFirstVertexIndex()+1] = vTempVerts[v]; pTessellationNavMesh->GetVertexPool()[pConnectionPoly->GetFirstVertexIndex()+2] = vEdgeMidpoint; // Calc min/max. // NB: This shit won't be necessary once the connection polys are ZERO-AREA. // But for now they are non-degenerate so I can see the buggers. #if __CONNECTION_POLYS_USE_PARENTS_MINMAX pConnectionPoly->m_MinMax = pPoly->m_MinMax; #else pConnectionPoly->m_MinMax.Set( Min(vTempVerts[lastv].x, Min(vEdgeMidpoint.x, vTempVerts[v].x)), Min(vTempVerts[lastv].y, Min(vEdgeMidpoint.y, vTempVerts[v].y)), Min(vTempVerts[lastv].z, Min(vEdgeMidpoint.z, vTempVerts[v].z)), Max(vTempVerts[lastv].x, Max(vEdgeMidpoint.x, vTempVerts[v].x)), Max(vTempVerts[lastv].y, Max(vEdgeMidpoint.y, vTempVerts[v].y)), Max(vTempVerts[lastv].z, Max(vEdgeMidpoint.z, vTempVerts[v].z)) ); #endif if(!CPathServerThread::OnFirstVisitingPoly(pConnectionPoly)) { #if !__FINAL m_PolyTessellationTimer->Stop(); m_pCurrentActivePathRequest->m_fMillisecsSpentInTessellation += (float) m_PolyTessellationTimer->GetTimeMS(); #endif Assert(0); return false; } Assert(pConnectionPoly->GetNavMeshIndex() == NAVMESH_INDEX_TESSELLATION); //****************************************************************************** // adjPoly is the polygon connected to this poly by this current lastv->v edge. const TAdjPoly & adjPoly = pNavMesh->GetAdjacentPoly(pPoly->GetFirstVertexIndex() + lastv); //********************************************************* // Link from the adjacent poly to this connection-poly const u32 iNavMesh = adjPoly.GetNavMeshIndex(pNavMesh->GetAdjacentMeshes()); if(iNavMesh != NAVMESH_NAVMESH_INDEX_NONE && adjPoly.GetPolyIndex() != NAVMESH_POLY_INDEX_NONE && adjPoly.GetAdjacencyType() == ADJACENCY_TYPE_NORMAL) { // Link back the adjacency from the neighbouring poly, to this fragment CNavMesh * pAdjNavMesh = CPathServer::GetNavMeshFromIndex(iNavMesh, domain); if(pAdjNavMesh) { TNavMeshPoly * pNeighbourPoly = pAdjNavMesh->GetPoly(adjPoly.GetPolyIndex()); for(a=0; aGetNumVertices(); a++) { TAdjPoly * linkBack = pAdjNavMesh->GetAdjacentPolysArray().Get( pNeighbourPoly->GetFirstVertexIndex() + a ); if(linkBack->GetNavMeshIndex(pAdjNavMesh->GetAdjacentMeshes()) == pNavMesh->GetIndexOfMesh() && linkBack->GetPolyIndex() == iPolyIndex) { linkBack->SetNavMeshIndex(NAVMESH_INDEX_TESSELLATION, pAdjNavMesh->GetAdjacentMeshes()); linkBack->SetPolyIndex(iDegenerateConnectionPolyIndex); } } } } // Link the long external edge of the connection-poly to the unchanged adjacent poly. // Use the link-type which originally connected this poly (eg. normal, drop-down, etc) TAdjPoly & externalAdjPoly = *pTessellationNavMesh->GetAdjacentPolysArray().Get(pConnectionPoly->GetFirstVertexIndex()); adjPoly.MakeCopy(externalAdjPoly); externalAdjPoly.SetNavMeshIndex(iNavMesh, pTessellationNavMesh->GetAdjacentMeshes()); externalAdjPoly.SetPolyIndex(adjPoly.GetPolyIndex()); externalAdjPoly.SetOriginalNavMeshIndex(NAVMESH_NAVMESH_INDEX_NONE, pTessellationNavMesh->GetAdjacentMeshes()); externalAdjPoly.SetOriginalPolyIndex(NAVMESH_POLY_INDEX_NONE); // Link edges 1 and 2 from the connection poly, to the two tessellation polys it adjoins. TAdjPoly & connectToAdj1 = *pTessellationNavMesh->GetAdjacentPolysArray().Get(pConnectionPoly->GetFirstVertexIndex() + 2); TAdjPoly & connectToAdj2 = *pTessellationNavMesh->GetAdjacentPolysArray().Get(pConnectionPoly->GetFirstVertexIndex() + 1); connectToAdj1.SetNavMeshIndex(NAVMESH_INDEX_TESSELLATION, pTessellationNavMesh->GetAdjacentMeshes()); connectToAdj1.SetPolyIndex(iNewPolyIndices[v*2]); connectToAdj1.SetAdjacencyType(ADJACENCY_TYPE_NORMAL); connectToAdj1.SetEdgeProvidesCover(false); connectToAdj1.SetHighDropOverEdge(false); connectToAdj1.SetAdjacencyDisabled(false); connectToAdj2.SetNavMeshIndex(NAVMESH_INDEX_TESSELLATION, pTessellationNavMesh->GetAdjacentMeshes()); connectToAdj2.SetPolyIndex(iNewPolyIndices[(v*2)+1]); connectToAdj2.SetAdjacencyType(ADJACENCY_TYPE_NORMAL); connectToAdj2.SetEdgeProvidesCover(false); connectToAdj2.SetHighDropOverEdge(false); connectToAdj2.SetAdjacencyDisabled(false); TNavMeshPoly * pConnPoly1 = pTessellationNavMesh->GetPoly(iNewPolyIndices[(v*2)+1]); TNavMeshPoly * pConnPoly2 = pTessellationNavMesh->GetPoly(iNewPolyIndices[(v*2)]); // Link the tessellation polys back to the connection polys TAdjPoly & tessPoly1ToConnectPoly = *pTessellationNavMesh->GetAdjacentPolysArray().Get(pConnPoly1->GetFirstVertexIndex()+1); tessPoly1ToConnectPoly.SetNavMeshIndex(NAVMESH_INDEX_TESSELLATION, pTessellationNavMesh->GetAdjacentMeshes()); tessPoly1ToConnectPoly.SetPolyIndex(iDegenerateConnectionPolyIndex); tessPoly1ToConnectPoly.SetAdjacencyType(ADJACENCY_TYPE_NORMAL); tessPoly1ToConnectPoly.SetEdgeProvidesCover(false); tessPoly1ToConnectPoly.SetHighDropOverEdge(false); tessPoly1ToConnectPoly.SetAdjacencyDisabled(false); TAdjPoly & tessPoly2ToConnectPoly = *pTessellationNavMesh->GetAdjacentPolysArray().Get(pConnPoly2->GetFirstVertexIndex()+1); tessPoly2ToConnectPoly.SetNavMeshIndex(NAVMESH_INDEX_TESSELLATION, pTessellationNavMesh->GetAdjacentMeshes()); tessPoly2ToConnectPoly.SetPolyIndex(iDegenerateConnectionPolyIndex); tessPoly2ToConnectPoly.SetAdjacencyType(ADJACENCY_TYPE_NORMAL); tessPoly2ToConnectPoly.SetEdgeProvidesCover(false); tessPoly2ToConnectPoly.SetHighDropOverEdge(false); tessPoly2ToConnectPoly.SetAdjacencyDisabled(false); // Identify which connection poly we will initially start on. if(pOutStartingPoly && iNavMesh == iReturnStartingPoly_NavMeshIndex && adjPoly.GetPolyIndex() == iReturnStartingPoly_PolyIndex) { *pOutStartingPoly = pConnectionPoly; } lastv = v; } //***************************************************** // Now set up the actual tessellated polygons const u32 iNumTessPolys = pPoly->GetNumVertices()*2; lastv = iNumTessPolys-1; for(v=0; vSetNumSpecialLinks( 0 ); if(outputFragments && (outputFragments->GetCount() < outputFragments->GetCapacity())) { outputFragments->Append() = pTessPoly; } // Clear this flag, so we know that the poly is now in use // NB : Must also set flag(s) and other variables which are inherited from parent poly pPoly->CopyDataIntoTessellatedPoly(*pTessPoly); pTessellationNavMesh->SetTessellatedTriangleSizeFlags(pTessPoly); // This is the edge which connects to the previous tessellated poly in a tri-fan around the central vertex TAdjPoly & prevAdjPoly = *pTessellationNavMesh->GetAdjacentPolysArray().Get(pTessPoly->GetFirstVertexIndex()); prevAdjPoly.SetNavMeshIndex(NAVMESH_INDEX_TESSELLATION, pTessellationNavMesh->GetAdjacentMeshes()); prevAdjPoly.SetPolyIndex(iNewPolyIndices[lastv]); prevAdjPoly.SetOriginalNavMeshIndex(NAVMESH_NAVMESH_INDEX_NONE, pTessellationNavMesh->GetAdjacentMeshes()); prevAdjPoly.SetOriginalPolyIndex(NAVMESH_POLY_INDEX_NONE); prevAdjPoly.SetAdjacencyType(ADJACENCY_TYPE_NORMAL); // This is the edge which connects to the next tessellated poly in a tri-fan around the central vertex TAdjPoly & nextAdjPoly = *pTessellationNavMesh->GetAdjacentPolysArray().Get(pTessPoly->GetFirstVertexIndex() + 2); nextAdjPoly.SetNavMeshIndex(NAVMESH_INDEX_TESSELLATION, pTessellationNavMesh->GetAdjacentMeshes()); nextAdjPoly.SetPolyIndex(iNewPolyIndices[(v+1)%iNumTessPolys]); nextAdjPoly.SetOriginalNavMeshIndex(NAVMESH_NAVMESH_INDEX_NONE, pTessellationNavMesh->GetAdjacentMeshes()); nextAdjPoly.SetOriginalPolyIndex(NAVMESH_POLY_INDEX_NONE); nextAdjPoly.SetAdjacencyType(ADJACENCY_TYPE_NORMAL); // If the poly we are tessellating is the end-poly of our pathsearch, then we need to set a new end-poly // by testing if the path end-pos is within each fragment. We will test the end-pos against each edge of // each fragment triangle, and if on the same side of all edges then this becomes the new end poly. if(bThisIsStartPoly) { const Vector3 * vTestPt = &m_pCurrentActivePathRequest->m_vPathStart; const Vector3 & vVec0 = pTessellationNavMesh->GetVertexPool()[pTessPoly->GetFirstVertexIndex()]; const Vector3 & vVec1 = pTessellationNavMesh->GetVertexPool()[pTessPoly->GetFirstVertexIndex() + 1]; float fLineSide = ((vVec1.x - vVec0.x) * (vTestPt->y - vVec0.y) - (vTestPt->x - vVec0.x) * (vVec1.y - vVec0.y)); if(fLineSide >= 0.0f) { Vector3 & vVec2 = pTessellationNavMesh->GetVertexPool()[pTessPoly->GetFirstVertexIndex() + 2]; fLineSide = ((vVec2.x - vVec0.x) * (vTestPt->y - vVec0.y) - (vTestPt->x - vVec0.x) * (vVec2.y - vVec0.y)); if(fLineSide <= 0.0f) { m_Vars.m_pStartPoly = pTessPoly; } } } if(bThisIsEndPoly) { const Vector3 * vTestPt = &m_pCurrentActivePathRequest->m_vPathEnd; const Vector3 & vVec0 = pTessellationNavMesh->GetVertexPool()[pTessPoly->GetFirstVertexIndex()]; const Vector3 & vVec1 = pTessellationNavMesh->GetVertexPool()[pTessPoly->GetFirstVertexIndex() + 1]; float fLineSide = ((vVec1.x - vVec0.x) * (vTestPt->y - vVec0.y) - (vTestPt->x - vVec0.x) * (vVec1.y - vVec0.y)); if(fLineSide >= 0.0f) { Vector3 & vVec2 = pTessellationNavMesh->GetVertexPool()[pTessPoly->GetFirstVertexIndex() + 2]; fLineSide = ((vVec2.x - vVec0.x) * (vTestPt->y - vVec0.y) - (vTestPt->x - vVec0.x) * (vVec2.y - vVec0.y)); if(fLineSide <= 0.0f) { m_Vars.m_pEndPoly = pTessPoly; } } } if(iNumSpecialLinks) { for(s32 sp=0; spGetVertexPool()[pTessPoly->GetFirstVertexIndex()]; const Vector3 & vVec1 = pTessellationNavMesh->GetVertexPool()[pTessPoly->GetFirstVertexIndex() + 1]; float fLineSide = ((vVec1.x - vVec0.x) * (vTestPt->y - vVec0.y) - (vTestPt->x - vVec0.x) * (vVec1.y - vVec0.y)); if(fLineSide >= 0.0f) { Vector3 & vVec2 = pTessellationNavMesh->GetVertexPool()[pTessPoly->GetFirstVertexIndex() + 2]; fLineSide = ((vVec2.x - vVec0.x) * (vTestPt->y - vVec0.y) - (vTestPt->x - vVec0.x) * (vVec2.y - vVec0.y)); if(fLineSide <= 0.0f) { if(specialLinkInfo[sp].bRetargetFromLink) // Retarget the 'from' link { specialLinkInfo[sp].pLink->SetLinksFromTessellationMesh( (u16)pTessellationNavMesh->GetPolyIndex(pTessPoly) ); } else // Retarget the 'to' link { specialLinkInfo[sp].pLink->SetLinksToTessellationMesh( (u16)pTessellationNavMesh->GetPolyIndex(pTessPoly) ); } //pTessPoly->OrFlags(NAVMESHPOLY_HAS_SPECIAL_LINKS); // flag tessellated poly as having special links pTessPoly->SetNumSpecialLinks( pTessPoly->GetNumSpecialLinks()+1 ); pTessPoly->SetSpecialLinksStartIndex( pPoly->GetSpecialLinksStartIndex() ); specialLinkInfo[sp].pLink = NULL; // null the pointer so we know that we've retargetted this link iNumSpecialLinks--; // dec this var, so that once all are found we no longer execute this code } } } } } lastv = v; } // Flag the old poly so that we know it has been temporarily replaced by tessellated fragments in the m_pTessellationNavMesh pPoly->OrFlags(NAVMESHPOLY_REPLACED_BY_TESSELLATION); if(pNavMesh == pTessellationNavMesh) { TTessInfo * pParentFragmentTessInfo = CPathServer::GetTessInfo(iPolyIndex); pParentFragmentTessInfo->m_bInUse = false; #if __REUSE_TESSELLATION_POLYS CPathServer::m_iCurrentNumTessellationPolys--; #endif } #if !__FINAL CPathServer::ms_iNumTessellationsThisPath++; m_PolyTessellationTimer->Stop(); if(m_pCurrentActivePathRequest) m_pCurrentActivePathRequest->m_fMillisecsSpentInTessellation += (float) m_PolyTessellationTimer->GetTimeMS(); #endif // !__FINAL if(pOutStartingPoly && !(*pOutStartingPoly)) { return false; } return true; } bool CPathServerThread::DoesNavMeshPolyIntersectAnyDynamicObjects(CNavMesh * UNUSED_PARAM(pNavMesh), TNavMeshPoly * pPoly) { return CDynamicObjectsContainer::DoesRegionIntersectAnyObjects(pPoly->m_MinMax); } //************************************************************************* // RemoveAndAddObjects // This function removes those objects which have been marked for deletion // and activates those which are newly added. This is to ensure that we // don't alter the active objects DURING any path request's processing. // The whole system is thread-safe, because any UpdateDynamicObject() calls // from the main thread address the object by indexing into the array, // rather than going through the linked-list structure //************************************************************************* void CPathServerThread::RemoveAndAddObjects(void) { CPathServer::ms_bCurrentlyRemovingAndAddingObjects = true; #ifdef GTA_ENGINE CHECK_FOR_THREAD_STALLS(m_DynamicObjectsCriticalSectionToken) sysCriticalSection dynamicObjectsCriticalSection(m_DynamicObjectsCriticalSectionToken); #else EnterCriticalSection(&m_DynamicObjectsCriticalSection); #endif TDynamicObject * pLast = NULL; TDynamicObject * pCurr = m_pFirstDynamicObject; TDynamicObject * pFirst = m_pFirstDynamicObject; s32 iInitialNumDynamic = CPathServer::m_iNumDynamicObjects; bool bRelinkList = false; while(pCurr) { // Remove those which have been flagged for deletion if(pCurr->m_bFlaggedForDeletion) { if(pLast) { pLast->m_pNext = pCurr->m_pNext; } else { m_pFirstDynamicObject = pCurr->m_pNext; } if(pCurr->m_pOwningGridCell) { Assert(pCurr->m_pOwningGridCell->m_pFirstDynamicObject == pCurr || (pCurr->m_pPrevObjInGridCell || pCurr->m_pNextObjInGridCell)); CDynamicObjectsContainer::RemoveObjectFromGridCell(pCurr); } #if __DEV else { Assert(!pCurr->m_pPrevObjInGridCell && !pCurr->m_pNextObjInGridCell); } #endif TDynamicObject * pTmp = pCurr; pCurr = pCurr->m_pNext; memset(pTmp, 0, sizeof(TDynamicObject)); CPathServer::m_iNumDynamicObjects--; } // Activate those which have been newly added else { pCurr->m_bIsActive = true; pLast = pCurr; pCurr = pCurr->m_pNext; } iInitialNumDynamic--; if(iInitialNumDynamic < 0) { Assertf(false, "Dynamic objects list has iterated too many times - is the list cyclic?"); Displayf("Dynamic objects list has iterated too many times - is the list cyclic?"); bRelinkList = true; break; } if(pCurr == pFirst) { Assertf(false, "Dynamic objects list has become cyclic - this loop will never end"); Displayf("Dynamic objects list has become cyclic - this loop will never end"); bRelinkList = true; break; } } if(bRelinkList) { RelinkDynamicObjectsList(); } #ifndef GTA_ENGINE LeaveCriticalSection(&m_DynamicObjectsCriticalSection); #endif CPathServer::ms_bCurrentlyRemovingAndAddingObjects = false; } void CPathServerThread::RelinkDynamicObjectsList() { Displayf("RelinkDynamicObjectsList - emergency repairs to cyclic list."); Assertf(false, "RelinkDynamicObjectsList - emergency repairs to cyclic list."); CPathServer::m_iNumDynamicObjects = 0; s32 i; for(i=0; im_pNext = &m_DynamicObjectsStore[i]; pLast = &m_DynamicObjectsStore[i]; } } } if(pLast) { pLast->m_pNext = NULL; } } //******************************************************************************** // UpdateAllDynamicObjectsPositions // This function updates all dynamic objects "m_Bounds" from the "m_NewBounds" // member which is set in the background from the main game thread. (Hence all // pathserver thread processing only uses "m_Bounds" member, to avoid syncing // problems. This function also activates new objects, and removes old ones. //******************************************************************************** void CPathServerThread::UpdateAllDynamicObjectsPositions() { #ifdef GTA_ENGINE LOCK_DYNAMIC_OBJECTS_DATA; #else EnterCriticalSection(&m_DynamicObjectsCriticalSection); #endif UpdateAllDynamicObjectsPositionsNoLock(); #ifndef GTA_ENGINE LeaveCriticalSection(&m_DynamicObjectsCriticalSection); #endif } #define __SPECIAL_CASE_IGNORE_BIKES_ON_THEIR_SIDE 1 void CPathServerThread::UpdateAllDynamicObjectsPositionsNoLock() { // If no objects have been added, removed or have changed position/orientation - then we don't need // to do anything here. We can save the overhead of locking the critical section, etc. if(CPathServer::ms_bHaveAnyDynamicObjectsChanged) { Vector3 vObjMin, vObjMax; CPathServer::ms_bHaveAnyDynamicObjectsChanged = false; TDynamicObject * pCurr = m_pFirstDynamicObject; while(pCurr) { // Update the bounds of any entities for whom we have new data if(pCurr->m_bNewBounds) { // If the main game isn't updating the new bounds, then we can copy them. // This is the only place that we may need to wait for the other thread if(pCurr->m_bCurrentlyUpdatingNewBounds) { #if __BANK && defined GTA_ENGINE CPathServer::m_iNumTimesCouldntUpdateObjectsDueToGameThreadAccess++; #endif } else { pCurr->m_bCurrentlyCopyingBounds = true; sys_lwsync(); sysMemCpy(&pCurr->m_Bounds, &pCurr->m_NewBounds, sizeof(TDynObjBounds)); sys_lwsync(); pCurr->m_bCurrentlyCopyingBounds = false; pCurr->m_bNeedsReInsertingIntoGridCells = true; pCurr->m_bNewBounds = false; pCurr->CalcMinMaxForObject(vObjMin, vObjMax); pCurr->m_bIsOutsideWorld = (vObjMin.x < -MINMAX_MAX_FLOAT_VAL || vObjMin.y < -MINMAX_MAX_FLOAT_VAL || vObjMin.z < -MINMAX_MAX_FLOAT_VAL || vObjMax.x >= MINMAX_MAX_FLOAT_VAL || vObjMax.y >= MINMAX_MAX_FLOAT_VAL || vObjMax.z >= MINMAX_MAX_FLOAT_VAL); } } pCurr = pCurr->m_pNext; } } //********************************************************************************************* // Now go through all objects which have moved and reclassify them wrt which gridcells they // are in. This also entails recalculating the min/max of all the objects within these cells. // The first step is to remove the object from the gridcell it is in, and then to add it // to the container again. static const float fSmallObjectHeight = 0.5f; TDynamicObject * pCurr = m_pFirstDynamicObject; while(pCurr) { if(pCurr->m_bIsActive) { // Work out if this object is an obstacle to us in its current orientation. // Some objects (doors, etc) may fall over and no longer pose an obstruction // due to being very thin. #if __DEACTIVATE_FLAT_OBJECTS pCurr->m_bIsCurrentlyAnObstacle = (pCurr->m_Bounds.GetTopZ() - pCurr->m_Bounds.GetBottomZ()) > fSmallObjectHeight; #else pCurr->m_bIsCurrentlyAnObstacle = true; #endif if(pCurr->m_bIsOutsideWorld) pCurr->m_bIsCurrentlyAnObstacle = false; #if __DEACTIVATE_UPROOTED_OBJECTS if(pCurr->m_bHasUprootLimit && DotProduct(pCurr->m_Bounds.m_vRight, ZAXIS) <= 0.5f) { pCurr->m_bIsCurrentlyAnObstacle = false; } #endif #if __SPECIAL_CASE_IGNORE_BIKES_ON_THEIR_SIDE if(pCurr->m_bIsMotorbike) { // For scripted routes, we consider the bike but with reduced box if it is on the side const float fSideThreshold = 0.5f; // 60 degs if(m_pCurrentActivePathRequest && m_pCurrentActivePathRequest->m_bScriptedRoute) { pCurr->m_bForceReducedBoundingBox = (Abs(pCurr->m_Bounds.m_fUpZ) < fSideThreshold); pCurr->m_bIsCurrentlyAnObstacle = true; } else { pCurr->m_bForceReducedBoundingBox = false; pCurr->m_bIsCurrentlyAnObstacle = (Abs(pCurr->m_Bounds.m_fUpZ) >= fSideThreshold); } } #endif if(pCurr->m_bInactiveDueToVelocity) { pCurr->m_bIsCurrentlyAnObstacle = false; } if(!pCurr->m_bActiveForNavigation) { pCurr->m_bIsCurrentlyAnObstacle = false; } // If this path is flagged to ignore all objects except those stuck down? if(m_pCurrentActivePathRequest && m_pCurrentActivePathRequest->m_bIgnoreNonSignificantObjects && !pCurr->m_bIsVehicle && !pCurr->m_bHasUprootLimit && !pCurr->m_bIsSignificant) { pCurr->m_bIsCurrentlyAnObstacle = false; } if(m_pCurrentActivePathRequest && m_pCurrentActivePathRequest->m_bIgnoreTypeVehicles && pCurr->m_bIsVehicle) { pCurr->m_bIsCurrentlyAnObstacle = false; } else if(m_pCurrentActivePathRequest && m_pCurrentActivePathRequest->m_bIgnoreTypeObjects && pCurr->m_bIsObject) { pCurr->m_bIsCurrentlyAnObstacle = false; } else if(m_pCurrentActivePathRequest && m_pCurrentActivePathRequest->m_bDontAvoidFire && pCurr->m_bIsFire) { pCurr->m_bIsCurrentlyAnObstacle = false; } // Deactivate blocking objects based on type of this path request if(m_pCurrentActivePathRequest && pCurr->m_bIsUserAddedBlockingObject) { if(m_pCurrentActivePathRequest->m_bWander && ((pCurr->m_iBlockingObjectFlags & TDynamicObject::BLOCKINGOBJECT_WANDERPATH)==0)) pCurr->m_bIsCurrentlyAnObstacle = false; else if(m_pCurrentActivePathRequest->m_bFleeTarget && ((pCurr->m_iBlockingObjectFlags & TDynamicObject::BLOCKINGOBJECT_FLEEPATH)==0)) pCurr->m_bIsCurrentlyAnObstacle = false; else if((pCurr->m_iBlockingObjectFlags & TDynamicObject::BLOCKINGOBJECT_SHORTESTPATH)==0) pCurr->m_bIsCurrentlyAnObstacle = false; } // Enable objects specifically in include list if(m_pCurrentActivePathRequest && m_pCurrentActivePathRequest->m_iNumIncludeObjects) { const TNavMeshIndex iThis = CPathServer::GetPathServerThread()->GetDynamicObjectIndex(pCurr); for(int o=0; om_iNumIncludeObjects; o++) { if(m_pCurrentActivePathRequest->m_IncludeObjects[o] == iThis) { pCurr->m_bIsCurrentlyAnObstacle = true; break; } } } // Disable objects specifically in exclude list if(m_pCurrentActiveRequest && m_pCurrentActiveRequest->m_iNumExcludeObjects) { const TNavMeshIndex iThis = CPathServer::GetPathServerThread()->GetDynamicObjectIndex(pCurr); for(int o=0; om_iNumExcludeObjects; o++) { if(m_pCurrentActiveRequest->m_ExcludeObjects[o] == iThis) { pCurr->m_bIsCurrentlyAnObstacle = false; break; } } } // If this needs reinserting into the grid-cells system, then do so here if(pCurr->m_bNeedsReInsertingIntoGridCells) { if(!pCurr->m_pOwningGridCell) { CDynamicObjectsContainer::AddObjectToGridCell(pCurr); pCurr->m_pOwningGridCell->m_bMinMaxOfObjectsNeedsRecalculating = true; } else { pCurr->m_pOwningGridCell->m_bMinMaxOfObjectsNeedsRecalculating = true; CDynamicObjectsContainer::RemoveObjectFromGridCell(pCurr); CDynamicObjectsContainer::AddObjectToGridCell(pCurr); pCurr->m_pOwningGridCell->m_bMinMaxOfObjectsNeedsRecalculating = true; } pCurr->m_bNeedsReInsertingIntoGridCells = false; } } pCurr = pCurr->m_pNext; } CDynamicObjectsContainer::RecalculateExtentsOfAllMarkedGrids(); } //**************************************************************************** // IsModelIndexConsideredAnObstacle // This function compares the model-index with the list of indices of // object types which we consider in the pathfinding as dynamic objects. //**************************************************************************** #ifndef GTA_ENGINE bool CPathServer::IsModelIndexConsideredAnObstacle(u32 UNUSED_PARAM(iModelIndex)) { return true; } #else // GTA_ENGINE bool CPathServer::IsModelIndexConsideredAnObstacle(u32 iModelIndex) { CBaseModelInfo * pModelInfo = CModelInfo::GetBaseModelInfo(fwModelId(strLocalIndex(iModelIndex))); // If NOT not-avoided-by-peds, then it is considered an obstacle return !(pModelInfo->GetNotAvoidedByPeds()); } #endif // GTA_ENGINE //****************************************************************************************** // MaybeAddDynamicObject // CWorld calls this function when objects are added to the world // Not all objects & entities are added, only those which pose significant obstructions. //****************************************************************************************** bool CPathServerGta::MaybeAddDynamicObject(CEntity * pEntity) { if(!ShouldAvoidObject(pEntity)) return false; #ifdef GTA_ENGINE CBaseModelInfo * pModelInfo = pEntity->GetBaseModelInfo(); #else // GTA_ENGINE CBaseModelInfo * pModelInfo = NULL; #endif // GTA_ENGINE return AddDynamicObject(pEntity, pModelInfo); } //****************************************************************************************** // MaybeRemoveDynamicObject // CWorld calls this function when objects are removed from the world //****************************************************************************************** void CPathServerGta::MaybeRemoveDynamicObject(CEntity * pEntity) { TDynamicObjectIndex iDynObjIndex; if(pEntity->GetType() == ENTITY_TYPE_OBJECT) { iDynObjIndex = ((CObject*)pEntity)->GetPathServerDynamicObjectIndex(); ((CObject*)pEntity)->SetPathServerDynamicObjectIndex(DYNAMIC_OBJECT_INDEX_NONE); } else if(pEntity->GetType() == ENTITY_TYPE_VEHICLE) { iDynObjIndex = ((CVehicle*)pEntity)->GetPathServerDynamicObjectIndex(); ((CVehicle*)pEntity)->SetPathServerDynamicObjectIndex(DYNAMIC_OBJECT_INDEX_NONE); } else { return; } // If iDynObjIndex is a valid index (and is not DYNAMIC_OBJECT_INDEX_NONE) then proceed if(iDynObjIndex < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS) { FlagDynamicObjectForRemoval(iDynObjIndex); #ifdef GTA_ENGINE if(pEntity->GetType() == ENTITY_TYPE_VEHICLE) { CVehicle * pVehicle = (CVehicle*)pEntity; for(s32 d=0; dGetNumDoors(); d++) { CCarDoor * pDoor = pVehicle->GetDoor(d); if(pDoor) { const TDynamicObjectIndex index = pDoor->GetPathServerDynamicObjectIndex(); if( index < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS ) { FlagDynamicObjectForRemoval(pDoor->GetPathServerDynamicObjectIndex()); pDoor->SetPathServerDynamicObjectIndex(DYNAMIC_OBJECT_INDEX_NONE); } } } } #endif } } #ifdef GTA_ENGINE bool CPathServerGta::RemoveFireObject(const CFire * pFire) { if(pFire->GetPathServerObjectIndex()GetPathServerObjectIndex()); } return false; } bool CPathServerGta::RemoveVehicleDoorObject(const CCarDoor * pDoor) { if(pDoor->GetPathServerDynamicObjectIndex()GetPathServerDynamicObjectIndex()); } return false; } #endif float GetHeading(const Matrix34 & mat) { return Selectf(Abs(mat.b.z) - 1.0f + VERY_SMALL_FLOAT, rage::Atan2f(mat.c.x,mat.a.x), rage::Atan2f(-mat.b.x, mat.b.y)); } float GetRoll(const Matrix34 & mat) { return Selectf(Abs(mat.b.z) - 1.0f + VERY_SMALL_FLOAT, 0.0f, rage::Atan2f(-mat.a.z, mat.c.z)); } float GetPitch(const Matrix34 & mat) { return AsinfSafe(mat.b.z); } #ifdef GTA_ENGINE //-------------------------------------------------------------------------------------------------- // NAME : ProcessAddDeferredDynamicObjects // PURPOSE : Process the deferred adding of scripted blocking objects to the pathfinder. // This is done at a safe point in the frame when we can ensure there are no path requests ongoing, // because it may be necessary to evict less important objects to add critical mission ones - and // this cannot be done whilst a path request is in progress. void CPathServer::ProcessAddDeferredDynamicObjects() { LOCK_NAVMESH_DATA; LOCK_DYNAMIC_OBJECTS_DATA; for(s32 i=0; i(obj.m_iScriptThreadId)); if(pExistingThread) strcpy(existingThreadName, pExistingThread->GetScriptName()); else strcpy(existingThreadName, "(unknown script - maybe already quit)"); char tmp[512]; sprintf(tmp, "ADD_NAVMESH_BLOCKING_OBJECT - a scripted blocking object already exists with exact same coordinates. Is this a duplicate?\nThe script which added the original instance was called \"%s\", the script now trying to add a duplicate object is called \"%s\".\nThe position of this object is (%.2f, %.2f, %.2f).", existingThreadName, CTheScripts::GetCurrentScriptNameAndProgramCounter(), deferredObj.m_vPosition.x, deferredObj.m_vPosition.y, deferredObj.m_vPosition.z); } } } #endif TDynamicObjectIndex index = AddBlockingObject(deferredObj.m_vPosition, deferredObj.m_vSize, deferredObj.m_fHeading, deferredObj.m_iBlockingFlags); Assert(index != DYNAMIC_OBJECT_INDEX_NONE); if(index == DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD) { ASSERT_ONLY(s32 iEvictIndex = ) EvictDynamicObjectForScript(); Assertf(iEvictIndex != -1, "EvictDynamicObjectForScript failed"); index = AddBlockingObject(deferredObj.m_vPosition, deferredObj.m_vSize, deferredObj.m_fHeading, deferredObj.m_iBlockingFlags); Assert(index != DYNAMIC_OBJECT_INDEX_NONE); if(index >= DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD) { Assertf(false, "AddScriptedBlockingObject() - was still unable to add dynamic object, even after evicting one for script."); } else { m_iNumScriptBlockingObjects++; } } if(index != DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD && index != DYNAMIC_OBJECT_INDEX_NONE) { TDynamicObject & obj = m_PathServerThread.m_DynamicObjectsStore[index]; Assert(obj.m_iScriptThreadId == 0xffffffff); obj.m_iScriptThreadId = deferredObj.m_iThreadId; obj.m_bScriptedBlockingObject = true; obj.m_bHasUprootLimit = true; m_ScriptedDynamicObjects[iSlot].m_iScriptObjectHandle = deferredObj.m_iObjectHandle; m_ScriptedDynamicObjects[iSlot].m_iObjectIndex = index; } } m_iNumDeferredAddDynamicObjects = 0; } s32 CPathServerGta::AddScriptedBlockingObject(const scrThreadId iThreadId, const Vector3 & vPosition, const Vector3 & vSize, const float fHeading, const s32 iBlockingFlags) { Assertf(m_iNumDeferredAddDynamicObjects < TScriptDeferredAddDynamicObject::ms_iMaxNum, "No more space for deferred add of scripted blocking object. Max %i reached. Please change your script to add more objects on the next frame,", TScriptDeferredAddDynamicObject::ms_iMaxNum); #if __ASSERT // Assert that we will have a free slot for this object s32 iNumFree=0; for(s32 s=0; s 0, "Scripts have added too many dynamic objects (%i), try cutting down number - are they all required?", MAX_NUM_SCRIPTED_DYNAMIC_OBJECTS); #endif if( m_iNumDeferredAddDynamicObjects < TScriptDeferredAddDynamicObject::ms_iMaxNum ) { m_ScriptDeferredAddDynamicObjects[m_iNumDeferredAddDynamicObjects].m_iObjectHandle = m_iNextScriptObjectHandle; m_ScriptDeferredAddDynamicObjects[m_iNumDeferredAddDynamicObjects].m_iThreadId = iThreadId; m_ScriptDeferredAddDynamicObjects[m_iNumDeferredAddDynamicObjects].m_vPosition = vPosition; m_ScriptDeferredAddDynamicObjects[m_iNumDeferredAddDynamicObjects].m_vSize = vSize; m_ScriptDeferredAddDynamicObjects[m_iNumDeferredAddDynamicObjects].m_fHeading = fHeading; m_ScriptDeferredAddDynamicObjects[m_iNumDeferredAddDynamicObjects].m_iBlockingFlags = iBlockingFlags; const s32 iObjHandle = m_ScriptDeferredAddDynamicObjects[m_iNumDeferredAddDynamicObjects].m_iObjectHandle; m_iNumDeferredAddDynamicObjects++; m_iNextScriptObjectHandle++; if(m_iNextScriptObjectHandle == INT_MAX) m_iNextScriptObjectHandle = 0; return iObjHandle; } return -1; } bool CPathServerGta::UpdateScriptedBlockingObject(const scrThreadId ASSERT_ONLY(iThreadId), const s32 iObjHandle, const Vector3 & vPosition, const Vector3 & vSize, const float fHeading, const s32 iBlockingFlags) { s32 s; // Now go through our list which pairs scripted object handles, with the object index for(s=0; s= 0 && iObjIndex < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS ) { TDynamicObject & obj = m_PathServerThread.m_DynamicObjectsStore[iObjIndex]; Assertf( obj.m_bIsUserAddedBlockingObject && obj.m_bScriptedBlockingObject, "Object is not a user/scripted dynamic object"); if( obj.m_bIsUserAddedBlockingObject && obj.m_bScriptedBlockingObject ) { Assertf( obj.m_iScriptThreadId != 0, "Object has no script thread ID; it was probably already removed."); Assertf(static_cast(iThreadId) == obj.m_iScriptThreadId || obj.m_iScriptThreadId==0xffffffff, "A different script is attempting to update blocking object %i to the one which created it", iObjHandle); obj.m_iBlockingObjectFlags = iBlockingFlags; UpdateBlockingObject((TDynamicObjectIndex)iObjIndex, vPosition, vSize, fHeading, false); return true; } } } } return false; } bool CPathServerGta::DoesScriptedBlockingObjectExist(const scrThreadId UNUSED_PARAM(iThreadId), const s32 iObjHandle) { Assert( GetIsValidPathServerDynamicObjectIndex(TDynamicObjectIndex(iObjHandle)) ); s32 s; // Now go through our list which pairs scripted object handles, with the object index for(s=0; s= 0 && iObjIndex < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS ) { TDynamicObject & obj = m_PathServerThread.m_DynamicObjectsStore[iObjIndex]; Assertf( obj.m_bIsUserAddedBlockingObject && obj.m_bScriptedBlockingObject, "Object is not a user/scripted dynamic object"); if( obj.m_bIsUserAddedBlockingObject && obj.m_bScriptedBlockingObject ) { Assertf( obj.m_iScriptThreadId != 0, "Object has no script thread ID; it was probably already removed."); // Remove if the object's script thread index matches that passed in, OR it is the dummy 0xffffffff value if( obj.m_iScriptThreadId == (u32)iThreadId || obj.m_iScriptThreadId == 0xffffffff ) { FlagDynamicObjectForRemoval( (TDynamicObjectIndex)iObjIndex); bRemoved = true; m_iNumScriptBlockingObjects--; break; } } } } } #if __ASSERT scrThread * pScrThread = scrThread::GetThread(iThreadId); const char * pScriptName = pScrThread ? pScrThread->GetScriptName() : "unknown"; Assertf(bRemoved, "Script \"%s\" is trying to remove dynamic object %i, which is not found.", pScriptName, iObjHandle); #endif return bRemoved; } void CPathServerGta::RemoveAllBlockingObjectsForScript(const scrThreadId iThreadId) { Assert(iThreadId != 0); s32 s; // First remove any which may still be waiting to be added for(s=0; s(iThreadId)) { m_ScriptDeferredAddDynamicObjects[s].m_iObjectHandle = -1; } } // Then remove any which are added for(s32 o=0; om_bFlaggedForDeletion) { Assert(pChosenObj->m_bIsObject || pChosenObj->m_bIsFire); if(pChosenObj->m_bIsObject) { #if __ASSERT Assert(pChosenObj->m_pEntity); if(pChosenObj->m_pEntity) Assertf(((CEntity*)pChosenObj->m_pEntity)->GetIsTypeObject(), "entity type is %i, but should be ENTITY_TYPE_OBJECT", ((CEntity*)pChosenObj->m_pEntity)->GetType()); #endif // Ensure that the entity owning this dynamic object is no longer associated with it. // TODO: We could probably set this to 'DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD' and thereby have the object // add itself to the pathfinder again once space became available. if(pChosenObj->m_pEntity) ((CObject*)(pChosenObj->m_pEntity))->SetPathServerDynamicObjectIndex(DYNAMIC_OBJECT_INDEX_NONE); } else if(pChosenObj->m_bIsFire) { g_fireMan.UnregisterPathserverID((TDynamicObjectIndex)iMinIndex); } } pChosenObj->m_pEntity = NULL; for(o=0; om_pNext == pChosenObj) { pObj->m_pNext = pChosenObj->m_pNext; break; } } } // If we're removing the head of our linked list, we need to set the head element to be the next if(m_PathServerThread.m_pFirstDynamicObject == pChosenObj) { m_PathServerThread.m_pFirstDynamicObject = m_PathServerThread.m_pFirstDynamicObject->m_pNext; } // Remove item from doubly-linked list if(pChosenObj->m_pPrevObjInGridCell) pChosenObj->m_pPrevObjInGridCell->m_pNextObjInGridCell = pChosenObj->m_pNextObjInGridCell; if(pChosenObj->m_pNextObjInGridCell) pChosenObj->m_pNextObjInGridCell->m_pPrevObjInGridCell = pChosenObj->m_pPrevObjInGridCell; // Replace list head pointer in grid cell, if its this object if(pChosenObj->m_pOwningGridCell && pChosenObj->m_pOwningGridCell->m_pFirstDynamicObject == pChosenObj) pChosenObj->m_pOwningGridCell->m_pFirstDynamicObject = pChosenObj->m_pNextObjInGridCell; pChosenObj->m_pPrevObjInGridCell = NULL; pChosenObj->m_pNextObjInGridCell = NULL; pChosenObj->m_pOwningGridCell = NULL; pChosenObj->m_pNext = NULL; pChosenObj->m_pEntity = NULL; m_iNumDynamicObjects--; m_iNumDynamicObjects = MAX(m_iNumDynamicObjects, 0); return iMinIndex; } #ifdef GTA_ENGINE // NAME : AddBlockingObject // PURPOSE : Adds a user-defined blocking object to the pathfinder // Flags are a combination of the TDynamicObject::BlockingObjectFlags enumeration, and define for which types of path the object is active TDynamicObjectIndex CPathServer::AddBlockingObject(const Vector3 & vPosition, const Vector3 & vSize, const float fHeading, const s32 iBlockingFlags) { if(m_iNumDynamicObjects == PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS) { return DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD; } Assertf((iBlockingFlags & TDynamicObject::BLOCKINGOBJECT_ALL_PATH_TYPES)!=0, "Blocking object has to respond to some variety of path request"); CHECK_FOR_THREAD_STALLS(m_DynamicObjectsCriticalSectionToken); sysCriticalSection dynamicObjectsCriticalSection(m_DynamicObjectsCriticalSectionToken); // Find a free slot u32 s; for(s=0; sGetType() == ENTITY_TYPE_OBJECT) { iDynObjIndex = ((CObject*)pEntity)->GetPathServerDynamicObjectIndex(); #ifdef GTA_ENGINE TDynamicObject & obj = m_PathServerThread.m_DynamicObjectsStore[iDynObjIndex]; // If the object has fragmented, then this is set from within the CBreakable class if(((CObject*)pEntity)->m_nObjectFlags.bBoundsNeedRecalculatingForNavigation) bForceUpdate = true; // If the object has been uprooted // Only if not a door. if(obj.m_bHasUprootLimit && !obj.m_bIsDoor && ((CObject*)pEntity)->m_nObjectFlags.bHasBeenUprooted) { obj.m_bHasUprootLimit = false; CPathServer::ms_bHaveAnyDynamicObjectsChanged = true; } nowInactive = ((CObject*)pEntity)->GetVelocity().Mag2() > (ms_fDynamicObjectVelocityThreshold*ms_fDynamicObjectVelocityThreshold) && !bForceActivate; obj.m_bInactiveDueToVelocity = nowInactive; #endif } else if(pEntity->GetType() == ENTITY_TYPE_VEHICLE) { iDynObjIndex = ((CVehicle*)pEntity)->GetPathServerDynamicObjectIndex(); #ifdef GTA_ENGINE TDynamicObject& dynObj = m_PathServerThread.m_DynamicObjectsStore[iDynObjIndex]; bool wasInactive = dynObj.m_bInactiveDueToVelocity; // Trains never deactivate due to velocity (url:bugstar:1039298) if(((CVehicle*)pEntity)->InheritsFromTrain()) { nowInactive = false; } else { nowInactive = ((CVehicle*)pEntity)->GetVelocity().Mag2() > (ms_fDynamicObjectVelocityThreshold*ms_fDynamicObjectVelocityThreshold) && !bForceActivate; } dynObj.m_bInactiveDueToVelocity = nowInactive; dynObj.m_bActiveForNavigation = ((CVehicle*)pEntity)->GetActiveForPedNavitation(); // If we should be inactive now and we weren't before, we may have to deactivate the doors. if(nowInactive && !wasInactive) { deactivateDoors = true; } #endif } else { Assert(0); return; } Assert(iDynObjIndex < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS); #ifdef GTA_ENGINE // Early out if this entity is traveling to fast if(nowInactive) { // If this is a vehicle we must also mark its doors as inactive! if(deactivateDoors) { Assert(pEntity->GetIsTypeVehicle()); const s32 iNumDoors = ((CVehicle*)pEntity)->GetNumDoors(); for(s32 d=0; dGetDoor(d)->GetPathServerDynamicObjectIndex(); if(iDoorObjIndex!=DYNAMIC_OBJECT_INDEX_NONE && iDoorObjIndex!=DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD) m_PathServerThread.m_DynamicObjectsStore[iDoorObjIndex].m_bInactiveDueToVelocity = true; } } return; } #endif //************************************************************************************** // Check whether this object has actually changed position/orientation significantly. // I think we can probably eventually avoid this test, because there is a mechanism // within the world/physics/netcode/etc which will already check for this.. //************************************************************************************** TDynamicObject & dynObj = m_PathServerThread.m_DynamicObjectsStore[iDynObjIndex]; // If it just happens that at this moment, the pathserver thread is copying over the // m_NewBounds data into the m_Bounds data - then don't bother to update this object. // NB : There is still a very small chance that the pathserver may read the data whilst // the game-thread is updating it, we'll have to keep an eye out for instances of dodgy // bounds. Hopefully the fact that they will not change significantly from frame to // frame will reduce any problems. I think this is better than forcing a kernel-level // synchronisation here, but maybe a spinlock would be a good idea here? if(dynObj.m_bCurrentlyCopyingBounds) { // If we are explicitly forcing an update, then we will want to keep trying until it is effective. if(bForceUpdate) { while(dynObj.m_bCurrentlyCopyingBounds) { sysIpcYield(1); } } // Otherwise we can return now, and the object will be updated later. else { return; } } // Check the entiy's orientation & position against the ones stored in the dynamic object. // If this object has new bound data ready which has not been copied from m_NewBounds to // m_Bounds, then check this. const TDynObjBounds & objBounds = dynObj.m_bNewBounds ? dynObj.m_NewBounds : dynObj.m_Bounds; #ifdef NAVGEN_TOOL Vector3 vDiff = pEntity->m_vPosition - objBounds.GetEntityOrigin(); const Matrix34 mat = pEntity->GetMatrix(); #else Vector3 vDiff = VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()) - objBounds.GetEntityOrigin(); const Matrix34 mat = MAT34V_TO_MATRIX34(pEntity->GetMatrix()); #endif // Same position ? if(!bForceUpdate && (vDiff.Mag2() < 0.1f)) { const float fRotThreshold = dynObj.m_bIsDoor ? (2.0f * DtoR) : (25.8f * DtoR); if( Abs(SubtractAngleShorter( objBounds.m_fHeading, GetHeading(mat) ) ) < fRotThreshold ) { if( Abs(SubtractAngleShorter( objBounds.m_fPitch, GetPitch(mat) ) ) < fRotThreshold ) { if( Abs(SubtractAngleShorter( objBounds.m_fRoll, GetRoll(mat) ) ) < fRotThreshold ) { return; } } } } //******************************************************************************************************** // Calculate the new bounds, etc for this object. // Store them in the "TDynamicObject::m_NewBounds" structure, and set the "TDynamicObject::m_bNewBounds" // flag - so that the next time we come across the object, we know to copy in the new values. //******************************************************************************************************** CPathServer::ms_bHaveAnyDynamicObjectsChanged = true; // Mark that we're updating this object dynObj.m_bCurrentlyUpdatingNewBounds = true; sys_lwsync(); TDynObjBoundsFuncs::CalcBoundsForEntity(dynObj.m_NewBounds, pEntity, &dynObj); dynObj.m_NewBounds.m_fHeading = GetHeading(mat); dynObj.m_NewBounds.m_fRoll = GetRoll(mat); dynObj.m_NewBounds.m_fPitch = GetPitch(mat); dynObj.m_NewBounds.m_fUpZ = mat.c.z; dynObj.m_bNewBounds = true; sys_lwsync(); dynObj.m_bCurrentlyUpdatingNewBounds = false; #ifdef GTA_ENGINE if(pEntity->GetIsTypeObject()) { CBaseModelInfo * pModelInfo = pEntity->GetBaseModelInfo(); const bool bIsFixed = pEntity->IsBaseFlagSet(fwEntity::IS_FIXED) || pModelInfo->GetIsFixed(); if(((CObject*)pEntity)->IsADoor()) { CDoor * pDoor = (CDoor*)pEntity; // Garage doors are a special case, because we can't just walk into them to open them. // We'll treat them as immovable (non-uprootable) objects, same as locked doors const bool bGargageDoor = (pDoor->GetDoorType()==CDoor::DOOR_TYPE_GARAGE || pDoor->GetDoorType()==CDoor::DOOR_TYPE_GARAGE_SC); const bool bOnlyVehicles = pDoor->GetTuning() && ((pDoor->GetTuning()->m_Flags.IsFlagSet(CDoorTuning::AutoOpensForMPVehicleWithPedsOnly) && CNetwork::IsGameInProgress()) || (pDoor->GetTuning()->m_Flags.IsFlagSet(CDoorTuning::AutoOpensForSPVehicleWithPedsOnly) && !CNetwork::IsGameInProgress())); const bool bOnlyPlayer = pDoor->GetTuning() && ((pDoor->GetTuning()->m_Flags.IsFlagSet(CDoorTuning::AutoOpensForMPPlayerPedsOnly) && CNetwork::IsGameInProgress()) || (pDoor->GetTuning()->m_Flags.IsFlagSet(CDoorTuning::AutoOpensForSPPlayerPedsOnly) && !CNetwork::IsGameInProgress())); // The openable state may have changed dynObj.m_bIsOpenable = (pModelInfo->GetUsesDoorPhysics() != 0) && CDoor::DoorTypeOpensForPedsWhenNotLocked(pDoor->GetDoorType()) && (!bIsFixed) && (bGargageDoor==false) && (bOnlyVehicles==false) && (bOnlyPlayer == false); // TODO: allow player peds to pathfind through when 'bOnlyPlayer' is true // 'm_bHasUprootLimit' doubles-up as a way of distinguishing locked doors dynObj.m_bHasUprootLimit = (bIsFixed || bGargageDoor || bOnlyVehicles); } else { fragInst * pFragInst = pEntity->GetFragInst(); dynObj.m_bHasUprootLimit = (pFragInst!=NULL && (pFragInst->GetTypePhysics()->GetMinMoveForce() > 0.0f) && !((CObject*)pEntity)->m_nObjectFlags.bHasBeenUprooted); dynObj.m_bIsOpenable = false; } // Always reset this flag ((CObject*)pEntity)->m_nObjectFlags.bBoundsNeedRecalculatingForNavigation = false; } else if(pEntity->GetIsTypeVehicle()) { // If the vehicle was updated, then force the doors to be updated also UpdateVehicleDoorsDynamicObjects((CVehicle*)pEntity, true); } #endif } //************************************************************************** // GetDoorComponents // This function obtains the components of the pVehicle's frag inst which // make up its doors. There will be a maximum of MAX_DOOR_COMPONENTS for // each door, and 4 doors will be queried for - making a max of 8 indices. // The actual num found will be returned. #ifdef GTA_ENGINE int TDynObjBoundsFuncs::GetDoorComponents(const CVehicle * pVehicle, int * iComponents, const s32 iMaxNum) { int iNumComponents = 0; for(int d=0; dGetNumDoors(); d++) { const CCarDoor * pDoor = pVehicle->GetDoor(d); if(pDoor->GetIsIntact(pVehicle) && !pDoor->GetIsClosed()) { iNumComponents += CCarEnterExit::GetDoorComponents(pVehicle, pDoor->GetHierarchyId(), &iComponents[iNumComponents], iMaxNum-iNumComponents); } } return iNumComponents; } int TDynObjBoundsFuncs::GetHeliIgnoreComponents(const CVehicle * pVehicle, int * iComponents, const s32 iMaxNum) { int iNumComponents = 0; if (pVehicle->InheritsFromHeli()) { const int iGroupIndex = pVehicle->GetFragInst()->GetGroupFromBoneIndex(pVehicle->GetBoneIndex(HELI_ROTOR_MAIN)); if (iGroupIndex!=-1) { const fragTypeGroup * pGroup = pVehicle->GetFragInst()->GetTypePhysics()->GetAllGroups()[iGroupIndex]; const int iFirstChildIndex = pGroup->GetChildFragmentIndex(); const int iNumChildren = pGroup->GetNumChildren(); for (int iChild = iFirstChildIndex; iChild < iFirstChildIndex+iNumChildren; iChild++) { iComponents[iNumComponents++] = iChild; if(iNumComponents >= iMaxNum) break; } } if(iNumComponents < iMaxNum) iComponents[iNumComponents++] = pVehicle->GetFragInst()->GetComponentFromBoneIndex(pVehicle->GetBoneIndex(HELI_ROTOR_REAR)); if(iNumComponents < iMaxNum) iComponents[iNumComponents++] = pVehicle->GetFragInst()->GetComponentFromBoneIndex(pVehicle->GetBoneIndex(VEH_CHASSIS_DUMMY)); } return iNumComponents; } s32 TDynObjBoundsFuncs::GetIgnoreComponentsForEntity(const CEntity * pEntity, s32 * pOut_ComponentsArray, const s32 iMaxNum) { int iNumIgnoreComponents = fwPathServer::GetExcludeVehicleDoorsFromNavigation() && pEntity->GetIsTypeVehicle() ? GetDoorComponents((CVehicle*)pEntity, pOut_ComponentsArray, iMaxNum) : 0; iNumIgnoreComponents += fwPathServer::GetExcludeVehicleDoorsFromNavigation() && pEntity->GetIsTypeVehicle() ? GetHeliIgnoreComponents((CVehicle*)pEntity, &pOut_ComponentsArray[iNumIgnoreComponents], iMaxNum-iNumIgnoreComponents) : 0; return iNumIgnoreComponents; } #endif void TDynObjBoundsFuncs::CalcBoundsForEntity(TDynObjBounds& bnds, CEntity * pEntity, TDynamicObject * pDynObj) { // Note that the entity's origin may well not be at its centre #ifdef NAVGEN_TOOL bnds.SetEntityOrigin(pEntity->m_vPosition); #else bnds.SetEntityOrigin(VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition())); #endif const float fExpandRadius = PATHSERVER_PED_RADIUS; #ifdef GTA_ENGINE // Special case hacks float fPedHalfHeight = CEntityBoundAI::ms_fPedHalfHeight; if(MI_RAILWAY_BARRIER_01.IsValid() && (pEntity->GetModelIndex() == MI_RAILWAY_BARRIER_01.GetModelIndex())) fPedHalfHeight = 4.0f; // Components to ignore int iIgnoreComponents[32]; // Must also fit the helicopter components int iNumIgnoreComponents = GetIgnoreComponentsForEntity(pEntity, iIgnoreComponents, 32); Assertf(iNumIgnoreComponents <= 32, "This is very bad, memory overwrite!"); CEntityBoundAI bound(*pEntity, bnds.GetEntityOrigin().z, fExpandRadius, true, NULL, iNumIgnoreComponents, iIgnoreComponents, 0, NULL, fPedHalfHeight); #else CEntityBoundAI bound(*pEntity, bnds.GetEntityOrigin().z, fExpandRadius, true); #endif bound.GetPlanes(bnds.m_vEdgePlaneNormals); Vector3 vCorners[4]; bound.GetCorners(vCorners); bnds.SetOrigin( (vCorners[0] + vCorners[1] + vCorners[2] + vCorners[3]) * 0.25f); // JB : Now using the CEntityBoundAI's radius, since vehicles don't seem to have a radius set anymore. spdSphere sphere; bound.GetSphere(sphere); pDynObj->m_vVertices[0].x = vCorners[0].x; pDynObj->m_vVertices[0].y = vCorners[0].y; pDynObj->m_vVertices[1].x = vCorners[1].x; pDynObj->m_vVertices[1].y = vCorners[1].y; pDynObj->m_vVertices[2].x = vCorners[2].x; pDynObj->m_vVertices[2].y = vCorners[2].y; pDynObj->m_vVertices[3].x = vCorners[3].x; pDynObj->m_vVertices[3].y = vCorners[3].y; #ifdef GTA_ENGINE // NB : New code in CEntityBoundAI now does a better job of finding the top/bottom Z vals for an entity // But we will still increase the bottom Z value, to make sure objects correctly sit into the ground. // This is most noticable for vehicles whose wheels lift their collision off the ground, we need to // pull down their bottom z a bit more than for most objects. const float fPushThroughGroundAmt = pEntity->GetIsTypeVehicle() ? 0.5f : 0.25f; bnds.SetTopZ( bound.GetTopZ() - (fExpandRadius-0.1f) ); bnds.SetBottomZ( bound.GetBottomZ() - fPushThroughGroundAmt ); #else bnds.SetTopZ( bnds.GetOrigin().z + 2.0f ); bnds.SetBottomZ( bnds.GetOrigin().z - 2.0f ); #endif // Construct the top & bottom planes for this object, using world UP & DOWN as normal vectors // TODO : No need to store the whole plane eq's for top & bottom planes (at all?) bnds.m_vEdgePlaneNormals[4] = Vector3(0.0f, 0.0f, 1.0f); bnds.m_vEdgePlaneNormals[4].w = - bnds.m_vEdgePlaneNormals[4].Dot3( Vector3(bnds.GetOrigin().x, bnds.GetOrigin().y, bnds.GetTopZ() ) ); bnds.m_vEdgePlaneNormals[5] = Vector3(0.0f, 0.0f, -1.0f); bnds.m_vEdgePlaneNormals[5].w = - bnds.m_vEdgePlaneNormals[5].Dot3( Vector3(bnds.GetOrigin().x, bnds.GetOrigin().y, bnds.GetBottomZ()) ); } #ifdef GTA_ENGINE bool TDynObjBoundsFuncs::CalcBoundsForVehicleDoor(TDynObjBounds& bnds, CCarDoor * pDoor, CVehicle * pParent, const Matrix34 & doorMat, TDynamicObject * pDynObj) { Assert(pDoor->GetIsIntact(pParent)); Assert((!pDoor->GetIsClosed()) || pDoor->GetForceOpenForNavigation()); bnds.SetEntityOrigin(doorMat.d); // Use reduced expansion radius if this door is being forced open for navigation. // This is a hack, but should help peds fleeing from vehicles who otherwise might get // trapped between front/rear doors which are all opened at the same time for peds to flee. const float fExpandRadius = pDoor->GetForceOpenForNavigation() ? 0.0f : PATHSERVER_PED_RADIUS; Vector3 vBoundBoxMin, vBoundBoxMax; if(!pDoor->GetLocalBoundingBox(pParent, vBoundBoxMin, vBoundBoxMax)) { Assert(false); return false; } Vector3 vTinnerDoors(fExpandRadius * 0.2f, fExpandRadius * 1.2f, fExpandRadius); CEntityBoundAI bound(vBoundBoxMin, vBoundBoxMax, doorMat, bnds.GetEntityOrigin().z, vTinnerDoors, true); bound.GetPlanes(bnds.m_vEdgePlaneNormals); Vector3 vCorners[4]; bound.GetCorners(vCorners); bnds.SetOrigin((vCorners[0] + vCorners[1] + vCorners[2] + vCorners[3]) * 0.25f); spdSphere sphere; bound.GetSphere(sphere); // bnds.m_fBoundingRadius = sphere.GetRadiusf(); pDynObj->m_vVertices[0].x = vCorners[0].x; pDynObj->m_vVertices[0].y = vCorners[0].y; pDynObj->m_vVertices[1].x = vCorners[1].x; pDynObj->m_vVertices[1].y = vCorners[1].y; pDynObj->m_vVertices[2].x = vCorners[2].x; pDynObj->m_vVertices[2].y = vCorners[2].y; pDynObj->m_vVertices[3].x = vCorners[3].x; pDynObj->m_vVertices[3].y = vCorners[3].y; #ifdef GTA_ENGINE // NB : New code in CEntityBoundAI now does a better job of finding the top/bottom Z vals for an entity // But we will still increase the bottom Z value, to make sure objects correctly sit into the ground. // This is most noticable for vehicles whose wheels lift their collision off the ground, we need to // pull down their bottom z a bit more than for most objects. static const float fPushThroughGroundAmt = 0.25f; bnds.SetTopZ( bound.GetTopZ() ); bnds.SetBottomZ( bound.GetBottomZ() - fPushThroughGroundAmt ); #else bnds.SetTopZ( bnds.m_vOrigin.z + 2.0f ); bnds.SetBottomZ( bnds.m_vOrigin.z - 2.0f ); #endif // Construct the top & bottom planes for this object, using world UP & DOWN as normal vectors // TODO : No need to store the whole plane eq's for top & bottom planes (at all?) bnds.m_vEdgePlaneNormals[4] = Vector3(0.0f, 0.0f, 1.0f); bnds.m_vEdgePlaneNormals[4].w = - bnds.m_vEdgePlaneNormals[4].Dot3(Vector3(bnds.GetOrigin().x, bnds.GetOrigin().y, bnds.GetTopZ() )); bnds.m_vEdgePlaneNormals[5] = Vector3(0.0f, 0.0f, -1.0f); bnds.m_vEdgePlaneNormals[5].w = - bnds.m_vEdgePlaneNormals[5].Dot3(Vector3(bnds.GetOrigin().x, bnds.GetOrigin().y, bnds.GetBottomZ() )); return true; } #endif // GTA_ENGINE #ifdef GTA_ENGINE void TDynObjBoundsFuncs::CalcBoundsForFire(TDynObjBounds& bnds, CFire * pFire, TDynamicObject * pDynObj) { Vector3 vPos; if (pFire->GetEntity()) { vPos = VEC3V_TO_VECTOR3(pFire->GetEntity()->GetTransform().GetPosition()); } else { Vec3V vFirePos = pFire->GetPositionWorld(); vPos = RCC_VECTOR3(vFirePos); } bnds.SetEntityOrigin(vPos); bnds.SetOrigin(vPos); bnds.SetTopZ( vPos.z + 1.5f ); bnds.SetBottomZ( vPos.z - 1.0f ); const float fRadius = pFire->GetMaxRadius(); // 0.5f; Vector3 vCorners[4] = { Vector3(-fRadius, fRadius, 0.0f), Vector3(-fRadius, -fRadius, 0.0f), Vector3(fRadius, -fRadius, 0.0f), Vector3(fRadius, fRadius, 0.0f) }; bnds.m_vEdgePlaneNormals[0] = Vector3(0.0f, 1.0f, 0.0f); bnds.m_vEdgePlaneNormals[1] = Vector3(-1.0f, 0.0f, 0.0f); bnds.m_vEdgePlaneNormals[2] = Vector3(0.0f, -1.0f, 0.0f); bnds.m_vEdgePlaneNormals[3] = Vector3(1.0f, 0.0f, 0.0f); bnds.m_vEdgePlaneNormals[4] = Vector3(0.0f, 0.0f, 1.0f); bnds.m_vEdgePlaneNormals[5] = Vector3(0.0f, 0.0f, -1.0f); bnds.m_fHeading = fwRandom::GetRandomNumberInRange(-PI, PI); bnds.m_fPitch = 0.0f; Matrix34 rotMat; rotMat.MakeRotateZ(bnds.m_fHeading); rotMat.d.Zero(); for(s32 p=0; p<4; p++) { rotMat.Transform3x3(vCorners[p]); Vector3 vNormal = bnds.m_vEdgePlaneNormals[p].GetVector3(); rotMat.Transform3x3(vNormal); bnds.m_vEdgePlaneNormals[p].SetVector3(vNormal); vCorners[p] += vPos; } pDynObj->m_vVertices[0].x = vCorners[0].x; pDynObj->m_vVertices[0].y = vCorners[0].y; pDynObj->m_vVertices[1].x = vCorners[1].x; pDynObj->m_vVertices[1].y = vCorners[1].y; pDynObj->m_vVertices[2].x = vCorners[2].x; pDynObj->m_vVertices[2].y = vCorners[2].y; pDynObj->m_vVertices[3].x = vCorners[3].x; pDynObj->m_vVertices[3].y = vCorners[3].y; bnds.m_vEdgePlaneNormals[0].w = - bnds.m_vEdgePlaneNormals[0].Dot3(vCorners[0]); bnds.m_vEdgePlaneNormals[1].w = - bnds.m_vEdgePlaneNormals[1].Dot3(vCorners[1]); bnds.m_vEdgePlaneNormals[2].w = - bnds.m_vEdgePlaneNormals[2].Dot3(vCorners[2]); bnds.m_vEdgePlaneNormals[3].w = - bnds.m_vEdgePlaneNormals[3].Dot3(vCorners[3]); bnds.m_vEdgePlaneNormals[4].w = - bnds.m_vEdgePlaneNormals[4].Dot3(Vector3(bnds.GetOrigin().x, bnds.GetOrigin().y, bnds.GetTopZ() )); bnds.m_vEdgePlaneNormals[5].w = - bnds.m_vEdgePlaneNormals[5].Dot3(Vector3(bnds.GetOrigin().x, bnds.GetOrigin().y, bnds.GetBottomZ() )); } #endif #ifdef GTA_ENGINE void TDynObjBoundsFuncs::CalcBoundsForBlockingObject(TDynObjBounds& bnds, const Vector3 & vPos, const Vector3 & vInputSize, const float fRotation, TDynamicObject * pDynObj) { Vector3 vSize = vInputSize; // Expand by standard ped radius vSize.x += PATHSERVER_PED_RADIUS * 2.0f; vSize.y += PATHSERVER_PED_RADIUS * 2.0f; Vector3 vHalfSize = vSize * 0.5f; bnds.SetEntityOrigin(vPos); bnds.SetOrigin(vPos); bnds.SetTopZ( vPos.z + vSize.z ); bnds.SetBottomZ( vPos.z - 1.0f ); //bnds.m_fBoundingRadius = vHalfSize.Mag(); Vector3 vCorners[4] = { Vector3(-vHalfSize.x, vHalfSize.y, 0.0f), Vector3(-vHalfSize.x, -vHalfSize.y, 0.0f), Vector3(vHalfSize.x, -vHalfSize.y, 0.0f), Vector3(vHalfSize.x, vHalfSize.y, 0.0f) }; bnds.m_vEdgePlaneNormals[0] = Vector3(0.0f, 1.0f, 0.0f); bnds.m_vEdgePlaneNormals[1] = Vector3(-1.0f, 0.0f, 0.0f); bnds.m_vEdgePlaneNormals[2] = Vector3(0.0f, -1.0f, 0.0f); bnds.m_vEdgePlaneNormals[3] = Vector3(1.0f, 0.0f, 0.0f); bnds.m_vEdgePlaneNormals[4] = Vector3(0.0f, 0.0, 1.0f); bnds.m_vEdgePlaneNormals[5] = Vector3(0.0f, 0.0, -1.0f); Matrix34 rotMat; rotMat.MakeRotateZ(fRotation); rotMat.d.Zero(); bnds.m_fHeading = fRotation; bnds.m_fPitch = 0.0f; for(s32 p=0; p<4; p++) { rotMat.Transform3x3(vCorners[p]); Vector3 vNormal = bnds.m_vEdgePlaneNormals[p].GetVector3(); rotMat.Transform3x3(vNormal); bnds.m_vEdgePlaneNormals[p].SetVector3(vNormal); vCorners[p] += vPos; } pDynObj->m_vVertices[0].x = vCorners[0].x; pDynObj->m_vVertices[0].y = vCorners[0].y; pDynObj->m_vVertices[1].x = vCorners[1].x; pDynObj->m_vVertices[1].y = vCorners[1].y; pDynObj->m_vVertices[2].x = vCorners[2].x; pDynObj->m_vVertices[2].y = vCorners[2].y; pDynObj->m_vVertices[3].x = vCorners[3].x; pDynObj->m_vVertices[3].y = vCorners[3].y; bnds.m_vEdgePlaneNormals[0].w = - bnds.m_vEdgePlaneNormals[0].Dot3(vCorners[0]); bnds.m_vEdgePlaneNormals[1].w = - bnds.m_vEdgePlaneNormals[1].Dot3(vCorners[1]); bnds.m_vEdgePlaneNormals[2].w = - bnds.m_vEdgePlaneNormals[2].Dot3(vCorners[2]); bnds.m_vEdgePlaneNormals[3].w = - bnds.m_vEdgePlaneNormals[3].Dot3(vCorners[3]); bnds.m_vEdgePlaneNormals[4].w = - bnds.m_vEdgePlaneNormals[4].Dot3(Vector3(bnds.GetOrigin().x, bnds.GetOrigin().y, bnds.GetTopZ() )); bnds.m_vEdgePlaneNormals[5].w = - bnds.m_vEdgePlaneNormals[5].Dot3(Vector3(bnds.GetOrigin().x, bnds.GetOrigin().y, bnds.GetBottomZ())); } #endif #ifdef GTA_ENGINE void CPathServerGta::UpdateFireObject(CFire * pFire) { Assert(pFire->GetPathServerObjectIndex()!=DYNAMIC_OBJECT_INDEX_NONE); TDynamicObject & dynObj = m_PathServerThread.m_DynamicObjectsStore[pFire->GetPathServerObjectIndex()]; if(dynObj.m_bCurrentlyCopyingBounds) return; #if __ASSERT && AI_OPTIMISATIONS_OFF const intptr_t iEntityPtr = reinterpret_cast(dynObj.m_pEntity); if(iEntityPtr != 0xFFFFFFFF) { if(iEntityPtr==0) { Assertf(false, "CPathServer::UpdateFireObject : expected m_pEntity to be 0xFFFFFFFF, but it was NULL"); } else { Assertf(false, "CPathServer::UpdateFireObject : expected m_pEntity to be 0xFFFFFFFF, but it was %p (entity type==%i)", dynObj.m_pEntity, dynObj.m_pEntity->GetType() ); } } #endif // Check the entiy's orientation & position against the ones stored in the dynamic object. // If this object has new bound data ready which has not been copied from m_NewBounds to // m_Bounds, then check this. Vector3 vPos; if (pFire->GetEntity()) { vPos = VEC3V_TO_VECTOR3(pFire->GetEntity()->GetTransform().GetPosition()); } else { Vec3V vFirePos = pFire->GetPositionWorld(); vPos = RCC_VECTOR3(vFirePos); } const TDynObjBounds & objBounds = dynObj.m_bNewBounds ? dynObj.m_NewBounds : dynObj.m_Bounds; const Vector3 vDiff = vPos - objBounds.GetEntityOrigin(); // Same position ? if(vDiff.Mag2() < 0.1f) return; CPathServer::ms_bHaveAnyDynamicObjectsChanged = true; // Mark that we're updating this object dynObj.m_bCurrentlyUpdatingNewBounds = true; sys_lwsync(); TDynObjBoundsFuncs::CalcBoundsForFire(dynObj.m_NewBounds, pFire, &dynObj); dynObj.m_bNewBounds = true; sys_lwsync(); dynObj.m_bCurrentlyUpdatingNewBounds = false; } #endif #ifdef GTA_ENGINE bool CPathServerGta::AddFireObject(CFire * pFire) { //static const float fMaxPosition = MINMAX_MAX_FLOAT_VAL; // const Vector3 vPos = VEC3V_TO_VECTOR3(pFire->GetPositionWorld()); if(m_iNumDynamicObjects == PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS) return false; CHECK_FOR_THREAD_STALLS(m_DynamicObjectsCriticalSectionToken); sysCriticalSection dynamicObjectsCriticalSection(m_DynamicObjectsCriticalSectionToken); // Find a free slot u32 s; for(s=0; sGetMatrixOffset()); dynObj.m_NewBounds.m_fHeading = GetHeading(mat); dynObj.m_NewBounds.m_fRoll = GetRoll(mat); dynObj.m_NewBounds.m_fPitch = GetPitch(mat); dynObj.m_NewBounds.m_fUpZ = mat.c.z; sysMemCpy(&dynObj.m_Bounds, &dynObj.m_NewBounds, sizeof(TDynObjBounds)); dynObj.CalcMinMaxForObject(); dynObj.m_iScriptThreadId = 0xffffffff; dynObj.m_bIsFire = true; dynObj.m_bIsSignificant = true; dynObj.m_pNext = m_PathServerThread.m_pFirstDynamicObject; m_PathServerThread.m_pFirstDynamicObject = &m_PathServerThread.m_DynamicObjectsStore[s]; m_iNumDynamicObjects++; pFire->SetPathServerObjectIndex((TDynamicObjectIndex)s); return true; } #endif //****************************************************************************************** // AddDynamicObject // Adds a dynamic object to the pathfinding system. This function may stall until the // dynamic object data is free to use. //****************************************************************************************** bool CPathServerGta::AddDynamicObject(CEntity * pEntity, CBaseModelInfo * UNUSED_PARAM(pModelInfo)) { //************************************************************************************* // Check whether the object is within the bounds of the map, and don't add if outside const float fMaxPosition = MINMAX_MAX_FLOAT_VAL; bool bInsideWorld; #ifdef NAVGEN_TOOL const Vector3 vPos = pEntity->m_vPosition; #else const Vector3 vPos = VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()); #endif if(vPos.x < -fMaxPosition || vPos.x > fMaxPosition || vPos.y < -fMaxPosition || vPos.y > fMaxPosition || vPos.z < -fMaxPosition || vPos.z > fMaxPosition) { bInsideWorld = false; } else { bInsideWorld = true; } //********************************************************************************************* // Can't add the object if we are already at max capacity, or the object is outside the world. // NEW : Or if the pathfinder is already busy with the dynamic objects. #ifndef GTA_ENGINE EnterCriticalSection(&m_PathServerThread.m_DynamicObjectsCriticalSection); const bool bObjectsInUse = false; #else const bool bObjectsInUse = (m_DynamicObjectsCriticalSectionToken.TryLock()==false); #endif if(m_iNumDynamicObjects == PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS || !bInsideWorld || bObjectsInUse) { if(pEntity->GetType() == ENTITY_TYPE_OBJECT) { Assert(((CObject*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_NONE || ((CObject*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); ((CObject*)pEntity)->SetPathServerDynamicObjectIndex(DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); } else if(pEntity->GetType() == ENTITY_TYPE_VEHICLE) { Assert(((CVehicle*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_NONE || ((CVehicle*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); ((CVehicle*)pEntity)->SetPathServerDynamicObjectIndex(DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); } else { Assert(0); } #ifdef GTA_ENGINE if(!bObjectsInUse) m_DynamicObjectsCriticalSectionToken.Unlock(); #endif return false; } #if !__FINAL if(CPathServer::m_bDisplayTimeTakenToAddDynamicObjects) START_PERF_TIMER(m_MiscTimer); #endif CPathServer::ms_bHaveAnyDynamicObjectsChanged = true; u32 s; // Find a free slot for(s=0; sGetType() == ENTITY_TYPE_OBJECT) { Assert(((CObject*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_NONE || ((CObject*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); ((CObject*)pEntity)->SetPathServerDynamicObjectIndex(DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); } else if(pEntity->GetType() == ENTITY_TYPE_VEHICLE) { Assert(((CVehicle*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_NONE || ((CVehicle*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); ((CVehicle*)pEntity)->SetPathServerDynamicObjectIndex(DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); } else { Assert(0); } #if !__FINAL if(CPathServer::m_bDisplayTimeTakenToAddDynamicObjects) { float fTimeTakenToAdd; STOP_PERF_TIMER(m_MiscTimer, fTimeTakenToAdd); printf("Dynamic object not added - time taken : %.3f\n", fTimeTakenToAdd); } #endif #ifndef GTA_ENGINE LeaveCriticalSection(&m_PathServerThread.m_DynamicObjectsCriticalSection); #else m_DynamicObjectsCriticalSectionToken.Unlock(); #endif return false; } #ifdef NAVGEN_TOOL const Matrix34 mat = pEntity->GetMatrix(); #else const Matrix34 mat = MAT34V_TO_MATRIX34(pEntity->GetMatrix()); #endif TDynamicObject & dynObj = m_PathServerThread.m_DynamicObjectsStore[s]; dynObj.SetFlagsForNewObject(); TDynObjBoundsFuncs::CalcBoundsForEntity(dynObj.m_NewBounds, pEntity, &dynObj); dynObj.m_NewBounds.m_fHeading = GetHeading(mat); dynObj.m_NewBounds.m_fRoll = GetRoll(mat); dynObj.m_NewBounds.m_fPitch = GetPitch(mat); dynObj.m_NewBounds.m_fUpZ = mat.c.z; sysMemCpy(&dynObj.m_Bounds, &dynObj.m_NewBounds, sizeof(TDynObjBounds)); dynObj.CalcMinMaxForObject(); dynObj.m_bIsFire = false; dynObj.m_bIsUserAddedBlockingObject = false; #ifdef GTA_ENGINE CBaseModelInfo * pModelInfo = pEntity->GetBaseModelInfo(); const bool bIsFixed = pEntity->IsBaseFlagSet(fwEntity::IS_FIXED) || pModelInfo->GetIsFixed(); #else dynObj.m_bIsOpenable = false; #endif #ifdef GTA_ENGINE dynObj.m_pEntity = pEntity; #else dynObj.m_pEntity = (fwEntity*)pEntity; // Hack for NavGenTest. #endif dynObj.m_pNext = m_PathServerThread.m_pFirstDynamicObject; m_PathServerThread.m_pFirstDynamicObject = &m_PathServerThread.m_DynamicObjectsStore[s]; m_iNumDynamicObjects++; // Set a flag inside the object, to notify that it's being avoided by the path-server. // This'll prevent potential collision events from being generated for it, etc. if(pEntity->GetType() == ENTITY_TYPE_OBJECT) { Assert(((CObject*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_NONE || ((CObject*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); Assert(s < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS); ((CObject*)pEntity)->SetPathServerDynamicObjectIndex((TDynamicObjectIndex) s); dynObj.m_bIsPushable = ((CObject*)pEntity)->GetMass() < CPathServerThread::ms_fMaxMassOfPushableObject; dynObj.m_bIsObject = true; #ifdef GTA_ENGINE dynObj.m_bInactiveDueToVelocity = ((CObject*)pEntity)->GetVelocity().Mag2() > (ms_fDynamicObjectVelocityThreshold*ms_fDynamicObjectVelocityThreshold); if(((CObject*)pEntity)->IsADoor()) { CDoor * pDoor = (CDoor*)pEntity; const bool bGargageDoor = (pDoor->GetDoorType()==CDoor::DOOR_TYPE_GARAGE || pDoor->GetDoorType()==CDoor::DOOR_TYPE_GARAGE_SC); const bool bOnlyVehicles = pDoor->GetTuning() && ((pDoor->GetTuning()->m_Flags.IsFlagSet(CDoorTuning::AutoOpensForMPVehicleWithPedsOnly) && CNetwork::IsGameInProgress()) || (pDoor->GetTuning()->m_Flags.IsFlagSet(CDoorTuning::AutoOpensForSPVehicleWithPedsOnly) && !CNetwork::IsGameInProgress())); const bool bOnlyPlayer = pDoor->GetTuning() && ((pDoor->GetTuning()->m_Flags.IsFlagSet(CDoorTuning::AutoOpensForMPPlayerPedsOnly) && CNetwork::IsGameInProgress()) || (pDoor->GetTuning()->m_Flags.IsFlagSet(CDoorTuning::AutoOpensForSPPlayerPedsOnly) && !CNetwork::IsGameInProgress())); // The openable state may have changed dynObj.m_bIsOpenable = (pModelInfo->GetUsesDoorPhysics() != 0) && CDoor::DoorTypeOpensForPedsWhenNotLocked(pDoor->GetDoorType()) && (!bIsFixed) && (bGargageDoor==false) && (bOnlyVehicles==false) && (bOnlyPlayer == false); // TODO: allow player peds to pathfind through when 'bOnlyPlayer' is true // 'm_bHasUprootLimit' doubles-up as a way of distinguishing locked doors dynObj.m_bHasUprootLimit = (bIsFixed || bGargageDoor || bOnlyVehicles); dynObj.m_bIsDoor = true; } else { fragInst * pFragInst = pEntity->GetFragInst(); dynObj.m_bHasUprootLimit = (pFragInst!=NULL && (pFragInst->GetTypePhysics()->GetMinMoveForce() > 0.0f) && !((CObject*)pEntity)->m_nObjectFlags.bHasBeenUprooted); // treat breakable glass as an uprootable object, until broken if(((CObject*)pEntity)->m_nDEflags.bIsBreakableGlass) { dynObj.m_bHasUprootLimit = true; dynObj.m_bIsBreakableGlass = true; } dynObj.m_bIsSignificant = IsObjectSignificant(pEntity); } #endif } else if(pEntity->GetType() == ENTITY_TYPE_VEHICLE) { Assert(((CVehicle*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_NONE || ((CVehicle*)pEntity)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); Assert(s < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS); ((CVehicle*)pEntity)->SetPathServerDynamicObjectIndex((TDynamicObjectIndex) s); dynObj.m_bIsPushable = ((CVehicle*)pEntity)->GetMass() < CPathServerThread::ms_fMaxMassOfPushableObject; dynObj.m_bIsVehicle = true; dynObj.m_bIsMotorbike = (((CVehicle*)pEntity)->GetVehicleType()==VEHICLE_TYPE_BIKE); dynObj.m_bHasUprootLimit = false; dynObj.m_bIsSignificant = true; // All vehicles for now #ifdef GTA_ENGINE dynObj.m_bActiveForNavigation = ((CVehicle*)pEntity)->GetActiveForPedNavitation(); dynObj.m_bInactiveDueToVelocity = ((CVehicle*)pEntity)->GetVelocity().Mag2() > (ms_fDynamicObjectVelocityThreshold*ms_fDynamicObjectVelocityThreshold); //------------------------------------------- // If this is a vehicle, then add the doors if(!dynObj.m_bIsMotorbike && ((CVehicle*)pEntity)->GetNumDoors()) { AddVehicleDoorsDynamicObjects((CVehicle*)pEntity); } #endif } else { Assert(0); } #ifndef GTA_ENGINE LeaveCriticalSection(&m_PathServerThread.m_DynamicObjectsCriticalSection); #else m_DynamicObjectsCriticalSectionToken.Unlock(); #endif return true; } #ifdef GTA_ENGINE void CPathServerGta::AddVehicleDoorsDynamicObjects(CVehicle * pVehicle) { s32 d; const s32 iNumDoors = pVehicle->GetNumDoors(); for(d=0; dGetDoor(d); AddVehicleDoorDynamicObject(pVehicle, pDoor); } } void CPathServerGta::AddVehicleDoorDynamicObject(CVehicle * pVehicle, CCarDoor * pDoor) { s32 s; if( pDoor->GetPathServerDynamicObjectIndex()!=DYNAMIC_OBJECT_INDEX_NONE && pDoor->GetPathServerDynamicObjectIndex()!=DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD ) { /* #if __ASSERT Displayf("url:bugstar:453546 - looks like vehicle door has already been added to navmesh. index=%i", pDoor->GetPathServerDynamicObjectIndex()); Assert(pDoor->GetPathServerDynamicObjectIndex()==DYNAMIC_OBJECT_INDEX_NONE || pDoor->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); #endif */ return; } if( (pDoor->GetIsClosed() && !pDoor->GetForceOpenForNavigation()) || !pDoor->GetIsIntact(pVehicle) || !pDoor->GetFlag(CCarDoor::IS_ARTICULATED) ) return; const eHierarchyId iDoorId = pDoor->GetHierarchyId(); if(iDoorId!=VEH_DOOR_DSIDE_F && iDoorId!=VEH_DOOR_DSIDE_R && iDoorId!=VEH_DOOR_PSIDE_F && iDoorId!=VEH_DOOR_PSIDE_R) { return; } if(m_iNumDynamicObjects == PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS) { Assert(((CCarDoor*)pDoor)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_NONE || ((CCarDoor*)pDoor)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); ((CCarDoor*)pDoor)->SetPathServerDynamicObjectIndex(DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); return; } CPathServer::ms_bHaveAnyDynamicObjectsChanged = true; // Find a free slot // TODO: this is crap, we should be able to get a free entry instantly for(s=0; sGetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_NONE || ((CCarDoor*)pDoor)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); ((CCarDoor*)pDoor)->SetPathServerDynamicObjectIndex(DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); return; } TDynamicObject & dynObj = m_PathServerThread.m_DynamicObjectsStore[s]; dynObj.SetFlagsForNewObject(); Matrix34 localMat, doorMat; if( !pDoor->GetLocalMatrix(pVehicle, localMat, pDoor->GetForceOpenForNavigation() ) ) { NAVMESH_OPTIMISATIONS_OFF_ONLY( Assert(false); ) return; } doorMat.Dot(localMat, MAT34V_TO_MATRIX34(pVehicle->GetMatrix())); TDynObjBoundsFuncs::CalcBoundsForVehicleDoor(dynObj.m_NewBounds, pDoor, pVehicle, doorMat, &dynObj); dynObj.m_NewBounds.m_fHeading = GetHeading(doorMat); dynObj.m_NewBounds.m_fRoll = GetRoll(doorMat); dynObj.m_NewBounds.m_fPitch = GetPitch(doorMat); dynObj.m_NewBounds.m_fUpZ = doorMat.c.z; sysMemCpy(&dynObj.m_Bounds, &dynObj.m_NewBounds, sizeof(TDynObjBounds)); dynObj.CalcMinMaxForObject(); dynObj.m_bInactiveDueToVelocity = ((CVehicle*)pVehicle)->GetVelocity().Mag2() > (ms_fDynamicObjectVelocityThreshold*ms_fDynamicObjectVelocityThreshold); dynObj.m_pCarDoor = pDoor; dynObj.m_pNext = m_PathServerThread.m_pFirstDynamicObject; m_PathServerThread.m_pFirstDynamicObject = &m_PathServerThread.m_DynamicObjectsStore[s]; m_iNumDynamicObjects++; Assert(((CCarDoor*)pDoor)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_NONE || ((CCarDoor*)pDoor)->GetPathServerDynamicObjectIndex() == DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD); Assert(s < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS); ((CCarDoor*)pDoor)->SetPathServerDynamicObjectIndex((TDynamicObjectIndex) s); dynObj.m_bIsPushable = false; dynObj.m_bIsVehicle = false; dynObj.m_bIsMotorbike = false; dynObj.m_bIsFire = false; dynObj.m_bIsUserAddedBlockingObject = false; dynObj.m_bHasUprootLimit = false; dynObj.m_bVehicleDoor = true; dynObj.m_bVehicleDoorAlignedWithXAxis = (iDoorId==VEH_DOOR_DSIDE_F || iDoorId==VEH_DOOR_DSIDE_R); } void CPathServerGta::UpdateVehicleDoorsDynamicObjects(CVehicle * pVehicle, const bool bForceUpdate) { const s32 iNumDoors = pVehicle->GetNumDoors(); for(s32 d=0; dGetDoor(d); UpdateVehicleDoorDynamicObject(pVehicle, pDoor, bForceUpdate); } } void CPathServerGta::UpdateVehicleDoorDynamicObject(CVehicle * pVehicle, CCarDoor * pDoor, const bool bForceUpdate) { if(!pDoor->GetIsIntact(pVehicle) || (pDoor->GetIsClosed() && !pDoor->GetForceOpenForNavigation())) return; const TDynamicObjectIndex iDynObjIndex = pDoor->GetPathServerDynamicObjectIndex(); if(iDynObjIndex==DYNAMIC_OBJECT_INDEX_NONE || iDynObjIndex==DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD) return; TDynamicObject & dynObj = m_PathServerThread.m_DynamicObjectsStore[iDynObjIndex]; if(dynObj.m_bCurrentlyCopyingBounds) { // If we are explicitly forcing an update, then we will want to keep trying until it is effective. if(bForceUpdate) { while(dynObj.m_bCurrentlyCopyingBounds) sysIpcYield(1); } // Otherwise we can return now, and the object will be updated later. else return; } const TDynObjBounds & objBounds = dynObj.m_bNewBounds ? dynObj.m_NewBounds : dynObj.m_Bounds; Matrix34 localMat, doorMat; if( !pDoor->GetLocalMatrix(pVehicle, localMat, pDoor->GetForceOpenForNavigation()) ) { Assert(false); return; } doorMat.Dot(localMat, MAT34V_TO_MATRIX34(pVehicle->GetMatrix())); const Vector3 vDiff = doorMat.d - objBounds.GetEntityOrigin(); // Same position ? if(!bForceUpdate && (vDiff.Mag2() < 0.1f)) { const float fRotThreshold = (2.0f * DtoR); if( Abs(SubtractAngleShorter( objBounds.m_fHeading, GetHeading(doorMat) ) ) < fRotThreshold ) { if( Abs(SubtractAngleShorter( objBounds.m_fPitch, GetPitch(doorMat) ) ) < fRotThreshold ) { if( Abs(SubtractAngleShorter( objBounds.m_fRoll, GetRoll(doorMat) ) ) < fRotThreshold ) { return; } } } } CPathServer::ms_bHaveAnyDynamicObjectsChanged = true; // Mark that we're updating this object dynObj.m_bCurrentlyUpdatingNewBounds = true; sys_lwsync(); TDynObjBoundsFuncs::CalcBoundsForVehicleDoor(dynObj.m_NewBounds, pDoor, pVehicle, doorMat, &dynObj); dynObj.m_NewBounds.m_fHeading = GetHeading(doorMat); dynObj.m_NewBounds.m_fRoll = GetRoll(doorMat); dynObj.m_NewBounds.m_fPitch = GetPitch(doorMat); dynObj.m_NewBounds.m_fUpZ = doorMat.c.z; dynObj.m_bNewBounds = true; dynObj.m_bInactiveDueToVelocity = ((CVehicle*)pVehicle)->GetVelocity().Mag2() > (ms_fDynamicObjectVelocityThreshold*ms_fDynamicObjectVelocityThreshold); sys_lwsync(); dynObj.m_bCurrentlyUpdatingNewBounds = false; } #endif // GTA_ENGINE bool CPathServer::FlagDynamicObjectForRemoval(const TDynamicObjectIndex iDynObjIndex) { Assert(iDynObjIndex < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS); TDynamicObject & dynObj = m_PathServerThread.m_DynamicObjectsStore[iDynObjIndex]; dynObj.m_bFlaggedForDeletion = true; CPathServer::ms_bHaveAnyDynamicObjectsChanged = true; return (iDynObjIndex != DYNAMIC_OBJECT_INDEX_NONE); } //*********************************************************************************************** // Called by constructor for some entity types. If this entity's model type has an associated // navmesh, then add a reference to it so that it will be streamed in by Request/Evict code // (see CPathServer::RequestAndEvictMeshes) #ifdef GTA_ENGINE bool CPathServerGta::MaybeRequestDynamicObjectNavMesh(CEntity * pEntity) { // Currently only call this for boats Assert(pEntity->GetIsTypeVehicle()); if(pEntity->GetIsTypeVehicle()) { CBaseModelInfo * pModelInfo = pEntity->GetBaseModelInfo(); CVehicleModelInfo * pVehModelInfo = (CVehicleModelInfo*)pModelInfo; if(pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_GEN_NAVMESH)) { int i; for(i=0; iGetModelIndex()) { // Set flag in vehicle so that it knows it has an associated navmesh CVehicle * pVehicle = (CVehicle*)pEntity; Assert(!pVehicle->m_nVehicleFlags.bHasDynamicNavMesh); pVehicle->m_nVehicleFlags.bHasDynamicNavMesh = true; ref.m_iNumRefs++; CPathServer::m_iNumDynamicNavMeshesInExistence++; return true; } } } } return false; } #endif //*********************************************************************************************** // Called by object destructors to del a ref if there is an associated navmesh #ifdef GTA_ENGINE bool CPathServerGta::MaybeRemoveDynamicObjectNavMesh(CEntity * pEntity) { // Currently only call this for vehicles Assert(pEntity->GetIsTypeVehicle()); if(pEntity->GetIsTypeVehicle()) { CVehicle * pVehicle = (CVehicle*)pEntity; if(pVehicle->m_nVehicleFlags.bHasDynamicNavMesh) { int i; for(i=0; iGetModelIndex()) { // Reset flag in vehicle so that it knows it no longer has an associated navmesh CVehicle * pVehicle = (CVehicle*)pEntity; Assert(pVehicle->m_nVehicleFlags.bHasDynamicNavMesh); pVehicle->m_nVehicleFlags.bHasDynamicNavMesh = false; ref.m_iNumRefs--; Assert(ref.m_iNumRefs >= 0); CPathServer::m_iNumDynamicNavMeshesInExistence--; Assert(CPathServer::m_iNumDynamicNavMeshesInExistence >= 0); return true; } } } } return false; } #endif// GTA_ENGINE bool Hack_AllowClothOnDynamicObject(const u32 iModelIndex) { if(MI_PROP_FNCLINK_04J.IsValid() && iModelIndex==MI_PROP_FNCLINK_04J.GetModelIndex()) return true; if(MI_PROP_FNCLINK_04K.IsValid() && iModelIndex==MI_PROP_FNCLINK_04K.GetModelIndex()) return true; if(MI_PROP_FNCLINK_04M.IsValid() && iModelIndex==MI_PROP_FNCLINK_04M.GetModelIndex()) return true; if(MI_PROP_FNCLINK_02I.IsValid() && iModelIndex==MI_PROP_FNCLINK_02I.GetModelIndex()) return true; if(MI_PROP_FNCLINK_02J.IsValid() && iModelIndex==MI_PROP_FNCLINK_02J.GetModelIndex()) return true; if(MI_PROP_AIR_WINDSOCK.IsValid() && iModelIndex==MI_PROP_AIR_WINDSOCK.GetModelIndex()) return true; if(MI_PROP_AIR_CARGO_01A.IsValid() && iModelIndex==MI_PROP_AIR_CARGO_01A.GetModelIndex()) return true; if(MI_PROP_AIR_CARGO_01B.IsValid() && iModelIndex==MI_PROP_AIR_CARGO_01B.GetModelIndex()) return true; if(MI_PROP_AIR_CARGO_01C.IsValid() && iModelIndex==MI_PROP_AIR_CARGO_01C.GetModelIndex()) return true; if(MI_PROP_AIR_CARGO_02A.IsValid() && iModelIndex==MI_PROP_AIR_CARGO_02A.GetModelIndex()) return true; if(MI_PROP_AIR_CARGO_02B.IsValid() && iModelIndex==MI_PROP_AIR_CARGO_02B.GetModelIndex()) return true; if(MI_PROP_AIR_TRAILER_1A.IsValid() && iModelIndex==MI_PROP_AIR_TRAILER_1A.GetModelIndex()) return true; if(MI_PROP_AIR_TRAILER_1B.IsValid() && iModelIndex==MI_PROP_AIR_TRAILER_1B.GetModelIndex()) return true; if(MI_PROP_AIR_TRAILER_1C.IsValid() && iModelIndex==MI_PROP_AIR_TRAILER_1C.GetModelIndex()) return true; if(MI_PROP_SKID_TENT_CLOTH.IsValid() && iModelIndex==MI_PROP_SKID_TENT_CLOTH.GetModelIndex()) return true; if(MI_PROP_SKID_TENT_01.IsValid() && iModelIndex==MI_PROP_SKID_TENT_01.GetModelIndex()) return true; if(MI_PROP_SKID_TENT_01B.IsValid() && iModelIndex==MI_PROP_SKID_TENT_01B.GetModelIndex()) return true; if(MI_PROP_SKID_TENT_03.IsValid() && iModelIndex==MI_PROP_SKID_TENT_03.GetModelIndex()) return true; if(MI_PROP_BEACH_PARASOL_01.IsValid() && iModelIndex==MI_PROP_BEACH_PARASOL_01.GetModelIndex()) return true; if(MI_PROP_BEACH_PARASOL_02.IsValid() && iModelIndex==MI_PROP_BEACH_PARASOL_02.GetModelIndex()) return true; if(MI_PROP_BEACH_PARASOL_03.IsValid() && iModelIndex==MI_PROP_BEACH_PARASOL_03.GetModelIndex()) return true; if(MI_PROP_BEACH_PARASOL_04.IsValid() && iModelIndex==MI_PROP_BEACH_PARASOL_04.GetModelIndex()) return true; if(MI_PROP_BEACH_PARASOL_05.IsValid() && iModelIndex==MI_PROP_BEACH_PARASOL_05.GetModelIndex()) return true; if(MI_PROP_BEACH_PARASOL_06.IsValid() && iModelIndex==MI_PROP_BEACH_PARASOL_06.GetModelIndex()) return true; if(MI_PROP_BEACH_PARASOL_07.IsValid() && iModelIndex==MI_PROP_BEACH_PARASOL_07.GetModelIndex()) return true; if(MI_PROP_BEACH_PARASOL_08.IsValid() && iModelIndex==MI_PROP_BEACH_PARASOL_08.GetModelIndex()) return true; if(MI_PROP_BEACH_PARASOL_09.IsValid() && iModelIndex==MI_PROP_BEACH_PARASOL_09.GetModelIndex()) return true; if(MI_PROP_BEACH_PARASOL_10.IsValid() && iModelIndex==MI_PROP_BEACH_PARASOL_10.GetModelIndex()) return true; return false; } bool CPathServerGta::ShouldAvoidObject(const CEntity* pEntity) { #ifdef GTA_ENGINE if(pEntity->GetType() != ENTITY_TYPE_OBJECT && pEntity->GetType() != ENTITY_TYPE_VEHICLE) { return false; } if(!pEntity->GetIsPhysical() || !pEntity->GetCurrentPhysicsInst() || !pEntity->IsCollisionEnabled()) { return false; } CBaseModelInfo * pModelInfo = pEntity->GetBaseModelInfo(); const bool bScriptObject = pEntity->GetOwnedBy() == ENTITY_OWNEDBY_SCRIPT; // Don't avoid things which have been 'baked-into' the navmesh, but also exist as dynamic objects (eg. some fragmentable stuff) if(pModelInfo->GetIsFixedForNavigation() && !bScriptObject) { return false; } // Don't avoid things which are flagged as being unimportant. // But never for doors. Tsk, why are artists marking doors as not-avoid? if(pModelInfo->GetNotAvoidedByPeds() && !pModelInfo->GetUsesDoorPhysics()) { return false; } // Don't avoid weapons if(pModelInfo->GetModelType() == MI_TYPE_WEAPON) { return false; } // Don't avoid cloth objects. If at some point complex objects are created which need avoiding, but which also // contain cloth - then we shall have to exclude the cloth phBound when calling ComputeBoxes() in CEntityBoundAI. if(pEntity->GetType() != ENTITY_TYPE_VEHICLE) { gtaFragType * fragType = pModelInfo->GetFragType(); if(fragType && fragType->GetNumEnvCloths()) { if(!Hack_AllowClothOnDynamicObject(pEntity->GetModelIndex())) { return false; } } } // Do a test on the bounding-box size of the entity. Really small entities need not be avoided. const Vector3 vMin = pEntity->GetBoundingBoxMin(); const Vector3 vMax = pEntity->GetBoundingBoxMax(); const Vector3 vSize = vMax - vMin; // Set a bottom limit for the size of objects which are avoided. This is in addition to the // artist-assigned CBaseModelInfo::GetNotAvoidedByPeds() flag. static const bool bIgnoreFixedObjects = true; static const float fMinSizeForNormalObjects = 0.3f; static const float fMinSizeForFragObjects = 6.0f; // this is intentionally very large as missions were being broken by debris confusing the pathfinder static const float fMinMassForObjects = 30.0f; if(pEntity->GetType() == ENTITY_TYPE_OBJECT) { CObject * pObj = (CObject*)pEntity; // Don't avoid pickups if(pObj->m_nObjectFlags.bIsPickUp) return false; // Don't avoid objects which are attached to things if(pObj->GetIsAttached()) return false; // Don't avoid objects which are fixed, they are already baked into the navmesh. // Unless these are script-created objects, which may be created/repositioned & therefore cannot be baked-in. const bool bIsFixed = pModelInfo->GetIsFixed() || (pEntity->IsBaseFlagSet(fwEntity::IS_FIXED) && !pObj->GetFixedByScriptOrCode()); if(bIgnoreFixedObjects && bIsFixed && !bScriptObject && !pObj->IsADoor()) return false; // Don't avoid objects which are deemed as small enough to ignore if(pObj->GetOwnedBy() == ENTITY_OWNEDBY_FRAGMENT_CACHE) { static const float fMassOfIgnorableFragment = 1000.0f; if(pObj->GetMass() < fMassOfIgnorableFragment && (vSize.x < fMinSizeForFragObjects && vSize.y < fMinSizeForFragObjects && vSize.z < fMinSizeForFragObjects) && (!SpecialCaseShouldAvoidFragmentObject(pObj))) return false; } else { if(vSize.x < fMinSizeForNormalObjects && vSize.y < fMinSizeForNormalObjects && vSize.z < fMinSizeForNormalObjects) return false; } // Don't avoid objects which have no uproot limit, and which are under a certain mass - these will be pushed aside // Skip this logic for script objects, which we can assume will always want to be avoided const bool bIsBreakableGlass = pObj->m_nDEflags.bIsBreakableGlass; const bool bIsSmall = (vSize.x < 0.75f) && (vSize.y < 0.75f) && (vSize.z < 0.75f); //Vector3 RefPos = Vector3(317.89f, 2621.94f, 43.51f); //RefPos.Dist(VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition())) < 0.1f && if (!bScriptObject && !bIsBreakableGlass && bIsSmall && pObj->GetMass() < fMinMassForObjects) { bool bIsTooStrong = false; const fragInst* pFragInst = pObj->GetFragInst(); const fragType* pFragType = pFragInst ? pFragInst->GetType() : NULL; const fragPhysicsLOD* pFragPhysics = pFragType ? pFragType->GetPhysics(0) : NULL; // Force highest lod just to be sure // float minMoveForce = pFragPhysics->GetMinMoveForce(); int nGroups = pFragPhysics ? pFragPhysics->GetNumChildGroups() : 0; for (int i = 0; i < nGroups; ++i) { fragTypeGroup* pGroup = pFragPhysics->GetGroup(i); float fPedScale = pGroup->GetPedScale(); float fStrength = pGroup->GetStrength(); // Logic same as walking in fragInstGta::ModifyImpulseMag, we can assume that ped will be at least moving float breakingImpulse = 75 * fPedScale; if (fStrength > breakingImpulse) { // Avoid since we cannot "break" it out of its position bIsTooStrong = true; } } const bool bHasUprootLimit = (pFragInst!=NULL && (pFragInst->GetTypePhysics()->GetMinMoveForce() > 0.0f) && !((CObject*)pEntity)->m_nObjectFlags.bHasBeenUprooted); if (!bIsTooStrong && !bHasUprootLimit) return false; } } else { if(vSize.x < fMinSizeForNormalObjects && vSize.y < fMinSizeForNormalObjects && vSize.z < fMinSizeForNormalObjects) return false; } #endif // GTA_ENGINE return true; } #ifdef GTA_ENGINE // HACKS for GTA5 // By default we don't avoid fragments who are smaller than 6m on all sides, and whose mass is under a give threshold (1000.0f) // That's a pretty large mass threshold - but as of writing its too risky to reduce it - so we'll instead special-case certain // models whose fragments we will always add into the pathfinder regardless of size/mass. bool CPathServerGta::SpecialCaseShouldAvoidFragmentObject(CObject * pObj) { const u32 mi = pObj->GetModelIndex(); if(mi == MI_PHONEBOX_4.GetModelIndex()) return true; return false; } #endif bool CPathServerGta::SpecialCaseDontRemoveSmashedGlass(CObject * pObj) { bool bIsGlassDoor = pObj->IsADoor(); if(!bIsGlassDoor) return false; static const int iNumPos = 10; static const Vec3V vDoorPositions[iNumPos] = { Vec3V(2509.743f,-266.551f,-38.965f), Vec3V(2527.861f,-273.087f,-58.573f), Vec3V(2530.856f,-273.880f,-58.573f), Vec3V(2536.156f,-283.601f,-58.573f), Vec3V(2533.647f,-284.940f,-58.573f), Vec3V(2506.558f,-282.690f,-58.573f), Vec3V(2503.288f,-276.873f,-58.573f), Vec3V(2523.927f,-274.690f,-58.573f), Vec3V(2475.100f,-263.802f,-70.546f), Vec3V(2467.545f,-272.236f,-70.546f) }; Vec3V vPos = pObj->GetTransform().GetPosition(); for(int i=0; iGetPedIntelligence()->FindTaskActiveMovementByType(CTaskTypes::TASK_MOVE_FOLLOW_NAVMESH); if (pExistingNavMeshTask && pExistingNavMeshTask->IsAvoidingNonMovableObjects()) return false; return true; } #endif // GTA_ENGINE // return false; } bool CPathServerGta::IsObjectSignificant(const CEntity* pEntity) { #ifdef GTA_ENGINE if (pEntity && pEntity->GetType() == ENTITY_TYPE_OBJECT) { const Vector3 vSize = pEntity->GetBoundingBoxMax() - pEntity->GetBoundingBoxMin(); const float fVolume = vSize.x * vSize.y * vSize.z; const bool bIsMinMass = ((CObject*)pEntity)->GetMass() > CPathServerThread::ms_fMinMassOfSmallSignificant; const bool bIsMinSize = fVolume > CPathServerThread::ms_fMinVolumeOfSignificant; const bool bIsSignificantSize = fVolume > CPathServerThread::ms_fBigVolumeOfSignificant; return ((bIsMinMass && bIsMinSize) || bIsSignificantSize); } #endif // GTA_ENGINE return false; } bool CPathServerThread::TestDynamicObjectLOSCallback(TDynamicObject * pObject, void * pData) { if(!pObject->m_bIsCurrentlyAnObstacle) return true; TDynObjLosCallbackData * pCallbackData = (TDynObjLosCallbackData*)pData; // Early-out if min/max of line don't overlap with min/max of object if(pCallbackData->fMinZOfStartAndEnd > pObject->m_Bounds.GetTopZ()) return true; /* if(CPathServer::ms_bSphereRayEarlyOutTestOnObjects) { if(geomDistances::Distance2SegToPoint(pCallbackData->vStartPos, pCallbackData->vEndPos - pCallbackData->vStartPos, pObject->m_Bounds.m_vOrigin) >= pObject->m_Bounds.m_fBoundingRadius) return true; } */ #if __ALLOW_TO_PUSH_VEHICLE_DOORS_CLOSED if(pObject->m_bVehicleDoor && pCallbackData->pPathServerThread->m_pCurrentActivePathRequest && pCallbackData->pPathServerThread->m_pCurrentActivePathRequest->m_bAllowToPushVehicleDoorsClosed) { const float fSign = (pObject->m_bVehicleDoorAlignedWithXAxis) ? 1.0f : -1.0f; const float dx = pCallbackData->vEndPos.x - pCallbackData->vStartPos.x; const float dy = pCallbackData->vEndPos.y - pCallbackData->vStartPos.y; const float fRight = pObject->m_Bounds.m_fHeading - HALF_PI; const Vector2 vRight(-rage::Sinf(fRight), rage::Cosf(fRight)); const float fDot = (dx * (vRight.x * fSign)) + (dy * (vRight.y * fSign)); if(fDot >= 0.0f) { return true; } } #endif Vector3 vClippedStart; Vector3 vClippedEnd; #if __USE_SECOND_OBJECT_LOS_TEST if(pCallbackData->fMaxZOfStartAndEnd < pObject->m_Bounds.GetBottomZ()) { if(pCallbackData->fMaxZOfStartAndEnd+ms_fHeightAboveFirstTestFor2ndDynObjLOS > pObject->m_Bounds.GetBottomZ()) { vClippedStart = Vector3(pCallbackData->vStartPos.x, pCallbackData->vStartPos.y, pCallbackData->vStartPos.z+ms_fHeightAboveFirstTestFor2ndDynObjLOS); vClippedEnd = Vector3(pCallbackData->vEndPos.x, pCallbackData->vEndPos.y, pCallbackData->vEndPos.z+ms_fHeightAboveFirstTestFor2ndDynObjLOS); } else { return true; } } else { vClippedStart = pCallbackData->vStartPos; vClippedEnd = pCallbackData->vEndPos; } #else if(pCallbackData->fMaxZOfStartAndEnd < pObject->m_Bounds.GetBottomZ()) return true; vClippedStart = pCallbackData->vStartPos; vClippedEnd = pCallbackData->vEndPos; #endif // __USE_SECOND_OBJECT_LOS_TEST s32 iNumPlanesStartPointIsBehind = 0; s32 pl; const float* fDynObjPlaneEpsilon = GetDynObjPlaneEpsilon(pObject); for(pl=0; plm_Bounds.m_vEdgePlaneNormals[pl].Dot3(vClippedStart) + pObject->m_Bounds.m_vEdgePlaneNormals[pl].w) + fDynObjPlaneEpsilon[pl]; const float fEndDot = (pObject->m_Bounds.m_vEdgePlaneNormals[pl].Dot3(vClippedEnd) + pObject->m_Bounds.m_vEdgePlaneNormals[pl].w) + fDynObjPlaneEpsilon[pl]; // Both start & end are in front of a plane, so no intersection is possible if(fStartDot > 0.0f && fEndDot > 0.0f) { break; } else if(fStartDot > 0.0f && fEndDot < 0.0f) { const float s = fStartDot / (fStartDot - fEndDot); vClippedStart = vClippedStart + ((vClippedEnd - vClippedStart) * s); } else if(fStartDot < 0.0f) { iNumPlanesStartPointIsBehind++; if(fEndDot > 0.0f) { const float s = fStartDot / (fStartDot - fEndDot); vClippedEnd = vClippedStart + ((vClippedEnd - vClippedStart) * s); } } } // If line wasn't clipped away to any plane, then we must have an intersection // But if the start point was inside, we'll let it go - since the ped is either // slightly inside the object, or is standing on top of it ! if(pl == NUM_DYNAMIC_OBJECT_PLANES) { pCallbackData->pPathServerThread->m_iDynamicObjLos_TotalNumObjectsHit++; // Have we hit an object which we can open ? if(pObject->m_bIsOpenable) { pCallbackData->pPathServerThread->m_iDynamicObjLos_NumOpenableObjectsHit++; } else { pCallbackData->bIntersection = true; return false; } } return true; } bool CPathServerThread::TestDynamicObjectLOS(const Vector3 & vStart, const Vector3 & vEnd) { #if !__FINAL CPathServer::ms_iNumTestDynamicObjectLOS++; #endif m_iDynamicObjLos_TotalNumObjectsHit = 0; m_iDynamicObjLos_NumOpenableObjectsHit = 0; //******************************************************************** // Initial test based on integer coord comparisons //******************************************************************** TShortMinMax minMax; float fMinX, fMaxX, fMinY, fMaxY, fMinZ, fMaxZ; if(vStart.x < vEnd.x) { fMinX = vStart.x; fMaxX = vEnd.x; } else { fMaxX = vStart.x; fMinX = vEnd.x; } if(vStart.y < vEnd.y) { fMinY = vStart.y; fMaxY = vEnd.y; } else { fMaxY = vStart.y; fMinY = vEnd.y; } if(vStart.z < vEnd.z) { fMinZ = vStart.z; fMaxZ = vEnd.z; } else { fMaxZ = vStart.z; fMinZ = vEnd.z; } minMax.SetFloat(fMinX, fMinY, fMinZ - 1.0f, fMaxX, fMaxY, fMaxZ + 1.0f); TDynObjLosCallbackData callbackData; callbackData.vStartPos = vStart; callbackData.vStartPos.z += ms_fHeightAboveNavMeshForDynObjLOS; callbackData.vEndPos = vEnd; callbackData.vEndPos.z += ms_fHeightAboveNavMeshForDynObjLOS; callbackData.fMinZOfStartAndEnd = Min(callbackData.vStartPos.z, callbackData.vEndPos.z); callbackData.fMaxZOfStartAndEnd = Max(callbackData.vStartPos.z, callbackData.vEndPos.z); callbackData.bIntersection = false; callbackData.pPathServerThread = this; CDynamicObjectsContainer::ForAllObjectsIntersectingRegion(minMax, TestDynamicObjectLOSCallback, (void*)&callbackData); return !(callbackData.bIntersection); } bool CPathServerThread::TestDynamicObjectLOSWithRadius(const Vector3 & vStart, const Vector3 & vEnd, const float fRadius) { Vector3 vDir = vEnd - vStart; vDir.Normalize(); const Vector3 vRight = CrossProduct(vDir, Vector3(0.0f,0.0f,1.0f)) * fRadius; if(TestDynamicObjectLOS(vStart - vRight, vEnd - vRight)) { if(TestDynamicObjectLOS(vStart + vRight, vEnd + vRight)) { return true; } } return false; } bool CPathServerThread::TestDynamicObjectLOS(const Vector3 & vStart, const Vector3 & vEnd, atArray & objectList) { #if !__FINAL CPathServer::ms_iNumTestDynamicObjectLOS++; #endif m_iDynamicObjLos_TotalNumObjectsHit = 0; m_iDynamicObjLos_NumOpenableObjectsHit = 0; // Must add a little bit of height here, since the points will actually be on the NavMesh const Vector3 vStartAbove = Vector3(vStart.x, vStart.y, vStart.z + ms_fHeightAboveNavMeshForDynObjLOS); const Vector3 vEndAbove = Vector3(vEnd.x, vEnd.y, vEnd.z + ms_fHeightAboveNavMeshForDynObjLOS); const float fMinHeight = rage::Min(vStartAbove.z, vEndAbove.z); const float fMaxHeight = rage::Max(vStartAbove.z, vEndAbove.z); #if __USE_SECOND_OBJECT_LOS_TEST const float fMaxHeightToTest = fMaxHeight+ms_fHeightAboveFirstTestFor2ndDynObjLOS; const Vector3 vStartAbove2 = Vector3(vStart.x, vStart.y, vStart.z + ms_fHeightAboveNavMeshForDynObjLOS + ms_fHeightAboveFirstTestFor2ndDynObjLOS); const Vector3 vEndAbove2 = Vector3(vEnd.x, vEnd.y, vEnd.z + ms_fHeightAboveNavMeshForDynObjLOS + ms_fHeightAboveFirstTestFor2ndDynObjLOS); #endif int pl; Vector3 vClippedStart, vClippedEnd; // Vector3 vVec = vEnd - vStart; int o = objectList.GetCount(); TDynamicObject ** ppElements = objectList.GetElements(); while(o--) { if (o) // Don't waste time prefetching a potentially invalid address (TODO: Maybe make this PC-specific?) PrefetchDC(ppElements[o-1]); TDynamicObject * pCurr = objectList[o]; const TDynObjBounds & bnds = pCurr->m_Bounds; if(!pCurr->m_bIsCurrentlyAnObstacle) continue; // Early-out cull if lowest start/end Z of line are both above the top of the object if(fMinHeight > bnds.GetTopZ()) continue; /* if(CPathServer::ms_bSphereRayEarlyOutTestOnObjects) { if(geomDistances::Distance2SegToPoint(vStart, vVec, bnds.m_vOrigin) >= bnds.m_fBoundingRadius) continue; } */ // For vehicle doors we can instantly discard tests if the start position is in front of the door // as in these cases we will always want for the door to be pushed closed #if __ALLOW_TO_PUSH_VEHICLE_DOORS_CLOSED if(pCurr->m_bVehicleDoor && m_pCurrentActivePathRequest && m_pCurrentActivePathRequest->m_bAllowToPushVehicleDoorsClosed) { const float fSign = (pCurr->m_bVehicleDoorAlignedWithXAxis) ? 1.0f : -1.0f; const float dx = vEnd.x - vStart.x; const float dy = vEnd.y - vStart.y; const float fRight = pCurr->m_Bounds.m_fHeading - HALF_PI; const Vector2 vRight(-rage::Sinf(fRight), rage::Cosf(fRight)); const float fDot = (dx * (vRight.x * fSign)) + (dy * (vRight.y * fSign)); if(fDot >= 0.0f ) { continue; } } #endif #if __USE_SECOND_OBJECT_LOS_TEST // If this object's lowest extent is *above* the height we normally use (ms_fHeightAboveNavMeshForDynObjLOS) for // object LOS tests, then use the extra height value instead ms_fHeightAboveFirstTestFor2ndDynObjLOS if(fMaxHeight < bnds.GetBottomZ()) { if(fMaxHeightToTest > bnds.GetBottomZ()) { vClippedStart = vStartAbove2; vClippedEnd = vEndAbove2; } else { continue; } } else { vClippedStart = vStartAbove; vClippedEnd = vEndAbove; } #else // Early-out cull if highest start/end Z of line are both below the top of the object if(fMaxHeight < bnds.GetBottomZ()) continue; vClippedStart = vStartAbove; vClippedEnd = vEndAbove; #endif // __USE_SECOND_OBJECT_LOS_TEST //----------------------------------------------------------------- // Firstly perform quick 2d linetests against the object's vertices s32 iNumPlanesStartPointIsBehind = 0; // Otherwise if we have a hit against the 2D edges, we can proceed to test top & bottom planes // to see whether this hit should be discounted { const float* fDynObjPlaneEpsilon = GetDynObjPlaneEpsilon(pCurr); for( pl=0; pl 0.0f && fEndDot > 0.0f) { break; } else if(fStartDot > 0.0f && fEndDot < 0.0f) { const float s = fStartDot / (fStartDot - fEndDot); vClippedStart = vClippedStart + ((vClippedEnd - vClippedStart) * s); } else if(fStartDot < 0.0f) { iNumPlanesStartPointIsBehind++; if(fEndDot > 0.0f) { const float s = fStartDot / (fStartDot - fEndDot); vClippedEnd = vClippedStart + ((vClippedEnd - vClippedStart) * s); } } } // If line wasn't clipped away to any plane, then we must have an intersection if(pl == NUM_DYNAMIC_OBJECT_PLANES) { m_iDynamicObjLos_TotalNumObjectsHit++; // Have we hit an object which we can open ? if(pCurr->m_bIsOpenable) { m_iDynamicObjLos_NumOpenableObjectsHit++; } else { return false; } } } } return true; } bool CPathServerThread::TestIsClimbClearOfObjects(u32 AdjacencyType, TNavMeshPoly* pPoly, TNavMeshPoly* pAdjPoly, CNavMesh* pNavMesh, CNavMesh* pAdjNavMesh) { Assertf(AdjacencyType == ADJACENCY_TYPE_CLIMB_LOW || AdjacencyType == ADJACENCY_TYPE_CLIMB_HIGH, "TestIsClimbClearOfObjects expect proper climb adjtype"); float fTestHeight = 0.f; if (AdjacencyType == ADJACENCY_TYPE_CLIMB_LOW) fTestHeight = 1.f; else if (AdjacencyType == ADJACENCY_TYPE_CLIMB_HIGH) fTestHeight = 2.f; Vector3 vPolyCenter; Vector3 vAdjPolyCenter; if(pNavMesh->GetIndexOfMesh()==NAVMESH_INDEX_TESSELLATION) pNavMesh->GetPolyCentroid(pNavMesh->GetPolyIndex(pPoly), vPolyCenter); else pNavMesh->GetPolyCentroidQuick(pPoly, vPolyCenter); if(pAdjNavMesh->GetIndexOfMesh()==NAVMESH_INDEX_TESSELLATION) pAdjNavMesh->GetPolyCentroid(pAdjNavMesh->GetPolyIndex(pAdjPoly), vAdjPolyCenter); else pAdjNavMesh->GetPolyCentroidQuick(pAdjPoly, vAdjPolyCenter); Vector3 vLinePos = vPolyCenter + ZAXIS * fTestHeight; Vector3 vLineTest = vAdjPolyCenter - vPolyCenter; vLineTest.z = 0.f; return TestDynamicObjectLOS(vLinePos, vLinePos + vLineTest); } bool CPathServerThread::DoesRegionIntersectAnyDynamicObjects(const Vector3 & vPosition, float fRadius) { //******************************************************************** // Initial test based on integer coord comparisons //******************************************************************** TShortMinMax minMax; minMax.SetFloat(vPosition.x - fRadius, vPosition.y - fRadius, vPosition.z - fRadius, vPosition.x + fRadius, vPosition.y + fRadius, vPosition.z + fRadius); bool bPossibleIntersection = false; #ifdef GTA_ENGINE CHECK_FOR_THREAD_STALLS(m_DynamicObjectsCriticalSectionToken) sysCriticalSection dynamicObjectsCriticalSection(m_DynamicObjectsCriticalSectionToken); #else // Wait until dynamic objects are available EnterCriticalSection(&m_DynamicObjectsCriticalSection); #endif TDynamicObject * pCurr = m_pFirstDynamicObject; while(pCurr) { if(!pCurr->m_bIsCurrentlyAnObstacle) { pCurr = pCurr->m_pNext; continue; } if(pCurr->m_bIsActive && minMax.Intersects(pCurr->m_MinMax)) { bPossibleIntersection = true; break; } pCurr = pCurr->m_pNext; } #ifndef GTA_ENGINE LeaveCriticalSection(&m_DynamicObjectsCriticalSection); #endif return bPossibleIntersection; } //***************************************************************************************** // DoesPositionIntersectAnyDynamicObjectsApproximate // This function just checks whether the AABBs of any objects and the vPosition overlap. bool CPathServerThread::DoesPositionIntersectAnyDynamicObjectsApproximate(const Vector3 & vPosition) const { TShortMinMax minMax; minMax.SetFloat(vPosition.x, vPosition.y, vPosition.z - 1.0f, vPosition.x, vPosition.y, vPosition.z + 1.0f); return CDynamicObjectsContainer::DoesRegionIntersectAnyObjects(minMax); } //***************************************************************************************** // DoesPositionIntersectAnyDynamicObjectsApproximate // This function just checks whether the AABBs of any objects and the vPosition overlap. bool CPathServerThread::DoesMinMaxIntersectAnyDynamicObjectsApproximate(const TShortMinMax & minMax) const { return CDynamicObjectsContainer::DoesRegionIntersectAnyObjects(minMax); } //********************************************************************************************************* // FindPositionClearOfDynamicObjects // // Given vPosition (typically the start/end point of a path) this function sees if it intersects any // dynamic objects, and if so it pushes the point out of the closest object edge by the edge-plane normal. // If the position is still inside an object, it continues the process a number of times. In some cases // the point will never get pushed completely clear of objects, and in this case a last-ditch attempt can // be made by jittering the position randomly. (NB: This 'bDoLastResortJittering' is by defualt never // used - instead, the navmesh task will look for the path again but will either contract objects' b-boxes // or will disable object avoidance completely. // "bTestLineOfSight" can be specified to make sure that a navmesh LOS exists (objects not considered) from // vPosition to the newly-adjusted position. This makes sure that we don't push the point clear through a // wall or do something else silly. // "bIgnoreOpenable", "bIgnoreClimbable" & "bIgnorePushable" can all be specified so that certain objects // are not considered obstacles. In practice only "bIgnoreOpenable" is used, since there's been problems // implementing the other two. //********************************************************************************************************* bool CPathServerThread::FindPositionClearOfDynamicObjects(Vector3 & vPosition, bool bDoLastResortJittering, bool bIgnoreOpenable, bool bTestLineOfSight, aiNavDomain domain) { //******************************************************************** // Initial test based on integer coord comparisons //******************************************************************** const Vector3 vInputPos = vPosition; TShortMinMax minMax; minMax.SetFloat(vPosition.x, vPosition.y, vPosition.z - 1.0f, vPosition.x, vPosition.y, vPosition.z + 1.0f); bool bPossibleIntersection = false; #ifdef GTA_ENGINE CHECK_FOR_THREAD_STALLS(m_DynamicObjectsCriticalSectionToken) sysCriticalSection dynamicObjectsCriticalSection(m_DynamicObjectsCriticalSectionToken); #else // Wait until dynamic objects are available EnterCriticalSection(&m_DynamicObjectsCriticalSection); #endif TDynamicObject * pCurr = m_pFirstDynamicObject; while(pCurr) { if(!pCurr->m_bIsActive || !pCurr->m_bIsCurrentlyAnObstacle || !minMax.Intersects(pCurr->m_MinMax) || (bIgnoreOpenable && pCurr->m_bIsOpenable) ) { pCurr->m_bPossibleIntersection = false; } else { pCurr->m_bPossibleIntersection = true; bPossibleIntersection = true; } pCurr = pCurr->m_pNext; } if(!bPossibleIntersection) { #ifndef GTA_ENGINE LeaveCriticalSection(&m_DynamicObjectsCriticalSection); #endif return true; } // We will try a number of times to push the point outside of any dynamic objects it is within. // If we fail each time (for example there are two objects right next to each other), then // we'll have to try another approach to find a nearby safe position. //static const float fExtraPushOutAmount = 0.25f; static const float fEdgeEpsilon = 0.1f; int iNumTries = 4; int p; //, prevp; Vector3 vObjMin, vObjMax; //float fDist1, fDist2, fDist3; //Vector3 vSegmentPlanes[4]; float fEdgePlaneDist; while(iNumTries) { bool bWasInsideAnyDynamicObject = false; pCurr = m_pFirstDynamicObject; while(pCurr) { if(pCurr->m_bPossibleIntersection) { Vector3 vNewPosition = vPosition; bool bInsideThisObject = false; RestartForThisObject: float fMinEdgePlaneDist = FLT_MAX; s32 iMinEdgePlane = -1; // See if we are inside this object at all const float* fDynObjPlaneEpsilon = GetDynObjPlaneEpsilon(pCurr); for(p=0; p<6; p++) { fEdgePlaneDist = (pCurr->m_Bounds.m_vEdgePlaneNormals[p].Dot3(vNewPosition) + pCurr->m_Bounds.m_vEdgePlaneNormals[p].w) + fDynObjPlaneEpsilon[p]; // If we're outside of any edge plane, then we cannot be within the object at all if(fEdgePlaneDist > fEdgeEpsilon) { // Undo any movement on any other planes bInsideThisObject = false; break; } if(p < 4 && Abs(fEdgePlaneDist) < fMinEdgePlaneDist) { fMinEdgePlaneDist = Abs(fEdgePlaneDist); iMinEdgePlane = p; } } if(p==6) { Assert(iMinEdgePlane != -1); vNewPosition += pCurr->m_Bounds.m_vEdgePlaneNormals[ iMinEdgePlane ].GetVector3() * (Abs(fMinEdgePlaneDist) + (fEdgeEpsilon*2.0f)); bInsideThisObject = true; goto RestartForThisObject; } //--------------------------------------------------------------------------------------- // Set the Position to that transformed, note this may be the original position if the // more accurate test found no intersection vPosition = vNewPosition; if( bInsideThisObject ) { bWasInsideAnyDynamicObject = true; } } pCurr = pCurr->m_pNext; } // If we ended up being clear of all objects, then we can quit if(!bWasInsideAnyDynamicObject) break; iNumTries--; } // Now let's have one final assertion that we are no longer inside any dynamic objects. // If we find that we ARE, then we shall have to employ some other method to move the // point into a safe area. For example, we could find a candidate navmesh poly for this // start/end point - and then sample random points within this poly and its neighbours // until a clear point is found. If we still cannot find a clear point, we will have to // return false - and the pathfinding will fail. float fRandomOffsetAmt = 0.5f; static const float fMaxRandOffset = 3.0f; s32 iNumTimesToDoLastResortCase = 8; Vector3 vAdjustedPosition = vPosition; RETRY_LAST_RESORT_CASE: float fMinZ = vPosition.z - 1.5f; float fMaxZ = vPosition.z + 1.5f; bool bPositionIsClear = true; pCurr = m_pFirstDynamicObject; while(pCurr) { if(!pCurr->m_bIsActive) { pCurr = pCurr->m_pNext; continue; } // Is this dynamic object anywhere near vPosition ? pCurr->m_MinMax.GetAsFloats(vObjMin, vObjMax); /* vObjMin.x = pCurr->m_Bounds.m_vOrigin.x - pCurr->m_Bounds.m_fBoundingRadius; vObjMin.y = pCurr->m_Bounds.m_vOrigin.y - pCurr->m_Bounds.m_fBoundingRadius; vObjMin.z = pCurr->m_Bounds.m_vOrigin.z - pCurr->m_Bounds.m_fBoundingRadius; vObjMax.x = pCurr->m_Bounds.m_vOrigin.x + pCurr->m_Bounds.m_fBoundingRadius; vObjMax.y = pCurr->m_Bounds.m_vOrigin.y + pCurr->m_Bounds.m_fBoundingRadius; vObjMax.z = pCurr->m_Bounds.m_vOrigin.z + pCurr->m_Bounds.m_fBoundingRadius; */ if(vPosition.x < vObjMin.x || vPosition.y < vObjMin.y || vPosition.z < vObjMin.z || vPosition.x > vObjMax.x || vPosition.y > vObjMax.y || vPosition.z > vObjMax.z || fMaxZ < pCurr->m_Bounds.GetBottomZ() || fMinZ > pCurr->m_Bounds.GetTopZ()) { // No bbox intersection } else { const float* fDynObjPlaneEpsilon = GetDynObjPlaneEpsilon(pCurr); for(p=0; p<4; p++) { const float fDist1 = (pCurr->m_Bounds.m_vEdgePlaneNormals[p].Dot3(vPosition) + pCurr->m_Bounds.m_vEdgePlaneNormals[p].w) + fDynObjPlaneEpsilon[p]; // If we're outside of any edge plane, then we cannot be within the object at all if(fDist1 > 0.0f) break; } if(p == 4) { bPositionIsClear = false; if(bDoLastResortJittering && iNumTimesToDoLastResortCase > 0) { iNumTimesToDoLastResortCase--; Vector2 vRandomOffset( fwRandom::GetRandomNumberInRange(-fRandomOffsetAmt, fRandomOffsetAmt), fwRandom::GetRandomNumberInRange(-fRandomOffsetAmt, fRandomOffsetAmt) ); vPosition.x = vAdjustedPosition.x + vRandomOffset.x; vPosition.y = vAdjustedPosition.y + vRandomOffset.y; if(fRandomOffsetAmt < fMaxRandOffset) fRandomOffsetAmt += 0.5f; goto RETRY_LAST_RESORT_CASE; } break; } } pCurr = pCurr->m_pNext; } #ifndef GTA_ENGINE LeaveCriticalSection(&m_DynamicObjectsCriticalSection); #endif if(bTestLineOfSight) { if(!TestNavMeshLOS(vInputPos, vPosition, m_Vars.m_iLosFlags, domain)) { vPosition = vInputPos; return false; } } //--------------------------------------------------------------------- /* if( iNumTries == iMaxNumTries ) { pCurr = m_pFirstDynamicObject; while(pCurr) { if( !pCurr->m_bIsActive || !pCurr->m_bPossibleIntersection ) { pCurr = pCurr->m_pNext; continue; } // See if we are inside this object at all const float* fDynObjPlaneEpsilon = GetDynObjPlaneEpsilon(pCurr); for(p=0; p<6; p++) { fDist1 = (DotProduct(pCurr->m_Bounds.m_vEdgePlaneNormals[p], vPosition) + pCurr->m_Bounds.m_fEdgePlaneDists[p]) + fDynObjPlaneEpsilon[p]; // If we're outside of any edge plane, then we cannot be within the object at all if(fDist1 > fEdgeEpsilon) { break; } } // If we're within all 6 planes.. if(p==6) { if( pCurr->m_bIsActive && !pCurr->m_bHasUprootLimit ) { pCurr->m_bIsCurrentlyAnObstacle = false; } } pCurr = pCurr->m_pNext; } } */ //--------------------------------------------------------------------- if (!bPositionIsClear) vPosition = vInputPos; return bPositionIsClear; } void CDynamicObjectsGridCell::RecalculateMinMaxOfObjectsInGrid(void) { m_MinMaxOfObjectsInGrid.SetInvalid(); const TDynamicObject * pObj = m_pFirstDynamicObject; while(pObj) { m_MinMaxOfObjectsInGrid.Union(pObj->m_MinMax); pObj = pObj->m_pNextObjInGridCell; } } void CDynamicObjectsContainer::Init(void) { Clear(); } void CDynamicObjectsContainer::Shutdown(void) { Clear(); } void CDynamicObjectsContainer::Clear(void) { for(int i=0; im_pOwningGridCell); Assert(!pObject->m_pPrevObjInGridCell); Assert(!pObject->m_pNextObjInGridCell); Vector3 vObjPos = pObject->m_Bounds.GetOrigin(); s32 iObjX = MINMAX_FIXEDPT_FROM_FLOAT(vObjPos.x); s32 iObjY = MINMAX_FIXEDPT_FROM_FLOAT(vObjPos.y); s32 iObjZ = MINMAX_FIXEDPT_FROM_FLOAT(vObjPos.z); CDynamicObjectsGridCell * pGridCell; CDynamicObjectsGridCell * pOwnerGridCell = NULL; CDynamicObjectsGridCell * pFirstEmptyGridCell = NULL; for(int i=0; im_pFirstDynamicObject) pFirstEmptyGridCell = pGridCell; // Is the object's origin contained within this grid cell? if(!pGridCell->m_MinMaxOfGrid.LiesWithin(iObjX, iObjY, iObjZ)) continue; pOwnerGridCell = pGridCell; break; } if(!pOwnerGridCell) { // Can we reuse this empty grid cell ? if(pFirstEmptyGridCell) { pOwnerGridCell = pFirstEmptyGridCell; } // Otherwise we'll have to allocate a new grid cell else { // NB (20/2/07) - These allocations are not threadsafe when more than one thread exists on a core (360) & are likely to cause BAD SHIT // to happen. You *must* change this code to use a Pool or something similar asap.. pOwnerGridCell = rage_new CDynamicObjectsGridCell(); if(ms_DynamicObjectGrids.GetCount() >= ms_DynamicObjectGrids.GetCapacity()) { ms_DynamicObjectGrids.Grow() = pOwnerGridCell; } else { ms_DynamicObjectGrids.Append() = pOwnerGridCell; } } // Set up this grid cell with the appropriate min/max for this cover point float fX = (vObjPos.x / CDynamicObjectsGridCell::ms_fSizeOfGrid); float fY = (vObjPos.y / CDynamicObjectsGridCell::ms_fSizeOfGrid); float fZ = (vObjPos.z / CDynamicObjectsGridCell::ms_fSizeOfGrid); s32 iX = (fX >= 0.0f) ? ((s32)fX) : ((s32)(fX-1.0f)); s32 iY = (fY >= 0.0f) ? ((s32)fY) : ((s32)(fY-1.0f)); s32 iZ = (fZ >= 0.0f) ? ((s32)fZ) : ((s32)(fZ-1.0f)); Vector3 vMins, vMaxs; vMins.x = ((float) iX) * CDynamicObjectsGridCell::ms_fSizeOfGrid; vMins.y = ((float) iY) * CDynamicObjectsGridCell::ms_fSizeOfGrid; vMins.z = ((float) iZ) * CDynamicObjectsGridCell::ms_fSizeOfGrid; vMaxs.x = vMins.x + CDynamicObjectsGridCell::ms_fSizeOfGrid; vMaxs.y = vMins.y + CDynamicObjectsGridCell::ms_fSizeOfGrid; vMaxs.z = vMins.z + CDynamicObjectsGridCell::ms_fSizeOfGrid; pOwnerGridCell->m_MinMaxOfGrid.SetFloat(vMins.x, vMins.y, vMins.z, vMaxs.x, vMaxs.y, vMaxs.z); pOwnerGridCell->m_MinMaxOfObjectsInGrid.SetInvalid(); #if __DEV #ifdef PATHSERVER_DEBUG_GRIDCELLS if(iObjX < pOwnerGridCell->m_MinMaxOfGrid.m_iMinX || iObjY < pOwnerGridCell->m_MinMaxOfGrid.m_iMinY || iObjZ < pOwnerGridCell->m_MinMaxOfGrid.m_iMinZ || iObjX >= pOwnerGridCell->m_MinMaxOfGrid.m_iMaxX || iObjY >= pOwnerGridCell->m_MinMaxOfGrid.m_iMaxY || iObjZ >= pOwnerGridCell->m_MinMaxOfGrid.m_iMaxZ) { printf("CDynamicObjectsContainer::AddObjectToGridCell()\n"); printf("Object position was outside of pOwnerGridCell after the new grid was initialised.\n"); printf("vObjPos = (%.2f, %.2f, %.2f)\n", vObjPos.x, vObjPos.y, vObjPos.z); printf("Grid Mins = (%.2f, %.2f, %.2f), Grid Maxs = (%.2f, %.2f, %.2f)\n", vMins.x, vMins.y, vMins.z, vMaxs.x, vMaxs.y, vMaxs.z); printf("iX = %i, iY = %i, iZ = %i\n", iX, iY, iZ); Assert(0); } #endif #endif } // Set up the pointers to insert this object into the cell's linked-list properly Assertf(pOwnerGridCell, "TDynamicObject must have a pOwnerGridCell"); pObject->m_pNextObjInGridCell = pOwnerGridCell->m_pFirstDynamicObject; pObject->m_pPrevObjInGridCell = NULL; if(pObject->m_pNextObjInGridCell) pObject->m_pNextObjInGridCell->m_pPrevObjInGridCell = pObject; pOwnerGridCell->m_pFirstDynamicObject = pObject; pObject->m_pOwningGridCell = pOwnerGridCell; return pOwnerGridCell; } void CDynamicObjectsContainer::RemoveObjectFromGridCell(TDynamicObject * pObject) { Assert(pObject->m_pOwningGridCell); // If we're the first one in the grid's list, then we need to update the grid's pointer accordingly if(pObject->m_pOwningGridCell->m_pFirstDynamicObject == pObject) { Assert(!pObject->m_pPrevObjInGridCell); pObject->m_pOwningGridCell->m_pFirstDynamicObject = pObject->m_pNextObjInGridCell; } // Link up prev/next pointers if(pObject->m_pPrevObjInGridCell) pObject->m_pPrevObjInGridCell->m_pNextObjInGridCell = pObject->m_pNextObjInGridCell; if(pObject->m_pNextObjInGridCell) pObject->m_pNextObjInGridCell->m_pPrevObjInGridCell = pObject->m_pPrevObjInGridCell; // NULL out the pointer to the grid cell which we are in pObject->m_pOwningGridCell = NULL; pObject->m_pPrevObjInGridCell = NULL; pObject->m_pNextObjInGridCell = NULL; } void CDynamicObjectsContainer::RecalculateExtentsOfAllMarkedGrids() { for(s32 i=0; im_bMinMaxOfObjectsNeedsRecalculating) { pGrid->RecalculateMinMaxOfObjectsInGrid(); pGrid->m_bMinMaxOfObjectsNeedsRecalculating = false; } } } void CDynamicObjectsContainer::AdjustAllBoundsByAmount(const TShortMinMax & UNUSED_PARAM(minMax), const float fRadiusDelta, const bool bInitialAdjustment) { const s32 iFixedAmount = MINMAX_FIXEDPT_FROM_FLOAT(fRadiusDelta); for(int c=0; cm_MinMaxOfGrid.m_iMinX -= (s16)iFixedAmount; pCell->m_MinMaxOfGrid.m_iMinY -= (s16)iFixedAmount; pCell->m_MinMaxOfGrid.m_iMinZ -= (s16)iFixedAmount; pCell->m_MinMaxOfGrid.m_iMaxX += (s16)iFixedAmount; pCell->m_MinMaxOfGrid.m_iMaxY += (s16)iFixedAmount; pCell->m_MinMaxOfGrid.m_iMaxZ += (s16)iFixedAmount; pCell->m_MinMaxOfObjectsInGrid.m_iMinX -= (s16)iFixedAmount; pCell->m_MinMaxOfObjectsInGrid.m_iMinY -= (s16)iFixedAmount; pCell->m_MinMaxOfObjectsInGrid.m_iMinZ -= (s16)iFixedAmount; pCell->m_MinMaxOfObjectsInGrid.m_iMaxX += (s16)iFixedAmount; pCell->m_MinMaxOfObjectsInGrid.m_iMaxY += (s16)iFixedAmount; pCell->m_MinMaxOfObjectsInGrid.m_iMaxZ += (s16)iFixedAmount; TDynamicObject * pObj = pCell->m_pFirstDynamicObject; while(pObj) { if(pObj->m_bIsActive && pObj->m_bIsCurrentlyAnObstacle && ((bool)pObj->m_bBoundsAdjustedForEntityWidth) != bInitialAdjustment) { pObj->m_MinMax.m_iMinX -= (s16)iFixedAmount; pObj->m_MinMax.m_iMinY -= (s16)iFixedAmount; pObj->m_MinMax.m_iMinZ -= (s16)iFixedAmount; pObj->m_MinMax.m_iMaxX += (s16)iFixedAmount; pObj->m_MinMax.m_iMaxY += (s16)iFixedAmount; pObj->m_MinMax.m_iMaxZ += (s16)iFixedAmount; pObj->m_bBoundsAdjustedForEntityWidth = bInitialAdjustment; } pObj = pObj->m_pNextObjInGridCell; } } } void CDynamicObjectsContainer::ForAllObjectsIntersectingRegion(const TShortMinMax & minMax, PerObjectCB callBackFn, void * pData) { for(s32 i=0; im_bMinMaxOfObjectsNeedsRecalculating); if(pGrid->m_pFirstDynamicObject && pGrid->m_MinMaxOfObjectsInGrid.Intersects(minMax)) { TDynamicObject * pObj = pGrid->m_pFirstDynamicObject; while(pObj) { if(pObj->m_MinMax.Intersects(minMax)) { if(!callBackFn(pObj, pData)) return; } pObj = pObj->m_pNextObjInGridCell; } } } } bool CDynamicObjectsContainer::DoesRegionIntersectAnyObjects(const TShortMinMax & minMax) { for(s32 i=0; im_bMinMaxOfObjectsNeedsRecalculating); if(pGrid->m_pFirstDynamicObject && pGrid->m_MinMaxOfObjectsInGrid.Intersects(minMax)) { TDynamicObject * pObj = pGrid->m_pFirstDynamicObject; while(pObj) { #ifdef GTA_ENGINE if(CNavMesh::ms_bUsePrefetching && pObj->m_pNextObjInGridCell) PrefetchObject(pObj->m_pNextObjInGridCell); #endif if(pObj->m_MinMax.Intersects(minMax)) return true; pObj = pObj->m_pNextObjInGridCell; } } } return false; } void CDynamicObjectsContainer::GetObjectsIntersectingRegion(const TShortMinMax & minMax, atArray & objectsList, const u32 iFlags, const s32 iMaxCount) { #if !__FINAL CPathServer::ms_iNumGetObjectsIntersectingRegion++; #endif const bool bIgnoreIfOpenable = ((iFlags & TDynamicObject::FLAG_IGNORE_OPENABLE)!=0); const bool bIgnoreIfNotAnObstable = ((iFlags & TDynamicObject::FLAG_IGNORE_NOT_OBSTACLE)!=0); for(s32 i=0; im_bMinMaxOfObjectsNeedsRecalculating); if(pGrid->m_pFirstDynamicObject && pGrid->m_MinMaxOfObjectsInGrid.Intersects(minMax)) { TDynamicObject * pObj = pGrid->m_pFirstDynamicObject; while(pObj) { #ifdef GTA_ENGINE if(CNavMesh::ms_bUsePrefetching && pObj->m_pNextObjInGridCell) PrefetchObject(pObj->m_pNextObjInGridCell); #endif if(bIgnoreIfOpenable && pObj->m_bIsOpenable) { pObj = pObj->m_pNextObjInGridCell; continue; } if(bIgnoreIfNotAnObstable && !pObj->m_bIsCurrentlyAnObstacle) { pObj = pObj->m_pNextObjInGridCell; continue; } if(pObj->m_MinMax.Intersects(minMax)) { if(objectsList.GetCount() >= iMaxCount) { return; } if(objectsList.GetCount()==objectsList.GetCapacity()) { Assertf(false, "CDynamicObjectsContainer::GetObjectsIntersectingRegion() - LIST FULL!\n"); return; } objectsList.Append() = pObj; } pObj = pObj->m_pNextObjInGridCell; } } } } void CDynamicObjectsContainer::GetObjectsIntersectingRegionUsingCache(const TShortMinMax & minMax, atArray & objectsList, const u32 iFlags, const s32 iMaxCount) { #if !__FINAL CPathServer::ms_iNumGetObjectsIntersectingRegion++; #endif const bool bIgnoreIfOpenable = ((iFlags & TDynamicObject::FLAG_IGNORE_OPENABLE)!=0); const bool bIgnoreIfNotAnObstable = ((iFlags & TDynamicObject::FLAG_IGNORE_NOT_OBSTACLE)!=0); CDynamicObjectsGridCell * gridCells[CDynamicObjectsContainer::ms_iMaxNumberOfGrids]; s32 iNumGridCells = CDynamicObjectsContainer::m_GridCellsCache.GetGridCellsIntersectingRegion(minMax, gridCells, CDynamicObjectsContainer::ms_iMaxNumberOfGrids); if(iNumGridCells == -1) { // Region wasn't in the cache (must be reasonably far away from the origin). // Fall back to the uncached code path. Give a NAVMESH_OPTIMISATION_OFF assert, because I'd like to know if this ever happens in regular gameplay. NAVMESH_OPTIMISATIONS_OFF_ONLY( Assertf(false, "Region wasn't in cache - falling back to uncached codepath."); ) GetObjectsIntersectingRegion(minMax, objectsList, iFlags, iMaxCount); return; } for(s32 i=0; im_bMinMaxOfObjectsNeedsRecalculating); //if(pGrid->m_pFirstDynamicObject && pGrid->m_MinMaxOfObjectsInGrid.Intersects(minMax)) { TDynamicObject * pObj = pGrid->m_pFirstDynamicObject; while(pObj) { #ifdef GTA_ENGINE if(CNavMesh::ms_bUsePrefetching && pObj->m_pNextObjInGridCell) PrefetchObject(pObj->m_pNextObjInGridCell); #endif if(bIgnoreIfOpenable && pObj->m_bIsOpenable) { pObj = pObj->m_pNextObjInGridCell; continue; } if(bIgnoreIfNotAnObstable && !pObj->m_bIsCurrentlyAnObstacle) { pObj = pObj->m_pNextObjInGridCell; continue; } if(pObj->m_MinMax.Intersects(minMax)) { if(objectsList.GetCount() >= iMaxCount) { return; } if(objectsList.GetCount()==objectsList.GetCapacity()) { Assertf(false, "CDynamicObjectsContainer::GetObjectsIntersectingRegion() - LIST FULL!\n"); return; } objectsList.Append() = pObj; } pObj = pObj->m_pNextObjInGridCell; } } } } void CGridCellsCache::Reset(const Vector3 & vOrigin) { const s32 iMaxNum = CGridCellsCache::ms_iNumCellsAcross*CGridCellsCache::ms_iNumCellsAcross; memset(m_pEntries, 0, sizeof(CEntry*)*iMaxNum); m_iNextFree = 0; const Vector3 vExtents(ms_fMaxRange, ms_fMaxRange, ms_fMaxRange); Vector3 vOriginRounded; vOriginRounded.x = (float) (((s32)(vOrigin.x/ms_fCacheCellSize)) * ms_fCacheCellSize); vOriginRounded.y = (float) (((s32)(vOrigin.y/ms_fCacheCellSize)) * ms_fCacheCellSize); vOriginRounded.z = (float) (((s32)(vOrigin.z/ms_fCacheCellSize)) * ms_fCacheCellSize); m_vMin = vOriginRounded - vExtents; m_vMax = vOriginRounded + vExtents; m_MinMax.Set(m_vMin, m_vMax); } bool CGridCellsCache::Add(CDynamicObjectsGridCell * pGridCell) { Assert(!pGridCell->m_bMinMaxOfObjectsNeedsRecalculating); if(!pGridCell->m_pFirstDynamicObject || !m_MinMax.Intersects(pGridCell->m_MinMaxOfObjectsInGrid)) { return true; } Vector3 vGridMin, vGridMax; pGridCell->m_MinMaxOfObjectsInGrid.GetAsFloats(vGridMin, vGridMax); const s32 iGridCellMinX = (s32) ((vGridMin.x - m_vMin.x) / ms_fCacheCellSize); const s32 iGridCellMinY = (s32) ((vGridMin.y - m_vMin.y) / ms_fCacheCellSize); const s32 iGridCellMaxX = (s32) ((vGridMax.x - m_vMin.x) / ms_fCacheCellSize) + 1; const s32 iGridCellMaxY = (s32) ((vGridMax.y - m_vMin.y) / ms_fCacheCellSize) + 1; if(iGridCellMinX < 0 || iGridCellMinX >= ms_iNumCellsAcross || iGridCellMinY < 0 || iGridCellMinY >= ms_iNumCellsAcross || iGridCellMaxX < 0 || iGridCellMaxX >= ms_iNumCellsAcross || iGridCellMaxY < 0 || iGridCellMaxY >= ms_iNumCellsAcross) { return true; } s32 x,y; for(x=iGridCellMinX; x= ms_iNumCacheCells) { // If we have run out of cached slots then we return false // The calling code can then turn off the cache for this path request return false; } const s32 iIndex = (y*ms_iNumCellsAcross)+x; m_pPool[m_iNextFree].m_pGridCell = pGridCell; m_pPool[m_iNextFree].m_pNextVertical = m_pEntries[iIndex]; m_pEntries[iIndex] = &m_pPool[m_iNextFree]; m_iNextFree++; } } return true; } s32 CGridCellsCache::GetGridCellsIntersectingRegion(const TShortMinMax & region, CDynamicObjectsGridCell ** ppGridCells, const s32 iMaxNumItems) { if(!region.Intersects(m_MinMax)) return -1; static u32 iTimeStamp = 0; iTimeStamp++; s32 iNumGrids = 0; Vector3 vRegionMin, vRegionMax; region.GetAsFloats(vRegionMin, vRegionMax); s32 iRegionMinX = (s32) ((vRegionMin.x - m_vMin.x) / ms_fCacheCellSize); s32 iRegionMinY = (s32) ((vRegionMin.y - m_vMin.y) / ms_fCacheCellSize); s32 iRegionMaxX = (s32) ((vRegionMax.x - m_vMin.x) / ms_fCacheCellSize) + 1; s32 iRegionMaxY = (s32) ((vRegionMax.y - m_vMin.y) / ms_fCacheCellSize) + 1; iRegionMinX = Max(iRegionMinX, 0); iRegionMinY = Max(iRegionMinY, 0); iRegionMaxX = Min(iRegionMaxX, ms_iNumCellsAcross); iRegionMaxY = Min(iRegionMaxY, ms_iNumCellsAcross); s32 x,y; for(x=iRegionMinX; x= 0 && iIndex < ms_iNumCellsAcross*ms_iNumCellsAcross); CEntry * pEntry = m_pEntries[iIndex]; while(pEntry) { if(pEntry->m_pGridCell->m_iTimeStamp != iTimeStamp) { pEntry->m_pGridCell->m_iTimeStamp = iTimeStamp; if(!region.Intersects(pEntry->m_pGridCell->m_MinMaxOfObjectsInGrid)) { // Disjoint } else { ppGridCells[iNumGrids++] = pEntry->m_pGridCell; if(iNumGrids >= iMaxNumItems) return iMaxNumItems; } } pEntry = pEntry->m_pNextVertical; } } } return iNumGrids; } void CDynamicObjectsContainer::InitGridCellsCache(const Vector3 & vSearchOrigin, const TShortMinMax & searchExtents) { ms_bCacheEnabled = true; m_GridCellsCache.Reset(vSearchOrigin); for(s32 g=0; gm_MinMaxOfObjectsInGrid.Intersects(searchExtents) ) { if (!m_GridCellsCache.Add(pGrid)) { ms_bCacheEnabled = false; break; } } } } bool CPathServer::TestPolyIntersectionAgainstObjectPlanes(const TDynamicObject * pObj, const TNavMeshPoly * pPoly, const Vector3 * pPolyVerts) { static Vector3 vClippedVerts1[NAVMESHPOLY_MAX_NUM_VERTICES*4]; // Large array sizes to be on safe side static Vector3 vClippedVerts2[NAVMESHPOLY_MAX_NUM_VERTICES*4]; const Vector3 * pSrcVerts = pPolyVerts; Vector3 * pDestVerts = &vClippedVerts2[0]; int iNumSrcPts = pPoly->GetNumVertices(); static const float fEps = 0.0f; const float* fDynObjPlaneEpsilon = CPathServerThread::GetDynObjPlaneEpsilon(pObj); int pl; for(pl=0; pl<4; pl++) { int iNumDestPts = 0; int lastv = iNumSrcPts-1; float fLastDist = (pObj->m_Bounds.m_vEdgePlaneNormals[pl].Dot3(pSrcVerts[lastv]) + pObj->m_Bounds.m_vEdgePlaneNormals[pl].w) + fDynObjPlaneEpsilon[pl]; int iLastSign = (fLastDist >= fEps) ? 1 : -1; for(int v=0; vm_Bounds.m_vEdgePlaneNormals[pl].Dot3(pSrcVerts[v]) + pObj->m_Bounds.m_vEdgePlaneNormals[pl].w) + fDynObjPlaneEpsilon[pl]; int iSign = (fDist >= fEps) ? 1 : -1; if(iLastSign < 0) { pDestVerts[iNumDestPts++] = pSrcVerts[lastv]; } if(iLastSign != iSign) { // Create vertex at plane, and add to front & back fragments float s = (fLastDist / (fLastDist - fDist)); pDestVerts[iNumDestPts++] = pSrcVerts[lastv] + ((pSrcVerts[v] - pSrcVerts[lastv]) * s); } lastv = v; fLastDist = fDist; iLastSign = iSign; } // Has the poly been clipped to nothing? If so then it is outside of the object if(!iNumDestPts) { return false; } iNumSrcPts = iNumDestPts; if(pDestVerts == &vClippedVerts2[0]) { pSrcVerts = &vClippedVerts2[0]; pDestVerts = &vClippedVerts1[0]; } else { pSrcVerts = &vClippedVerts1[0]; pDestVerts = &vClippedVerts2[0]; } } return true; } //------------------------------------------------------------------------------------------------------- // // New tessellation scheme.. // //-------------------------------------------------------------------------------------------------------