Files
GTASource/game/Vehicles/vehiclepopulation_shared.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

551 lines
18 KiB
C++

#include "vehicles/vehiclepopulation.h"
#include "camera/CamInterface.h"
#include "vehicles/vehpopulation_channel.h"
#include "fwscene/world/WorldLimits.h"
#include "fwsys/timer.h"
#include "profile/timebars.h"
#include "game/PopMultiplierAreas.h"
AI_OPTIMISATIONS()
AI_VEHICLE_OPTIMISATIONS()
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateGenerationLinkData
// PURPOSE : Makes a list of all path links where both nodes are within the max
// distance given. This list is then alter used to generate vehicle
// placement positions and initial path following data.
// PARAMETERS : popCtrlCentre - the center about which to base the links within a given
// radius.
// maxNodeDist - the maximum distance to collect nodes.
// RETURNS : false if update has been skipped, true otherwise
/////////////////////////////////////////////////////////////////////////////////
void CVehiclePopulation::GatherPotentialVehGenLinks(
const Vector3& popCtrlCentre,
const Vector2& sidewallFrustumOffset,
float maxNodeDistBase,
float maxNodeDist,
bool bUseKeyholeShape,
float fKeyhole_InViewInnerRadius,
float fKeyhole_InViewOuterRadius,
float fKeyhole_BehindInnerRadius,
float fKeyhole_BehindOuterRadius,
const Vector3& popCtrlDir,
float fKeyhole_CosHalfFOV,
u32& numGenLinks,
CVehGenLink genLinks[MAX_GENERATION_LINKS],
float& averageLinkHeight,
CVehGenSphere * apOverlappingAreas,
s32 iNumOverlappingAreas
#if __SPU
, PopMultiplierArea *PopMultiplierAreaArray,
s32 iNumTrafficAreas
#endif //__SPU
)
{
PF_AUTO_PUSH_DETAIL("Gather Potential Veh Gen Links");
//note the number of links we didn't even check this cycle
BANK_ONLY(ms_iNumExcessDisparateLinks = ms_iNumActiveLinks - rage::Min(ms_iNumActiveLinks, ms_iCurrentActiveLink));
fKeyhole_InViewInnerRadius *= fKeyhole_InViewInnerRadius;
fKeyhole_InViewOuterRadius *= fKeyhole_InViewOuterRadius;
fKeyhole_BehindInnerRadius *= fKeyhole_BehindInnerRadius;
fKeyhole_BehindOuterRadius *= fKeyhole_BehindOuterRadius;
// Reset the link count.
u32 lNumGenLinks = 0;
averageLinkHeight = 0.0f;
u32 nodesConsideredForAverageLinkHeight = 0;
// Store this for clarity and a slight optimization.
Vector4 maxNodeDistBase2;
maxNodeDistBase2.Set(maxNodeDistBase * maxNodeDistBase);
Vector4 maxNodeDist2;
maxNodeDist2.Set(maxNodeDist * maxNodeDist);
const float centerMaxX = popCtrlCentre.x + maxNodeDist;
const float centerMinX = popCtrlCentre.x - maxNodeDist;
const float centerMaxY = popCtrlCentre.y + maxNodeDist;
const float centerMinY = popCtrlCentre.y - maxNodeDist;
const Vector3 sidewallFrustumOffsetXYZ(sidewallFrustumOffset.x, sidewallFrustumOffset.y, 0.0f);
// Go through all valid links in all loaded regions.
const u32 numRegions = PATHFINDREGIONS;
for (u32 region = 0; region < numRegions; ++region)
{
// Make sure this region is actually loaded in.
if (!ThePaths.apRegions[region])
{
continue;
}
CPathRegion* curRegion = ThePaths.apRegions[region];
if (!curRegion->NumNodes)
{
continue;
}
// Determine if the region and the generation area possibly intersect.
// If the interval distance along either axis is negative then the areas intersect
// along that axis.
const float regionMinX = ThePaths.FindXCoorsForRegion(FIND_X_FROM_REGION_INDEX(region));
const float regionMaxX = regionMinX + PATHFINDREGIONSIZEX;
const float regionMinY = ThePaths.FindYCoorsForRegion(FIND_Y_FROM_REGION_INDEX(region));
const float regionMaxY = regionMinY + PATHFINDREGIONSIZEY;
const float xAxisIntervalDist = (centerMinX<regionMinX)?(regionMinX-centerMaxX):(centerMinX-regionMaxX);
const float yAxisIntervalDist = (centerMinY<regionMinY)?(regionMinY-centerMaxY):(centerMinY-regionMaxY);
const bool outOfRange = (xAxisIntervalDist >= 0) || (yAxisIntervalDist >= 0);
if(outOfRange)
{
continue;
}
{
// We don't need to go through all of the node as they are sorted by y-coordinate.
s32 startNode, endNode;
ThePaths.FindStartAndEndNodeBasedOnYRange(region, centerMinY, centerMaxY, startNode, endNode);
//CPathNode* pVeryFirstNode = &curRegion->aNodes[0];
CPathNode* pFirstNode = &curRegion->aNodes[startNode];
CPathNode* pLastNode = &curRegion->aNodes[endNode];
for (CPathNode* pNode = pFirstNode; pNode != pLastNode; ++pNode)
{
PrefetchDC(pNode + 1);
if (!Verifyf(!pNode->m_address.IsEmpty(), "Warning, found empty path node!"))
continue;
// Make sure this node isn't too far away.
bool nodeOutsideMaxDist = false;
Vector3 pos;
pNode->GetCoors(pos);
Vector4 nodeDist2 = (pos - popCtrlCentre).XYMag2V().xyzw;
if (nodeDist2.IsGreaterThan(maxNodeDist2))
{
nodeOutsideMaxDist = true;
}
if (nodeDist2.IsLessThanAll(maxNodeDistBase2))
{
// Divide by 1000 to avoid precision issues
averageLinkHeight += pos.GetZ() / 1000.0f;
++nodesConsideredForAverageLinkHeight;
}
// Optionally classify nodes wrt the population keyhole shape.
// We might use this, in combination with a more frequent update frequency,
// when the rangeScale is great to enable use to spawn cars at a greater
// distance from the origin without blowing the MAX_GENERATION_LINKS limit.
if(bUseKeyholeShape)
{
const bool bInOuterBand = nodeDist2.x > fKeyhole_InViewInnerRadius && nodeDist2.x < fKeyhole_InViewOuterRadius;
bool bInSidewalls = nodeDist2.x > fKeyhole_BehindOuterRadius && nodeDist2.x < fKeyhole_InViewOuterRadius;
const bool bInInnerBand = nodeDist2.x > fKeyhole_BehindInnerRadius && nodeDist2.x < fKeyhole_BehindOuterRadius;
if(bInOuterBand || bInInnerBand || bInSidewalls)
{
Vector3 vPopCenterToNode = pos - popCtrlCentre;
vPopCenterToNode.z = 0.0f;
vPopCenterToNode.NormalizeFast();
const float fDot = popCtrlDir.Dot(vPopCenterToNode);
const bool bInFOV = (fDot > fKeyhole_CosHalfFOV);
bInSidewalls = bInSidewalls && !bInFOV && fDot >= 0.0f;
if(bInSidewalls)
{
Vector3 vOffsetCenterToNode = pos - (popCtrlCentre - sidewallFrustumOffsetXYZ);
vOffsetCenterToNode.z = 0.0f;
vOffsetCenterToNode.NormalizeFast();
const float fOffsetDot = popCtrlDir.Dot(vOffsetCenterToNode);
bInSidewalls = (fOffsetDot > fKeyhole_CosHalfFOV);
}
// Is node within the outer band?
if(bInFOV && bInOuterBand)
{
}
// Is node within the sidewalls?
else if(bInSidewalls)
{
}
// Is node within the inner band?
else if(!bInFOV && bInInnerBand)
{
}
// Otherwise node is within neither, and flag as outside view
else
{
continue;
}
}
else
{
continue;
}
}
// Never create vehicles on links connected to junction nodes.
// JB: Vehicle population used to do this, but it causes too many horrible problems
// especially now that we have some complex light patterns with the junction editor.
if(pNode->IsJunctionNode() && !pNode->IsFalseJunction())
{
continue;
}
// Ignore nodes which are switched off
if(pNode->m_2.m_switchedOff)
{
continue;
}
// Certain special function nodes should not be considered for vehicle creation.
if(pNode->HasSpecialFunction_VehicleCreation())
{
continue;
}
// Check the nodes linked off this one.
for (u32 iLink = 0; iLink < pNode->NumLinks(); ++iLink)
{
const CPathNodeLink* link = &ThePaths.GetNodesLink(pNode, iLink);
#if VEHGEN_LINKS_IN_BOTH_DIRECTIONS
if(link->m_1.m_LanesToOtherNode == 0)
{
continue;
}
#endif // VEHGEN_LINKS_IN_BOTH_DIRECTIONS
// Make sure the other nodeIndex in the link is valid.
const CNodeAddress adjNode = link->m_OtherNode;
if (!Verifyf(!adjNode.IsEmpty(), "Warning, found empty path node on link!"))
continue;
const u32 adjNodeRegion = adjNode.GetRegion();
if (!ThePaths.apRegions[adjNodeRegion])
{
continue;
}
CPathRegion* adjRegion = ThePaths.apRegions[adjNodeRegion];
if (!adjRegion->aNodes)
{
continue;
}
// Make sure to only consider each pair only once.
const s32 adjNodeIndex = adjNode.GetIndex();
const CPathNode* pNeighbourNode = &adjRegion->aNodes[adjNodeIndex];
#if !VEHGEN_LINKS_IN_BOTH_DIRECTIONS
if(pNode >= pNeighbourNode)
{
continue;
}
#endif // !VEHGEN_LINKS_IN_BOTH_DIRECTIONS
// Never create cars inside junctions
if(pNeighbourNode->IsJunctionNode() && !pNeighbourNode->IsFalseJunction())
{
continue;
}
// Ignore nodes which are switched off
if(pNeighbourNode->m_2.m_switchedOff)
{
continue;
}
if (link->m_1.m_bShortCut)
{
continue;
}
////if the target node has multiple ways to go, but the junction flag
////isn't set (checked above), don't allow spawning here
//if (pNeighbourNode->m_2.m_slipJunction)
//{
// continue;
//}
// Check if dead end roads are banned.
if(!ms_allowSpawningFromDeadEndRoads && ((pNode->m_2.m_deadEndness) || (pNeighbourNode->m_2.m_deadEndness)))
{
continue;
}
// Don't spawn on ignore nav links
if (link->m_1.m_bDontUseForNavigation)
{
continue;
}
// We don't want to place car on a dead end or heading towards the dead end.
// This had been disabled for Jimmy--re-enabled for V -JM
if(ms_spawnCareAboutDeadEnds)
{
const bool currentDirectionDeadEnds = pNeighbourNode->m_2.m_deadEndness && link->m_1.m_LeadsToDeadEnd;
const bool reverseDirectionDeadEnds = pNode->m_2.m_deadEndness && link->m_1.m_LeadsFromDeadEnd;
if(currentDirectionDeadEnds && reverseDirectionDeadEnds)
{
continue;
}
}
// Certain special function nodes should not be considered for vehicle creation.
if(pNeighbourNode->HasSpecialFunction_VehicleCreation() || pNeighbourNode->IsTrafficLight())
{
continue;
}
// Make sure both nodes aren't too far away.
Vector3 adjPos;
pNeighbourNode->GetCoors(adjPos);
const Vector4 adjNodeDist2 = (adjPos-popCtrlCentre).XYMag2V().xyzw;
if(nodeOutsideMaxDist && adjNodeDist2.IsGreaterThan(maxNodeDist2))
{
continue;
}
s32 iDensity = ( (((int)pNode->m_2.m_density) + ((int)pNeighbourNode->m_2.m_density)) >> 1);
#if __SPU
// Use the CThePopMultiplierAreas to apply local multipliers to vehicle density
if(iNumTrafficAreas != 0)
{
Vector3 vPos = (pos+adjPos)*0.5f;
// Re-written CThePopMultiplierAreas::GetTrafficDensityMultiplier:
float fMultiplier = 1.0f;
int iNumVisited = 0;
for(s32 i=0; i < MAX_POP_MULTIPLIER_AREAS; i++)
{
if(PopMultiplierAreaArray[i].m_init && PopMultiplierAreaArray[i].m_bCameraGlobalMultiplier == false)
{
if (PopMultiplierAreaArray[i].m_isSphere)
{
spdSphere sphere(RCC_VEC3V(PopMultiplierAreaArray[i].m_minWS), ScalarV(PopMultiplierAreaArray[i].GetRadius()));
if(sphere.ContainsPoint(RCC_VEC3V(vPos)))
{
fMultiplier *= PopMultiplierAreaArray[i].m_trafficDensityMultiplier;
}
}
else
{
spdAABB box(RCC_VEC3V(PopMultiplierAreaArray[i].m_minWS), RCC_VEC3V(PopMultiplierAreaArray[i].m_maxWS));
if(box.ContainsPoint(RCC_VEC3V(vPos)))
{
fMultiplier *= PopMultiplierAreaArray[i].m_trafficDensityMultiplier;
}
}
}
if( PopMultiplierAreaArray[i].m_trafficDensityMultiplier != 1.0f )
{
iNumVisited++;
if(iNumVisited == iNumTrafficAreas)
break;
}
}
// End of CThePopMultiplierAreas::GetTrafficDensityMultiplier.
iDensity = (s32) ((float)iDensity * fMultiplier);
iDensity = Clamp(iDensity, 0, 15);
}
#endif // __SPU
if(iDensity == 0)
{
continue;
}
// See if we can add this node pair and make sure to quit out when we
// have filled up our max generation links.
if(lNumGenLinks < MAX_GENERATION_LINKS)
{
Assertf(region == pNode->m_address.GetRegion(), "Bad region %d on node, expected %d", pNode->m_address.GetRegion(), region);
#if VEHGEN_CACHE_LINK_NEXT_JUNCTION_NODE
bool bHaveNodesChanged = genLinks[lNumGenLinks].m_nodeA.GetIndex() != pNode->m_address.GetIndex() ||
genLinks[lNumGenLinks].m_nodeB.GetIndex() != adjNodeIndex;
#endif
genLinks[lNumGenLinks].m_nodeA.Set(region, pNode->m_address.GetIndex());
genLinks[lNumGenLinks].m_nodeB.Set(adjNodeRegion, adjNodeIndex);
genLinks[lNumGenLinks].m_iMinX = ((s16)Min(pos.x, adjPos.x)) - 1;
genLinks[lNumGenLinks].m_iMinY = ((s16)Min(pos.y, adjPos.y)) - 1;
genLinks[lNumGenLinks].m_iMinZ = ((s16)Min(pos.z, adjPos.z)) - 1;
genLinks[lNumGenLinks].m_iMaxX = ((s16)Max(pos.x, adjPos.x)) + 1;
genLinks[lNumGenLinks].m_iMaxY = ((s16)Max(pos.y, adjPos.y)) + 1;
genLinks[lNumGenLinks].m_iMaxZ = ((s16)Max(pos.z, adjPos.z)) + 1;
genLinks[lNumGenLinks].SetNearbyVehiclesCount(0.0f);
genLinks[lNumGenLinks].m_iInitialDensity = iDensity;
genLinks[lNumGenLinks].m_iDensity = iDensity;
// TODO: Why not do a first order cull of which 'overlapping areas' intersect the population keyhole shape,
// and pass this culled list into the job instead?
int r;
for(r=0; r<iNumOverlappingAreas; r++)
{
const s32 dX = ((genLinks[lNumGenLinks].m_iMinX + genLinks[lNumGenLinks].m_iMaxX) >> 1) - apOverlappingAreas[r].m_iPosX;
const s32 dY = ((genLinks[lNumGenLinks].m_iMinY + genLinks[lNumGenLinks].m_iMaxY) >> 1) - apOverlappingAreas[r].m_iPosY;
const s32 dZ = ((genLinks[lNumGenLinks].m_iMinZ + genLinks[lNumGenLinks].m_iMaxZ) >> 1) - apOverlappingAreas[r].m_iPosZ;
if((dX*dX) + (dY*dY) + (dZ*dZ) < apOverlappingAreas[r].m_iRadius*apOverlappingAreas[r].m_iRadius)
{
break;
}
}
genLinks[lNumGenLinks].m_bOverlappingRoads = (r != iNumOverlappingAreas);
#if VEHGEN_STORE_DEBUG_INFO_IN_LINKS
const s32 regionLinkIndex = pNode->m_startIndexOfLinks + iLink;
genLinks[lNumGenLinks].m_iLinkRegion = (u16)region;
genLinks[lNumGenLinks].m_iLinkIndex = (u16)regionLinkIndex;
genLinks[lNumGenLinks].m_iVehGenLink = (u16)lNumGenLinks;
genLinks[lNumGenLinks].m_iFromIndex = 0;
genLinks[lNumGenLinks].m_iToIndex = 0;
#endif
#if __BANK
genLinks[lNumGenLinks].m_iFailType = Fail_NoSpawnAttemptedYet;
#endif
Vector3 vLink = adjPos - pos;
float fLinkLength = vLink.Mag();
float fLinkGrade = vLink.GetZ() / vLink.XYMag();
// I've given up asserting about link lengths, let's just clamp at 255 and be done with it.
fLinkLength = MIN(fLinkLength, 255.0f);
genLinks[lNumGenLinks].SetLinkLengths( fLinkLength );
genLinks[lNumGenLinks].SetLinkGrade( fLinkGrade );
genLinks[lNumGenLinks].SetLinkHeading( fwAngle::LimitRadianAngleFast( -Atan2f(vLink.x, vLink.y) ) );
#if VEHGEN_LINKS_IN_BOTH_DIRECTIONS
genLinks[lNumGenLinks].m_iLinkLanes = static_cast<u8>(link->m_1.m_LanesToOtherNode);
#else
genLinks[lNumGenLinks].m_iLinkLanes = static_cast<u8>(link->m_1.m_LanesToOtherNode) + static_cast<u8>(link->m_1.m_LanesFromOtherNode);
#endif
genLinks[lNumGenLinks].m_bOnPlayersRoad = (pNode->m_1.m_onPlayersRoad || pNeighbourNode->m_1.m_onPlayersRoad);
genLinks[lNumGenLinks].m_bUpToDate = false;
genLinks[lNumGenLinks].m_iLastUpdateTime = 0;
genLinks[lNumGenLinks].m_iCategory = CPopGenShape::GC_Off;
genLinks[lNumGenLinks].m_fScore = 0.0f;
#if VEHGEN_CACHE_LINK_NEXT_JUNCTION_NODE
if(bHaveNodesChanged)
{
CacheNextJunctionNode(genLinks[lNumGenLinks], link, pNode);
}
#endif
++lNumGenLinks;
}
else
{
// Assert left here for debugging purposes
// We shouldn't usually run out of links for general gameplay, and we now have a new method of
// gathering nearby links for large distances (sniper scope, etc) which culls to the keyhole shape.
// vehPopDebugf2("lNumGenLinks has reached max (%i)", MAX_GENERATION_LINKS);
}
}// END for each link off current node.
}// END for each node in region.
}// END of SPU only batching
}// END for each region.
if (nodesConsideredForAverageLinkHeight > 0)
{
averageLinkHeight = (averageLinkHeight / nodesConsideredForAverageLinkHeight) * 1000.0f;
}
numGenLinks = lNumGenLinks;
}
#if VEHGEN_CACHE_LINK_NEXT_JUNCTION_NODE
//this is too expensive to use - idealy we'd like to cache this data when building data offline
void CVehiclePopulation::CacheNextJunctionNode(CVehGenLink& genLink, const CPathNodeLink* nodeLink, const CPathNode* pOriginNode)
{
genLink.m_junctionNode.SetEmpty();
const CPathNode* pCurrentNode = ThePaths.FindNodePointerSafe(nodeLink->m_OtherNode);
const CPathNode* pPreviousNode = pOriginNode;
if(pCurrentNode)
{
u8 iNumAttempts = 0;
static u8 s_iLinksToCheck = 20;
while(iNumAttempts < s_iLinksToCheck && pCurrentNode != NULL)
{
if(pCurrentNode->IsTrafficLight())
{
if(iNumAttempts != 1)
{
genLink.m_junctionNode.Set(pCurrentNode->GetAddrRegion(), pCurrentNode->m_address.GetIndex());
}
break;
}
const CPathNode* pThisNode = pCurrentNode;
pCurrentNode = NULL;
for (u32 iLink = 0; iLink < pThisNode->NumLinks(); ++iLink)
{
const CPathNodeLink* link = &ThePaths.GetNodesLink(pThisNode, iLink);
if (link->m_1.m_bShortCut || link->m_1.m_bDontUseForNavigation
#if VEHGEN_LINKS_IN_BOTH_DIRECTIONS
|| link->m_1.m_LanesToOtherNode == 0
#endif
)
{
continue;
}
const CPathNode * pNeighbourNode = ThePaths.FindNodePointerSafe(link->m_OtherNode);
if( !pNeighbourNode || pNeighbourNode == pPreviousNode )
{
continue;
}
//found what we want
if( pNeighbourNode->IsTrafficLight() )
{
pCurrentNode = pNeighbourNode;
break;
}
if(pNeighbourNode->m_2.m_switchedOff || pNeighbourNode->m_2.m_deadEndness)
{
continue;
}
if(pCurrentNode == NULL)
{
//accept the first good link as our next one
pPreviousNode = pThisNode;
pCurrentNode = pNeighbourNode;
}
}
++iNumAttempts;
}
}
}
#endif