Files
GTASource/game/vehicleAi/Pathfind_Build.cpp

3473 lines
116 KiB
C++
Raw Permalink Normal View History

2025-02-23 17:40:52 +08:00
#include "Pathfind_Build.h"
//framework headers
#include "fwscene/stores/mapdatastore.h"
#include "fwscene/world/WorldLimits.h"
#include "fwmaths/geomutil.h"
#include "fwsys/fileexts.h"
#include "control/trafficLights.h"
#include "objects/dummyobject.h"
#include "objects/object.h"
#include "Objects/ProcObjects.h"
#include "physics/physics.h"
#include "renderer/water.h"
#include "scene/FileLoader.h"
#include "scene/streamer/SceneStreamerMgr.h"
#include "streaming/streaming.h"
#include "system/fileMgr.h"
#include "vehicles/virtualroad.h"
AI_OPTIMISATIONS()
AI_VEHICLE_OPTIMISATIONS()
VEHICLE_OPTIMISATIONS()
RAGE_DEFINE_CHANNEL(pathbuild)
PARAM(nosnap, "[paths] don't snap nodes to collision during build process.");
PARAM(nocamber, "[paths] don't calculate camber (tilt) values during build process.");
PARAM(nosortnodes, "[paths] don't sort nodes during build process, only for debugging -buildpaths as this will really fuck up in-game algorithms.");
PARAM(generateIsland, "[paths] generating nodes for heist island");
#define MERGEDISTCAR (1.0f) // Nodes are considered at the same coordinate
#define MERGEDISTPED (1.0f) // within this distance
CPathFindBuild::CPathFindBuild()
{
PathNodeIndex = 0;
ClearTempNodes();
//Not required //Init(INIT_CORE);
AllocateMemoryForBuildingPaths();
}
CPathFindBuild::~CPathFindBuild()
{
FreeMemoryForBuildingPaths();
}
void CPathFindBuild::ClearTempNodes()
{
TempNodes = NULL;
TempLinks = NULL;
NumTempNodes = NumTempLinks = 0;
}
void CPathFindBuild::AllocateMemoryForBuildingPaths()
{
// Create the region array
// Allocate some memory to play with so that we can build the files as required by the streaming.
for (s32 Region = 0; Region < PATHFINDREGIONS; Region++)
{
//Assert(!apRegions[Region]);
apRegions[Region] = rage_aligned_new(16) CPathRegion();
apRegions[Region]->aNodes = (CPathNode *) sysMemAllocator::GetMaster().Allocate(MAXNODESPERREGION * sizeof(CPathNode), 16, MEMTYPE_RESOURCE_VIRTUAL);
Assert(apRegions[Region]->aNodes);
memset(apRegions[Region]->aNodes, 0, MAXNODESPERREGION * sizeof(CPathNode));
for (s32 T = 0; T < MAXNODESPERREGION; T++)
{
apRegions[Region]->aNodes[T].m_distanceToTarget = PF_VERYLARGENUMBER;
}
apRegions[Region]->aLinks = (CPathNodeLink *) sysMemAllocator::GetMaster().Allocate( (MAXLINKSPERREGION) * sizeof(CPathNodeLink) , 16, MEMTYPE_RESOURCE_VIRTUAL);
Assert(apRegions[Region]->aLinks);
// Set all the adjacent nodes to emptyAddr. This is important because the dynamic adjacent nodes(at the end)
// should be set to emptyAddr so that we know they are not being used yet.
// Make sure the extra adjacent nodes(for dynamic links)are set to emptyAddr.
for(s32 regionLinkIndexToClear = 0; regionLinkIndexToClear <(MAXLINKSPERREGION); regionLinkIndexToClear++)
{
apRegions[Region]->aLinks[regionLinkIndexToClear].m_OtherNode.SetEmpty();
}
apRegions[Region]->aVirtualJunctions = (CPathVirtualJunction *) sysMemAllocator::GetMaster().Allocate(MAXJUNCTIONSPERREGION * sizeof(CPathVirtualJunction), 16, MEMTYPE_RESOURCE_VIRTUAL);
Assert(apRegions[Region]->aVirtualJunctions);
apRegions[Region]->aHeightSamples = (u8*) sysMemAllocator::GetMaster().Allocate(MAXHEIGHTSAMPLESPERREGION * sizeof(u8), 16, MEMTYPE_RESOURCE_VIRTUAL);
Assert(apRegions[Region]->aHeightSamples);
apRegions[Region]->NumNodes = 0;
apRegions[Region]->NumNodesCarNodes = 0;
apRegions[Region]->NumNodesPedNodes = 0;
apRegions[Region]->NumLinks = 0;
apRegions[Region]->NumJunctions = 0;
apRegions[Region]->NumHeightSamples = 0;
}
// allocate the TempNode and TempLink arrays
TempNodes = (CTempNode*)sysMemAllocator::GetMaster().Allocate(sizeof(CTempNode[PF_TEMPNODES]), 16, MEMTYPE_RESOURCE_VIRTUAL);
Assert(TempNodes);
memset(TempNodes, 0, PF_TEMPNODES * sizeof(CTempNode));
TempLinks = (CTempLink*)sysMemAllocator::GetMaster().Allocate(sizeof(CTempLink[PF_TEMPLINKS]), 16, MEMTYPE_RESOURCE_VIRTUAL);
Assert(TempLinks);
memset(TempLinks, 0, PF_TEMPLINKS * sizeof(CTempLink));
NumTempNodes = NumTempLinks = 0;
}
void CPathFindBuild::FreeMemoryForBuildingPaths()
{
// Free the memory we have allocated so that the normal streaming stuff can now take over.
for (s32 Region = 0; Region < PATHFINDREGIONS; Region++)
{
apRegions[Region]->Unload();
delete apRegions[Region];
apRegions[Region] = NULL;
}
sysMemAllocator::GetMaster().Free(TempNodes);
sysMemAllocator::GetMaster().Free(TempLinks);
ClearTempNodes();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SavePathFindData
// PURPOSE : Save out the data in pathfind struct
/////////////////////////////////////////////////////////////////////////////////
void CPathFindBuild::SavePathFindData(void)
{
#if !__FINAL
Displayf("Buildpaths:SavePathFindData\n");
for (s32 Region = 0; Region < PATHFINDREGIONS; Region++)
{
s32 iRegion = Region;
if (PARAM_generateIsland.Get())
{
iRegion += ThePaths.sm_iHeistsSize;
}
char tmp[50];
CFileMgr::SetDir("common:/data/paths/");
sprintf(tmp, "nodes%d.ind", iRegion);
apRegions[Region]->SaveXML(tmp);
CFileMgr::SetDir("");
}
#endif
}
void CPathFindBuild::IdentifySlipLaneNodes()
{
for (s32 Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
continue;
for (s32 Node = 0; Node < apRegions[Region]->NumNodes; Node++)
{
Vector3 vNodePos;
CPathNode * pNode = &((apRegions[Region]->aNodes)[Node]);
pNode->GetCoors(vNodePos);
for(u32 iLink = 0; iLink < pNode->NumLinks(); iLink++)
{
//CPathNodeLink link = GetNodesLink(pNode, iLink);
ASSERT_ONLY(CNodeAddress linkNodeAddr = GetNodesLinkedNodeAddr(pNode, iLink);)
Assert(IsRegionLoaded(linkNodeAddr));
CPathNode* pLinkedNode = GetNodesLinkedNode(pNode, iLink);
s32 iSlipLane = CPathNodeRouteSearchHelper::GetSlipLaneNodeLinkIndex(pLinkedNode, pNode->m_address, *this);
if(iSlipLane != -1)
{
// We've identified the slip node
CPathNode * pSlipNode = GetNodesLinkedNode(pLinkedNode, iSlipLane);
pSlipNode->m_1.m_slipLane = true;
// Now iterate along the links towards the junction node, marking all nodes between also as sliplanes
// If we find 8 nodes before hitting the junction, then bail out
CPathNode* pLastNode = pSlipNode;
CPathNode* pNextNode = pLastNode;
s32 iMaxSlipNodes = 16;
do
{
if (FindNumberNonShortcutLinksForNode(pLastNode) != 2)
break;
CPathNode* pCurrentNode = pNextNode;
//pNextNode = (GetNodesLinkedNode(pLastNode, 0) == pLastNode) ? GetNodesLinkedNode(pLastNode, 1) : GetNodesLinkedNode(pLastNode, 0);
for (int i = 0; i < pCurrentNode->NumLinks(); i++)
{
//don't consider shortcut links
if (GetNodesLink(pCurrentNode, i).IsShortCut())
{
continue;
}
if (GetNodesLinkedNode(pCurrentNode, i) == pLastNode)
{
continue;
}
//prevent backtracking to the slipj
if (GetNodesLinkedNode(pCurrentNode, i) == pLinkedNode)
{
continue;
}
pNextNode = GetNodesLinkedNode(pCurrentNode, i);
Assert(pNextNode);
break;
}
if(pNextNode->IsJunctionNode() || FindNumberNonShortcutLinksForNode(pNextNode) != 2 || pNextNode->m_1.m_slipLane)
{
break;
}
pNextNode->m_1.m_slipLane = true;
pLastNode = pCurrentNode;
} while(iMaxSlipNodes-- > 0);
}
}
}
}
}
// FUNCTION : PreprocessJunctions
// PURPOSE : Perform preprocessing for junctions:
// 1) set SPECIAL_TRAFFIC_LIGHT special-function on all nodes which are entrances to templated junctions
void CPathFindBuild::PreprocessJunctions()
{
Vector3 vNodePos;
for(int j=0; j<CJunctions::GetNumJunctionTemplates(); j++)
{
CJunctionTemplate & temp = CJunctions::GetJunctionTemplate(j);
const bool bEmpty = (temp.m_iFlags & CJunctionTemplate::Flag_NonEmpty)==0;
const bool bRailwayCrossing = (temp.m_iFlags & CJunctionTemplate::Flag_RailwayCrossing)!=0;
if( !bEmpty )
{
//----------------------------------------------------------------------------------------------------
// For all junction nodes in templated junctions, ensure that the "m_qualifiesAsJunction" flag is set
for(int n=0; n<temp.m_iNumJunctionNodes; n++)
{
CPathNode * pClosestJunctionNode = NULL;
float fClosestDistXY = FLT_MAX;
for (s32 Region = 0; (Region < PATHFINDREGIONS); Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
continue;
for (s32 Node = 0; (Node < apRegions[Region]->NumNodes); Node++)
{
CPathNode * pNode = &((apRegions[Region]->aNodes)[Node]);
if(pNode->HasSpecialFunction())
continue;
if(pNode->NumLinks() <= 2 && !bRailwayCrossing )
continue;
pNode->GetCoors(vNodePos);
float fDistXY = (vNodePos - temp.m_vJunctionNodePositions[n]).XYMag();
if( vNodePos.IsClose( temp.m_vJunctionNodePositions[n], 2.0f) && fDistXY < fClosestDistXY )
{
fClosestDistXY = fDistXY;
pClosestJunctionNode = pNode;
break;
}
}
}
if(pClosestJunctionNode)
pClosestJunctionNode->m_2.m_qualifiesAsJunction = true;
}
for(int e=0; e<temp.m_iNumEntrances; e++)
{
// For each entrance to this junction, find which pathnode corresponds to it
CJunctionTemplate::CEntrance & entrance = temp.m_Entrances[e];
bool bNodeFound = false;
for (s32 Region = 0; (Region < PATHFINDREGIONS) && !bNodeFound; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
continue;
for (s32 Node = 0; (Node < apRegions[Region]->NumNodes) && !bNodeFound; Node++)
{
CPathNode * pNode = &((apRegions[Region]->aNodes)[Node]);
if(pNode->HasSpecialFunction())
continue;
//if(pNode->NumLinks()!=2)
// continue;
pNode->GetCoors(vNodePos);
float fEntranceNodeDistXZ = (vNodePos - entrance.m_vNodePosition).XYMag();
if(fEntranceNodeDistXZ < CJunctions::ms_fEntranceNodePosEps &&
IsClose(vNodePos.z, entrance.m_vNodePosition.z, 3.0f))
{
pNode->m_1.m_specialFunction = SPECIAL_TRAFFIC_LIGHT;
bNodeFound = true;
break;
}
}
}
}
}
}
}
bool SortByCarThenYCoord(const CTempNode& a, const CTempNode& b)
{
if (a.IsPedNode() == b.IsPedNode())
{
return a.m_Coors.y < b.m_Coors.y; // both ped or both car, sort by Y val
}
else
{
return !a.IsPedNode() && b.IsPedNode(); // a "less than" b iff a is a car, b is a ped
}
}
int QSortByCarThenYCoord(const void* a, const void* b)
{
const CTempNode* aa = static_cast<const CTempNode*>(a);
const CTempNode* bb = static_cast<const CTempNode*>(b);
if(SortByCarThenYCoord(*aa, *bb))
{
return -1;
}
else if(SortByCarThenYCoord(*bb, *aa))
{
return 1;
}
else
{
return 0;
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SortPathNodes
// PURPOSE : Sort the nodes so that the car nodes come first and then the ped nodes.
/////////////////////////////////////////////////////////////////////////////////
void CPathFindBuild::SortPathNodes( CTempNode *TempNodeList, CTempLink *UNUSED_PARAM(TempLinkList),
s32 NumTempNodes, s32 UNUSED_PARAM(NumTempLinks))
{
Displayf("Buildpaths:SortPathNodes\n");
// For some reason, this didn't output the right order, the array would mostly be sorted,
// but maybe 10% or so of the nodes would be in a slightly incorrect order relative to their neighbors:
// std::sort(&TempNodeList[0], &TempNodeList[NumTempNodes], SortByCarThenYCoord);
// qsort() seems to work without any problems, so I guess we'll use that as a workaround:
qsort((void*)TempNodeList, NumTempNodes, sizeof(CTempNode), QSortByCarThenYCoord);
}
void CPathFindBuild::PreparePathData(void)
{
USE_MEMBUCKET(MEMBUCKET_GAMEPLAY);
Displayf("PreparePathData\n");
{
// File wasn't here. This means we have to build the (streaming) files from the map data.
// We can only do this if there actually is data read in for the
// paths. If there is no data there is no point in preparing it.
if (TempNodes && TempLinks)
{
// Add the nodes automatically generated by open areas.
// The areas are specified in max as a circular chain of nodes. (Not necessarily convex)
AddOpenSpaceNodes();
// We process the cars and pedestrian graphs in the same list.
// We have to sort the nodes first so that the car nodes come first and the ped nodes after.
if(!PARAM_nosortnodes.Get())
{
SortPathNodes(TempNodes, TempLinks, NumTempNodes, NumTempLinks);
}
PreparePathDataForType(TempNodes, TempLinks, NumTempNodes, NumTempLinks, MERGEDISTCAR);
// Total number of car nodes
CountFloodFillGroups();
bool doSnap = true;
bool doCamber = true;
bool doJunctions = true;
#if !__FINAL
if(PARAM_nosnap.Get())
{
doSnap = false;
}
if(PARAM_nocamber.Get())
{
doCamber = false;
}
#endif // !__FINAL
if(doSnap || doCamber || doJunctions)
{
SnapToGround(doSnap, doCamber, doJunctions);
}
#ifdef DEBUG
#if __WIN32PC // Don't do this on PS2 (slow)
CheckGrid();
#endif
#endif
#if __DEV
// Build the distantlights also, since it uses the same data
m_DistantLights.BuildData(TempNodes, TempLinks, NumTempLinks);
m_DistantLights2.BuildData(TempNodes, TempLinks, NumTempLinks);
#endif //__DEV
}
#if __DEV
s32 Region;
// Do a check to make sure nodes point 2 ways.
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
CheckPathNodeIntegrityForZone(Region);
}
#endif //__DEV
// Save file out now
SavePathFindData();
}
Displayf("Done with PreparePathData\n");
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : QualifiesAsJunction
// PURPOSE : Works out whether this node should be considered a junction.
/////////////////////////////////////////////////////////////////////////////////
bool CPathFindBuild::QualifiesAsJunction(const CPathNode* pNode)
{
Assert(pNode);
Assert(IsRegionLoaded(pNode->m_address));
Assert(!pNode->m_address.IsEmpty());
if (!pNode->m_2.m_qualifiesAsJunction)
{
return false;
}
//if this junction isn't on the highway, early out
if (!pNode->IsHighway())
{
return true;
}
//otherwise, all the junction entrances need to be on the highway for this to fail
for (int i = 0; i < pNode->NumLinks(); i++)
{
CPathNode* pOtherNode = GetNodesLinkedNode(pNode, i);
if (pOtherNode && !pOtherNode->IsHighway())
{
return true;
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : PreparePathDataForType
// PURPOSE : Will go through all the blocks in the map and extract the node
// data from them. The links between the nodes will then be
// established so that route finding can be done.
/////////////////////////////////////////////////////////////////////////////////
void CPathFindBuild::PreparePathDataForType( CTempNode *TempNodeList, CTempLink *TempLinkList,
s32 NumTempNodes, s32 NumTempLinks,
float MergeDist)
{
#define fMaxLinkLengthSqr (200.0f*200.0f)
Displayf("Buildpaths:Prepare path data\n");
Displayf("Buildpaths:Prepare path data - check duplicate nodes\n");
s32 Region;
// Test whether we have any double nodes.
#if __ASSERT
const float fDoubleNodeEpsSqr = 0.1f*0.1f;
bool bDoubleNodes = false;
for (s32 i = 0; i < NumTempNodes-1; i++)
{
for (s32 ii = i+1; ii < NumTempNodes; ii++)
{
float distSqr = (TempNodeList[i].m_Coors - TempNodeList[ii].m_Coors).Mag2();
if (distSqr < fDoubleNodeEpsSqr)
{
Displayf("Double node at: %f %f %f\n", TempNodeList[i].m_Coors.x, TempNodeList[i].m_Coors.y, TempNodeList[i].m_Coors.z);
ASSERT_ONLY(bDoubleNodes = true);
}
}
}
Assertf((bDoubleNodes==false), "Double nodes (nodes at identical nodes) found. Check debug output");
#endif
Displayf("Buildpaths:Prepare path data - resolve links\n");
// The links point to nodes in the same file. We have to change the Node1 and Node2 fields
// in the links to point to the nodes within the full array as opposed to within the file.
for (s32 Link = 0; Link < NumTempLinks; Link++)
{
for (s32 Node = 0; Node < NumTempNodes; Node++)
{
if (TempNodeList[Node].m_FileId == TempLinkList[Link].m_FileId)
{
if (TempLinkList[Link].m_Node1 == TempNodeList[Node].m_NodeIndex)
{
TempLinkList[Link].m_Node1 = Node | (1<<31);
}
if (TempLinkList[Link].m_Node2 == TempNodeList[Node].m_NodeIndex)
{
TempLinkList[Link].m_Node2 = Node | (1<<31);
}
}
}
}
for (s32 Link = 0; Link < NumTempLinks; Link++)
{
Assert(TempLinkList[Link].m_Node1 & (1<<31));
Assert(TempLinkList[Link].m_Node2 & (1<<31));
TempLinkList[Link].m_Node1 &= (~(1<<31));
TempLinkList[Link].m_Node2 &= (~(1<<31));
}
// Make sure there are no links of ridiculous length
for (s32 l = 0; l < NumTempLinks; l++)
{
const Vector3 & C1 = TempNodeList[TempLinkList[l].m_Node1].m_Coors;
const Vector3 & C2 = TempNodeList[TempLinkList[l].m_Node2].m_Coors;
float fLinkLength = (C1 - C2).Mag2();
Assertf(fLinkLength < fMaxLinkLengthSqr, "Massively huge link detected #3 from (%.1f, %.1f, %.1f) to (%.1f, %.1f, %.1f).. That's %.1fm long!", C1.x, C1.y, C1.z, C2.x, C2.y, C2.z, fLinkLength);
if(fLinkLength >= fMaxLinkLengthSqr)
{
Errorf("Massively huge link detected #3 from (%.1f, %.1f, %.1f) to (%.1f, %.1f, %.1f).. That's %.1fm long!", C1.x, C1.y, C1.z, C2.x, C2.y, C2.z, fLinkLength);
}
}
Displayf("Buildpaths:Prepare path data - check link semantics\n");
// Make sure there are no ped nodes connected to non ped nodes
for (s32 l = 0; l < NumTempLinks; l++)
{
s32 special1 = TempNodeList[TempLinkList[l].m_Node1].m_SpecialFunction;
s32 special2 = TempNodeList[TempLinkList[l].m_Node2].m_SpecialFunction;
const bool bCrossingNodesOk = (special1==SPECIAL_PED_CROSSING)==(special2==SPECIAL_PED_CROSSING);
const bool bDrivewayCrossingNodesOk = (special1==SPECIAL_PED_DRIVEWAY_CROSSING)==(special2==SPECIAL_PED_DRIVEWAY_CROSSING);
const bool bSpecialNodesOk = (special1==SPECIAL_PED_ASSISTED_MOVEMENT)==(special2==SPECIAL_PED_ASSISTED_MOVEMENT);
Assertf( bCrossingNodesOk, "Ped crossing node connected to non ped crossing node %f %f", TempNodeList[TempLinkList[l].m_Node1].m_Coors.x, TempNodeList[TempLinkList[l].m_Node1].m_Coors.y);
Assertf( bDrivewayCrossingNodesOk, "Ped driveway crossing node connected to non ped driveway crossing node %f %f", TempNodeList[TempLinkList[l].m_Node1].m_Coors.x, TempNodeList[TempLinkList[l].m_Node1].m_Coors.y);
Assertf( bSpecialNodesOk, "Ped assisted movement node connected to non assisted movement crossing node %f %f", TempNodeList[TempLinkList[l].m_Node1].m_Coors.x, TempNodeList[TempLinkList[l].m_Node1].m_Coors.y);
if(!bCrossingNodesOk)
{
Errorf("Ped crossing node connected to non ped crossing node %f %f", TempNodeList[TempLinkList[l].m_Node1].m_Coors.x, TempNodeList[TempLinkList[l].m_Node1].m_Coors.y);
}
if(!bDrivewayCrossingNodesOk)
{
Errorf("Ped driveway crossing node connected to non ped driveway crossing node %f %f", TempNodeList[TempLinkList[l].m_Node1].m_Coors.x, TempNodeList[TempLinkList[l].m_Node1].m_Coors.y);
}
if(!bSpecialNodesOk)
{
Errorf("Ped assisted movement node connected to non assisted movement crossing node %f %f", TempNodeList[TempLinkList[l].m_Node1].m_Coors.x, TempNodeList[TempLinkList[l].m_Node1].m_Coors.y);
}
}
Displayf("Buildpaths:Merge nodes\n");
const float fMergeDistSqr = MergeDist*MergeDist;
// Nodes that are near each other and from different files have to be merged.
// Note: The links pointing to the node that was removed have to be modified also.
for (s32 Node1 = 0; Node1 < NumTempNodes-1; Node1++)
{
for (s32 Node2 = Node1+1; Node2 < NumTempNodes; Node2++)
{
if (TempNodeList[Node1].m_FileId != TempNodeList[Node2].m_FileId)
{
if ((TempNodeList[Node1].m_Coors - TempNodeList[Node2].m_Coors).Mag2() < fMergeDistSqr)
{
// These 2 nodes have to be merged. Node2 gets removed. (Node2 is greater than Node1)
for (s32 NodeX = Node2; NodeX < NumTempNodes-1; NodeX++)
{
TempNodeList[NodeX] = TempNodeList[NodeX+1];
}
// link pointing to Node2 now have to point to Node1
// link pointing to nodes after Node2 have to point one node lower as the nodes have shifted.
for (s32 link = 0; link < NumTempLinks; link++)
{
if (TempLinkList[link].m_Node1 == Node2)
{
TempLinkList[link].m_Node1 = Node1;
}
else if (TempLinkList[link].m_Node1 > Node2)
{
TempLinkList[link].m_Node1--;
}
if (TempLinkList[link].m_Node2 == Node2)
{
TempLinkList[link].m_Node2 = Node1;
}
else if (TempLinkList[link].m_Node2 > Node2)
{
TempLinkList[link].m_Node2--;
}
}
// Make sure our loop still works.
Node2--;
NumTempNodes--;
}
}
}
}
// Make sure there are no links of ridiculous length
#if __ASSERT
for (s32 l = 0; l < NumTempLinks; l++)
{
const Vector3 & C1 = TempNodeList[TempLinkList[l].m_Node1].m_Coors;
const Vector3 & C2 = TempNodeList[TempLinkList[l].m_Node2].m_Coors;
float fLinkLength = (C1 - C2).Mag2();
Assertf(fLinkLength < fMaxLinkLengthSqr, "Massively huge link detected #3 from (%.1f, %.1f, %.1f) to (%.1f, %.1f, %.1f).. That's %.1fm long!", C1.x, C1.y, C1.z, C2.x, C2.y, C2.z, fLinkLength);
}
#endif
Displayf("Buildpaths:Remove isolated nodes\n");
// Remove nodes that have no neighbours. This should save the artists a lot of work and avoid nasty
// crashes and cars getting confused.
for (s32 Node = 0; Node < NumTempNodes; Node++)
{
s32 Link;
for (Link = 0; Link < NumTempLinks; Link++)
{
if (TempLinkList[Link].m_Node1 == Node || TempLinkList[Link].m_Node2 == Node)
{
break;
}
}
if (Link == NumTempLinks)
{
// This node has no links using it and should be removed. (Unless it is a network restart point. They can be isolated)
{
for (s32 NodeX = Node; NodeX < NumTempNodes-1; NodeX++)
{
TempNodeList[NodeX] = TempNodeList[NodeX+1];
}
// link pointing to nodes after Node2 have to point one node lower as the nodes have shifted.
for (s32 link = 0; link < NumTempLinks; link++)
{
if (TempLinkList[link].m_Node1 > Node)
{
TempLinkList[link].m_Node1--;
}
if (TempLinkList[link].m_Node2 > Node)
{
TempLinkList[link].m_Node2--;
}
}
// Make sure our loop still works.
Node--;
NumTempNodes--;
}
}
}
// Make sure there are no links of ridiculous length
#if __ASSERT
for (s32 l = 0; l < NumTempLinks; l++)
{
const Vector3 & C1 = TempNodeList[TempLinkList[l].m_Node1].m_Coors;
const Vector3 & C2 = TempNodeList[TempLinkList[l].m_Node2].m_Coors;
float fLinkLength = (C1 - C2).Mag2();
Assertf(fLinkLength < fMaxLinkLengthSqr, "Massively huge link detected #3 from (%.1f, %.1f, %.1f) to (%.1f, %.1f, %.1f).. That's %.1fm long!", C1.x, C1.y, C1.z, C2.x, C2.y, C2.z, fLinkLength);
}
#endif
Displayf("Buildpaths:Connect open space nodes to roads\n");
// Artist placed nodes form connections with open-space nodes so that cars can travel
// from roads onto open planes and vice versa.
for (s32 Node = 0; Node < NumTempNodes; Node++)
{
if (!TempNodeList[Node].m_SpecialFunction == SPECIAL_USE_NONE)
{
for (s32 Node2 = 0; Node2 < NumTempNodes; Node2++)
{
if (TempNodeList[Node].m_SpecialFunction == SPECIAL_OPEN_SPACE)
{
if ( (TempNodeList[Node].m_Coors - TempNodeList[Node2].m_Coors).XYMag() < OPEN_SPACE_SPACING)
{
// These 2 are close together and should form a link.
TempLinks[NumTempLinks].m_FileId = 0;
TempLinks[NumTempLinks].m_Node1 = Node;
TempLinks[NumTempLinks].m_Node2 = Node2;
TempLinks[NumTempLinks].m_Lanes1To2 = 1;
TempLinks[NumTempLinks].m_Lanes2To1 = 1;
TempLinks[NumTempLinks].m_Width = 0;
TempLinks[NumTempLinks].m_NarrowRoad = true;
TempLinks[NumTempLinks].m_bGpsCanGoBothWays = true;
TempLinks[NumTempLinks].m_bDontUseForNavigation = false;
TempLinks[NumTempLinks].m_bBlockIfNoLanes = false;
NumTempLinks++;
PathBuildFatalAssertf(NumTempLinks < PF_TEMPLINKS,"");
}
}
}
}
}
Displayf("Buildpaths:Assign indices\n");
// Assign each node an index within its block of the world (CNodeAddress)
// and store the node.
for (s32 Node = 0; Node < NumTempNodes; Node++)
{
Region = FindRegionForCoors(TempNodeList[Node].m_Coors.x, TempNodeList[Node].m_Coors.y);
Assert(apRegions[Region]);
CPathNode *pNode = &apRegions[Region]->aNodes[apRegions[Region]->NumNodes];
pNode->SetCoors(TempNodeList[Node].m_Coors);
Assertf(Sign(TempNodeList[Node].m_Coors.x) == Sign(pNode->m_coorsX) || pNode->m_coorsX==0, "\nOh dear, pathnode (%.1f, %.1f, %.1f) outside of X range", TempNodeList[Node].m_Coors.x, TempNodeList[Node].m_Coors.y, TempNodeList[Node].m_Coors.z);
Assertf(Sign(TempNodeList[Node].m_Coors.y) == Sign(pNode->m_coorsY) || pNode->m_coorsY==0, "\nOh dear, pathnode (%.1f, %.1f, %.1f) outside of Y range", TempNodeList[Node].m_Coors.x, TempNodeList[Node].m_Coors.y, TempNodeList[Node].m_Coors.z);
pNode->m_address.Set(Region, apRegions[Region]->NumNodes);
pNode->m_streetNameHash = TempNodeList[Node].m_StreetNameHash;
pNode->m_1.m_group = 0; // Filled in later
pNode->m_2.m_numLinks = 0; // Filled in later
pNode->m_2.m_slipJunction = false;
pNode->m_startIndexOfLinks = 0; // Filled in later
pNode->m_2.m_deadEndness = 0; // Filled in later
pNode->m_2.m_switchedOff = TempNodeList[Node].m_bSwitchedOff;
pNode->m_2.m_switchedOffOriginal = TempNodeList[Node].m_bSwitchedOff;
pNode->m_2.m_noGps = TempNodeList[Node].m_bNoGps;
pNode->m_2.m_waterNode = TempNodeList[Node].m_bWaterNode;
pNode->m_2.m_alreadyFound = false;
pNode->m_2.m_highwayOrLowBridge = TempNodeList[Node].m_bHighwayOrLowBridge;
pNode->m_2.m_speed = TempNodeList[Node].m_Speed;
pNode->m_2.m_density = TempNodeList[Node].m_Density;
pNode->m_1.m_specialFunction = TempNodeList[Node].m_SpecialFunction;
pNode->m_2.m_inTunnel = TempNodeList[Node].m_bAdditionalTunnelFlag;
pNode->m_1.m_cannotGoLeft = TempNodeList[Node].m_bNoLeftTurns;
pNode->m_2.m_leftOnly = TempNodeList[Node].m_bLeftOnly;
pNode->m_1.m_cannotGoRight = TempNodeList[Node].m_bNoRightTurns;
pNode->m_1.m_Offroad = TempNodeList[Node].m_bOffroad;
pNode->m_1.m_noBigVehicles = TempNodeList[Node].m_bNoBigVehicles;
pNode->m_1.m_indicateKeepLeft = TempNodeList[Node].m_bIndicateKeepLeft;
pNode->m_1.m_indicateKeepRight = TempNodeList[Node].m_bIndicateKeepRight;
pNode->m_1.m_slipLane = TempNodeList[Node].m_bSlipNode;
// float waterz;
// if (Water::GetWaterLevel(TempNodeList[Node].m_Coors, &waterz, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL) &&
// waterz > TempNodeList[Node].m_Coors.z + 2.0f)
// {
// pNode->m_2.m_inTunnel = true;
// }
TempNodeList[Node].m_NodeAddress.Set(Region, apRegions[Region]->NumNodes);
apRegions[Region]->NumNodes++;
PathBuildFatalAssertf(apRegions[Region]->NumNodes < MAXNODESPERREGION, "Too Many Nodes in this Region");
}
#define DISTMULT_SPEED_0 (2.0f)
#define DISTMULT_SPEED_2 (0.9f)
#define DISTMULT_OPEN_SPACE (0.5f)
Displayf("Buildpaths:Store links\n");
// Store the links that connect to this node.
for (s32 Node = 0; Node < NumTempNodes; Node++)
{
Region = TempNodeList[Node].m_NodeAddress.GetRegion();
s32 IndexWithinRegion = TempNodeList[Node].m_NodeAddress.GetIndex();
CPathNode *pNode = &apRegions[Region]->aNodes[IndexWithinRegion];
// Maximum number storable in m_startIndexOfLinks
// TODO: We can/should change this to a u16 really
PathBuildFatalAssertf(apRegions[Region]->NumLinks <= 32767, "Too Many Links in this Region");
pNode->m_startIndexOfLinks = (s16)(apRegions[Region]->NumLinks);
// Find the links that connect this node.
for (s32 Link = 0; Link < NumTempLinks; Link++)
{
s32 NumLinksForThisNode = 0;
if (TempLinkList[Link].m_Node1 == Node)
{
s32 OtherNode = TempLinkList[Link].m_Node2;
// Calculate a distance multiplier based on the speed of the nodes. This way the path finding can be
// discouraged to take routes that have low speeds (back alleys, petrol stations, the limo garage near the start point)
float distMultiplier = 1.0f;
if (TempNodeList[Node].m_Speed == 0 && TempNodeList[OtherNode].m_Speed == 0)
{
distMultiplier = DISTMULT_SPEED_0;
}
else if (TempNodeList[Node].m_Speed >= 2 && TempNodeList[OtherNode].m_Speed >= 2)
{
distMultiplier = DISTMULT_SPEED_2;
}
if (TempNodeList[Node].m_bOpenSpace || TempNodeList[OtherNode].m_bOpenSpace)
{
distMultiplier *= DISTMULT_OPEN_SPACE;
}
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_OtherNode = TempNodeList[OtherNode].m_NodeAddress;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_Distance = Clamp((u8)(distMultiplier*(TempNodeList[Node].m_Coors - TempNodeList[OtherNode].m_Coors).Mag()), (u8)1, (u8)255);
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_Width = TempLinkList[Link].m_Width;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_LanesToOtherNode = TempLinkList[Link].m_Lanes1To2;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_LanesFromOtherNode = TempLinkList[Link].m_Lanes2To1;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_NarrowRoad = TempLinkList[Link].m_NarrowRoad;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_bGpsCanGoBothWays = TempLinkList[Link].m_bGpsCanGoBothWays;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_bShortCut = TempLinkList[Link].m_bShortCut;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_bDontUseForNavigation = TempLinkList[Link].m_bDontUseForNavigation;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_bBlockIfNoLanes = TempLinkList[Link].m_bBlockIfNoLanes;
NumLinksForThisNode++;
PathBuildFatalAssertf(NumLinksForThisNode < PF_MAXLINKSPERNODE, "");
PathBuildFatalAssertf(pNode->m_2.m_numLinks < 31,"");
PathBuildFatalAssertf(apRegions[Region]->NumLinks < MAXLINKSPERREGION,"");
pNode->m_2.m_numLinks++;
apRegions[Region]->NumLinks++;
}
else if (TempLinkList[Link].m_Node2 == Node)
{
s32 OtherNode = TempLinkList[Link].m_Node1;
// Calculate a distance multiplier based on the speed of the nodes. This way the path finding can be
// discouraged to take routes that have low speeds (back alleys, petrol stations, the limo garage near the start point)
float distMultiplier = 1.0f;
if (TempNodeList[Node].m_Speed == 0 && TempNodeList[OtherNode].m_Speed == 0)
{
distMultiplier = DISTMULT_SPEED_0;
}
else if (TempNodeList[Node].m_Speed >= 2 && TempNodeList[OtherNode].m_Speed >= 2)
{
distMultiplier = DISTMULT_SPEED_2;
}
if (TempNodeList[Node].m_bOpenSpace || TempNodeList[OtherNode].m_bOpenSpace)
{
distMultiplier *= DISTMULT_OPEN_SPACE;
}
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_OtherNode = TempNodeList[OtherNode].m_NodeAddress;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_Distance = Clamp((u8)(distMultiplier*(TempNodeList[Node].m_Coors - TempNodeList[OtherNode].m_Coors).Mag()), (u8)1, (u8)255);
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_Width = TempLinkList[Link].m_Width;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_LanesToOtherNode = TempLinkList[Link].m_Lanes2To1;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_LanesFromOtherNode = TempLinkList[Link].m_Lanes1To2;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_NarrowRoad = TempLinkList[Link].m_NarrowRoad;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_bGpsCanGoBothWays = TempLinkList[Link].m_bGpsCanGoBothWays;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_bShortCut = TempLinkList[Link].m_bShortCut;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_bDontUseForNavigation = TempLinkList[Link].m_bDontUseForNavigation;
apRegions[Region]->aLinks[apRegions[Region]->NumLinks].m_1.m_bBlockIfNoLanes = TempLinkList[Link].m_bBlockIfNoLanes;
NumLinksForThisNode++;
PathBuildFatalAssertf(NumLinksForThisNode < PF_MAXLINKSPERNODE,"");
PathBuildFatalAssertf(pNode->m_2.m_numLinks < 31,"");
PathBuildFatalAssertf(apRegions[Region]->NumLinks < MAXLINKSPERREGION,"");
pNode->m_2.m_numLinks++;
apRegions[Region]->NumLinks++;
}
}
}
#if __DEV
// Do a check to make sure nodes point 2 ways.
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
CheckPathNodeIntegrityForZone(Region);
}
#endif //DEV
Displayf("Buildpaths:Update dead ends\n");
// For the roads we detect the ones on a dead end so we can get the computer controlled
// cars to avoid these.
bool ChangeHasHappened = true;
u32 LiveLinks, DeadEndLinks, HighestDeadEndness;
// Mark the nodes next to a dead end as dead end as well.
while (ChangeHasHappened)
{
ChangeHasHappened = false;
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
{
continue;
}
for (s32 Node = 0; Node < apRegions[Region]->NumNodes; Node++)
{
CPathNode *pNode = &((apRegions[Region]->aNodes)[Node]);
if (!pNode->m_2.m_deadEndness)
{
// Count the number of non dead end neighbours.
LiveLinks = 0;
DeadEndLinks = 0;
HighestDeadEndness = 0;
for(u32 linkNum = 0; linkNum < pNode->NumLinks(); linkNum++)
{
ASSERT_ONLY(CNodeAddress linkNodeAddr = GetNodesLinkedNodeAddr(pNode, linkNum);)
Assert(IsRegionLoaded(linkNodeAddr));
CPathNode* pLinkedNode = GetNodesLinkedNode(pNode, linkNum);
bool bSpecialFunctionIsThroughNode = false;
switch(pLinkedNode->m_1.m_specialFunction)
{
case SPECIAL_USE_NONE:
case SPECIAL_DROPOFF_GOODS:
case SPECIAL_DRIVE_THROUGH:
case SPECIAL_DRIVE_THROUGH_WINDOW:
case SPECIAL_DROPOFF_GOODS_UNLOAD:
case SPECIAL_SMALL_WORK_VEHICLES:
case SPECIAL_PETROL_STATION:
case SPECIAL_DROPOFF_PASSENGERS:
case SPECIAL_DROPOFF_PASSENGERS_UNLOAD:
case SPECIAL_TRAFFIC_LIGHT:
case SPECIAL_GIVE_WAY:
case SPECIAL_FORCE_JUNCTION:
case SPECIAL_RESTRICTED_AREA:
bSpecialFunctionIsThroughNode = true;
break;
}
if ( bSpecialFunctionIsThroughNode &&
(!pLinkedNode->m_2.m_switchedOffOriginal || pNode->m_2.m_switchedOffOriginal))
{
if(pLinkedNode->m_2.m_deadEndness)
{
DeadEndLinks++;
HighestDeadEndness = MAX(pLinkedNode->m_2.m_deadEndness, HighestDeadEndness);
}
else
{
LiveLinks++;
}
}
}
if (LiveLinks < 2)
{
if (DeadEndLinks >= 2) // We have more than 1 deadend neighbours. We're at a junction.
{
Assert(HighestDeadEndness <= 6);
pNode->m_2.m_deadEndness = MIN(HighestDeadEndness + 1, 7);
}
else
{
pNode->m_2.m_deadEndness = MAX(1, HighestDeadEndness);
}
ChangeHasHappened = true;
}
}
}
}
}
// Change the deadendness so that the higher number is further from the main road (so that cars will always drive towards lower deadendness.
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
{
continue;
}
for (s32 Node = 0; Node < apRegions[Region]->NumNodes; Node++)
{
CPathNode *pNode = &((apRegions[Region]->aNodes)[Node]);
if (pNode->m_2.m_deadEndness)
{
pNode->m_2.m_deadEndness = 8 - pNode->m_2.m_deadEndness;
}
}
}
// Preprocess "CPathFind::ThisNodeWillLeadIntoADeadEnd()"
// This now has to be stored in the link between two nodes, because the code can't be SPU'd (without a lot of work)
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
continue;
for (s32 Node = 0; Node < apRegions[Region]->NumNodes; Node++)
{
CPathNode *pNode = &((apRegions[Region]->aNodes)[Node]);
for(int l=0; l<pNode->NumLinks(); l++)
{
CPathNodeLink & link = GetNodesLink(pNode, l);
// to start with, set both to be false
link.m_1.m_LeadsToDeadEnd = false;
link.m_1.m_LeadsFromDeadEnd = false;
CPathNode * pOtherNode = FindNodePointerSafe(link.m_OtherNode);
Assert(pOtherNode);
if(pOtherNode)
{
link.m_1.m_LeadsToDeadEnd = ThisNodeWillLeadIntoADeadEnd(pOtherNode, pNode);
}
}
}
}
// Now iterate though all nodes & links again, and set the 'm_LeadsFromDeadEnd' for the
// opposite direction in each link.
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
continue;
for (s32 Node = 0; Node < apRegions[Region]->NumNodes; Node++)
{
CPathNode *pNode = &((apRegions[Region]->aNodes)[Node]);
for(int l=0; l<pNode->NumLinks(); l++)
{
CPathNodeLink & link = GetNodesLink(pNode, l);
CPathNode * pOtherNode = FindNodePointerSafe(link.m_OtherNode);
Assert(pOtherNode);
if(pOtherNode)
{
s16 iLinkBack;
if( FindNodesLinkIndexBetween2Nodes(pOtherNode->m_address, pNode->m_address, iLinkBack) )
{
const CPathNodeLink & linkBack = GetNodesLink( pOtherNode, iLinkBack );
link.m_1.m_LeadsFromDeadEnd = linkBack.m_1.m_LeadsToDeadEnd;
}
}
}
}
}
Displayf("Buildpaths:shift nodes at junction (almost there John)\n");
// Single direction nodes can get merged into a road with more lanes. If this is the case
// the lanes need to be shifted at the junction so that traffic doesn't infringe upon traffic
// it is supposed to carry on alongside with.
// Goes through all the nodes going into this junction and identify neighbouring ones that are
// one way and that are within a certain angle of eachother.
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
{
continue;
}
for (s32 Node = 0; Node < apRegions[Region]->NumNodes; Node++)
{
CPathNode *pNode = &((apRegions[Region]->aNodes)[Node]);
if(pNode->NumLinks() > 2)
{
#define MAX_N (32)
PathBuildFatalAssertf(pNode->NumLinks() < MAX_N,"");
s32 num = 0;
struct sDir{
Vector2 diff;
float orientation;
CPathNodeLink *pLink;
CPathNodeLink *pOppositeLink;
bool bCanBeMerged;
bool bSwitchedOff;
};
sDir aDirs[MAX_N];
for(u32 n = 0; n < pNode->NumLinks(); n++)
{
CPathNodeLink *pLink = &GetNodesLink(pNode, n);
if (pLink->m_1.m_LanesFromOtherNode == 0 || pLink->m_1.m_LanesToOtherNode == 0)
{
// One way. Add link to be considered.
CNodeAddress otherNodeAddr = pLink->m_OtherNode;
CPathNode *pOtherNode = FindNodePointer(otherNodeAddr);
Vector2 ourCoors, otherCoors;
pNode->GetCoors2(ourCoors);
pOtherNode->GetCoors2(otherCoors);
aDirs[num].diff = otherCoors - ourCoors;
aDirs[num].diff.Normalize();
aDirs[num].bCanBeMerged = true;
aDirs[num].bSwitchedOff = pOtherNode->m_2.m_switchedOffOriginal;
aDirs[num].orientation = rage::Atan2f(-aDirs[num].diff.x, aDirs[num].diff.y);
aDirs[num].pLink = pLink;
s16 iLink = -1;
const bool bLinkFound = FindNodesLinkIndexBetween2Nodes(otherNodeAddr, pNode->m_address, iLink);
if(!bLinkFound)//can't find the link so just continue
{
continue;
}
aDirs[num].pOppositeLink = &GetNodesLink(pOtherNode, iLink);
num++;
}
}
if (num >= 2)
{
// Sort the connecting nodes in clockwise order
bool bChange = true;
while (bChange)
{
bChange = false;
for (s32 e = 0; e < num-1; e++)
{
if (aDirs[e].orientation > aDirs[e+1].orientation)
{
sDir temp = aDirs[e];
aDirs[e] = aDirs[e+1];
aDirs[e+1] = temp;
bChange = true;
}
}
}
}
// Now go through and find the closest pair of nodes.
s32 bestCandidate = 0;
while (bestCandidate >= 0)
{
bestCandidate = -1;
float smallestOrientationDiff = PI * 0.35f; // Cut off for roads considered to be merging.
float averageOrientation = 0.0f;
for (s32 e = 0; e < num; e++)
{
if (aDirs[e].bCanBeMerged && aDirs[(e+1)%num].bCanBeMerged && (aDirs[e].bSwitchedOff == aDirs[(e+1)%num].bSwitchedOff) )
{
float orientDiffSigned = aDirs[ (e+1)%num ].orientation - aDirs[e].orientation;
orientDiffSigned = fwAngle::LimitRadianAngle(orientDiffSigned);
float orientDiff = rage::Abs(orientDiffSigned);
if (orientDiff < smallestOrientationDiff)
{
smallestOrientationDiff = orientDiff;
bestCandidate = e;
averageOrientation = aDirs[e].orientation + orientDiffSigned * 0.5f;
}
}
}
if (bestCandidate >= 0)
{
// Found the 2 nodes that we consider being part of the same road.
s32 n1 = bestCandidate;
s32 n2 = (n1+1) % num;
// Before we do the shifting business we make sure there are links
// on the other side of the junction that can accomodate all these lanes.
// If this is not the case we don't shift as it causes cars to deviate from
// their path unnecessarily.
//s32 lanesWeSupplyToTheJunction = aDirs[n1].pLink->m_1.m_LanesFromOtherNode + aDirs[n2].pLink->m_1.m_LanesFromOtherNode;
//s32 lanesWeTakeFromTheJunction = aDirs[n1].pLink->m_1.m_LanesToOtherNode + aDirs[n2].pLink->m_1.m_LanesToOtherNode;
s32 lanesThatCanBeTakenFromTheJunction = 0;
s32 lanesThatCanBeSuppliedToTheJunction = 0;
for(u32 n = 0; n < pNode->NumLinks(); n++)
{
CPathNodeLink *pLink = &GetNodesLink(pNode, n);
if (pLink != aDirs[n1].pLink && pLink != aDirs[n2].pLink)
{
CNodeAddress otherNodeAddr = pLink->m_OtherNode;
CPathNode *pOtherNode = FindNodePointer(otherNodeAddr);
Vector2 ourCoors, otherCoors;
pNode->GetCoors2(ourCoors);
pOtherNode->GetCoors2(otherCoors);
Vector2 diff = otherCoors - ourCoors;
diff.Normalize();
float orientation = rage::Atan2f(-diff.x, diff.y);
float angleFromExactlyOpposite = averageOrientation - orientation + PI;
angleFromExactlyOpposite = fwAngle::LimitRadianAngle(angleFromExactlyOpposite);
if (rage::Abs(angleFromExactlyOpposite) < PI * 0.5f)
{
lanesThatCanBeSuppliedToTheJunction += pLink->m_1.m_LanesFromOtherNode;
lanesThatCanBeTakenFromTheJunction += pLink->m_1.m_LanesToOtherNode;
}
}
}
// Only merge the lanes if on the other side the road is wide enough.
// if (lanesThatCanBeSuppliedToTheJunction >= lanesWeTakeFromTheJunction &&
// lanesThatCanBeTakenFromTheJunction >= lanesWeSupplyToTheJunction)
// {
// float orientDiff = aDirs[n1].orientation - aDirs[n2].orientation;
// orientDiff = fwAngle::LimitRadianAngle(orientDiff);
//
// if (orientDiff < 0.0f)
// {
// aDirs[n1].pLink->m_1.m_LanesShiftedToRightAtOriginNode = true;
// aDirs[n1].pOppositeLink->m_1.m_LanesShiftedToLeftAtOtherNode = true;
// aDirs[n2].pLink->m_1.m_LanesShiftedToLeftAtOriginNode = true;
// aDirs[n2].pOppositeLink->m_1.m_LanesShiftedToRightAtOtherNode = true;
// }
// else
// {
// aDirs[n1].pLink->m_1.m_LanesShiftedToLeftAtOriginNode = true;
// aDirs[n1].pOppositeLink->m_1.m_LanesShiftedToRightAtOtherNode = true;
// aDirs[n2].pLink->m_1.m_LanesShiftedToRightAtOriginNode = true;
// aDirs[n2].pOppositeLink->m_1.m_LanesShiftedToLeftAtOtherNode = true;
// }
// }
aDirs[n1].bCanBeMerged = false; // Done with these.
aDirs[n2].bCanBeMerged = false;
}
}
}
}
}
Displayf("Buildpaths:do street names\n");
// Extend the street names until a junction is hit.
// This way John doesn't have to set the street name for each and every node.
bool bChangedMade = true;
while (bChangedMade)
{
bChangedMade = false;
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
{
continue;
}
for (s32 Node = 0; Node < apRegions[Region]->NumNodes; Node++)
{
CPathNode *pNode = &((apRegions[Region]->aNodes)[Node]);
if(pNode->m_streetNameHash==0 && pNode->NumLinks() <= 2)
{
// Where the streets have no name,
// we try to copy a name from a neighbouring node.
for(u32 Link = 0; Link < pNode->NumLinks(); Link++)
{
ASSERT_ONLY(CNodeAddress linkNodeAddr = GetNodesLinkedNodeAddr(pNode, Link);)
Assert(IsRegionLoaded(linkNodeAddr));
CPathNode* pLinkedNode = GetNodesLinkedNode(pNode, Link);
if(pLinkedNode->m_streetNameHash != 0)
{
pNode->m_streetNameHash = pLinkedNode->m_streetNameHash;
bChangedMade = true;
}
}
}
}
}
}
Displayf("Buildpaths: extend low bridge\n");
// Extend the low bridge until junction is hit.
// This way John doesn't have to set the low bridge for each and every node.
bChangedMade = true;
while (bChangedMade)
{
bChangedMade = false;
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
{
continue;
}
for (s32 Node = 0; Node < apRegions[Region]->NumNodes; Node++)
{
CPathNode *pNode = &((apRegions[Region]->aNodes)[Node]);
if(pNode->m_2.m_waterNode && !pNode->m_2.m_highwayOrLowBridge && pNode->NumLinks() <= 2)
{
for(u32 Link = 0; Link < pNode->NumLinks(); Link++)
{
ASSERT_ONLY(CNodeAddress linkNodeAddr = GetNodesLinkedNodeAddr(pNode, Link);)
PathBuildFatalAssertf(IsRegionLoaded(linkNodeAddr), "");
CPathNode* pLinkedNode = GetNodesLinkedNode(pNode, Link);
if(pLinkedNode->m_2.m_waterNode && pLinkedNode->m_2.m_highwayOrLowBridge)
{
pNode->m_2.m_highwayOrLowBridge = true;
bChangedMade = true;
}
}
}
}
}
}
Vector3 vDirToValidNeighbourNode[32];
const float fSameDotEps = CPathFind::sm_fSlipLaneDotThreshold;
Displayf("Buildpaths:Find junctions\n");
// Work out for each node whether it would be a junction. More than 2 nodes that aren't special.
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
{
continue;
}
for (s32 Node = 0; Node < apRegions[Region]->NumNodes; Node++)
{
Vector3 vNodePos;
CPathNode * pNode = &((apRegions[Region]->aNodes)[Node]);
pNode->GetCoors(vNodePos);
s32 validNeigbours = 0;
for(u32 Link = 0; Link < pNode->NumLinks(); Link++)
{
CPathNodeLink pathNodeLink = GetNodesLink(pNode, Link);
ASSERT_ONLY(CNodeAddress linkNodeAddr = GetNodesLinkedNodeAddr(pNode, Link);)
Assert(IsRegionLoaded(linkNodeAddr));
CPathNode* pLinkedNode = GetNodesLinkedNode(pNode, Link);
if(pNode->m_1.m_specialFunction != SPECIAL_OPEN_SPACE
&& pLinkedNode->m_1.m_specialFunction != SPECIAL_PARKING_PARALLEL
&& pLinkedNode->m_1.m_specialFunction != SPECIAL_PARKING_PERPENDICULAR
&& !pathNodeLink.m_1.m_bShortCut)
{
//let's just create a junction regardless of whether or not
//any nodes are switched off--we hvae a lot of places where we
//want cars entering the road from switched-off parking lots
//to do junction logic
//if(pNode->m_2.m_switchedOff || !pLinkedNode->m_2.m_switchedOff)
{
Vector3 vNeighbour;
pLinkedNode->GetCoors(vNeighbour);
vDirToValidNeighbourNode[validNeigbours] = vNeighbour - vNodePos;
vDirToValidNeighbourNode[validNeigbours].z = 0.0f;
vDirToValidNeighbourNode[validNeigbours].Normalize();
validNeigbours++;
}
}
}
if (validNeigbours > 2 && pNode->m_1.m_specialFunction != SPECIAL_OPEN_SPACE)
{
bool bQualifiesAsJunction = true;
// Identify slip-lanes; these will have 3 links, 2 of which will be almost exactly the same direction.
// Never mark these up as Junction nodes - or we will have cars waiting wherever they exist
if(validNeigbours == 3)
{
if(DotProduct(vDirToValidNeighbourNode[0], vDirToValidNeighbourNode[1]) > fSameDotEps ||
DotProduct(vDirToValidNeighbourNode[1], vDirToValidNeighbourNode[2]) > fSameDotEps ||
DotProduct(vDirToValidNeighbourNode[2], vDirToValidNeighbourNode[0]) > fSameDotEps)
{
bQualifiesAsJunction = false;
}
}
pNode->m_2.m_qualifiesAsJunction = bQualifiesAsJunction;
if (pNode->m_2.m_qualifiesAsJunction && !QualifiesAsJunction(pNode))
{
pNode->m_2.m_qualifiesAsJunction = false;
}
//look for junctions that only have one inbound lane and filter them out
if (pNode->m_2.m_qualifiesAsJunction)
{
int iNumInboundLinks = 0;
for (int Link=0; Link < pNode->NumLinks(); Link++)
{
CPathNodeLink pathNodeLink = GetNodesLink(pNode, Link);
ASSERT_ONLY(CNodeAddress linkNodeAddr = GetNodesLinkedNodeAddr(pNode, Link);)
Assert(IsRegionLoaded(linkNodeAddr));
CPathNode* pLinkedNode = GetNodesLinkedNode(pNode, Link);
if (pathNodeLink.IsShortCut())
{
continue;
}
//traffic lights or give ways should be a signal that we actually
//want to do junction logic here
if (pLinkedNode->IsGiveWay() || pLinkedNode->IsTrafficLight())
{
iNumInboundLinks = 64; //arbitrary >1 sentinal value
break;
}
if (pathNodeLink.m_1.m_LanesFromOtherNode > 0)
{
++iNumInboundLinks;
}
}
//if we have 1 or fewer inbound links, it's probably a fork
//in the road, in which case we don't need to do the junction logic
if (iNumInboundLinks < 2)
{
pNode->m_2.m_qualifiesAsJunction = false;
}
}
//iterate through each entry-exit pair and see if any of them have multiple valid ways out
//it's not a junction if none of them do
//unless they signal they want it to be a junction with traffic lights or give-ways
if (pNode->m_2.m_qualifiesAsJunction)
{
//int iNumEntriesWithMultipleValidExits = 0;
bool bFoundEntryWMultipleValidExits = false;
for (int iEntryLink=0; iEntryLink < pNode->NumLinks(); iEntryLink++)
{
int iNumValidWaysOut = 0;
CPathNodeLink EntryLink = GetNodesLink(pNode, iEntryLink);
ASSERT_ONLY(CNodeAddress EntrylinkNodeAddr = GetNodesLinkedNodeAddr(pNode, iEntryLink);)
Assert(IsRegionLoaded(EntrylinkNodeAddr));
CPathNode* pEntryNode = GetNodesLinkedNode(pNode, iEntryLink);
if (pEntryNode->IsGiveWay() || pEntryNode->IsTrafficLight())
{
//early out if we find a traffic light or give-way
//iNumEntriesWithMultipleValidExits = 64;
bFoundEntryWMultipleValidExits = true;
break;
}
if (EntryLink.IsShortCut())
{
//ignore shortcuts
continue;
}
if (EntryLink.m_1.m_LanesFromOtherNode < 1)
{
continue;
}
for (int iExitLink=0; iExitLink < pNode->NumLinks(); iExitLink++)
{
CPathNodeLink ExitLink = GetNodesLink(pNode, iExitLink);
ASSERT_ONLY(CNodeAddress ExitLinkNodeAddr = GetNodesLinkedNodeAddr(pNode, iExitLink);)
Assert(IsRegionLoaded(ExitLinkNodeAddr));
CPathNode* pExitNode = GetNodesLinkedNode(pNode, iExitLink);
if (pExitNode->IsGiveWay() || pExitNode->IsTrafficLight())
{
//early out if we find a traffic light or give-way
iNumValidWaysOut = 64; //random >1 sentinal value
//iNumEntriesWithMultipleValidExits = 64;
bFoundEntryWMultipleValidExits = true;
break;
}
if (ExitLink.IsShortCut())
{
//ignore shortcuts
continue;
}
if (pExitNode->m_address == pEntryNode->m_address)
{
//u-turning is not a valid way out
continue;
}
if (ExitLink.m_1.m_LanesToOtherNode < 1)
{
continue;
}
//now try and calculate the direction
float fDotProduct = 0.0f, fLeftness = 0.0f;
bool bSharpTurn = false;
CPathNodeRouteSearchHelper::FindJunctionTurnDirection(EmptyNodeAddress, pEntryNode->m_address, pNode->m_address, pExitNode->m_address
, &bSharpTurn, &fLeftness, CPathNodeRouteSearchHelper::sm_fDefaultDotThresholdForRoadStraightAhead, &fDotProduct, *this);
if (!bSharpTurn)
{
iNumValidWaysOut++;
}
}
if (iNumValidWaysOut > 1)
{
bFoundEntryWMultipleValidExits = true;
}
// if (iNumValidWaysOut > 2)
// {
// //iNumEntriesWithMultipleValidExits = 64;
// bFoundEntryWMultipleValidExits = true;
// break;
// }
// else if (iNumValidWaysOut > 1)
// {
// iNumEntriesWithMultipleValidExits++;
// }
}
// if (iNumEntriesWithMultipleValidExits < 2)
// {
// pNode->m_2.m_qualifiesAsJunction = false;
// }
if (!bFoundEntryWMultipleValidExits)
{
pNode->m_2.m_qualifiesAsJunction = false;
}
}
}
else
{
pNode->m_2.m_qualifiesAsJunction = false;
}
//STUPID STUPID HACK GET RID OF THIS
#if HACK_GTA4
if (pNode->GetPos().Dist2(Vector3(717.0f, -1010.5f, 29.0f)) < 1.0f * 1.0f)
{
pNode->m_2.m_qualifiesAsJunction = false;
}
#endif //HACK_GTA4
//let the "force junction" special function override whatever we calculated above
if (pNode->m_1.m_specialFunction == SPECIAL_FORCE_JUNCTION)
{
pNode->m_2.m_qualifiesAsJunction = true;
}
if (validNeigbours > 2)
{
pNode->m_2.m_slipJunction = true;
}
else
{
pNode->m_2.m_slipJunction = false;
}
}
for (s32 Node = 0; Node < apRegions[Region]->NumNodes; Node++)
{
CPathNode * pNode = &((apRegions[Region]->aNodes)[Node]);
if (pNode->NumLinks() > 2)
{
Vector3 vNodeCoords;
pNode->GetCoors(vNodeCoords);
//iterate through each of our links, looking for two that are collinear
//if we find them, check that one looks like a sliplane and the other doesn't
//this used to tag up the non-sliplane node as "cannotGoLeft", but I found enough
//instances of cases where we want the non-sliplane to be allowed to go left in the world
//that the default is now that you can go left, and in places where we don't want that to happen
//it will have to be marked up in the world
for(u32 iLink = 0; iLink < pNode->NumLinks(); iLink++)
{
ASSERT_ONLY(CNodeAddress iLinkNodeAddr = GetNodesLinkedNodeAddr(pNode, iLink);)
Assert(IsRegionLoaded(iLinkNodeAddr));
CPathNode* piLinkedNode = GetNodesLinkedNode(pNode, iLink);
CPathNodeLink& riLink = GetNodesLink(pNode, iLink);
Vector3 viNodeCoords;
piLinkedNode->GetCoors(viNodeCoords);
Vector3 vBaseToI = viNodeCoords - vNodeCoords;
vBaseToI.z = 0.0f;
vBaseToI.NormalizeSafe();
for (u32 jLink = 0; jLink < pNode->NumLinks(); jLink++)
{
if (iLink == jLink)
{
continue;
}
ASSERT_ONLY(CNodeAddress jLinkNodeAddr = GetNodesLinkedNodeAddr(pNode, jLink);)
PathBuildFatalAssertf(IsRegionLoaded(jLinkNodeAddr),"");
CPathNode* pjLinkedNode = GetNodesLinkedNode(pNode, jLink);
const CPathNodeLink & rjLink = GetNodesLink(pNode, jLink);
{
//test links
if (riLink.m_1.m_LanesToOtherNode == 0 &&
riLink.m_1.m_LanesFromOtherNode == 1 &&
riLink.m_1.m_Width == 0 &&
rjLink.m_1.m_Width > 0)
{
Vector3 vjNodeCoords;
pjLinkedNode->GetCoors(vjNodeCoords);
Vector3 vBaseToJ = vjNodeCoords - vNodeCoords;
vBaseToJ.z = 0.0f;
vBaseToJ.NormalizeSafe();
//test for colinearity
if (vBaseToI.Dot(vBaseToJ) > CPathFind::sm_fSlipLaneDotThreshold)
{
//i is the sliplane link
piLinkedNode->m_2.m_leftOnly = true;
}
}
else if (rjLink.m_1.m_LanesToOtherNode == 0 &&
rjLink.m_1.m_LanesFromOtherNode == 1 &&
rjLink.m_1.m_Width == 0 &&
riLink.m_1.m_Width > 0)
{
Vector3 vjNodeCoords;
pjLinkedNode->GetCoors(vjNodeCoords);
Vector3 vBaseToJ = vjNodeCoords - vNodeCoords;
vBaseToJ.z = 0.0f;
vBaseToJ.NormalizeSafe();
//test for colinearity
if (vBaseToI.Dot(vBaseToJ) > CPathFind::sm_fSlipLaneDotThreshold)
{
//j is the sliplane link
pjLinkedNode->m_2.m_leftOnly = true;
}
}
}
}
}
}
}
}
// Flag which nodes are slip lanes, using CPathNodeRouteSearchHelper::GetSlipLaneNodeLinkIndex()
Displayf("Buildpaths:Identify Sliplanes\n");
IdentifySlipLaneNodes();
// For all nodes which are entrances to templated junctions, set the SPECIAL_TRAFFIC_LIGHT attribute
Displayf("Buildpaths:Preprocess junctions\n");
PreprocessJunctions();
// Count the nodes of each type in each Region.
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region])
{
continue;
}
apRegions[Region]->NumNodesCarNodes = 0;
while (apRegions[Region]->NumNodesCarNodes < apRegions[Region]->NumNodes && !apRegions[Region]->aNodes[apRegions[Region]->NumNodesCarNodes].IsPedNode() )
{
apRegions[Region]->NumNodesCarNodes++;
}
apRegions[Region]->NumNodesPedNodes = apRegions[Region]->NumNodes - apRegions[Region]->NumNodesCarNodes;
}
// Now set the m_2.m_distanceHash value. This is a value that represents a distance in meters to the next node but
// carries on from node to node. Used by the car placement code.
// Count the nodes of each type in each Region.
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region])
{
continue;
}
for (s32 node = 0; node < apRegions[Region]->NumNodesCarNodes; node++)
{
CPathNode *pNode = &apRegions[Region]->aNodes[node];
if (pNode->m_2.m_distanceHash == 0)
{
pNode->m_2.m_distanceHash = 1;
FloodFillDistanceHashes();
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ThisNodeWillLeadIntoADeadEnd
// PURPOSE : This little helper function will test whether a node will eventually lead
// to a dead end.
/////////////////////////////////////////////////////////////////////////////////
bool CPathFindBuild::ThisNodeWillLeadIntoADeadEnd(const CPathNode *pArgToNode, const CPathNode *pArgFromNode) const
{
const CPathNode *pSuccessFullCandidate;
const CPathNode *pToNode = pArgToNode;
const CPathNode *pFromNode = pArgFromNode;
u32 currentDeadEndNess = pToNode->m_2.m_deadEndness;
if (currentDeadEndNess == 0)
{
return false;
}
if(pArgToNode->IsPedNode() || pArgFromNode->IsPedNode())
{
return false;
}
while (1)
{
// Go through the neighbouring nodes and count the viable ones.
pSuccessFullCandidate = NULL;
s32 numSuccessFullNodes = 0;
for(u32 Link = 0; Link < pToNode->NumLinks(); Link++)
{
CNodeAddress Candidate = GetNodesLinkedNodeAddr(pToNode, Link);
if(!IsRegionLoaded(Candidate))
{
continue;
}
const CPathNode *pCandidate = FindNodePointer(Candidate);
if(!pCandidate || (pCandidate == pFromNode))
{
continue;
}
if(pCandidate->HasSpecialFunction_Driving())
{
continue;
}
if (pCandidate->m_2.m_deadEndness < currentDeadEndNess)
{
return false; // We found a node that leads back to the main network.
}
// Only traverse this link if it has the same dead-endness as this node, if its more we know its taking us away from the network.
else if (pCandidate->m_2.m_deadEndness == currentDeadEndNess
&& (!pCandidate->m_2.m_switchedOffOriginal || pToNode->m_2.m_switchedOffOriginal))
{
numSuccessFullNodes++;
pSuccessFullCandidate = pCandidate;
}
}
if (numSuccessFullNodes >= 2)
{ // We've just hit a junction with more than 2 new nodes neither of which lead back to the node network.
return true;
}
if (numSuccessFullNodes == 0)
{ // We've just hit a node without suitable neighbours. This is the dead end itself. Call it a day.
return true;
}
// We have found the 1 suitable other node. Continue looking down this path.
pFromNode = pToNode;
pToNode = pSuccessFullCandidate;
// The following line will catch the case where the nodes are in an isolated circle.
// The code would otherwise go round forever.
if (pToNode == pArgToNode)
{
return false;
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SetDistanceHashRecursively
// PURPOSE : Goes through neighbours of this node to set the distance hash
/////////////////////////////////////////////////////////////////////////////////
void CPathFindBuild::FloodFillDistanceHashes()
{
bool bChange = true;
while (bChange)
{
bChange = false;
for (s32 Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region])
{
continue;
}
for (s32 node = 0; node < apRegions[Region]->NumNodesCarNodes; node++)
{
CPathNode *pNode = &apRegions[Region]->aNodes[node];
if (pNode->m_2.m_distanceHash == 0)
{
for(u32 n = 0; n < pNode->NumLinks(); n++)
{
CPathNodeLink* pLink = &GetNodesLink(pNode, n);
CPathNode *pNeighbourNode = FindNodePointer(pLink->m_OtherNode);
if (pNeighbourNode->m_2.m_distanceHash)
{
Vector3 crs1, crs2;
pNode->GetCoors(crs1);
pNeighbourNode->GetCoors(crs2);
s32 dist = MAX(1, ((s32)(crs1-crs2).Mag()) );
pNode->m_2.m_distanceHash = pNeighbourNode->m_2.m_distanceHash + dist;
if (pNode->m_2.m_distanceHash == 0) pNode->m_2.m_distanceHash = 1;
bChange = true;
}
}
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : AddOpenSpaceNodes
// PURPOSE : Goes through the open space that have been set up in max and adds the nodes
// to fill them up.
/////////////////////////////////////////////////////////////////////////////////
//float openspaceX[OPEN_SPACE_POINTS] = { 2444.0f, 2532.0f, 2437.0f, 2850.0f, 2755.0f, 2626.0f, 2434.0f, 2206.0f, 1916.0f, 1848.0f, 1688.0f, 1591.0f, 1496.0f, 1414.0f, 1328.0f, 1623.0f, 1852.0f, 2377.0f };
//float openspaceY[OPEN_SPACE_POINTS] = { 728.0f, 501.0f, 295.0f, 105.0f, -63.0f, -78.0f, -140.0f, -413.0f, -322.0f, -35.0f, 18.0f, -116.0f, -45.0f, 105.0f, 576.0f, 891.0f, 1238.0f, 1238.0f };
#define OPEN_SPACE_NUM_X ((int)((WORLDLIMITS_REP_XMAX - WORLDLIMITS_REP_XMIN) / OPEN_SPACE_SPACING))
#define OPEN_SPACE_NUM_Y ((int)((WORLDLIMITS_REP_YMAX - WORLDLIMITS_REP_YMIN) / OPEN_SPACE_SPACING))
void CPathFindBuild::RemoveNode(int nodeIndex)
{
// Any links that still point to this node will have to be set to -1.
for (int link = 0; link < NumTempLinks; link++)
{
if (TempLinks[link].m_FileId == TempNodes[nodeIndex].m_FileId)
{
if (TempLinks[link].m_Node1 == TempNodes[nodeIndex].m_NodeIndex)
{
TempLinks[link].m_Node1 = -1;
}
// else if (TempLinks[link].m_Node1 > node)
// {
// TempLinks[link].m_Node1--;
// }
if (TempLinks[link].m_Node2 == TempNodes[nodeIndex].m_NodeIndex)
{
TempLinks[link].m_Node2 = -1;
}
// else if (TempLinks[link].m_Node2 > node)
// {
// TempLinks[link].m_Node2--;
// }
}
}
for (s32 i = nodeIndex; i < NumTempNodes-1; i++)
{
TempNodes[i] = TempNodes[i+1];
}
NumTempNodes--;
}
void CPathFindBuild::RemoveLink(int link)
{
for (s32 i = link; i < NumTempLinks-1; i++)
{
TempLinks[i] = TempLinks[i+1];
}
NumTempLinks--;
}
bool CPathFindBuild::FindNextNodeAndRemoveLinkAndNode(int nodeIndex, int &nextNodeIndex)
{
if(NumTempLinks == 0) return false; // should never happen.
Assert(nodeIndex < NumTempNodes);
int link = 0;
while (link < NumTempLinks && (TempLinks[link].m_FileId != TempNodes[nodeIndex].m_FileId || (TempLinks[link].m_Node1 != TempNodes[nodeIndex].m_NodeIndex && TempLinks[link].m_Node2 != TempNodes[nodeIndex].m_NodeIndex) ) )
{
link++;
}
Assertf(link < NumTempLinks, "Could not find link to connect to open space boundary node. Badly set up open space\n");
if (link >= NumTempLinks) return false; // should never happen.
int nextNodeId;
if (TempLinks[link].m_Node1 == TempNodes[nodeIndex].m_NodeIndex)
{
nextNodeId = TempLinks[link].m_Node2;
}
else
{
nextNodeId = TempLinks[link].m_Node1;
}
RemoveNode(nodeIndex);
RemoveLink(link);
if (nextNodeId < 0) // We have gone round and found the initial node (that was removed)
{
return false;
}
nextNodeIndex = 0;
while (nextNodeIndex < NumTempNodes && TempNodes[nextNodeIndex].m_NodeIndex != nextNodeId)
{
nextNodeIndex++;
}
Assert(nextNodeIndex < NumTempNodes);
// Assert(chainNextNodeIndex != currentNodeIndex);
// if (nextNode >= node)
// {
// nextNode--; // because we removed a node.
// }
return true;
};
void CPathFindBuild::FindOpenSpacePos(int nodeX, int nodeY, float &coorsX, float &coorsY)
{
float stepX = (WORLDLIMITS_REP_XMAX - WORLDLIMITS_REP_XMIN) / OPEN_SPACE_NUM_X;
float stepY = (WORLDLIMITS_REP_YMAX - WORLDLIMITS_REP_YMIN) / OPEN_SPACE_NUM_Y;
coorsX = WORLDLIMITS_REP_XMIN + stepX * (0.5f + nodeX);
coorsY = WORLDLIMITS_REP_YMIN + stepY * (0.5f + nodeY);
Assert(coorsX >= WORLDLIMITS_REP_XMIN && coorsX <= WORLDLIMITS_REP_XMAX);
Assert(coorsY >= WORLDLIMITS_REP_YMIN && coorsY <= WORLDLIMITS_REP_YMAX);
}
#define MAX_OPEN_SPACE_BOUNDARY_POINTS (1000)
void CPathFindBuild::AddOpenSpaceNodes()
{
Assert(TempNodes);
Assert(TempLinks);
if(NumTempNodes == 0)
{
return;
}
Displayf("Buildpaths:AddOpenSpaceNodes\n");
// Allocate some (large) temporary storage for the open space nodes.
bool *pNodes = (bool *)sysMemAllocator::GetMaster().Allocate(sizeof(bool) * OPEN_SPACE_NUM_X * OPEN_SPACE_NUM_Y, 16, MEMTYPE_RESOURCE_VIRTUAL);
Assert(pNodes);
int *pNodesIndex = (int *)sysMemAllocator::GetMaster().Allocate(sizeof(int) * OPEN_SPACE_NUM_X * OPEN_SPACE_NUM_Y, 16, MEMTYPE_RESOURCE_VIRTUAL);
Assert(pNodesIndex);
float *pNodesZ = (float *)sysMemAllocator::GetMaster().Allocate(sizeof(float) * OPEN_SPACE_NUM_X * OPEN_SPACE_NUM_Y, 16, MEMTYPE_RESOURCE_VIRTUAL);
Assert(pNodesZ);
// Clear the grid of potential open space nodes.
for (s32 iX = 0; iX < OPEN_SPACE_NUM_X; iX++)
{
for (s32 iY = 0; iY < OPEN_SPACE_NUM_Y; iY++)
{
pNodes[iX+iY*OPEN_SPACE_NUM_X] = false;
pNodesIndex[iX+iY*OPEN_SPACE_NUM_X] = 0;
pNodesZ[iX+iY*OPEN_SPACE_NUM_X] = 0.0f;
}
}
float openSpaceX[MAX_OPEN_SPACE_BOUNDARY_POINTS];
float openSpaceY[MAX_OPEN_SPACE_BOUNDARY_POINTS];
float openSpaceZ[MAX_OPEN_SPACE_BOUNDARY_POINTS];
bool bNewOpenSpaceSequence[MAX_OPEN_SPACE_BOUNDARY_POINTS];
s32 numOpenSpacePoints = 0;
// First we need to identify the chains of open space boundary nodes and store them in temporary storage.
// We also need to remove the nodes and links that are part of the chain.
s32 currentNodeIndex = 0;
while(numOpenSpacePoints < MAX_OPEN_SPACE_BOUNDARY_POINTS) // Make sure the number of open space points isn't exceeded.
{
// Find the next open space node in the temp nodes.
while (currentNodeIndex < NumTempNodes && !TempNodes[currentNodeIndex].m_bOpenSpace )
{
++currentNodeIndex;
}
if(currentNodeIndex == NumTempNodes)
{
break;
}
// Add the open space point.
bNewOpenSpaceSequence[numOpenSpacePoints] = true;
openSpaceX[numOpenSpacePoints] = TempNodes[currentNodeIndex].m_Coors.x;
openSpaceY[numOpenSpacePoints] = TempNodes[currentNodeIndex].m_Coors.y;
openSpaceZ[numOpenSpacePoints] = TempNodes[currentNodeIndex].m_Coors.z;
++numOpenSpacePoints;
// Make sure the number of open space points isn't exceeded.
if(numOpenSpacePoints == MAX_OPEN_SPACE_BOUNDARY_POINTS)
{
break;
}
// Add the open space points along this chain and remove the links
// and nodes of the chain as we move along.
int chainCurrentNodeIndex = currentNodeIndex;
int chainNextNodeIndex = 0;
while( (numOpenSpacePoints < MAX_OPEN_SPACE_BOUNDARY_POINTS) &&
FindNextNodeAndRemoveLinkAndNode(chainCurrentNodeIndex, chainNextNodeIndex))
{
// Add the open space point of the chain link.
bNewOpenSpaceSequence[numOpenSpacePoints] = false;
openSpaceX[numOpenSpacePoints] = TempNodes[chainNextNodeIndex].m_Coors.x;
openSpaceY[numOpenSpacePoints] = TempNodes[chainNextNodeIndex].m_Coors.y;
openSpaceZ[numOpenSpacePoints] = TempNodes[chainNextNodeIndex].m_Coors.z;
++numOpenSpacePoints;
// Advance along the chain.
chainCurrentNodeIndex = chainNextNodeIndex;
}
// Go to the next node.
++currentNodeIndex;
}
// Now that the OpenSpace nodes have been removed from the list we can use them to create
// new nodes that actually fill in the area.
int numOpenSpacePointsVisited = 0;
while (numOpenSpacePointsVisited < numOpenSpacePoints)
{
int numPointsInSequence = 1;
while (numOpenSpacePointsVisited+numPointsInSequence < numOpenSpacePoints && bNewOpenSpaceSequence[numOpenSpacePointsVisited+numPointsInSequence] == false)
{
numPointsInSequence++;
}
float openSpaceMinX, openSpaceMinY, openSpaceMaxX, openSpaceMaxY;
openSpaceMinX = openSpaceMaxX = openSpaceX[numOpenSpacePointsVisited];
openSpaceMinY = openSpaceMaxY = openSpaceY[numOpenSpacePointsVisited];
for (int n = 1; n < numPointsInSequence; n++)
{
openSpaceMinX = rage::Min(openSpaceMinX, openSpaceX[numOpenSpacePointsVisited+n]);
openSpaceMaxX = rage::Max(openSpaceMaxX, openSpaceX[numOpenSpacePointsVisited+n]);
openSpaceMinY = rage::Min(openSpaceMinY, openSpaceY[numOpenSpacePointsVisited+n]);
openSpaceMaxY = rage::Max(openSpaceMaxY, openSpaceY[numOpenSpacePointsVisited+n]);
}
// Go through all of the potential nodes for all of the open spaces defined and test whether they are inside.
for (s32 iX = 0; iX < OPEN_SPACE_NUM_X; iX++)
{
for (s32 iY = 0; iY < OPEN_SPACE_NUM_Y; iY++)
{
float posX, posY;
FindOpenSpacePos(iX, iY, posX, posY);
if (posX >= openSpaceMinX && posX <= openSpaceMaxX && posY >= openSpaceMinY && posY <= openSpaceMaxY)
{
// Find out whether this point lies within the area specified by calculating the number of intersections with
// the line segments.
int numIntersections = 0;
for (int l = 0; l < numPointsInSequence; l++)
{
float point1X = openSpaceX[numOpenSpacePointsVisited+l];
float point1Y = openSpaceY[numOpenSpacePointsVisited+l];
float point2X = openSpaceX[numOpenSpacePointsVisited + ((l+1)%numPointsInSequence)];
float point2Y = openSpaceY[numOpenSpacePointsVisited + ((l+1)%numPointsInSequence)];
float alongLine = (posX - point1X) / (point2X - point1X);
if (alongLine >= 0.0f && alongLine <= 1.0f)
{
float intersectY = point1Y + alongLine * (point2Y - point1Y);
if (posY < intersectY)
{
numIntersections++;
}
}
}
if (numIntersections & 1)
{
pNodes[iX+iY*OPEN_SPACE_NUM_X] = true;
// To calculate a height value we calculate a contribution from each line segment
float totalContribution = 0.0f, totalHeight = 0.0f;
for (int l = 0; l < numPointsInSequence; l++)
{
float point1X = openSpaceX[numOpenSpacePointsVisited+l];
float point1Y = openSpaceY[numOpenSpacePointsVisited+l];
float point1Z = openSpaceZ[numOpenSpacePointsVisited+l];
float point2X = openSpaceX[numOpenSpacePointsVisited + ((l+1)%numPointsInSequence)];
float point2Y = openSpaceY[numOpenSpacePointsVisited + ((l+1)%numPointsInSequence)];
float point2Z = openSpaceZ[numOpenSpacePointsVisited + ((l+1)%numPointsInSequence)];
Assertf(point1Z >= -100.0f && point1Z <= 500.0f, "Map generated open space node outwidth reasonable z range");
Assertf(point2Z >= -100.0f && point2Z <= 500.0f, "Map generated open space node outwidth reasonable z range");
float dist = fwGeom::DistToLine(Vector3(point1X, point1Y, 0.0f), Vector3(point2X, point2Y, 0.0f), Vector3(posX, posY, 0.0f) );
float alongLine = (posX - point1X) / (point2X - point1X);
alongLine = rage::Max(0.0f, alongLine);
alongLine = rage::Min(1.0f, alongLine);
float height = (1.0f - alongLine) * point1Z + alongLine * point2Z;
float weight = 10.0f / (dist + 0.1f);
totalHeight += height * weight;
totalContribution += weight;
}
pNodesZ[iX+iY*OPEN_SPACE_NUM_X] = totalHeight / totalContribution;
Assertf(pNodesZ[iX+iY*OPEN_SPACE_NUM_X] >= -100.0f && pNodesZ[iX+iY*OPEN_SPACE_NUM_X] <= 500.0f, "Calculated open space node outwidth reasonable z range");
}
}
}
}
numOpenSpacePointsVisited += numPointsInSequence;
}
// Now go through all the nodes that are marked as within an open space.
// Add these nodes to the list and also add the connections to neighbours.
int nodesAdded = 0;
int linksAdded = 0;
for (s32 iX = 0; iX < OPEN_SPACE_NUM_X; iX++)
{
for (s32 iY = 0; iY < OPEN_SPACE_NUM_Y; iY++)
{
if (!pNodes[iX+iY*OPEN_SPACE_NUM_X])
{
continue;
}
float posX, posY;
FindOpenSpacePos(iX, iY, posX, posY);
TempNodes[NumTempNodes].m_bAdditionalTunnelFlag = false;
// Register the new node.
StoreVehicleNode("open_space", nodesAdded, posX, posY, pNodesZ[iX+iY*OPEN_SPACE_NUM_X],
true, false, false, //waternode
SPECIAL_OPEN_SPACE, 1, 1.0f, 0, false, true, false, true, false, false, false, false, false, false, false, false, false);
pNodesIndex[iX+iY*OPEN_SPACE_NUM_X] = nodesAdded;
nodesAdded++;
}
}
// Now we need to go through and register the links between nearby nodes.
for (s32 iX = 0; iX < OPEN_SPACE_NUM_X; iX++)
{
for (s32 iY = 0; iY < OPEN_SPACE_NUM_Y; iY++)
{
if (!pNodes[iX+iY*OPEN_SPACE_NUM_X])
{
continue;
}
// Register the connections with neighbours.
static int neighbourX[16] = { -1, 0, 1, 0, -1, -1, 1, 1, 2, 1, -1, -2, -2, -1, 1, 2 };
static int neighbourY[16] = { 0, -1, 0, 1, -1, 1, -1, 1, 1, 2, 2, 1, -1, -2, -2, -1 };
for (int link = 0; link < 16; link++)
{
int neighbouriX = iX + neighbourX[link];
int neighbouriY = iY + neighbourY[link];
if (neighbouriX < 0 || neighbouriX >= OPEN_SPACE_NUM_X ||
neighbouriY < 0 || neighbouriY >= OPEN_SPACE_NUM_Y)
{
continue;
}
if (!pNodes[neighbouriX+neighbouriY*OPEN_SPACE_NUM_X])
{
continue;
}
if (pNodesIndex[iX+iY*OPEN_SPACE_NUM_X] >= pNodesIndex[neighbouriX+neighbouriY*OPEN_SPACE_NUM_X])
{
continue;
}
StoreVehicleLink("open_space", pNodesIndex[iX+iY*OPEN_SPACE_NUM_X], pNodesIndex[neighbouriX+neighbouriY*OPEN_SPACE_NUM_X],
1, 1, 0.0f, true, true, false, false, false);
linksAdded++;
}
}
}
// Free the (large) temporary storage for the open space nodes.
sysMemAllocator::GetMaster().Free(pNodes);
sysMemAllocator::GetMaster().Free(pNodesIndex);
sysMemAllocator::GetMaster().Free(pNodesZ);
Displayf("AddOpenSpaceNodes added %d nodes and %d links\n", nodesAdded, linksAdded);
}
/////////////////////////////////////////////////////////////////////////////////
//
// FUNCTION : SnapToGround
// PURPOSE : Loads the collision on the map and snaps the nodes to the correct height and also
// samples the tilt. The tilt is used to improve the transition from virtual road surface to
// real road surface.
//
/////////////////////////////////////////////////////////////////////////////////
int CPathFindBuild::QuantizeTiltValue(float angle)
{
float smallestDiff = rage::Abs(saTiltValues[0] - angle);
int smallestDiffIndex = 0;
for (int loop = 1; loop < NUM_NODE_TILT_VALUES; loop++)
{
float diff = rage::Abs(saTiltValues[loop] - angle);
if (diff < smallestDiff)
{
smallestDiffIndex = loop;
smallestDiff = diff;
}
}
return smallestDiffIndex;
}
int CPathFindBuild::GetNextLargerTilt(int i)
{
CompileTimeAssert(NUM_NODE_TILT_VALUES==29);
if(i<0) return -1;
else if(i<14) return i+1;
else if(i==14) return -1;
else if(i==15) return 0;
else if(i<=28) return i-1;
else return -1;
}
void CPathFindBuild::SnapToGround(const bool bDoSnap, const bool bDoCamber, const bool bDoJunctions)
{
Displayf("Buildpaths: SnapToGround\n");
g_procObjMan.Init();
if(bDoSnap)
{
for (int regionX = 0; regionX < PATHFINDMAPSPLIT; regionX++)
{
for (int regionY = 0; regionY < PATHFINDMAPSPLIT; regionY++)
{
int Region = FIND_REGION_INDEX(regionX, regionY);
Displayf("Buildpaths: SnapToGround Region:%i (%i,%i)\n", Region, regionX, regionY);
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
{
continue;
}
for (s32 node = 0; node < apRegions[Region]->NumNodes; node++)
{
CPathNode *pNode = &apRegions[Region]->aNodes[node];
Vector3 posn;
pNode->GetCoors(posn);
// LoadScene crashes, so let's not do it
if (false && !INSTANCE_STORE.GetBoxStreamer().HasLoadedAboutPos(RCC_VEC3V(posn), fwBoxStreamerAsset::MASK_MAPDATA))
{
g_SceneStreamerMgr.LoadScene(posn);
}
CStreaming::LoadAllRequestedObjects();
CPhysics::LoadAboutPosition(RCC_VEC3V(posn));
CStreaming::LoadAllRequestedObjects();
float maxSnap = 1.0f;
if (pNode->m_1.m_specialFunction == SPECIAL_OPEN_SPACE)
{
maxSnap = 10.0f;
}
u32 roomId = 0;
float nearestHeight = FindNearestGroundHeightForBuildPaths(posn, maxSnap, &roomId);
if (rage::Abs(nearestHeight - posn.z) < maxSnap)
{
posn.z = nearestHeight;
}
pNode->SetCoors(posn);
pNode->m_2.m_inTunnel = (roomId == 0 ? 0 : 1);
Assertf(Sign(posn.x) == Sign(pNode->m_coorsX), "Oh dear, pathnode (%.1f, %.1f, %.1f) outside of X range", posn.x, posn.y, posn.z);
Assertf(Sign(posn.y) == Sign(pNode->m_coorsY), "Oh dear, pathnode (%.1f, %.1f, %.1f) outside of Y range", posn.x, posn.y, posn.z);
} //end node iterator
} //end regionY iterator
}
}
if (bDoJunctions)
{
for (int regionX = 0; regionX < PATHFINDMAPSPLIT; regionX++)
{
for (int regionY = 0; regionY < PATHFINDMAPSPLIT; regionY++)
{
int Region = FIND_REGION_INDEX(regionX, regionY);
Displayf("Buildpaths: Virtual Junctions Region:%i (%i,%i)\n", Region, regionX, regionY);
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
{
continue;
}
for (s32 node = 0; node < apRegions[Region]->NumNodes; node++)
{
CPathNode *pNode = &apRegions[Region]->aNodes[node];
//also create a virtual junction now if this is a junction node
if (pNode->IsJunctionNode())
{
//create a cjunction and deploy it
CJunction junction;
junction.Clear();
junction.Deploy(pNode->m_address, true, *this);
//const Vector3 posn = pNode->GetPos();
const Vector3 vJunctionMin = junction.GetJunctionMin();
const Vector3 vJunctionMax = junction.GetJunctionMax();
CStreaming::LoadAllRequestedObjects();
CPhysics::LoadAboutPosition(RCC_VEC3V(vJunctionMin));
CPhysics::LoadAboutPosition(RCC_VEC3V(vJunctionMax));
CStreaming::LoadAllRequestedObjects();
CPathVirtualJunction* pThisJunction = &apRegions[Region]->aVirtualJunctions[apRegions[Region]->NumJunctions];
const int iStartIndexOfHeightSamples = apRegions[Region]->NumHeightSamples;
CVirtualJunctionInterface junctionInterface(pThisJunction, apRegions[Region]);
junctionInterface.InitAndSample(&junction, 2.0f, iStartIndexOfHeightSamples, *this);
//add this junction into the binary map
//region index -> junction index
apRegions[Region]->JunctionMap.JunctionMap.Insert(pNode->m_address.RegionAndIndex(), apRegions[Region]->NumJunctions);
apRegions[Region]->NumJunctions++;
}
}
//sort the binary map for this region
apRegions[Region]->JunctionMap.JunctionMap.FinishInsertion();
}
}
}
//--------------------------------------------------------------------------------------------------
// Now go through the links and store the angle of the road surface to be used by the transition
// to virtual surface code.
if(bDoCamber)
{
for (int regionX = 0; regionX < PATHFINDMAPSPLIT; regionX++)
{
for (int regionY = 0; regionY < PATHFINDMAPSPLIT; regionY++)
{
int Region = FIND_REGION_INDEX(regionX, regionY);
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
{
continue;
}
Displayf("Buildpaths: Camber (Angle Links) Region:%i (%i,%i)\n", Region, regionX, regionY);
for (s32 node = 0; node < apRegions[Region]->NumNodes; node++)
{
CPathNode *pNode = &apRegions[Region]->aNodes[node];
Vector3 posn;
pNode->GetCoors(posn);
u32 roomId = 0;
FindNearestGroundHeightForBuildPaths(posn,10.0f, &roomId);
pNode->m_2.m_inTunnel = (roomId == 0) ? 0 : 1;
for(u32 iLink = 0; iLink < pNode->NumLinks(); iLink++)
{
CPathNodeLink *pLink = &GetNodesLink(pNode, iLink);
CPathNode *pOtherNode = GetNodesLinkedNode(pNode, iLink);
if(!pOtherNode)
{
continue;
}
Vector3 posn2;
pOtherNode->GetCoors(posn2);
Vector3 vLinkCenter = (posn + posn2) * 0.5f;
// Ignore certain nodes when it comes to analyzing tilt calculations
bool bIgnoreSpecialLinkTiltErrors = false;
bIgnoreSpecialLinkTiltErrors = bIgnoreSpecialLinkTiltErrors || (pNode->m_1.m_specialFunction == SPECIAL_PED_ASSISTED_MOVEMENT) || (pOtherNode->m_1.m_specialFunction == SPECIAL_PED_ASSISTED_MOVEMENT);
bIgnoreSpecialLinkTiltErrors = bIgnoreSpecialLinkTiltErrors || (pNode->m_1.m_specialFunction == SPECIAL_PED_DRIVEWAY_CROSSING) || (pOtherNode->m_1.m_specialFunction == SPECIAL_PED_DRIVEWAY_CROSSING);
bIgnoreSpecialLinkTiltErrors = bIgnoreSpecialLinkTiltErrors || (pNode->m_1.m_specialFunction == SPECIAL_PED_CROSSING) || (pOtherNode->m_1.m_specialFunction == SPECIAL_PED_CROSSING);
bIgnoreSpecialLinkTiltErrors = bIgnoreSpecialLinkTiltErrors || pNode->m_2.m_waterNode || pOtherNode->m_2.m_waterNode;
static bool bIgnoreNoNav = true;
bIgnoreSpecialLinkTiltErrors = bIgnoreSpecialLinkTiltErrors || (bIgnoreNoNav && pLink->m_1.m_bDontUseForNavigation);
// Other links should be considered "irregular" in the sense that they are likely to go off normal roads (i.e. shortcuts)
bool bLinkIsIrregularType = pLink->m_1.m_bShortCut;
Vector3 vPerpDir = Vector3(posn2.y - posn.y, posn.x - posn2.x, 0.0f);
vPerpDir.Normalize();
// Width of link for camber determination
float fCamberProbesHorizontalOffset;
float fRoadWidthLeft, fRoadWidthRight;
const bool bAllLanesThoughCentre = (pLink->m_1.m_Width == ALL_LANES_THROUGH_CENTRE_FLAG_VAL);
FindRoadBoundaries(
pLink->m_1.m_LanesToOtherNode,
pLink->m_1.m_LanesFromOtherNode,
static_cast<float>(pLink->m_1.m_Width),
pLink->m_1.m_NarrowRoad,
bAllLanesThoughCentre,
fRoadWidthLeft, fRoadWidthRight);
fCamberProbesHorizontalOffset = Max(fRoadWidthRight-ROAD_EDGE_BUFFER_FOR_CAMBER_CALCULATION,0.1f);
// Find the tilt
static bool bUseComplex = true;
int iTilt = 0, iFalloff = 0;
if(!bUseComplex)
{
FindTiltForLink_Simple(vLinkCenter,vPerpDir,fCamberProbesHorizontalOffset,iTilt);
}
else
{
float fCentreMedianWidth = bAllLanesThoughCentre ? 0.0f : ((float)(pLink->m_1.m_Width)/2.0f);
FindTiltForLink_Complex(vLinkCenter,vPerpDir,fCamberProbesHorizontalOffset,fCentreMedianWidth,!bIgnoreSpecialLinkTiltErrors,bLinkIsIrregularType,iTilt,iFalloff);
}
pLink->m_1.m_Tilt = iTilt;
pLink->m_1.m_TiltFalloff = iFalloff;
}
}
}
}
}
g_procObjMan.Shutdown();
}
void CPathFindBuild::FindTiltForLink_Simple(const Vector3 & vLinkCenter, const Vector3 & vLinkPerp, float fWidth, int & iTiltOut)
{
CStreaming::LoadAllRequestedObjects();
CPhysics::LoadAboutPosition(RCC_VEC3V(vLinkCenter));
CStreaming::LoadAllRequestedObjects();
// Find heights
const Vector3 vSample1 = vLinkCenter + fWidth * vLinkPerp;
const Vector3 vSample2 = vLinkCenter;
float fHeight1 = FindNearestGroundHeightForBuildPaths(vSample1, 10.0f, NULL);
float fHeight2 = FindNearestGroundHeightForBuildPaths(vSample2, 10.0f, NULL);
float fAngle = rage::Atan2f( (fHeight1 - fHeight2) , fWidth );
iTiltOut = QuantizeTiltValue(fAngle);
}
float CPathFindBuild::CalcSumErrorSqForLine(int iNumSamples, float * fZSamples, float fWidth, float fSlope)
{
float fSumErrorSq = 0.0f;
for(int iSample=0; iSample<iNumSamples; iSample++)
{
const float fX = (fWidth * iSample)/(iNumSamples-1);
const float fZFromLine = fX * fSlope;
const float fError = fZFromLine - fZSamples[iSample];
fSumErrorSq += square(fError);
}
return sqrtf(fSumErrorSq);
}
float CPathFindBuild::ZForLineWithFallof(float fX, float fWidth, float fSlope, float fFalloffWidth, float fFalloffHeight)
{
const float fZFromLine = fX * fSlope;
const float fXDistFromEdge = fWidth - fX;
const float fZFromFalloff = - fFalloffHeight * Max( 1.0f - fXDistFromEdge / fFalloffWidth, 0.0f);
return fZFromLine + fZFromFalloff;
}
float CPathFindBuild::CalcSumErrorSqForLineWithFalloff(int iNumSamples, float * fZSamples, float fWidth, float fSlope, float fFalloffWidth, float fFalloffHeight)
{
float fSumErrorSq = 0.0f;
for(int iSample=0; iSample<iNumSamples; iSample++)
{
const float fX = (fWidth * iSample)/(iNumSamples-1);
const float fZ = ZForLineWithFallof(fX,fWidth,fSlope,fFalloffWidth,fFalloffHeight);
const float fError = fZ - fZSamples[iSample];
fSumErrorSq += square(fError);
}
return sqrtf(fSumErrorSq);
}
void CPathFindBuild::CalcMaxAndAverageErrorForLineWithFalloff(int iNumSamples, float * fZSamples, float fWidth, float fSlope, float fFalloffWidth, float fFalloffHeight, float & fErrorMaxOut, float & fErrorAveOut)
{
float fErrorSum = 0.0f;
fErrorMaxOut = 0.0f;
for(int iSample=0; iSample<iNumSamples; iSample++)
{
const float fX = (fWidth * iSample)/(iNumSamples-1);
const float fZ = ZForLineWithFallof(fX,fWidth,fSlope,fFalloffWidth,fFalloffHeight);
const float fErrorAbs = fabsf(fZ - fZSamples[iSample]);
fErrorSum += fErrorAbs;
if(fErrorAbs > fErrorMaxOut)
{
fErrorMaxOut = fErrorAbs;
}
}
fErrorAveOut = fErrorSum / iNumSamples;
}
#if !__NO_OUTPUT
void CPathFindBuild::PrintMaterialsOnLink(int kNumSamples, phMaterialMgr::Id * pMaterialIds)
{
Printf(" - Materials: ");
for(int i=0; i<kNumSamples; i++)
{
const char * pMtlName = PGTAMATERIALMGR->GetMaterial(pMaterialIds[i]).GetName();
u32 mtlNameHashed = atStringHash(pMtlName);
bool bIsIrregularMaterial = IsMaterialIrregularSurface(pMaterialIds[i]);
Printf("(%s,0x%x,%d) ",pMtlName,mtlNameHashed,bIsIrregularMaterial);
}
Displayf("");
}
#endif
bool CPathFindBuild::IsMaterialIrregularSurface(phMaterialMgr::Id mtlId)
{
static u32 mtlsWithIrregularSurface[] =
{
0x09424a9f, // "dirt_track"
0x72623394, // "grass"
0x79c6b603, // "gravel_small"
0xd5fa0b8b, // "mud_hard"
0x8eea76dd, // "sand_compact"
0x97fc89ba, // "sand_loose"
0xb6b0644e, // "sand_track"
};
int kNumMaterialsToIgnore = NELEM(mtlsWithIrregularSurface);
const char * pMtlName = PGTAMATERIALMGR->GetMaterial(mtlId).GetName();
u32 mtlNameHashed = atStringHash(pMtlName);
for(int i=0; i<kNumMaterialsToIgnore; i++)
{
if(mtlNameHashed == mtlsWithIrregularSurface[i])
{
return true;
}
}
return false;
}
bool CPathFindBuild::IsLinkAllIrregularMaterials(int kNumSamples, phMaterialMgr::Id * pMaterialIds)
{
bool bAllIrregularMaterials = true;
for(int i=0; i<kNumSamples; i++)
{
bAllIrregularMaterials = bAllIrregularMaterials && IsMaterialIrregularSurface(pMaterialIds[i]);
}
return bAllIrregularMaterials;
}
void CPathFindBuild::FindTiltForLink_Complex(const Vector3 & vLinkCenter, const Vector3 & vLinkPerp, float fTotalWidth, float fCentreWidth, bool bReportIfInaccurate, bool bIsIrregularLinkType, int & iTiltOut, int & iFalloffOut)
{
CStreaming::LoadAllRequestedObjects();
CPhysics::LoadAboutPosition(RCC_VEC3V(vLinkCenter));
CStreaming::LoadAllRequestedObjects();
// Sample height at several points along the perpendicular
Vector3 vStart = vLinkCenter;
Vector3 vEnd = vLinkCenter + fTotalWidth * vLinkPerp;
const int kNumSamples = 5;
Assert(kNumSamples>1);
float fZ[kNumSamples];
phMaterialMgr::Id materialHits[kNumSamples];
// Possibly skip probes on the central median. If there is a median and the road is wider than the indicated median, then
// consider samples within the median to be at the height of the link center. Avoids spurious samples at the height of the median.
static bool bEnableMedianSkipping = true;
const float fNonMedianBuffer = 1.5f; // Amount the width must exceed the central median width in order to trigger skipping probes on the central median.
bool bSkipSamplesWithinMedian = bEnableMedianSkipping && (fTotalWidth - fCentreWidth > fNonMedianBuffer);
for(int iSample=0; iSample<kNumSamples; iSample++)
{
Vector3 vSample;
const float fT = ((float)iSample)/(kNumSamples-1);
vSample.Lerp(fT,vStart,vEnd);
float fDist = fT * fTotalWidth;
if(bSkipSamplesWithinMedian && fDist < fCentreWidth)
{
fZ[iSample] = 0.0f;
}
else
{
static float fRange = 5.0f;
fZ[iSample] = FindNearestGroundHeightForBuildPaths(vSample,fRange,NULL,materialHits+iSample);
fZ[iSample] -= vLinkCenter.z;
}
}
// Calc the best quantized angle from best floating-point angle
int iTiltBestLine = -1;
float fErrorSqBestLine = FLT_MAX;
{
// Simplified version of slope/intercept regression. In this case the intercept is fixed (to be the link point).
float fSumXX = 0.0f;
float fSumXZ = 0.0f;
for(int iSample=0; iSample<kNumSamples; iSample++)
{
const float fX = fTotalWidth * ((float)iSample)/(kNumSamples-1);
fSumXX += square(fX);
const float fXZ = fX * fZ[iSample];
fSumXZ += fXZ;
}
const float fSlope = fSumXZ / fSumXX;
const float fAngle = atanf(fSlope);
iTiltBestLine = QuantizeTiltValue(fAngle);
fErrorSqBestLine = CalcSumErrorSqForLine(kNumSamples,fZ,fTotalWidth,fSlope);
}
iTiltOut = iTiltBestLine;
iFalloffOut = 0;
const float fFalloffWidth = LINK_TILT_FALLOFF_WIDTH;
const float fFalloffHeight = LINK_TILT_FALLOFF_HEIGHT;
// Check higher tilts with a falloff
int iTiltBestLineWithFalloff = iTiltBestLine;
float fErrorSqBestLineWithFalloff = FLT_MAX;
{
const int kNumHigherTiltsToTest = 2;
for(int i=0, iTilt=iTiltBestLine; i<kNumHigherTiltsToTest && iTilt>=0; i++)
{
float fSlope = tanf(saTiltValues[iTilt]);
float fErrorSq = CalcSumErrorSqForLineWithFalloff(kNumSamples,fZ,fTotalWidth,fSlope,fFalloffWidth,fFalloffHeight);
if(fErrorSq < fErrorSqBestLineWithFalloff)
{
iTiltBestLineWithFalloff = iTilt;
fErrorSqBestLineWithFalloff = fErrorSq;
}
iTilt = GetNextLargerTilt(iTilt);
}
}
static bool bUseFalloff = true;
if(bUseFalloff && fErrorSqBestLine > fErrorSqBestLineWithFalloff)
{
//Displayf("(falloff line better) %d %d, at %0.2f, %0.2f",iTiltBestLine,iTiltBestLineWithFalloff);
//for(int iSample=0; iSample<kNumSamples; iSample++)
//{
// const float fX = fWidth * ((float)iSample)/(kNumSamples-1);
// Displayf("%d %0.3f %0.3f",iSample,fX,fZ[iSample]);
//}
iTiltOut = iTiltBestLineWithFalloff;
iFalloffOut = 1;
}
if(bReportIfInaccurate)
{
float fSlope = tanf(saTiltValues[iTiltOut]);
float fErrorMax, fErrorAve;
CalcMaxAndAverageErrorForLineWithFalloff(kNumSamples,fZ,fTotalWidth,fSlope,fFalloffWidth,fFalloffHeight,fErrorMax,fErrorAve);
// If a link exceeds these error thresholds, then report it.
// For now, start with large error values and only decrease these after those bugs are fixed. Later these could be command-line parameters.
static float fMaxErrorThresholdNormal = 0.50f;
static float fAverageErrorThresholdNormal = 0.25f;
static float fMaxErrorThresholdIrregular = 0.50f;
static float fAverageErrorThresholdIrregular = 0.25f;
// "Irregular" links are shortcuts (and other types of links?) that are likely to travel off of normal roads, or
// links that are entirely on off-road materials (like rock, dirt, etc.). Links that are partially on off-road materials
// are not considered irregular since it is possible they are too wide and going from on-road to off-road.
bool bIsIrregular = bIsIrregularLinkType || IsLinkAllIrregularMaterials(kNumSamples,materialHits);
float fMaxErrorThreshold = bIsIrregular ? fMaxErrorThresholdIrregular : fMaxErrorThresholdNormal;
float fAverageErrorThreshold = bIsIrregular ? fAverageErrorThresholdIrregular : fAverageErrorThresholdNormal;
if(fErrorMax > fMaxErrorThreshold || fErrorAve > fAverageErrorThreshold)
{
Printf("Link at (%0.2f %0.2f %0.2f) with high error - %0.2f max, %0.2f average. ",vLinkCenter.x,vLinkCenter.y,vLinkCenter.z,fErrorMax,fErrorAve);
OUTPUT_ONLY(PrintMaterialsOnLink(kNumSamples,materialHits);)
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CountFloodFillGroups
// PURPOSE : The idea here is to do a flood fill and count the number of groups
// we encounter this way. Hopefully we would get one group. If we get
// more than that the grid is not connected and the artists have made
// a mistake. We can then display a little coloured flag for each
// group so that we can find the bit where the connection is broken.
/////////////////////////////////////////////////////////////////////////////////
void CPathFindBuild::CountFloodFillGroups()
{
Displayf("Buildpaths:CountFloodFillGroups\n");
CPathNode *pCurrNode, *pCandidateNode;
CPathNode *pToDoList, *pSearchNode;
u32 link;
s32 StartNode=0, EndNode=0, Node;
CNodeAddress CandidateNode;
u16 CurrentColour;
for (s32 Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodesCarNodes)
{
continue;
}
StartNode = 0;
EndNode = apRegions[Region]->NumNodesCarNodes;
// Clear the nodes
for (Node = StartNode; Node < EndNode; Node++)
{
(apRegions[Region]->aNodes)[Node].m_1.m_group = 0;
}
}
CurrentColour = 0;
while (1) // Keep going through new groups
{
CurrentColour++; // New m_1.m_group
if (CurrentColour > 15000) // Too many groups. Something's seriously wrong
{
Assertf(0, "Too many groups (network restart points?)");
}
// Find a node to begin with (the first not coloured node)
s32 SearchRegion = 0;
s32 SearchNode;
while (1)
{
if(apRegions[SearchRegion])
{
StartNode = 0;
EndNode = apRegions[SearchRegion]->NumNodesCarNodes;
for (SearchNode = StartNode; SearchNode < EndNode; SearchNode++)
{
pSearchNode = &(apRegions[SearchRegion]->aNodes)[SearchNode];
if (pSearchNode->m_1.m_group == 0 &&
pSearchNode->m_2.m_noGps == 0 && // Uncomment if we got no space left in m_group
pSearchNode->m_2.m_waterNode == 0 && // Only used by AI and there aint lots of these nodes anyway
pSearchNode->m_1.m_specialFunction != SPECIAL_HIDING_NODE &&
pSearchNode->m_1.m_specialFunction != SPECIAL_PED_CROSSING &&
pSearchNode->m_1.m_specialFunction != SPECIAL_PED_DRIVEWAY_CROSSING &&
pSearchNode->m_1.m_specialFunction != SPECIAL_PED_ASSISTED_MOVEMENT)
{
// We found a non coloured node. Use this to flood fill the area.
Vector3 nodePos(0.0f, 0.0f, 0.0f);
pSearchNode->GetCoors(nodePos);
Displayf("New island search from node: %f %f %f", nodePos.x, nodePos.y, nodePos.z);
goto ___FoundANodeToGrowFrom;
}
}
}
SearchRegion++;
if (SearchRegion >= PATHFINDREGIONS)
{
Displayf("Number of groups:%d\n", CurrentColour);
return; // No non-coloured nodes left. Our work is done here.
}
}
___FoundANodeToGrowFrom:
// Put the start node on the list
pToDoList = pSearchNode;
pToDoList->SetNext(NULL);
pSearchNode->m_1.m_group = (u8)CurrentColour;
if(pToDoList->NumLinks() == 0)
{ // Print out isolated nodes
#if __DEV
Vector3 nodePos(0.0f, 0.0f, 0.0f);
pToDoList->GetCoors(nodePos);
Displayf("Single car node: %f %f %f\n", nodePos.x, nodePos.y, nodePos.z);
#endif // __DEV
}
while (pToDoList) // while still nodes left
{
// Go through the list for this Hash value.
pCurrNode = pToDoList;
pToDoList = pToDoList->GetNext();
// Process the neighbours
for(link = 0; link < pCurrNode->NumLinks(); link++)
{
CandidateNode = GetNodesLinkedNodeAddr(pCurrNode, link);
pCandidateNode = FindNodePointer(CandidateNode);
if (pCandidateNode->m_1.m_group == 0 && !pCandidateNode->m_2.m_noGps)
{
pCandidateNode->m_1.m_group =(u8)CurrentColour; // laneCol this bloke in
// Since group is only 5 bits; 32 turns into 0 locking up the algorithm. This is bad.
if (!pCandidateNode->m_1.m_group) pCandidateNode->m_1.m_group = 7; // Fudge this for now (It's only a test anyway)
pCandidateNode->SetNext(pToDoList);
pToDoList = pCandidateNode;
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CheckGrid
// PURPOSE : Any check we do to make sure the grid is allright should be put here.
// Among other things we look for isolated ped nodes.
/////////////////////////////////////////////////////////////////////////////////
void CPathFindBuild::CheckGrid(void)
{
Displayf("Buildpaths:CheckGrid\n");
s32 IsolatedNodes, Node, NumLinks, Node1, Node2, Region;
bool bAssertAtEnd = false;
// Go through the pedestrian nodes and count the ones without neighbours.
// This should not happen.
IsolatedNodes = 0;
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region])
{
continue;
}
for (Node = 0; Node < apRegions[Region]->NumNodesCarNodes; Node++)
{
NumLinks = apRegions[Region]->aNodes[Node].NumLinks();
if (NumLinks == 0)
{
IsolatedNodes++;
// Also place a marker
#if __DEV
Vector3 nodePos(0.0f, 0.0f, 0.0f);
apRegions[Region]->aNodes[Node].GetCoors(nodePos);
Displayf("Isolated node: %f %f %f\n", nodePos.x, nodePos.y, nodePos.z );
#endif // __DEV
//Vector3 Temp = apRegions[Region]->aNodes[Node].GetCoors();
}
}
}
if (IsolatedNodes)
{
Displayf("***************** BAD\n");
Displayf("***************** BAD\n");
Displayf("***************** There are %d isolated path nodes.\n", IsolatedNodes);
}
// We don't want any nodes that have a lanes coming in but no lanes going out.
// They will create pile ups and are usually a sign of badly placed nodes.
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region])
{
continue;
}
for (Node = 0; Node < apRegions[Region]->NumNodesCarNodes; Node++)
{
CNodeAddress TestNode;
TestNode.Set(Region, Node);
NumLinks = apRegions[Region]->aNodes[Node].NumLinks();
if (NumLinks != 0)
{
if (apRegions[Region]->aNodes[Node].m_1.m_specialFunction != SPECIAL_PARKING_PARALLEL &&
apRegions[Region]->aNodes[Node].m_1.m_specialFunction != SPECIAL_PARKING_PERPENDICULAR)
{
s32 nodeStartIndexOfLinks = apRegions[Region]->aNodes[Node].m_startIndexOfLinks;
bool bLanesIn = false;
bool bLanesOut = false;
for (s32 N = 0; N < NumLinks; N++)
{
CPathNodeLink *pAdjLink = &apRegions[Region]->aLinks[nodeStartIndexOfLinks+N];
if (pAdjLink->m_1.m_LanesFromOtherNode) bLanesIn = true;
if (pAdjLink->m_1.m_LanesToOtherNode) bLanesOut = true;
}
if (bLanesIn && !bLanesOut)
{
Vector3 nodePos(0.0f, 0.0f, 0.0f);
apRegions[Region]->aNodes[Node].GetCoors(nodePos);
Displayf("Node with only lanes coming in: %f %f %f\n", nodePos.x, nodePos.y, nodePos.z );
}
}
}
}
}
// We will go through the nodes and make sure they're all different.
// Nodes with identical coordinates are obviously a mistake.
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region])
{
continue;
}
for (Node1 = 0; Node1 < (apRegions[Region]->NumNodesCarNodes-1); Node1++)
{
for (Node2 = Node1+1; Node2 < apRegions[Region]->NumNodesCarNodes; Node2++)
{
Vector3 node1Coors;
Vector3 node2Coors;
apRegions[Region]->aNodes[Node1].GetCoors(node1Coors);
apRegions[Region]->aNodes[Node2].GetCoors(node2Coors);
if (node1Coors == node2Coors/*apRegions[Region]->aNodes[Node1].GetCoorsX() == apRegions[Region]->aNodes[Node2].GetCoorsX() && apRegions[Region]->aNodes[Node1].GetCoorsY() == apRegions[Region]->aNodes[Node2].GetCoorsY() && apRegions[Region]->aNodes[Node1].GetCoors().z == apRegions[Region]->aNodes[Node2].GetCoors().z*/)
{
Displayf("ALERT: 2 car nodes have identical coordinates\n");
Displayf("Coordinates:%f %f %f.\n", node1Coors.x, node1Coors.y, node1Coors.z);
bAssertAtEnd = true;
}
}
}
}
// Also for ped nodes
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region] || !apRegions[Region]->NumNodes)
{
continue;
}
for (Node1 = apRegions[Region]->NumNodesCarNodes; Node1 < (apRegions[Region]->NumNodes-1); Node1++)
{
for (Node2 = Node1+1; Node2 < apRegions[Region]->NumNodes; Node2++)
{
Vector3 node1Coors;
Vector3 node2Coors;
apRegions[Region]->aNodes[Node1].GetCoors(node1Coors);
apRegions[Region]->aNodes[Node2].GetCoors(node2Coors);
if (node1Coors == node2Coors/*apRegions[Region]->aNodes[Node1].GetCoors().x == apRegions[Region]->aNodes[Node2].GetCoors().x && apRegions[Region]->aNodes[Node1].GetCoors().y == apRegions[Region]->aNodes[Node2].GetCoors().y && apRegions[Region]->aNodes[Node1].GetCoors().z == apRegions[Region]->aNodes[Node2].GetCoors().z*/)
{
Displayf("ALERT: 2 ped nodes have identical coordinates\n");
#if __DEV
Vector3 nodePos(0.0f, 0.0f, 0.0f);
apRegions[Region]->aNodes[Node1].GetCoors(nodePos);
Displayf("Coordinates:%f %f %f.\n", nodePos.x, nodePos.y, nodePos.z );
#endif // __DEV
bAssertAtEnd = true;
}
}
}
}
if (bAssertAtEnd) { Assert(0); }
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CalcRoadDensity
// PURPOSE : Calculates the average density of roads in a certain area. The idea
// is that areas with more roads in them get more cars created. This
// way a country lane doesn't get flooded with cars and a motorway
// would get a reasonable number of cars.
// The result should be about 1.0f for an average road density
/////////////////////////////////////////////////////////////////////////////////
float CPathFindBuild::CalcRoadDensity(float TestX, float TestY)
{
float DensityCount = 0;
s32 Node;
u32 Link;
s32 Region;
s32 DbgNodesInArea = 0;
float Length, DeltaX, DeltaY;
CPathNode *pNode, *pOtherNode;
CNodeAddress OtherNode;
#define AREA_OF_INTEREST (80.0f) // in meters in +X, -X, +Y and -Y direction.
#define AVERAGE_VALUE (2500.0f) // What is an average value that we could expect
// Go through all the nodes in the world and count the ones that are within our
// area of interest.
for (Region = 0; Region < PATHFINDREGIONS; Region++)
{
if(!apRegions[Region])
{
continue;
}
if (apRegions[Region] && apRegions[Region]->aNodes) // Make sure this Region is loaded
{
for (Node = 0; Node < apRegions[Region]->NumNodesCarNodes; Node++)
{
pNode = &apRegions[Region]->aNodes[Node];
Vector2 nodePos(0.0f, 0.0f);
pNode->GetCoors2(nodePos);
if ( rage::Abs(nodePos.x - TestX) < AREA_OF_INTEREST &&
rage::Abs(nodePos.y - TestY) < AREA_OF_INTEREST)
{ // This node needs to be counted.
DbgNodesInArea++;
if(pNode->NumLinks()) // No nodes without mates please(bad level)
{
// Go through the links (to count the number of lanes
for(Link = 0; Link < pNode->NumLinks(); Link++)
{
CPathNodeLink *pLink = &GetNodesLink(pNode, Link);
OtherNode = pLink->m_OtherNode;
if (IsRegionLoaded(OtherNode))
{
pOtherNode = FindNodePointer(OtherNode);
Vector2 otherNodePos(0.0f, 0.0f);
pOtherNode->GetCoors2(otherNodePos);
// Calculate the length of the link.
DeltaX = nodePos.x - otherNodePos.x;
DeltaY = nodePos.y - otherNodePos.y;
Length = rage::Sqrtf(DeltaX*DeltaX + DeltaY*DeltaY);
DensityCount += Length * pLink->m_1.m_LanesFromOtherNode;
DensityCount += Length * pLink->m_1.m_LanesToOtherNode;
}
}
}
}
}
}
}
return(DensityCount / AVERAGE_VALUE);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : StoreVehicleNode
// PURPOSE : Stores one node to be used by vehicles.
// pFileName String of the name of the file it is read from.
// NodeIndex is the index of the node within the file.
// X, Y, Z coordinates in world space in meters.
// bSwitchedOff. If this is true cars shouldn't go here.
// bRoadBlock. This is a good place for a roadblock.
// bWaterNode. This node is for boats and stuff.
// bUnderBridge. This node is under a bridge so big boats shouldn't go here.
// SpecialFunction. Toll booth, parking place etc
// Speed (0 = slow, 1 = normal, 2 = fast)
// Width. Size of the space between opposite lanes in meters
/////////////////////////////////////////////////////////////////////////////////
void CPathFindBuild::StoreVehicleNode(const char *pFileName, s32 NodeIndex, float X, float Y, float Z,
bool bSwitchedOff, bool bLowBridge, bool bWaterNode,
u32 SpecialFunction, s32 Speed, float Density, u32 StreetNameHash,
bool bHighway, bool bNoGps, bool bAdditionalTunnelFlag, bool bOpenSpace,
bool UNUSED_PARAM(usesHighResCoors), bool bLeftOnly, bool bNoLeftTurns,
bool bNoRightTurns, bool bOffroad, bool bNoBigVehicles,
bool bIndicateKeepLeft, bool bIndicateKeepRight, bool bIsSlipNode)
{
if (!TempNodes)
{ // If we're not looking to rebuild the path data we don't have to store this
return;
}
Assert(SpecialFunction < SPECIAL_USE_LAST);
PathBuildFatalAssertf(NumTempNodes < PF_TEMPNODES, "");
TempNodes[NumTempNodes].m_Coors.x = X;
TempNodes[NumTempNodes].m_Coors.y = Y;
TempNodes[NumTempNodes].m_Coors.z = Z;
TempNodes[NumTempNodes].m_NodeIndex = NodeIndex;
TempNodes[NumTempNodes].m_FileId = atStringHash(pFileName);
TempNodes[NumTempNodes].m_StreetNameHash = StreetNameHash;
TempNodes[NumTempNodes].m_bSwitchedOff = bSwitchedOff;
TempNodes[NumTempNodes].m_SpecialFunction = (u8)(SpecialFunction);
TempNodes[NumTempNodes].m_Speed = (u8)(Speed);
TempNodes[NumTempNodes].m_bNoGps = bNoGps;
TempNodes[NumTempNodes].m_bAdditionalTunnelFlag = bAdditionalTunnelFlag;
TempNodes[NumTempNodes].m_bWaterNode = bWaterNode;
TempNodes[NumTempNodes].m_bOpenSpace = bOpenSpace;
TempNodes[NumTempNodes].m_bLeftOnly = bLeftOnly;
TempNodes[NumTempNodes].m_bNoLeftTurns = bNoLeftTurns;
TempNodes[NumTempNodes].m_bNoRightTurns = bNoRightTurns;
TempNodes[NumTempNodes].m_bOffroad = bOffroad;
TempNodes[NumTempNodes].m_bNoBigVehicles = bNoBigVehicles;
TempNodes[NumTempNodes].m_bIndicateKeepLeft = bIndicateKeepLeft;
TempNodes[NumTempNodes].m_bIndicateKeepRight = bIndicateKeepRight;
TempNodes[NumTempNodes].m_bSlipNode = bIsSlipNode;
if (bWaterNode)
{
TempNodes[NumTempNodes].m_bHighwayOrLowBridge = bLowBridge;
}
else
{
TempNodes[NumTempNodes].m_bHighwayOrLowBridge = bHighway;
}
TempNodes[NumTempNodes].m_Density = (u8)(rage::round(rage::Min(Density, 1.0f) * 15.0f));
NumTempNodes++;
PathBuildFatalAssertf(NumTempNodes < PF_TEMPNODES,"");
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : StoreVehicleLink
// PURPOSE : Stores a link between two vehicle nodes.
// pFileName String of the name of the file it is read from.
// Node1 and Node2 are the 2 nodes that the link connects.
// Lanes1To2 The number of lanes that go from Node1 to Node2
// Lanes2To1 The number of lanes that go the other way
// Width. Size of the space between opposite lanes in meters
/////////////////////////////////////////////////////////////////////////////////
void CPathFindBuild::StoreVehicleLink( const char *pFileName, s32 Node1, s32 Node2,
s32 Lanes1To2, s32 Lanes2To1, float Width,
bool bNarrowRoad, bool bGpsCanGoBothWays, bool bShortcut,
bool bIgnoreForNav, bool bBlockIfNoLanes )
{
Assertf(Node1 <= 100000 && Node2 <= 100000, "link with corrupt values (%d, %d) in %s", Node1, Node2, pFileName);
// Find the nodes that this link connects.
// The following is just debug code to detect links between the same node.
#if __DEV
if (Node1 == Node2)
{
// Try to find the node that is the problem (hopefully it is already read in)
u32 FileId = atStringHash(pFileName);
s32 TestNode = 0;
s32 NodeToFind = Node1;
char debugText[100];
sprintf(debugText, "There is a link set up between 1 node and itself (%s)", pFileName);
while (TestNode < NumTempNodes && NodeToFind >= 0)
{
if (FileId == TempNodes[TestNode].m_FileId)
{
NodeToFind--;
if (NodeToFind < 0)
{ // We found our node.
sprintf(debugText, " %f %f %f", TempNodes[NumTempNodes].m_Coors.x, TempNodes[NumTempNodes].m_Coors.y, TempNodes[NumTempNodes].m_Coors.z);
}
}
TestNode++;
}
Assertf(0, "%s", debugText);
}
#endif
if (!TempLinks)
{ // If we're not looking to rebuild the path data we don't have to store this
return;
}
PathBuildFatalAssertf(NumTempLinks < PF_TEMPLINKS, "");
// This can be a link between car nodes or ped nodes.
TempLinks[NumTempLinks].m_FileId = atStringHash(pFileName);
TempLinks[NumTempLinks].m_Node1 = Node1;
TempLinks[NumTempLinks].m_Node2 = Node2;
TempLinks[NumTempLinks].m_Lanes1To2 = (u8)(Lanes1To2);
TempLinks[NumTempLinks].m_Lanes2To1 = (u8)(Lanes2To1);
TempLinks[NumTempLinks].m_Width = (u8)rage::Min(Width, 14.0f);
if (Width < 0.0f)
{
TempLinks[NumTempLinks].m_Width = ALL_LANES_THROUGH_CENTRE_FLAG_VAL;
}
TempLinks[NumTempLinks].m_NarrowRoad = bNarrowRoad;
TempLinks[NumTempLinks].m_bGpsCanGoBothWays = bGpsCanGoBothWays;
TempLinks[NumTempLinks].m_bShortCut = bShortcut;
TempLinks[NumTempLinks].m_bDontUseForNavigation = bIgnoreForNav;
TempLinks[NumTempLinks].m_bBlockIfNoLanes = bBlockIfNoLanes;
NumTempLinks++;
}
/*
// JAY, not sure if this is required.
void CPathFindBuild::CreateRegionsForPathBuild()
{
// CFileMgr::SetDir("platform:/data/paths/");
{
// Allocate some memory to play with so that we can build the files as required by the streaming.
for (s32 Region = 0; Region < PATHFINDREGIONS; Region++)
{
// DEBUG!! -AC, MEMLEAK: fix?
Assert(!apRegions[Region]);
apRegions[Region] = rage_new CPathRegion();
Assert(apRegions[Region]);
// END DEBUG!!
//apRegions[Region]->aNodes = (CPathNode *) GtaMalloc(MAXNODESPERREGION * sizeof(CPathNode));
apRegions[Region]->aNodes = (CPathNode *) sysMemAllocator::GetMaster().Allocate(MAXNODESPERREGION * sizeof(CPathNode), 16, MEMTYPE_RESOURCE_VIRTUAL);
Assert(apRegions[Region]->aNodes);
memset(apRegions[Region]->aNodes, 0, MAXNODESPERREGION * sizeof(CPathNode));
for (s32 T = 0; T < MAXNODESPERREGION; T++)
{
apRegions[Region]->aNodes[T].m_distanceToTarget = PF_VERYLARGENUMBER;
}
//apRegions[Region]->aLinks = (CPathNodeLink *) GtaMalloc((MAXLINKSPREREGION + (MAX_NUM_DYNAMIC_LINKS * PF_MAXNUMNODESPEROBJECT)) * sizeof(CPathNodeLink));
apRegions[Region]->aLinks = (CPathNodeLink *) sysMemAllocator::GetMaster().Allocate( (MAXLINKSPREREGION) * sizeof(CPathNodeLink) , 16, MEMTYPE_RESOURCE_VIRTUAL);
Assert(apRegions[Region]->aLinks);
// Set all the adjacent nodes to emptyAddr. This is important because the dynamic adjacent nodes(at the end)
// should be set to emptyAddr so that we know they are not being used yet.
// Make sure the extra adjacent nodes(for dynamic links)are set to emptyAddr.
for(s32 regionLinkIndexToClear = 0; regionLinkIndexToClear <(MAXLINKSPREREGION); regionLinkIndexToClear++)
{
apRegions[Region]->aLinks[regionLinkIndexToClear].m_OtherNode.SetEmpty();
}
apRegions[Region]->NumNodes = 0;
apRegions[Region]->NumNodesCarNodes = 0;
apRegions[Region]->NumNodesPedNodes = 0;
apRegions[Region]->NumLinks = 0;
}
}
// CFileMgr::SetDir("");
}
*/
// JAY, robbed from here:-
// name: CFileLoader::LoadScene
// description: Load Object instance information from IPL file
// in: pLevelName = pointer to level name string e.g "INDUSTRIAL"
bool CPathFindBuild::ReadPathInfoFromIPL(const char* pFileName)
{
#define FILELOADER_COMMENT_CHAR '#'
enum IPLLoadingStatus {
LOADING_NOTHING,
LOADING_IGNORE,
LOADING_PATHNODES,
LOADING_PATHLINKS,
};
char binaryIplName[RAGE_MAX_PATH];
strcpy(binaryIplName, pFileName);
char* pDotIndex;
pDotIndex = strrchr(binaryIplName,'.');
*pDotIndex = '\0';
if(ASSET.Exists(binaryIplName, PLACEMENTS_FILE_EXT))
return false;
char* pLine;
IPLLoadingStatus status = LOADING_NOTHING;
FileHandle fid;
fid = CFileMgr::OpenFile(pFileName, "rb");
Assertf( CFileMgr::IsValidFileHandle(fid), "Can not open '%s'", pFileName);
if(!CFileMgr::IsValidFileHandle(fid))
return false;
// first, strip out all commas and escape sequence chars, and replace them with spaces
while ((pLine = CFileMgr::ReadLine(fid)) != NULL)
{
// if beginning of line is the end it is empty
if(*pLine == '\0')
continue;
// ignore lines starting with # they are comments
if(*pLine == FILELOADER_COMMENT_CHAR)
continue;
if(status == LOADING_NOTHING)
{
// 'vnod' tag indicates start of path nodes
if(*pLine == 'v' && *(pLine+1) == 'n' && *(pLine+2) == 'o' && *(pLine+3) =='d')
{
status = LOADING_PATHNODES;
PathNodeIndex = 0;
}
// 'link' tag indicates start of path nodes
else if(*pLine == 'l' && *(pLine+1) == 'i' && *(pLine+2) == 'n' && *(pLine+3) =='k')
status = LOADING_PATHLINKS;
}
else
{
// 'end' tag indicates end of current section
if(*pLine == 'e' && *(pLine+1) == 'n' && *(pLine+2) == 'd')
{
status = LOADING_NOTHING;
}
else
{
switch(status)
{
case LOADING_PATHNODES:
{
float x, y, z;
u32 SwitchedOff, WaterNode, LowBridge, Speed, SpecialFunction, StreetNameHash = 0, Highway = 0, NoGps = 0, AdditionalTunnelFlag = 0, OpenSpaceBoundary = 0, LeftOnly = 0, NoLeftTurns = 0, NoRightTurns = 0, Offroad = 0, NoBigVehicles = 0, IndicateKeepLeft = 0, IndicateKeepRight = 0, IsSlipNode = 0;
float Density = 1.0f;
ASSERT_ONLY(s32 numArgs =) sscanf(pLine, "%f %f %f %d %d %d %d %d %f %d %d %d %d %d %d %d %d %d %d %d %d %d",
&x, &y, &z, &SwitchedOff, &WaterNode, &LowBridge, &Speed, &SpecialFunction, &Density, &StreetNameHash
, &Highway, &NoGps, &AdditionalTunnelFlag, &OpenSpaceBoundary, &NoLeftTurns, &LeftOnly, &Offroad
, &NoRightTurns, &NoBigVehicles, &IndicateKeepLeft, &IndicateKeepRight, &IsSlipNode);
Assert(numArgs >= 8);
//void StoreVehicleNode(const char *pFileName, s32 NodeIndex, float X, float Y, float Z,
// bool bSwitchedOff, bool bLowBridge, bool bWaterNode,
// u32 SpecialFunction, s32 Speed, float Density, u32 StreetNameHash,
// bool bHighway, bool bNoGps, bool bAdditionalTunnelFlag, bool bOpenSpace, bool usesHighResCoors);
StoreVehicleNode(pFileName, PathNodeIndex, x, y, z, SwitchedOff != 0, LowBridge != 0, WaterNode != 0, SpecialFunction,
Speed, Density, StreetNameHash, Highway != 0, NoGps != 0, AdditionalTunnelFlag != 0, OpenSpaceBoundary != 0, false,
LeftOnly != 0, NoLeftTurns != 0, NoRightTurns != 0, Offroad != 0, NoBigVehicles != 0,
IndicateKeepLeft != 0, IndicateKeepRight != 0, IsSlipNode != 0);
PathNodeIndex++;
}
break;
case LOADING_PATHLINKS:
{
#define FLAG_VEHLINK_NARROW (1<<0)
#define FLAG_VEHLINK_GPSBOTHWAYS (1<<1)
#define FLAG_VEHLINK_BLOCKIFNOLANES (1<<2)
#define FLAG_VEHLINK_SHORTCUT (1<<3)
#define FLAG_VEHLINK_IGNOREFORNAV (1<<4)
u32 Node1, Node2, Lanes1To2, Lanes2To1;
float Width;
s32 flags = 0;
ASSERT_ONLY(s32 numArgs =) sscanf(pLine, "%d %d %f %d %d %d",
&Node1, &Node2, &Width, &Lanes1To2, &Lanes2To1, &flags);
Assert(numArgs >= 6);
// void StoreVehicleLink(const char *pFileName, s32 Node1, s32 Node2,
// s32 Lanes1To2, s32 Lanes2To1, float Width, bool bNarrowRoad, bool bGpsCanGoBothWays);
const bool bNarrow = (flags&FLAG_VEHLINK_NARROW) != 0;
const bool bGpsBothWays = (flags&FLAG_VEHLINK_GPSBOTHWAYS) != 0;
const bool bShortcut = (flags&FLAG_VEHLINK_SHORTCUT) != 0;
const bool bIgnoreForNav = (flags&FLAG_VEHLINK_IGNOREFORNAV) != 0;
const bool bBlockIfNoLanes = (flags&FLAG_VEHLINK_BLOCKIFNOLANES) != 0;
StoreVehicleLink( pFileName, Node1, Node2, Lanes1To2, Lanes2To1, Width,
bNarrow, bGpsBothWays, bShortcut, bIgnoreForNav, bBlockIfNoLanes);
}
break;
default:
break;
}
}
// If status is still IGNORE then found a line so jump out of reading
// this file
if(status == LOADING_IGNORE)
{
break;
}
}
}
CFileMgr::CloseFile(fid);
return true;
}
/* TEST - JAY - this may need to go back in if path data is sored in IPL files that aren't paths.IPL
bool CPathFindBuild::LoadIplFiles()
{
USE_MEMBUCKET(MEMBUCKET_WORLD);
const CDataFileMgr::DataFile* pData = DATAFILEMGR.GetFirstFile(CDataFileMgr::IPL_FILE);
atArray<const fiDevice*> m_iplDeviceArray;
atArray<u32> m_filenameHashArray;
m_iplDeviceArray.Reserve(100);
m_filenameHashArray.Reserve(100);
while(DATAFILEMGR.IsValid(pData))
{
char basename[32];
ASSET.BaseName(basename, sizeof(basename), ASSET.FileName(pData->m_filename));
const fiDevice* pDevice = fiDevice::GetDevice(pData->m_filename);
u32 filenameHash = atStringHash(basename);
// search for file in list with different device. If we find one then don't load this one
s32 i;
for(i=0; i<m_iplDeviceArray.GetCount(); i++)
{
if(m_iplDeviceArray[i] != pDevice &&
m_filenameHashArray[i] == filenameHash)
break;
}
// If a version of this file hasn't been loaded already then load it
if(i == m_iplDeviceArray.GetCount())
{
ReadPathInfoFromIPL(pData->m_filename); // DW - IPLs are loaded here
}
else
{
Displayf("Ignoring %s as we have loaded a version of this already", pData->m_filename);
}
// add to lists
m_iplDeviceArray.PushAndGrow(pDevice);
m_filenameHashArray.PushAndGrow(filenameHash);
pData = DATAFILEMGR.GetNextFile(pData);
}
return true;
}
*/