5158 lines
179 KiB
C++
5158 lines
179 KiB
C++
#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; v1<pPoly->GetNumVertices(); 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<TNavMeshPoly*> * 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; s<pPoly->GetNumSpecialLinks(); 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; v<pPoly->GetNumVertices(); v++)
|
|
{
|
|
pNavMesh->GetVertex( pNavMesh->GetPolyVertexIndex(pPoly, v), vTempVerts[v] );
|
|
}
|
|
|
|
//*************************************
|
|
// 'Allocate' the tessellation polys
|
|
|
|
lastv = pPoly->GetNumVertices()-1;
|
|
for(v=0; v<pPoly->GetNumVertices(); 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; v<pPoly->GetNumVertices(); 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; a<pNeighbourPoly->GetNumVertices(); 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; v<iNumTessPolys; v++)
|
|
{
|
|
// Set up the new poly & vertices
|
|
TNavMeshPoly * pTessPoly = pNewPolys[v];
|
|
|
|
pTessPoly->SetNumSpecialLinks( 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; sp<NAVMESHPOLY_MAX_SPECIAL_LINKS; sp++)
|
|
{
|
|
if( specialLinkInfo[sp].pLink ) // Only if we've not already retargetted this link
|
|
{
|
|
const Vector3 * vTestPt = &specialLinkInfo[sp].vPosition;
|
|
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)
|
|
{
|
|
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; i<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS; i++)
|
|
{
|
|
if(!m_DynamicObjectsStore[i].IsObjectSlotAvailable())
|
|
{
|
|
CPathServer::m_iNumDynamicObjects++;
|
|
}
|
|
}
|
|
|
|
if(!CPathServer::m_iNumDynamicObjects)
|
|
{
|
|
m_pFirstDynamicObject = NULL;
|
|
return;
|
|
}
|
|
|
|
m_pFirstDynamicObject = NULL;
|
|
TDynamicObject * pLast = NULL;
|
|
|
|
for(i=0; i<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS; i++)
|
|
{
|
|
if(!m_DynamicObjectsStore[i].IsObjectSlotAvailable())
|
|
{
|
|
if(!m_pFirstDynamicObject)
|
|
{
|
|
m_pFirstDynamicObject = &m_DynamicObjectsStore[i];
|
|
pLast = m_pFirstDynamicObject;
|
|
}
|
|
else
|
|
{
|
|
pLast->m_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; o<m_pCurrentActivePathRequest->m_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; o<m_pCurrentActiveRequest->m_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; d<pVehicle->GetNumDoors(); 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()<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS)
|
|
{
|
|
return FlagDynamicObjectForRemoval(pFire->GetPathServerObjectIndex());
|
|
}
|
|
return false;
|
|
}
|
|
bool CPathServerGta::RemoveVehicleDoorObject(const CCarDoor * pDoor)
|
|
{
|
|
if(pDoor->GetPathServerDynamicObjectIndex()<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS)
|
|
{
|
|
return FlagDynamicObjectForRemoval(pDoor->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<m_iNumDeferredAddDynamicObjects; i++)
|
|
{
|
|
CPathServer::ms_bHaveAnyDynamicObjectsChanged = true;
|
|
|
|
TScriptDeferredAddDynamicObject & deferredObj = m_ScriptDeferredAddDynamicObjects[i];
|
|
|
|
// Object handle may have been set to -1 if object was removed before it could be added here
|
|
if(deferredObj.m_iObjectHandle == -1)
|
|
continue;
|
|
|
|
// Find a free slot to store this object.
|
|
s32 iSlot;
|
|
for(iSlot=0; iSlot<MAX_NUM_SCRIPTED_DYNAMIC_OBJECTS; iSlot++)
|
|
{
|
|
if(m_ScriptedDynamicObjects[iSlot].m_iScriptObjectHandle == -1)
|
|
break;
|
|
}
|
|
Assertf(iSlot != MAX_NUM_SCRIPTED_DYNAMIC_OBJECTS, "Scripts have added too many dynamic objects (%i), try cutting down number - are they all required?", MAX_NUM_SCRIPTED_DYNAMIC_OBJECTS);
|
|
if(iSlot == MAX_NUM_SCRIPTED_DYNAMIC_OBJECTS)
|
|
{
|
|
break;
|
|
}
|
|
|
|
#if __ASSERT
|
|
// In dev builds, we iterate through the objects array to make sure we don't have an identical scripted object
|
|
// This is just a precaution in case designers are careless about adding objects multiple times.
|
|
for(s32 o=0; o<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS; o++)
|
|
{
|
|
TDynamicObject & obj = m_PathServerThread.m_DynamicObjectsStore[o];
|
|
if(!obj.IsObjectSlotAvailable() && obj.m_iScriptThreadId != 0)
|
|
{
|
|
const Vector3 vDiff = obj.m_Bounds.GetOrigin() - deferredObj.m_vPosition;
|
|
if(vDiff.Mag() <= SMALL_FLOAT)
|
|
{
|
|
char existingThreadName[256];
|
|
scrThread * pExistingThread = scrThread::GetThread(static_cast<scrThreadId>(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<MAX_NUM_SCRIPTED_DYNAMIC_OBJECTS; s++)
|
|
{
|
|
if(m_ScriptedDynamicObjects[s].m_iScriptObjectHandle == -1)
|
|
iNumFree++;
|
|
}
|
|
iNumFree -= m_iNumDeferredAddDynamicObjects;
|
|
Assertf(iNumFree > 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<MAX_NUM_SCRIPTED_DYNAMIC_OBJECTS; s++)
|
|
{
|
|
if(m_ScriptedDynamicObjects[s].m_iScriptObjectHandle == iObjHandle)
|
|
{
|
|
const s32 iObjIndex = m_ScriptedDynamicObjects[s].m_iObjectIndex;
|
|
|
|
if( iObjIndex >= 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<u32>(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<MAX_NUM_SCRIPTED_DYNAMIC_OBJECTS; s++)
|
|
{
|
|
if(m_ScriptedDynamicObjects[s].m_iScriptObjectHandle == iObjHandle)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CPathServerGta::RemoveScriptedBlockingObject(const scrThreadId iThreadId, const s32 iObjHandle)
|
|
{
|
|
s32 s;
|
|
|
|
bool bRemoved = false;
|
|
|
|
// Firstly remove any occurance of this which is still in the deferred add list;
|
|
// just in case the script terminated/removed before the object could even be added!
|
|
for(s=0; s<m_iNumDeferredAddDynamicObjects; s++)
|
|
{
|
|
if(m_ScriptDeferredAddDynamicObjects[s].m_iObjectHandle == iObjHandle)
|
|
{
|
|
m_ScriptDeferredAddDynamicObjects[s].m_iObjectHandle = -1;
|
|
bRemoved = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now go through our list which pairs scripted object handles, with the object index
|
|
for(s=0; s<MAX_NUM_SCRIPTED_DYNAMIC_OBJECTS; s++)
|
|
{
|
|
if(m_ScriptedDynamicObjects[s].m_iScriptObjectHandle == iObjHandle)
|
|
{
|
|
const s32 iObjIndex = m_ScriptedDynamicObjects[s].m_iObjectIndex;
|
|
|
|
m_ScriptedDynamicObjects[s].m_iScriptObjectHandle = -1;
|
|
m_ScriptedDynamicObjects[s].m_iObjectIndex = DYNAMIC_OBJECT_INDEX_NONE;
|
|
|
|
if( iObjIndex >= 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<m_iNumDeferredAddDynamicObjects; s++)
|
|
{
|
|
if(m_ScriptDeferredAddDynamicObjects[s].m_iThreadId == static_cast<u32>(iThreadId))
|
|
{
|
|
m_ScriptDeferredAddDynamicObjects[s].m_iObjectHandle = -1;
|
|
}
|
|
}
|
|
|
|
// Then remove any which are added
|
|
for(s32 o=0; o<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS; o++)
|
|
{
|
|
TDynamicObject & obj = m_PathServerThread.m_DynamicObjectsStore[o];
|
|
if( obj.m_bIsUserAddedBlockingObject && obj.m_bScriptedBlockingObject && obj.m_iScriptThreadId == (u32)iThreadId )
|
|
{
|
|
for(s=0; s<MAX_NUM_SCRIPTED_DYNAMIC_OBJECTS; s++)
|
|
{
|
|
if(m_ScriptedDynamicObjects[s].m_iObjectIndex == o)
|
|
{
|
|
// TODO: Ensure that this is the same script ID as added it
|
|
RemoveScriptedBlockingObject( iThreadId, m_ScriptedDynamicObjects[s].m_iScriptObjectHandle );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
s32 CPathServer::EvictDynamicObjectForScript()
|
|
{
|
|
// Make a note that this has been called
|
|
Displayf("CPathServer::EvictDynamicObjectForScript()..");
|
|
|
|
const float fMaxDist = 500.0f;
|
|
float fMinScore = FLT_MAX;
|
|
s32 iMinIndex = -1;
|
|
s32 o;
|
|
Vector3 vMin, vMax, vSize;
|
|
|
|
// Iterate through array of dynamic objects, looking for a suitable one to remove.
|
|
// We will only choose one which is the bounds for a CObject.
|
|
// Choose the object with the smallest bounding box.
|
|
for(o=0; o<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS; o++)
|
|
{
|
|
TDynamicObject & obj = m_PathServerThread.m_DynamicObjectsStore[o];
|
|
|
|
// Don't consider certain types for removal
|
|
if(obj.m_bIsUserAddedBlockingObject || obj.m_bScriptedBlockingObject)
|
|
continue;
|
|
// Don't replace objects which are already in line for removal
|
|
if(obj.m_bFlaggedForDeletion)
|
|
continue;
|
|
|
|
// Only obstacles which are representing a CObject type, or a CFire
|
|
if(obj.m_bIsObject || obj.m_bIsFire)
|
|
{
|
|
// Rate the object on size
|
|
obj.m_MinMax.GetAsFloats(vMin, vMax);
|
|
vSize = vMax-vMin;
|
|
const float fVol = vSize.x * vSize.y * vSize.z;
|
|
const float fSizeScore = (fVol * fVol);
|
|
|
|
// Rate the object on distance; those closer to the navmesh origin score higher -
|
|
// we are looking to remove the object with the lowest score.
|
|
const Vector3 vDelta = m_vOrigin - obj.m_Bounds.GetOrigin();
|
|
float fDistanceScore = Max( 0.0f, fMaxDist - vDelta.Mag() );
|
|
fDistanceScore = (fDistanceScore * fDistanceScore) * (obj.m_bHasUprootLimit ? 1.0f : 0.5f);
|
|
|
|
float fScore = fSizeScore + fDistanceScore;
|
|
|
|
// Heavily penalize the removal of objects with any uproot limit,
|
|
// we only want to remove one of these as a very last resort.
|
|
// Applying a large penalty essentially splits regular/uproot-limit
|
|
// objects into two bands, except in the case of very large objects
|
|
if(obj.m_bIsSignificant && !obj.m_bIsFire)
|
|
fScore += 100000.0f;
|
|
if(obj.m_bHasUprootLimit)
|
|
fScore += 2000000.0f;
|
|
|
|
if(fScore < fMinScore)
|
|
{
|
|
fMinScore = fScore;
|
|
iMinIndex = o;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Not found any space? Return now..
|
|
if(iMinIndex == -1)
|
|
return -1;
|
|
|
|
// Fix up the linked list of dynamic objects, since we are about to remove one
|
|
TDynamicObject * pChosenObj = &m_PathServerThread.m_DynamicObjectsStore[iMinIndex];
|
|
|
|
// IF WE SELECTED AN OBJECT WHICH IS ALREADY FLAGGED FOR DELETION, THEN ITS VERY LIKELY
|
|
// THAT ITS ENTITY POINTER WILL BE POINTING AT AN *ALREADY-DELETED* ENTITY (0XDDDDDDDD)
|
|
if(!pChosenObj->m_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; o<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS; o++)
|
|
{
|
|
TDynamicObject * pObj = &m_PathServerThread.m_DynamicObjectsStore[o];
|
|
if(pObj != pChosenObj)
|
|
{
|
|
if(pObj->m_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; s<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS; s++)
|
|
{
|
|
if(m_PathServerThread.m_DynamicObjectsStore[s].IsObjectSlotAvailable())
|
|
break;
|
|
}
|
|
|
|
// Couldn't find one ?
|
|
if(s == PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS)
|
|
{
|
|
return DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD;
|
|
}
|
|
|
|
CPathServer::ms_bHaveAnyDynamicObjectsChanged = true;
|
|
TDynamicObject & dynObj = m_PathServerThread.m_DynamicObjectsStore[s];
|
|
dynObj.SetFlagsForNewObject();
|
|
|
|
TDynObjBoundsFuncs::CalcBoundsForBlockingObject(dynObj.m_NewBounds, vPosition, vSize, fHeading, &dynObj);
|
|
|
|
Matrix34 rotMat;
|
|
rotMat.Identity();
|
|
rotMat.RotateZ(fHeading);
|
|
|
|
dynObj.m_NewBounds.m_fHeading = GetHeading(rotMat);
|
|
dynObj.m_NewBounds.m_fRoll = GetRoll(rotMat);
|
|
dynObj.m_NewBounds.m_fPitch = GetPitch(rotMat);
|
|
dynObj.m_NewBounds.m_fUpZ = rotMat.c.z;
|
|
|
|
sysMemCpy(&dynObj.m_Bounds, &dynObj.m_NewBounds, sizeof(TDynObjBounds));
|
|
|
|
dynObj.CalcMinMaxForObject();
|
|
dynObj.m_bIsUserAddedBlockingObject = true;
|
|
dynObj.m_iBlockingObjectFlags = iBlockingFlags;
|
|
dynObj.m_iScriptThreadId = 0xffffffff;
|
|
|
|
dynObj.m_pNext = m_PathServerThread.m_pFirstDynamicObject;
|
|
m_PathServerThread.m_pFirstDynamicObject = &m_PathServerThread.m_DynamicObjectsStore[s];
|
|
m_iNumDynamicObjects++;
|
|
|
|
return (TDynamicObjectIndex) s;
|
|
}
|
|
#endif // GTA_ENGINE
|
|
|
|
bool CPathServer::RemoveBlockingObject(const TDynamicObjectIndex iObjIndex)
|
|
{
|
|
if(iObjIndex < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS)
|
|
{
|
|
return FlagDynamicObjectForRemoval(iObjIndex);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//******************************************************************************************
|
|
// UpdateBlockingObject
|
|
// Forces the update of a script/code added dynamic object
|
|
//
|
|
// NB: If the only thing which has changed is the size, and bForceUpdate is not specified,
|
|
// then the object will not be updated.
|
|
//******************************************************************************************
|
|
|
|
void CPathServer::UpdateBlockingObject(const TDynamicObjectIndex iObjIndex, const Vector3 & vPosition, const Vector3 & vSize, const float fHeading, bool bForceUpdate, u32 * pBlockingFlags)
|
|
{
|
|
Assert(iObjIndex < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS);
|
|
if(iObjIndex < PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS)
|
|
{
|
|
TDynamicObject & dynObj = m_PathServerThread.m_DynamicObjectsStore[iObjIndex];
|
|
|
|
#if __ASSERT
|
|
if(!dynObj.m_bIsUserAddedBlockingObject)
|
|
{
|
|
Printf("------------------------------------------\n");
|
|
Printf("dynObj.m_bFlaggedForDeletion: %i\n", dynObj.m_bFlaggedForDeletion ? 1 : 0);
|
|
Printf("dynObj.m_pEntity: 0x%p\n", dynObj.m_pEntity);
|
|
Printf("dynObj.m_bIsActive: %i\n", dynObj.m_bIsActive ? 1 : 0);
|
|
Printf("dynObj.m_bIsObject: %i\n", dynObj.m_bIsObject ? 1 : 0);
|
|
Printf("dynObj.m_bIsVehicle: %i\n", dynObj.m_bIsVehicle ? 1 : 0);
|
|
Printf("dynObj.m_bVehicleDoor: %i\n", dynObj.m_bVehicleDoor ? 1 : 0);
|
|
Printf("dynObj.m_bIsFire: %i\n", dynObj.m_bIsFire ? 1 : 0);
|
|
Printf("dynObj.m_bScriptedBlockingObject: %i\n", dynObj.m_bScriptedBlockingObject ? 1 : 0);
|
|
Printf("dynObj.m_iScriptThreadId: %u\n", dynObj.m_iScriptThreadId);
|
|
Printf("dynObj.m_pOwningGridCell: 0x%p\n", dynObj.m_pOwningGridCell);
|
|
|
|
Assertf(0, "Call to UpdateBlockingObject() with wrong obj index - please add TTY to bug as it contains some info");
|
|
}
|
|
#endif
|
|
|
|
// Update optional blocking flags
|
|
if(pBlockingFlags)
|
|
dynObj.m_iBlockingObjectFlags = *pBlockingFlags;
|
|
|
|
|
|
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;
|
|
|
|
if( !bForceUpdate )
|
|
{
|
|
static const float fAngEps = 4.0f * DtoR;
|
|
if( IsClose( SubtractAngleShorter(objBounds.m_fHeading, fHeading), 0.0f, fAngEps ) && vPosition.IsClose(objBounds.GetEntityOrigin(), 0.1f) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
CPathServer::ms_bHaveAnyDynamicObjectsChanged = true;
|
|
|
|
// Mark that we're updating this object
|
|
dynObj.m_bCurrentlyUpdatingNewBounds = true;
|
|
sys_lwsync();
|
|
|
|
TDynObjBoundsFuncs::CalcBoundsForBlockingObject(dynObj.m_NewBounds, vPosition, vSize, fHeading, &dynObj);
|
|
|
|
dynObj.m_bNewBounds = true;
|
|
|
|
sys_lwsync();
|
|
dynObj.m_bCurrentlyUpdatingNewBounds = false;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
//******************************************************************************************
|
|
// UpdateDynamicObject
|
|
// this function is called whenever a dynamic object has moved or rotated.
|
|
//******************************************************************************************
|
|
void CPathServerGta::UpdateDynamicObject(CEntity * pEntity, bool bForceUpdate, bool bForceActivate)
|
|
{
|
|
TDynamicObjectIndex iDynObjIndex;
|
|
#ifdef GTA_ENGINE
|
|
bool deactivateDoors = false;
|
|
bool nowInactive;
|
|
#endif
|
|
if(pEntity->GetType() == 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; d<iNumDoors; d++)
|
|
{
|
|
const TDynamicObjectIndex iDoorObjIndex = ((CVehicle*)pEntity)->GetDoor(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; d<pVehicle->GetNumDoors(); 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<intptr_t>(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; s<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS; s++)
|
|
{
|
|
if(m_PathServerThread.m_DynamicObjectsStore[s].IsObjectSlotAvailable())
|
|
break;
|
|
}
|
|
|
|
// Couldn't find one ?
|
|
if(s == PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CPathServer::ms_bHaveAnyDynamicObjectsChanged = true;
|
|
TDynamicObject & dynObj = m_PathServerThread.m_DynamicObjectsStore[s];
|
|
dynObj.SetFlagsForNewObject();
|
|
|
|
TDynObjBoundsFuncs::CalcBoundsForFire(dynObj.m_NewBounds, pFire, &dynObj);
|
|
|
|
Matrix34 mat = MAT34V_TO_MATRIX34(pFire->GetMatrixOffset());
|
|
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; s<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS; s++)
|
|
{
|
|
if(m_PathServerThread.m_DynamicObjectsStore[s].IsObjectSlotAvailable())
|
|
break;
|
|
}
|
|
// Couldn't find one ?
|
|
if(s == PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS)
|
|
{
|
|
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);
|
|
}
|
|
|
|
#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; d<iNumDoors; d++)
|
|
{
|
|
CCarDoor * pDoor = pVehicle->GetDoor(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; s<PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS; s++)
|
|
{
|
|
if(m_PathServerThread.m_DynamicObjectsStore[s].IsObjectSlotAvailable())
|
|
break;
|
|
}
|
|
// Couldn't find one ?
|
|
if(s == 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;
|
|
}
|
|
|
|
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; d<iNumDoors; d++)
|
|
{
|
|
CCarDoor * pDoor = pVehicle->GetDoor(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; i<m_DynamicNavMeshStore.GetNum(); i++)
|
|
{
|
|
CModelInfoNavMeshRef & ref = m_DynamicNavMeshStore.Get(i);
|
|
if(ref.m_iModelIndex.Get()==(s32)pEntity->GetModelIndex())
|
|
{
|
|
// 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; i<m_DynamicNavMeshStore.GetNum(); i++)
|
|
{
|
|
CModelInfoNavMeshRef & ref = m_DynamicNavMeshStore.Get(i);
|
|
if(ref.m_iModelIndex.Get() == (s32)pEntity->GetModelIndex())
|
|
{
|
|
// 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; i<iNumPos; i++)
|
|
{
|
|
Vec3V vMin = vDoorPositions[i] - Vec3V(2.0f,2.0f,2.0f);
|
|
Vec3V vMax = vDoorPositions[i] + Vec3V(2.0f,2.0f,2.0f);
|
|
|
|
bool bWithinAABB = IsGreaterThanAll(vPos, vMin) && IsGreaterThanAll(vMax, vPos);
|
|
if(bWithinAABB)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CPathServerGta::ShouldPedAvoidObject(const CPed* pPed, const CEntity* pEntity)
|
|
{
|
|
#ifdef GTA_ENGINE
|
|
if (IsObjectSignificant(pEntity)) // Always ignore these objects
|
|
return true;
|
|
|
|
if (ShouldAvoidObject(pEntity)) // Ignore these if we are in that pathfinder mode were we ignore some objects
|
|
{
|
|
// Do we ever ignore all objects? If not, maybe we don't even need to check this task!
|
|
CTaskMoveFollowNavMesh * pExistingNavMeshTask = (CTaskMoveFollowNavMesh*)pPed->GetPedIntelligence()->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; pl<NUM_DYNAMIC_OBJECT_PLANES; pl++)
|
|
{
|
|
const float fStartDot = (pObject->m_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<TDynamicObject*> & 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<NUM_DYNAMIC_OBJECT_PLANES; pl++ )
|
|
{
|
|
const float fStartDot = (bnds.m_vEdgePlaneNormals[pl].Dot3(vClippedStart) + bnds.m_vEdgePlaneNormals[pl].w) + fDynObjPlaneEpsilon[pl];
|
|
const float fEndDot = (bnds.m_vEdgePlaneNormals[pl].Dot3(vClippedEnd) + bnds.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
|
|
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; i<ms_DynamicObjectGrids.GetCount(); i++)
|
|
{
|
|
CDynamicObjectsGridCell * pGridCell = ms_DynamicObjectGrids[i];
|
|
Assert(pGridCell);
|
|
delete pGridCell;
|
|
}
|
|
|
|
ms_DynamicObjectGrids.Reset();
|
|
|
|
ms_bCacheEnabled = false;
|
|
}
|
|
|
|
//**********************************************************************************************
|
|
// AddObjectToGridCell
|
|
// Go through our array of grid-cells and try to find one which the object lies within.
|
|
// If one is not found, then see if we had an empty (unused) one which we can reuse.
|
|
// Failing that, create a new grid cell and add to the array.
|
|
// Finally add the object to the grid cell, and return a pointer to it.
|
|
//**********************************************************************************************
|
|
|
|
CDynamicObjectsGridCell *
|
|
CDynamicObjectsContainer::AddObjectToGridCell(TDynamicObject * pObject)
|
|
{
|
|
Assert(!pObject->m_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; i<ms_DynamicObjectGrids.GetCount(); i++)
|
|
{
|
|
pGridCell = ms_DynamicObjectGrids[i];
|
|
Assert(pGridCell);
|
|
|
|
// Keep a record of the first unused gridcell we come across
|
|
if(!pFirstEmptyGridCell && !pGridCell->m_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; i<ms_DynamicObjectGrids.GetCount(); i++)
|
|
{
|
|
CDynamicObjectsGridCell * pGrid = ms_DynamicObjectGrids[i];
|
|
if(pGrid->m_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; c<ms_DynamicObjectGrids.size(); c++)
|
|
{
|
|
CDynamicObjectsGridCell * pCell = ms_DynamicObjectGrids[c];
|
|
|
|
pCell->m_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; i<ms_DynamicObjectGrids.GetCount(); i++)
|
|
{
|
|
CDynamicObjectsGridCell * pGrid = ms_DynamicObjectGrids[i];
|
|
Assert(!pGrid->m_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; i<ms_DynamicObjectGrids.GetCount(); i++)
|
|
{
|
|
CDynamicObjectsGridCell * pGrid = ms_DynamicObjectGrids[i];
|
|
Assert(!pGrid->m_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<TDynamicObject*> & 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; i<ms_DynamicObjectGrids.GetCount(); i++)
|
|
{
|
|
CDynamicObjectsGridCell * pGrid = ms_DynamicObjectGrids[i];
|
|
Assert(!pGrid->m_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<TDynamicObject*> & 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; i<iNumGridCells; i++)
|
|
{
|
|
CDynamicObjectsGridCell * pGrid = gridCells[i];
|
|
Assert(!pGrid->m_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<iGridCellMaxX; x++)
|
|
{
|
|
for(y=iGridCellMinY; y<iGridCellMaxY; y++)
|
|
{
|
|
#if NAVMESH_OPTIMISATIONS_OFF
|
|
//Assert(m_iNextFree < ms_iNumCacheCells);
|
|
#endif
|
|
if(m_iNextFree >= 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<iRegionMaxX; x++)
|
|
{
|
|
for(y=iRegionMinY; y<iRegionMaxY; y++)
|
|
{
|
|
const s32 iIndex = (y*ms_iNumCellsAcross)+x;
|
|
|
|
FastAssert(iIndex >= 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; g<ms_DynamicObjectGrids.GetCount(); g++)
|
|
{
|
|
CDynamicObjectsGridCell * pGrid = ms_DynamicObjectGrids[g];
|
|
if( pGrid->m_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; v<iNumSrcPts; v++)
|
|
{
|
|
float fDist = (pObj->m_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..
|
|
//
|
|
//-------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|