#include "common.h"
#include "General.h"
#include "FileMgr.h" // only needed for empty function
#include "Camera.h"
#include "Vehicle.h"
#include "World.h"
#include "Lines.h" // for debug
#include "PathFind.h"
bool gbShowPedPaths;
bool gbShowCarPaths;
bool gbShowCarPathsLinks;
CPathFind ThePaths;
#define MAX_DIST INT16_MAX-1
#define MIN_PED_ROUTE_DISTANCE 23.8f
#define NUMTEMPNODES 4000
#define NUMDETACHED_CARS 100
#define NUMDETACHED_PEDS 50
// object flags:
// 1 UseInRoadBlock
// 2 east/west road(?)
CPathInfoForObject *InfoForTileCars;
CPathInfoForObject *InfoForTilePeds;
// unused
CTempDetachedNode *DetachedNodesCars;
CTempDetachedNode *DetachedNodesPeds;
bool
CPedPath::CalcPedRoute(int8 pathType, CVector position, CVector destination, CVector *pointPoses, int16 *pointsFound, int16 maxPoints)
{
*pointsFound = 0;
CVector vecDistance = destination - position;
if (Abs(vecDistance.x) > MIN_PED_ROUTE_DISTANCE || Abs(vecDistance.y) > MIN_PED_ROUTE_DISTANCE || Abs(vecDistance.z) > MIN_PED_ROUTE_DISTANCE)
return false;
CVector vecPos = (position + destination) * 0.5f;
CVector vecSectorStartPos (vecPos.x - 14.0f, vecPos.y - 14.0f, vecPos.z);
CVector2D vecSectorEndPos (vecPos.x + 28.0f, vecPos.x + 28.0f);
const int16 nodeStartX = (position.x - vecSectorStartPos.x) / 0.7f;
const int16 nodeStartY = (position.y - vecSectorStartPos.y) / 0.7f;
const int16 nodeEndX = (destination.x - vecSectorStartPos.x) / 0.7f;
const int16 nodeEndY = (destination.y - vecSectorStartPos.y) / 0.7f;
if (nodeStartX == nodeEndX && nodeStartY == nodeEndY)
return false;
CPedPathNode pathNodes[40][40];
CPedPathNode pathNodesList[416];
for (int32 x = 0; x < 40; x++) {
for (int32 y = 0; y < 40; y++) {
pathNodes[x][y].bBlockade = false;
pathNodes[x][y].id = INT16_MAX;
pathNodes[x][y].nodeIdX = x;
pathNodes[x][y].nodeIdY = y;
}
}
CWorld::AdvanceCurrentScanCode();
if (pathType != ROUTE_NO_BLOCKADE) {
const int32 nStartX = Max(CWorld::GetSectorIndexX(vecSectorStartPos.x), 0);
const int32 nStartY = Max(CWorld::GetSectorIndexY(vecSectorStartPos.y), 0);
const int32 nEndX = Min(CWorld::GetSectorIndexX(vecSectorEndPos.x), NUMSECTORS_X - 1);
const int32 nEndY = Min(CWorld::GetSectorIndexY(vecSectorEndPos.y), NUMSECTORS_Y - 1);
for (int32 y = nStartY; y <= nEndY; y++) {
for (int32 x = nStartX; x <= nEndX; x++) {
CSector *pSector = CWorld::GetSector(x, y);
AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_VEHICLES], pathNodes, &vecSectorStartPos);
AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], pathNodes, &vecSectorStartPos);
AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_OBJECTS], pathNodes, &vecSectorStartPos);
AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], pathNodes, &vecSectorStartPos);
}
}
}
for (int32 i = 0; i < 416; i++) {
pathNodesList[i].prev = nil;
pathNodesList[i].next = nil;
}
CPedPathNode *pStartPathNode = &pathNodes[nodeStartX][nodeStartY];
CPedPathNode *pEndPathNode = &pathNodes[nodeEndX][nodeEndY];
pEndPathNode->bBlockade = false;
pEndPathNode->id = 0;
pEndPathNode->prev = nil;
pEndPathNode->next = pathNodesList;
pathNodesList[0].prev = pEndPathNode;
int32 pathNodeIndex = 0;
CPedPathNode *pPreviousNode = nil;
for (; pathNodeIndex < 414; pathNodeIndex++)
{
pPreviousNode = pathNodesList[pathNodeIndex].prev;
while (pPreviousNode && pPreviousNode != pStartPathNode) {
const uint8 nodeIdX = pPreviousNode->nodeIdX;
const uint8 nodeIdY = pPreviousNode->nodeIdY;
if (nodeIdX > 0) {
AddNodeToPathList(&pathNodes[nodeIdX - 1][nodeIdY], pathNodeIndex + 5, pathNodesList);
if (nodeIdY > 0)
AddNodeToPathList(&pathNodes[nodeIdX - 1][nodeIdY - 1], pathNodeIndex + 7, pathNodesList);
if (nodeIdY < 39)
AddNodeToPathList(&pathNodes[nodeIdX - 1][nodeIdY + 1], pathNodeIndex + 7, pathNodesList);
}
if (nodeIdX < 39) {
AddNodeToPathList(&pathNodes[nodeIdX + 1][nodeIdY], pathNodeIndex + 5, pathNodesList);
if (nodeIdY > 0)
AddNodeToPathList(&pathNodes[nodeIdX + 1][nodeIdY - 1], pathNodeIndex + 7, pathNodesList);
if (nodeIdY < 39)
AddNodeToPathList(&pathNodes[nodeIdX + 1][nodeIdY + 1], pathNodeIndex + 7, pathNodesList);
}
if (nodeIdY > 0)
AddNodeToPathList(&pathNodes[nodeIdX][nodeIdY - 1], pathNodeIndex + 5, pathNodesList);
if (nodeIdY < 39)
AddNodeToPathList(&pathNodes[nodeIdX][nodeIdY + 1], pathNodeIndex + 5, pathNodesList);
pPreviousNode = pPreviousNode->prev;
if (!pPreviousNode)
break;
}
if (pPreviousNode && pPreviousNode == pStartPathNode)
break;
}
if (pathNodeIndex == 414)
return false;
CPedPathNode *pPathNode = pStartPathNode;
for (*pointsFound = 0; pPathNode != pEndPathNode && *pointsFound < maxPoints; ++ *pointsFound) {
const uint8 nodeIdX = pPathNode->nodeIdX;
const uint8 nodeIdY = pPathNode->nodeIdY;
if (nodeIdX > 0 && pathNodes[nodeIdX - 1][nodeIdY].id + 5 == pPathNode->id)
pPathNode = &pathNodes[nodeIdX - 1][nodeIdY];
else if (nodeIdX > 39 && pathNodes[nodeIdX + 1][nodeIdY].id + 5 == pPathNode->id)
pPathNode = &pathNodes[nodeIdX + 1][nodeIdY];
else if (nodeIdY > 0 && pathNodes[nodeIdX][nodeIdY - 1].id + 5 == pPathNode->id)
pPathNode = &pathNodes[nodeIdX][nodeIdY - 1];
else if (nodeIdY > 39 && pathNodes[nodeIdX][nodeIdY + 1].id + 5 == pPathNode->id)
pPathNode = &pathNodes[nodeIdX][nodeIdY + 1];
else if (nodeIdX > 0 && nodeIdY > 0 && pathNodes[nodeIdX - 1][nodeIdY - 1].id + 7 == pPathNode->id)
pPathNode = &pathNodes[nodeIdX - 1][nodeIdY - 1];
else if (nodeIdX > 0 && nodeIdY < 39 && pathNodes[nodeIdX - 1][nodeIdY + 1].id + 7 == pPathNode->id)
pPathNode = &pathNodes[nodeIdX - 1][nodeIdY + 1];
else if (nodeIdX < 39 && nodeIdY > 0 && pathNodes[nodeIdX + 1][nodeIdY - 1].id + 7 == pPathNode->id)
pPathNode = &pathNodes[nodeIdX + 1][nodeIdY - 1];
else if (nodeIdX < 39 && nodeIdY < 39 && pathNodes[nodeIdX + 1][nodeIdY + 1].id + 7 == pPathNode->id)
pPathNode = &pathNodes[nodeIdX + 1][nodeIdY + 1];
pointPoses[*pointsFound] = vecSectorStartPos;
pointPoses[*pointsFound].x += pPathNode->nodeIdX * 0.7f;
pointPoses[*pointsFound].y += pPathNode->nodeIdY * 0.7f;
}
return true;
}
void
CPedPath::AddNodeToPathList(CPedPathNode *pNodeToAdd, int16 id, CPedPathNode *pNodeList)
{
if (!pNodeToAdd->bBlockade && id < pNodeToAdd->id) {
if (pNodeToAdd->id != INT16_MAX)
RemoveNodeFromList(pNodeToAdd);
AddNodeToList(pNodeToAdd, id, pNodeList);
}
}
void
CPedPath::RemoveNodeFromList(CPedPathNode *pNode)
{
pNode->next->prev = pNode->prev;
if (pNode->prev)
pNode->prev->next = pNode->next;
}
void
CPedPath::AddNodeToList(CPedPathNode *pNode, int16 index, CPedPathNode *pList)
{
pNode->prev = pList[index].prev;
pNode->next = &pList[index];
if (pList[index].prev)
pList[index].prev->next = pNode;
pList[index].prev = pNode;
pNode->id = index;
}
void
CPedPath::AddBlockadeSectorList(CPtrList& list, CPedPathNode(*pathNodes)[40], CVector *pPosition)
{
CPtrNode* listNode = list.first;
while (listNode) {
CEntity* pEntity = (CEntity*)listNode->item;
if (pEntity->m_scanCode != CWorld::GetCurrentScanCode() && pEntity->bUsesCollision) {
pEntity->m_scanCode = CWorld::GetCurrentScanCode();
AddBlockade(pEntity, pathNodes, pPosition);
}
listNode = listNode->next;
}
}
void
CPedPath::AddBlockade(CEntity *pEntity, CPedPathNode(*pathNodes)[40], CVector *pPosition)
{
const CColBox& boundingBox = pEntity->GetColModel()->boundingBox;
const float fBoundMaxY = boundingBox.max.y + 0.3f;
const float fBoundMinY = boundingBox.min.y - 0.3f;
const float fBoundMaxX = boundingBox.max.x + 0.3f;
const float fDistanceX = pPosition->x - pEntity->m_matrix.GetPosition().x;
const float fDistanceY = pPosition->y - pEntity->m_matrix.GetPosition().y;
const float fBoundRadius = pEntity->GetBoundRadius();
CVector vecBoundCentre;
pEntity->GetBoundCentre(vecBoundCentre);
if (vecBoundCentre.x + fBoundRadius >= pPosition->x &&
vecBoundCentre.y + fBoundRadius >= pPosition->y &&
vecBoundCentre.x - fBoundRadius <= pPosition->x + 28.0f &&
vecBoundCentre.y - fBoundRadius <= pPosition->y + 28.0f) {
for (int16 x = 0; x < 40; x++) {
const float pointX = x * 0.7f + fDistanceX;
for (int16 y = 0; y < 40; y++) {
if (!pathNodes[x][y].bBlockade) {
const float pointY = y * 0.7f + fDistanceY;
CVector2D point(pointX, pointY);
if (fBoundMaxX > Abs(DotProduct2D(point, pEntity->m_matrix.GetRight()))) {
float fDotProduct = DotProduct2D(point, pEntity->m_matrix.GetForward());
if (fBoundMaxY > fDotProduct && fBoundMinY < fDotProduct)
pathNodes[x][y].bBlockade = true;
}
}
}
}
}
}
void
CPathFind::Init(void)
{
int i;
m_numPathNodes = 0;
m_numMapObjects = 0;
m_numConnections = 0;
m_numCarPathLinks = 0;
unk = 0;
for(i = 0; i < NUM_PATHNODES; i++)
m_pathNodes[i].distance = MAX_DIST;
}
void
CPathFind::AllocatePathFindInfoMem(int16 numPathGroups)
{
delete[] InfoForTileCars;
InfoForTileCars = nil;
delete[] InfoForTilePeds;
InfoForTilePeds = nil;
// NB: MIAMI doesn't use numPathGroups here but hardcodes 4500
InfoForTileCars = new CPathInfoForObject[12*numPathGroups];
memset(InfoForTileCars, 0, 12*numPathGroups*sizeof(CPathInfoForObject));
InfoForTilePeds = new CPathInfoForObject[12*numPathGroups];
memset(InfoForTilePeds, 0, 12*numPathGroups*sizeof(CPathInfoForObject));
// unused
delete[] DetachedNodesCars;
DetachedNodesCars = nil;
delete[] DetachedNodesPeds;
DetachedNodesPeds = nil;
DetachedNodesCars = new CTempDetachedNode[NUMDETACHED_CARS];
memset(DetachedNodesCars, 0, NUMDETACHED_CARS*sizeof(CTempDetachedNode));
DetachedNodesPeds = new CTempDetachedNode[NUMDETACHED_PEDS];
memset(DetachedNodesPeds, 0, NUMDETACHED_PEDS*sizeof(CTempDetachedNode));
}
void
CPathFind::RegisterMapObject(CTreadable *mapObject)
{
m_mapObjects[m_numMapObjects++] = mapObject;
}
void
CPathFind::StoreNodeInfoPed(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, int16 width, bool crossing)
{
int i, j;
i = id*12 + node;
InfoForTilePeds[i].type = type;
InfoForTilePeds[i].next = next;
InfoForTilePeds[i].x = x;
InfoForTilePeds[i].y = y;
InfoForTilePeds[i].z = z;
InfoForTilePeds[i].numLeftLanes = 0;
InfoForTilePeds[i].numRightLanes = 0;
InfoForTilePeds[i].crossing = crossing;
if(type)
for(i = 0; i < node; i++){
j = id*12 + i;
if(x == InfoForTilePeds[j].x && y == InfoForTilePeds[j].y){
printf("^^^^^^^^^^^^^ AARON IS TOO CHICKEN TO EAT MEAT!\n");
printf("Several ped nodes on one road segment have identical coordinates (%d==%d && %d==%d)\n",
x, InfoForTilePeds[j].x, y, InfoForTilePeds[j].y);
printf("Modelindex of cullprit: %d\n\n", id);
}
}
}
void
CPathFind::StoreNodeInfoCar(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, int16 width, int8 numLeft, int8 numRight)
{
int i, j;
i = id*12 + node;
InfoForTileCars[i].type = type;
InfoForTileCars[i].next = next;
InfoForTileCars[i].x = x;
InfoForTileCars[i].y = y;
InfoForTileCars[i].z = z;
InfoForTileCars[i].numLeftLanes = numLeft;
InfoForTileCars[i].numRightLanes = numRight;
if(type)
for(i = 0; i < node; i++){
j = id*12 + i;
if(x == InfoForTileCars[j].x && y == InfoForTileCars[j].y){
printf("^^^^^^^^^^^^^ AARON IS TOO CHICKEN TO EAT MEAT!\n");
printf("Several car nodes on one road segment have identical coordinates (%d==%d && %d==%d)\n",
x, InfoForTileCars[j].x, y, InfoForTileCars[j].y);
printf("Modelindex of cullprit: %d\n\n", id);
}
}
}
void
CPathFind::CalcNodeCoors(int16 x, int16 y, int16 z, int id, CVector *out)
{
CVector pos;
pos.x = x / 16.0f;
pos.y = y / 16.0f;
pos.z = z / 16.0f;
*out = m_mapObjects[id]->GetMatrix() * pos;
}
bool
CPathFind::LoadPathFindData(void)
{
CFileMgr::SetDir("");
return false;
}
void
CPathFind::PreparePathData(void)
{
int i, j, k;
int numExtern, numIntern, numLanes;
float maxX, maxY;
CTempNode *tempNodes;
printf("PreparePathData\n");
if(!CPathFind::LoadPathFindData() && // empty
InfoForTileCars && InfoForTilePeds &&
DetachedNodesCars && DetachedNodesPeds
){
tempNodes = new CTempNode[NUMTEMPNODES];
m_numConnections = 0;
for(i = 0; i < PATHNODESIZE; i++)
m_pathNodes[i].unkBits = 0;
for(i = 0; i < PATHNODESIZE; i++){
numExtern = 0;
numIntern = 0;
for(j = 0; j < 12; j++){
if(InfoForTileCars[i*12 + j].type == NodeTypeExtern)
numExtern++;
if(InfoForTileCars[i*12 + j].type == NodeTypeIntern)
numIntern++;
}
if(numIntern > 1 && numExtern != 2)
printf("ILLEGAL BLOCK. MORE THAN 1 INTERNALS AND NOT 2 EXTERNALS (Modelindex:%d)\n", i);
}
for(i = 0; i < PATHNODESIZE; i++)
for(j = 0; j < 12; j++)
if(InfoForTileCars[i*12 + j].type == NodeTypeExtern){
// MIAMI has MI:%d here but no argument for it
if(InfoForTileCars[i*12 + j].numLeftLanes < 0)
printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i);
if(InfoForTileCars[i*12 + j].numRightLanes < 0)
printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i);
if(InfoForTileCars[i*12 + j].numLeftLanes + InfoForTileCars[i*12 + j].numRightLanes <= 0)
printf("ILLEGAL BLOCK. NO LANES IN NODE (Obj:%d)\n", i);
}
m_numPathNodes = 0;
PreparePathDataForType(PATH_CAR, tempNodes, InfoForTileCars, 1.0f, DetachedNodesCars, NUMDETACHED_CARS);
m_numCarPathNodes = m_numPathNodes;
PreparePathDataForType(PATH_PED, tempNodes, InfoForTilePeds, 1.0f, DetachedNodesPeds, NUMDETACHED_PEDS);
m_numPedPathNodes = m_numPathNodes - m_numCarPathNodes;
// TODO: figure out what exactly is going on here
// Some roads seem to get a west/east flag
for(i = 0; i < m_numMapObjects; i++){
numExtern = 0;
numIntern = 0;
numLanes = 0;
maxX = 0.0f;
maxY = 0.0f;
for(j = 0; j < 12; j++){
k = i*12 + j;
if(InfoForTileCars[k].type == NodeTypeExtern){
numExtern++;
if(InfoForTileCars[k].numLeftLanes + InfoForTileCars[k].numRightLanes > numLanes)
numLanes = InfoForTileCars[k].numLeftLanes + InfoForTileCars[k].numRightLanes;
maxX = Max(maxX, Abs(InfoForTileCars[k].x));
maxY = Max(maxY, Abs(InfoForTileCars[k].y));
}else if(InfoForTileCars[k].type == NodeTypeIntern)
numIntern++;
}
if(numIntern == 1 && numExtern == 2){
if(numLanes < 4){
if((i & 7) == 4){ // WHAT?
m_objectFlags[i] |= UseInRoadBlock;
if(maxX > maxY)
m_objectFlags[i] |= ObjectEastWest;
else
m_objectFlags[i] &= ~ObjectEastWest;
}
}else{
m_objectFlags[i] |= UseInRoadBlock;
if(maxX > maxY)
m_objectFlags[i] |= ObjectEastWest;
else
m_objectFlags[i] &= ~ObjectEastWest;
}
}
}
delete[] tempNodes;
CountFloodFillGroups(PATH_CAR);
CountFloodFillGroups(PATH_PED);
delete[] InfoForTileCars;
InfoForTileCars = nil;
delete[] InfoForTilePeds;
InfoForTilePeds = nil;
delete[] DetachedNodesCars;
DetachedNodesCars = nil;
delete[] DetachedNodesPeds;
DetachedNodesPeds = nil;
}
printf("Done with PreparePathData\n");
}
/* String together connected nodes in a list by a flood fill algorithm */
void
CPathFind::CountFloodFillGroups(uint8 type)
{
int start, end;
int i, l;
uint16 n;
CPathNode *node, *prev;
switch(type){
case PATH_CAR:
start = 0;
end = m_numCarPathNodes;
break;
case PATH_PED:
start = m_numCarPathNodes;
end = start + m_numPedPathNodes;
break;
}
for(i = start; i < end; i++)
m_pathNodes[i].group = 0;
n = 0;
for(;;){
n++;
if(n > 1500){
for(i = start; m_pathNodes[i].group && i < end; i++);
printf("NumNodes:%d Accounted for:%d\n", end - start, i - start);
}
// Look for unvisited node
for(i = start; m_pathNodes[i].group && i < end; i++);
if(i == end)
break;
node = &m_pathNodes[i];
node->SetNext(nil);
node->group = n;
if(node->numLinks == 0){
if(type == PATH_CAR)
printf("Single car node: %f %f %f (%d)\n",
node->GetX(), node->GetY(), node->GetZ(), m_mapObjects[node->objectIndex]->GetModelIndex());
else
printf("Single ped node: %f %f %f\n",
node->GetX(), node->GetY(), node->GetZ());
}
while(node){
prev = node;
node = node->GetNext();
for(i = 0; i < prev->numLinks; i++){
l = ConnectedNode(prev->firstLink + i);
if(m_pathNodes[l].group == 0){
m_pathNodes[l].group = n;
if(m_pathNodes[l].group == 0)
m_pathNodes[l].group = INT8_MIN;
m_pathNodes[l].SetNext(node);
node = &m_pathNodes[l];
}
}
}
}
m_numGroups[type] = n-1;
printf("GraphType:%d. FloodFill groups:%d\n", type, n);
}
int32 TempListLength;
void
CPathFind::PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo,
float maxdist, CTempDetachedNode *detachednodes, int numDetached)
{
static CVector CoorsXFormed;
int i, j, k, l;
int l1, l2;
int start;
float posx, posy;
float dx, dy, mag;
float nearestDist;
int nearestId;
int next;
int oldNumPathNodes, oldNumLinks;
float dist;
int iseg, jseg;
int istart, jstart;
int done, cont;
int tileStart;
oldNumPathNodes = m_numPathNodes;
oldNumLinks = m_numConnections;
#define OBJECTINDEX(n) (m_pathNodes[(n)].objectIndex)
// Initialize map objects
for(i = 0; i < m_numMapObjects; i++)
for(j = 0; j < 12; j++)
m_mapObjects[i]->m_nodeIndices[type][j] = -1;
// Calculate internal nodes, store them and connect them to defining object
for(i = 0; i < m_numMapObjects; i++){
tileStart = m_numPathNodes;
start = 12 * m_mapObjects[i]->GetModelIndex();
for(j = 0; j < 12; j++){
if(objectpathinfo[start + j].type == NodeTypeIntern){
CalcNodeCoors(
objectpathinfo[start + j].x,
objectpathinfo[start + j].y,
objectpathinfo[start + j].z,
i,
&CoorsXFormed);
m_pathNodes[m_numPathNodes].SetPosition(CoorsXFormed);
OBJECTINDEX(m_numPathNodes) = i;
m_pathNodes[m_numPathNodes].unkBits = 1;
m_mapObjects[i]->m_nodeIndices[type][j] = m_numPathNodes;
m_numPathNodes++;
}
}
}
// Insert external nodes into TempList
TempListLength = 0;
for(i = 0; i < m_numMapObjects; i++){
start = 12 * m_mapObjects[i]->GetModelIndex();
for(j = 0; j < 12; j++){
if(objectpathinfo[start + j].type != NodeTypeExtern)
continue;
CalcNodeCoors(
objectpathinfo[start + j].x,
objectpathinfo[start + j].y,
objectpathinfo[start + j].z,
i,
&CoorsXFormed);
// find closest unconnected node
nearestId = -1;
nearestDist = maxdist;
for(k = 0; k < TempListLength; k++){
if(tempnodes[k].linkState != 1)
continue;
dx = tempnodes[k].pos.x - CoorsXFormed.x;
if(Abs(dx) < nearestDist){
dy = tempnodes[k].pos.y - CoorsXFormed.y;
if(Abs(dy) < nearestDist){
nearestDist = Max(Abs(dx), Abs(dy));
nearestId = k;
}
}
}
if(nearestId < 0){
// None found, add this one to temp list
tempnodes[TempListLength].pos = CoorsXFormed;
next = objectpathinfo[start + j].next;
if(next < 0){
// no link from this node, find link to this node
next = 0;
for(k = start; j != objectpathinfo[k].next; k++)
next++;
}
// link to connecting internal node
tempnodes[TempListLength].link1 = m_mapObjects[i]->m_nodeIndices[type][next];
if(type == PATH_CAR){
tempnodes[TempListLength].numLeftLanes = objectpathinfo[start + j].numLeftLanes;
tempnodes[TempListLength].numRightLanes = objectpathinfo[start + j].numRightLanes;
}
tempnodes[TempListLength++].linkState = 1;
}else{
// Found nearest, connect it to our neighbour
next = objectpathinfo[start + j].next;
if(next < 0){
// no link from this node, find link to this node
next = 0;
for(k = start; j != objectpathinfo[k].next; k++)
next++;
}
tempnodes[nearestId].link2 = m_mapObjects[i]->m_nodeIndices[type][next];
tempnodes[nearestId].linkState = 2;
// collapse this node with nearest we found
dx = m_pathNodes[tempnodes[nearestId].link1].GetX() - m_pathNodes[tempnodes[nearestId].link2].GetX();
dy = m_pathNodes[tempnodes[nearestId].link1].GetY() - m_pathNodes[tempnodes[nearestId].link2].GetY();
tempnodes[nearestId].pos = (tempnodes[nearestId].pos + CoorsXFormed)*0.5f;
mag = Sqrt(dx*dx + dy*dy);
tempnodes[nearestId].dirX = dx/mag;
tempnodes[nearestId].dirY = dy/mag;
// do something when number of lanes doesn't agree
if(type == PATH_CAR)
if(tempnodes[nearestId].numLeftLanes != 0 && tempnodes[nearestId].numRightLanes != 0 &&
(objectpathinfo[start + j].numLeftLanes == 0 || objectpathinfo[start + j].numRightLanes == 0)){
// why switch left and right here?
tempnodes[nearestId].numLeftLanes = objectpathinfo[start + j].numRightLanes;
tempnodes[nearestId].numRightLanes = objectpathinfo[start + j].numLeftLanes;
}
}
}
}
// Loop through previously added internal nodes and link them
for(i = oldNumPathNodes; i < m_numPathNodes; i++){
// Init link
m_pathNodes[i].numLinks = 0;
m_pathNodes[i].firstLink = m_numConnections;
// See if node connects to external nodes
for(j = 0; j < TempListLength; j++){
if(tempnodes[j].linkState != 2)
continue;
// Add link to other side of the external
// NB this clears the flags in MIAMI
if(tempnodes[j].link1 == i)
m_connections[m_numConnections] = tempnodes[j].link2;
else if(tempnodes[j].link2 == i)
m_connections[m_numConnections] = tempnodes[j].link1;
else
continue;
dist = (m_pathNodes[i].GetPosition() - m_pathNodes[ConnectedNode(m_numConnections)].GetPosition()).Magnitude();
m_distances[m_numConnections] = dist;
m_connectionFlags[m_numConnections].flags = 0;
if(type == PATH_CAR){
// IMPROVE: use a goto here
// Find existing car path link
for(k = 0; k < m_numCarPathLinks; k++){
if(m_carPathLinks[k].dir.x == tempnodes[j].dirX &&
m_carPathLinks[k].dir.y == tempnodes[j].dirY &&
m_carPathLinks[k].pos.x == tempnodes[j].pos.x &&
m_carPathLinks[k].pos.y == tempnodes[j].pos.y){
m_carPathConnections[m_numConnections] = k;
k = m_numCarPathLinks;
}
}
// k is m_numCarPathLinks+1 if we found one
if(k == m_numCarPathLinks){
m_carPathLinks[m_numCarPathLinks].dir.x = tempnodes[j].dirX;
m_carPathLinks[m_numCarPathLinks].dir.y = tempnodes[j].dirY;
m_carPathLinks[m_numCarPathLinks].pos.x = tempnodes[j].pos.x;
m_carPathLinks[m_numCarPathLinks].pos.y = tempnodes[j].pos.y;
m_carPathLinks[m_numCarPathLinks].pathNodeIndex = i;
m_carPathLinks[m_numCarPathLinks].numLeftLanes = tempnodes[j].numLeftLanes;
m_carPathLinks[m_numCarPathLinks].numRightLanes = tempnodes[j].numRightLanes;
m_carPathLinks[m_numCarPathLinks].trafficLightType = 0;
assert(m_numCarPathLinks <= NUM_CARPATHLINKS);
m_carPathConnections[m_numConnections] = m_numCarPathLinks++;
}
}
m_pathNodes[i].numLinks++;
m_numConnections++;
}
// Find i inside path segment
iseg = 0;
for(j = Max(oldNumPathNodes, i-12); j < i; j++)
if(OBJECTINDEX(j) == OBJECTINDEX(i))
iseg++;
istart = 12 * m_mapObjects[m_pathNodes[i].objectIndex]->GetModelIndex();
// Add links to other internal nodes
for(j = Max(oldNumPathNodes, i-12); j < Min(m_numPathNodes, i+12); j++){
if(OBJECTINDEX(i) != OBJECTINDEX(j) || i == j)
continue;
// N.B.: in every path segment, the externals have to be at the end
jseg = j-i + iseg;
jstart = 12 * m_mapObjects[m_pathNodes[j].objectIndex]->GetModelIndex();
if(objectpathinfo[istart + iseg].next == jseg ||
objectpathinfo[jstart + jseg].next == iseg){
// Found a link between i and jConnectionSetCrossesRoad
// NB this clears the flags in MIAMI
m_connections[m_numConnections] = j;
dist = (m_pathNodes[i].GetPosition() - m_pathNodes[j].GetPosition()).Magnitude();
m_distances[m_numConnections] = dist;
if(type == PATH_CAR){
posx = (m_pathNodes[i].GetX() + m_pathNodes[j].GetX())*0.5f;
posy = (m_pathNodes[i].GetY() + m_pathNodes[j].GetY())*0.5f;
dx = m_pathNodes[j].GetX() - m_pathNodes[i].GetX();
dy = m_pathNodes[j].GetY() - m_pathNodes[i].GetY();
mag = Sqrt(dx*dx + dy*dy);
dx /= mag;
dy /= mag;
if(i < j){
dx = -dx;
dy = -dy;
}
// IMPROVE: use a goto here
// Find existing car path link
for(k = 0; k < m_numCarPathLinks; k++){
if(m_carPathLinks[k].dir.x == dx &&
m_carPathLinks[k].dir.y == dy &&
m_carPathLinks[k].pos.x == posx &&
m_carPathLinks[k].pos.y == posy){
m_carPathConnections[m_numConnections] = k;
k = m_numCarPathLinks;
}
}
// k is m_numCarPathLinks+1 if we found one
if(k == m_numCarPathLinks){
m_carPathLinks[m_numCarPathLinks].dir.x = dx;
m_carPathLinks[m_numCarPathLinks].dir.y = dy;
m_carPathLinks[m_numCarPathLinks].pos.x = posx;
m_carPathLinks[m_numCarPathLinks].pos.y = posy;
m_carPathLinks[m_numCarPathLinks].pathNodeIndex = i;
m_carPathLinks[m_numCarPathLinks].numLeftLanes = -1;
m_carPathLinks[m_numCarPathLinks].numRightLanes = -1;
m_carPathLinks[m_numCarPathLinks].trafficLightType = 0;
assert(m_numCarPathLinks <= NUM_CARPATHLINKS);
m_carPathConnections[m_numConnections] = m_numCarPathLinks++;
}
}else{
// Crosses road
if(objectpathinfo[istart + iseg].next == jseg && objectpathinfo[istart + iseg].crossing ||
objectpathinfo[jstart + jseg].next == iseg && objectpathinfo[jstart + jseg].crossing)
m_connectionFlags[m_numConnections].bCrossesRoad = true;
else
m_connectionFlags[m_numConnections].bCrossesRoad = false;
}
m_pathNodes[i].numLinks++;
m_numConnections++;
}
}
}
if(type == PATH_CAR){
done = 0;
// Set number of lanes for all nodes somehow
// very strange code
for(k = 0; !done && k < 10; k++){
done = 1;
for(i = 0; i < m_numPathNodes; i++){
if(m_pathNodes[i].numLinks != 2)
continue;
l1 = m_carPathConnections[m_pathNodes[i].firstLink];
l2 = m_carPathConnections[m_pathNodes[i].firstLink+1];
if(m_carPathLinks[l1].numLeftLanes == -1 &&
m_carPathLinks[l2].numLeftLanes != -1){
done = 0;
if(m_carPathLinks[l2].pathNodeIndex == i){
// why switch left and right here?
m_carPathLinks[l1].numLeftLanes = m_carPathLinks[l2].numRightLanes;
m_carPathLinks[l1].numRightLanes = m_carPathLinks[l2].numLeftLanes;
}else{
m_carPathLinks[l1].numLeftLanes = m_carPathLinks[l2].numLeftLanes;
m_carPathLinks[l1].numRightLanes = m_carPathLinks[l2].numRightLanes;
}
m_carPathLinks[l1].pathNodeIndex = i;
}else if(m_carPathLinks[l1].numLeftLanes != -1 &&
m_carPathLinks[l2].numLeftLanes == -1){
done = 0;
if(m_carPathLinks[l1].pathNodeIndex == i){
// why switch left and right here?
m_carPathLinks[l2].numLeftLanes = m_carPathLinks[l1].numRightLanes;
m_carPathLinks[l2].numRightLanes = m_carPathLinks[l1].numLeftLanes;
}else{
m_carPathLinks[l2].numLeftLanes = m_carPathLinks[l1].numLeftLanes;
m_carPathLinks[l2].numRightLanes = m_carPathLinks[l1].numRightLanes;
}
m_carPathLinks[l2].pathNodeIndex = i;
}else if(m_carPathLinks[l1].numLeftLanes == -1 &&
m_carPathLinks[l2].numLeftLanes == -1)
done = 0;
}
}
// Fall back to default values for number of lanes
for(i = 0; i < m_numPathNodes; i++)
for(j = 0; j < m_pathNodes[i].numLinks; j++){
k = m_carPathConnections[m_pathNodes[i].firstLink + j];
if(m_carPathLinks[k].numLeftLanes < 0)
m_carPathLinks[k].numLeftLanes = 1;
if(m_carPathLinks[k].numRightLanes < 0)
m_carPathLinks[k].numRightLanes = 1;
}
}
// Set flags for car nodes
if(type == PATH_CAR){
do{
cont = 0;
for(i = 0; i < m_numPathNodes; i++){
m_pathNodes[i].bDisabled = false;
m_pathNodes[i].bBetweenLevels = false;
// See if node is a dead end, if so, we're not done yet
if(!m_pathNodes[i].bDeadEnd){
k = 0;
for(j = 0; j < m_pathNodes[i].numLinks; j++)
if(!m_pathNodes[ConnectedNode(m_pathNodes[i].firstLink + j)].bDeadEnd)
k++;
if(k < 2){
m_pathNodes[i].bDeadEnd = true;
cont = 1;
}
}
}
}while(cont);
}
// Remove isolated ped nodes
if(type == PATH_PED)
for(i = oldNumPathNodes; i < m_numPathNodes; i++){
if(m_pathNodes[i].numLinks != 0)
continue;
// Remove node
for(j = i; j < m_numPathNodes-1; j++)
m_pathNodes[j] = m_pathNodes[j+1];
// Fix links
for(j = oldNumLinks; j < m_numConnections; j++){
int node = ConnectedNode(j);
if(node >= i)
m_connections[j] = node-1;
}
// Also in treadables
for(j = 0; j < m_numMapObjects; j++)
for(k = 0; k < 12; k++){
if(m_mapObjects[j]->m_nodeIndices[PATH_PED][k] == i){
// remove this one
for(l = k; l < 12-1; l++)
m_mapObjects[j]->m_nodeIndices[PATH_PED][l] = m_mapObjects[j]->m_nodeIndices[PATH_PED][l+1];
m_mapObjects[j]->m_nodeIndices[PATH_PED][11] = -1;
}else if(m_mapObjects[j]->m_nodeIndices[PATH_PED][k] > i)
m_mapObjects[j]->m_nodeIndices[PATH_PED][k]--;
}
i--;
m_numPathNodes--;
}
}
float
CPathFind::CalcRoadDensity(float x, float y)
{
int i, j;
float density = 0.0f;
for(i = 0; i < m_numCarPathNodes; i++){
if(Abs(m_pathNodes[i].GetX() - x) < 80.0f &&
Abs(m_pathNodes[i].GetY() - y) < 80.0f &&
m_pathNodes[i].numLinks > 0){
for(j = 0; j < m_pathNodes[i].numLinks; j++){
int next = ConnectedNode(m_pathNodes[i].firstLink + j);
float dist = (m_pathNodes[i].GetPosition() - m_pathNodes[next].GetPosition()).Magnitude2D();
next = m_carPathConnections[m_pathNodes[i].firstLink + j];
density += m_carPathLinks[next].numLeftLanes * dist;
density += m_carPathLinks[next].numRightLanes * dist;
if(m_carPathLinks[next].numLeftLanes < 0)
printf("Link from object %d to %d (MIs)\n",
m_mapObjects[m_pathNodes[i].objectIndex]->GetModelIndex(),
m_mapObjects[m_pathNodes[ConnectedNode(m_pathNodes[i].firstLink + j)].objectIndex]->GetModelIndex());
if(m_carPathLinks[next].numRightLanes < 0)
printf("Link from object %d to %d (MIs)\n",
m_mapObjects[m_pathNodes[i].objectIndex]->GetModelIndex(),
m_mapObjects[m_pathNodes[ConnectedNode(m_pathNodes[i].firstLink + j)].objectIndex]->GetModelIndex());
}
}
}
return density/2500.0f;
}
bool
CPathFind::TestForPedTrafficLight(CPathNode *n1, CPathNode *n2)
{
int i;
for(i = 0; i < n1->numLinks; i++)
if(&m_pathNodes[ConnectedNode(n1->firstLink + i)] == n2)
return ConnectionHasTrafficLight(n1->firstLink + i);
return false;
}
bool
CPathFind::TestCrossesRoad(CPathNode *n1, CPathNode *n2)
{
int i;
for(i = 0; i < n1->numLinks; i++)
if(&m_pathNodes[ConnectedNode(n1->firstLink + i)] == n2)
return ConnectionCrossesRoad(n1->firstLink + i);
return false;
}
void
CPathFind::AddNodeToList(CPathNode *node, int32 listId)
{
int i = listId & 0x1FF;
node->SetNext(m_searchNodes[i].GetNext());
node->SetPrev(&m_searchNodes[i]);
if(m_searchNodes[i].GetNext())
m_searchNodes[i].GetNext()->SetPrev(node);
m_searchNodes[i].SetNext(node);
node->distance = listId;
}
void
CPathFind::RemoveNodeFromList(CPathNode *node)
{
node->GetPrev()->SetNext(node->GetNext());
if(node->GetNext())
node->GetNext()->SetPrev(node->GetPrev());
}
void
CPathFind::RemoveBadStartNode(CVector pos, CPathNode **nodes, int16 *n)
{
int i;
if(*n < 2)
return;
if(DotProduct2D(nodes[1]->GetPosition() - pos, nodes[0]->GetPosition() - pos) < 0.0f){
(*n)--;
for(i = 0; i < *n; i++)
nodes[i] = nodes[i+1];
}
}
#ifdef GTA_BRIDGE
void
CPathFind::SetLinksBridgeLights(float x1, float x2, float y1, float y2, bool enable)
{
int i;
for(i = 0; i < m_numCarPathLinks; i++){
CVector2D pos = m_carPathLinks[i].GetPosition();
if(x1 < pos.x && pos.x < x2 &&
y1 < pos.y && pos.y < y2)
m_carPathLinks[i].bBridgeLights = enable;
}
}
#endif
void
CPathFind::SwitchOffNodeAndNeighbours(int32 nodeId, bool disable)
{
int i, next;
m_pathNodes[nodeId].bDisabled = disable;
if(m_pathNodes[nodeId].numLinks < 3)
for(i = 0; i < m_pathNodes[nodeId].numLinks; i++){
next = ConnectedNode(m_pathNodes[nodeId].firstLink + i);
if(m_pathNodes[next].bDisabled != disable &&
m_pathNodes[next].numLinks < 3)
SwitchOffNodeAndNeighbours(next, disable);
}
}
void
CPathFind::SwitchRoadsOffInArea(float x1, float x2, float y1, float y2, float z1, float z2, bool disable)
{
int i;
for(i = 0; i < m_numCarPathNodes; i++){
CVector pos = m_pathNodes[i].GetPosition();
if(x1 <= pos.x && pos.x <= x2 &&
y1 <= pos.y && pos.y <= y2 &&
z1 <= pos.z && pos.z <= z2 &&
disable != m_pathNodes[i].bDisabled)
SwitchOffNodeAndNeighbours(i, disable);
}
}
void
CPathFind::SwitchPedRoadsOffInArea(float x1, float x2, float y1, float y2, float z1, float z2, bool disable)
{
int i;
for(i = m_numCarPathNodes; i < m_numPathNodes; i++){
CVector pos = m_pathNodes[i].GetPosition();
if(x1 <= pos.x && pos.x <= x2 &&
y1 <= pos.y && pos.y <= y2 &&
z1 <= pos.z && pos.z <= z2 &&
disable != m_pathNodes[i].bDisabled)
SwitchOffNodeAndNeighbours(i, disable);
}
}
void
CPathFind::SwitchRoadsInAngledArea(float x1, float y1, float z1, float x2, float y2, float z2, float length, uint8 type, uint8 mode)
{
int i;
int firstNode, lastNode;
// this is NOT PATH_CAR
if(type != 0){
firstNode = 0;
lastNode = m_numCarPathNodes;
}else{
firstNode = m_numCarPathNodes;
lastNode = m_numPathNodes;
}
if(z1 > z2){
float tmp = z2;
z2 = z1;
z1 = tmp;
}
// angle of vector from p2 to p1
float angle = CGeneral::GetRadianAngleBetweenPoints(x1, y1, x2, y2) + HALFPI;
while(angle < 0.0f) angle += TWOPI;
while(angle > TWOPI) angle -= TWOPI;
// vector from p1 to p2
CVector2D v12(x2 - x1, y2 - y1);
float len12 = v12.Magnitude();
v12 /= len12;
// vector from p2 to new point p3
CVector2D v23(Sin(angle)*length, -(Cos(angle)*length));
v23 /= v23.Magnitude(); // obivously just 'length' but whatever
bool disable = mode == SWITCH_OFF;
for(i = firstNode; i < lastNode; i++){
CVector pos = m_pathNodes[i].GetPosition();
if(pos.z < z1 || pos.z > z2)
continue;
CVector2D d(pos.x - x1, pos.y - y1);
float dot = DotProduct2D(d, v12);
if(dot < 0.0f || dot > len12)
continue;
dot = DotProduct2D(d, v23);
if(dot < 0.0f || dot > length)
continue;
if(m_pathNodes[i].bDisabled != disable)
SwitchOffNodeAndNeighbours(i, disable);
}
}
void
CPathFind::MarkRoadsBetweenLevelsNodeAndNeighbours(int32 nodeId)
{
int i, next;
m_pathNodes[nodeId].bBetweenLevels = true;
if(m_pathNodes[nodeId].numLinks < 3)
for(i = 0; i < m_pathNodes[nodeId].numLinks; i++){
next = ConnectedNode(m_pathNodes[nodeId].firstLink + i);
if(!m_pathNodes[next].bBetweenLevels &&
m_pathNodes[next].numLinks < 3)
MarkRoadsBetweenLevelsNodeAndNeighbours(next);
}
}
void
CPathFind::MarkRoadsBetweenLevelsInArea(float x1, float x2, float y1, float y2, float z1, float z2)
{
int i;
for(i = 0; i < m_numPathNodes; i++){
CVector pos = m_pathNodes[i].GetPosition();
if(x1 < pos.x && pos.x < x2 &&
y1 < pos.y && pos.y < y2 &&
z1 < pos.z && pos.z < z2)
MarkRoadsBetweenLevelsNodeAndNeighbours(i);
}
}
void
CPathFind::PedMarkRoadsBetweenLevelsInArea(float x1, float x2, float y1, float y2, float z1, float z2)
{
int i;
for(i = m_numCarPathNodes; i < m_numPathNodes; i++){
CVector pos = m_pathNodes[i].GetPosition();
if(x1 < pos.x && pos.x < x2 &&
y1 < pos.y && pos.y < y2 &&
z1 < pos.z && pos.z < z2)
MarkRoadsBetweenLevelsNodeAndNeighbours(i);
}
}
int32
CPathFind::FindNodeClosestToCoors(CVector coors, uint8 type, float distLimit, bool ignoreDisabled, bool ignoreBetweenLevels)
{
int i;
int firstNode, lastNode;
float dist;
float closestDist = 10000.0f;
int closestNode = 0;
switch(type){
case PATH_CAR:
firstNode = 0;
lastNode = m_numCarPathNodes;
break;
case PATH_PED:
firstNode = m_numCarPathNodes;
lastNode = m_numPathNodes;
break;
}
for(i = firstNode; i < lastNode; i++){
if(ignoreDisabled && m_pathNodes[i].bDisabled) continue;
if(ignoreBetweenLevels && m_pathNodes[i].bBetweenLevels) continue;
switch(m_pathNodes[i].unkBits){
case 1:
case 2:
dist = Abs(m_pathNodes[i].GetX() - coors.x) +
Abs(m_pathNodes[i].GetY() - coors.y) +
3.0f*Abs(m_pathNodes[i].GetZ() - coors.z);
if(dist < closestDist){
closestDist = dist;
closestNode = i;
}
break;
}
}
return closestDist < distLimit ? closestNode : -1;
}
int32
CPathFind::FindNodeClosestToCoorsFavourDirection(CVector coors, uint8 type, float dirX, float dirY)
{
int i;
int firstNode, lastNode;
float dist, dX, dY;
NormalizeXY(dirX, dirY);
float closestDist = 10000.0f;
int closestNode = 0;
switch(type){
case PATH_CAR:
firstNode = 0;
lastNode = m_numCarPathNodes;
break;
case PATH_PED:
firstNode = m_numCarPathNodes;
lastNode = m_numPathNodes;
break;
}
for(i = firstNode; i < lastNode; i++){
switch(m_pathNodes[i].unkBits){
case 1:
case 2:
dX = m_pathNodes[i].GetX() - coors.x;
dY = m_pathNodes[i].GetY() - coors.y;
dist = Abs(dX) + Abs(dY) +
3.0f*Abs(m_pathNodes[i].GetZ() - coors.z);
if(dist < closestDist){
NormalizeXY(dX, dY);
dist -= (dX*dirX + dY*dirY - 1.0f)*20.0f;
if(dist < closestDist){
closestDist = dist;
closestNode = i;
}
}
break;
}
}
return closestNode;
}
float
CPathFind::FindNodeOrientationForCarPlacement(int32 nodeId)
{
if(m_pathNodes[nodeId].numLinks == 0)
return 0.0f;
CVector dir = m_pathNodes[ConnectedNode(m_pathNodes[nodeId].firstLink)].GetPosition() - m_pathNodes[nodeId].GetPosition();
dir.z = 0.0f;
dir.Normalise();
return RADTODEG(dir.Heading());
}
float
CPathFind::FindNodeOrientationForCarPlacementFacingDestination(int32 nodeId, float x, float y, bool towards)
{
int i;
CVector targetDir(x - m_pathNodes[nodeId].GetX(), y - m_pathNodes[nodeId].GetY(), 0.0f);
targetDir.Normalise();
CVector dir;
if(m_pathNodes[nodeId].numLinks == 0)
return 0.0f;
int bestNode = ConnectedNode(m_pathNodes[nodeId].firstLink);
#ifdef FIX_BUGS
float bestDot = towards ? -2.0f : 2.0f;
#else
int bestDot = towards ? -2 : 2; // why int?
#endif
for(i = 0; i < m_pathNodes[nodeId].numLinks; i++){
dir = m_pathNodes[ConnectedNode(m_pathNodes[nodeId].firstLink + i)].GetPosition() - m_pathNodes[nodeId].GetPosition();
dir.z = 0.0f;
dir.Normalise();
float angle = DotProduct2D(dir, targetDir);
if(towards){
if(angle > bestDot){
bestDot = angle;
bestNode = ConnectedNode(m_pathNodes[nodeId].firstLink + i);
}
}else{
if(angle < bestDot){
bestDot = angle;
bestNode = ConnectedNode(m_pathNodes[nodeId].firstLink + i);
}
}
}
dir = m_pathNodes[bestNode].GetPosition() - m_pathNodes[nodeId].GetPosition();
dir.z = 0.0f;
dir.Normalise();
return RADTODEG(dir.Heading());
}
bool
CPathFind::NewGenerateCarCreationCoors(float x, float y, float dirX, float dirY, float spawnDist, float angleLimit, bool forward, CVector *pPosition, int32 *pNode1, int32 *pNode2, float *pPositionBetweenNodes, bool ignoreDisabled)
{
int i, j;
int node1, node2;
float dist1, dist2, d1, d2;
if(m_numCarPathNodes == 0)
return false;
for(i = 0; i < 500; i++){
node1 = (CGeneral::GetRandomNumber()>>3) % m_numCarPathNodes;
if(m_pathNodes[node1].bDisabled && !ignoreDisabled)
continue;
dist1 = Distance2D(m_pathNodes[node1].GetPosition(), x, y);
if(dist1 < spawnDist + 60.0f){
d1 = dist1 - spawnDist;
for(j = 0; j < m_pathNodes[node1].numLinks; j++){
node2 = ConnectedNode(m_pathNodes[node1].firstLink + j);
if(m_pathNodes[node2].bDisabled && !ignoreDisabled)
continue;
dist2 = Distance2D(m_pathNodes[node2].GetPosition(), x, y);
d2 = dist2 - spawnDist;
if(d1*d2 < 0.0f){
// nodes are on different sides of spawn distance
float f2 = Abs(d1)/(Abs(d1) + Abs(d2));
float f1 = 1.0f - f2;
*pPositionBetweenNodes = f2;
CVector pos = m_pathNodes[node1].GetPosition()*f1 + m_pathNodes[node2].GetPosition()*f2;
CVector2D dist2d(pos.x - x, pos.y - y);
dist2d.Normalise(); // done manually in the game
float dot = DotProduct2D(dist2d, CVector2D(dirX, dirY));
if(forward){
if(dot > angleLimit){
*pNode1 = node1;
*pNode2 = node2;
*pPosition = pos;
return true;
}
}else{
if(dot <= angleLimit){
*pNode1 = node1;
*pNode2 = node2;
*pPosition = pos;
return true;
}
}
}
}
}
}
return false;
}
bool
CPathFind::GeneratePedCreationCoors(float x, float y, float minDist, float maxDist, float minDistOffScreen, float maxDistOffScreen, CVector *pPosition, int32 *pNode1, int32 *pNode2, float *pPositionBetweenNodes, CMatrix *camMatrix)
{
int i;
int node1, node2;
if(m_numPedPathNodes == 0)
return false;
for(i = 0; i < 400; i++){
node1 = m_numCarPathNodes + CGeneral::GetRandomNumber() % m_numPedPathNodes;
if(DistanceSqr2D(m_pathNodes[node1].GetPosition(), x, y) < sq(maxDist+30.0f)){
if(m_pathNodes[node1].numLinks == 0)
continue;
int link = m_pathNodes[node1].firstLink + CGeneral::GetRandomNumber() % m_pathNodes[node1].numLinks;
if(ConnectionCrossesRoad(link))
continue;
node2 = ConnectedNode(link);
if(m_pathNodes[node1].bDisabled || m_pathNodes[node2].bDisabled)
continue;
float f2 = (CGeneral::GetRandomNumber()&0xFF)/256.0f;
float f1 = 1.0f - f2;
*pPositionBetweenNodes = f2;
CVector pos = m_pathNodes[node1].GetPosition()*f1 + m_pathNodes[node2].GetPosition()*f2;
if(Distance2D(pos, x, y) < maxDist+20.0f){
pos.x += ((CGeneral::GetRandomNumber()&0xFF)-128)*0.01f;
pos.y += ((CGeneral::GetRandomNumber()&0xFF)-128)*0.01f;
float dist = Distance2D(pos, x, y);
bool visible;
if(camMatrix)
visible = TheCamera.IsSphereVisible(pos, 2.0f, camMatrix);
else
visible = TheCamera.IsSphereVisible(pos, 2.0f);
if(!visible){
minDist = minDistOffScreen;
maxDist = maxDistOffScreen;
}
if(minDist < dist && dist < maxDist){
*pNode1 = node1;
*pNode2 = node2;
*pPosition = pos;
bool found;
float groundZ = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z+2.0f, &found);
if(!found)
return false;
if(Abs(groundZ - pos.z) > 3.0f)
return false;
pPosition->z = groundZ;
return true;
}
}
}
}
return false;
}
CTreadable*
CPathFind::FindRoadObjectClosestToCoors(CVector coors, uint8 type)
{
int i, j, k;
int node1, node2;
CTreadable *closestMapObj = nil;
float closestDist = 10000.0f;
for(i = 0; i < m_numMapObjects; i++){
CTreadable *mapObj = m_mapObjects[i];
if(mapObj->m_nodeIndices[type][0] < 0)
continue;
CVector vDist = mapObj->GetPosition() - coors;
float fDist = Abs(vDist.x) + Abs(vDist.y) + Abs(vDist.z);
if(fDist < 200.0f || fDist < closestDist)
for(j = 0; j < 12; j++){
node1 = mapObj->m_nodeIndices[type][j];
if(node1 < 0)
break;
// FIX: game uses ThePaths here explicitly
for(k = 0; k < m_pathNodes[node1].numLinks; k++){
node2 = ConnectedNode(m_pathNodes[node1].firstLink + k);
float lineDist = CCollision::DistToLine(&m_pathNodes[node1].GetPosition(), &m_pathNodes[node2].GetPosition(), &coors);
if(lineDist < closestDist){
closestDist = lineDist;
if((coors - m_pathNodes[node1].GetPosition()).MagnitudeSqr() < (coors - m_pathNodes[node2].GetPosition()).MagnitudeSqr())
closestMapObj = m_mapObjects[m_pathNodes[node1].objectIndex];
else
closestMapObj = m_mapObjects[m_pathNodes[node2].objectIndex];
}
}
}
}
return closestMapObj;
}
void
CPathFind::FindNextNodeWandering(uint8 type, CVector coors, CPathNode **lastNode, CPathNode **nextNode, uint8 curDir, uint8 *nextDir)
{
int i;
CPathNode *node;
if(lastNode == nil || (node = *lastNode) == nil || (coors - (*lastNode)->GetPosition()).MagnitudeSqr() > 7.0f){
// need to find the node we're coming from
node = nil;
CTreadable *obj = FindRoadObjectClosestToCoors(coors, type);
float nodeDist = 1000000000.0f;
for(i = 0; i < 12; i++){
if(obj->m_nodeIndices[type][i] < 0)
break;
float dist = (coors - m_pathNodes[obj->m_nodeIndices[type][i]].GetPosition()).MagnitudeSqr();
if(dist < nodeDist){
nodeDist = dist;
node = &m_pathNodes[obj->m_nodeIndices[type][i]];
}
}
}
CVector2D vCurDir(Sin(curDir*PI/4.0f), Cos(curDir * PI / 4.0f));
*nextNode = 0;
float bestDot = -999999.0f;
for(i = 0; i < node->numLinks; i++){
int next = ConnectedNode(node->firstLink+i);
if(!node->bDisabled && m_pathNodes[next].bDisabled)
continue;
CVector pedCoors = coors;
pedCoors.z += 1.0f;
CVector nodeCoors = m_pathNodes[next].GetPosition();
nodeCoors.z += 1.0f;
if(!CWorld::GetIsLineOfSightClear(pedCoors, nodeCoors, true, false, false, false, false, false))
continue;
CVector2D nodeDir = m_pathNodes[next].GetPosition() - node->GetPosition();
nodeDir.Normalise();
float dot = DotProduct2D(nodeDir, vCurDir);
if(dot >= bestDot){
*nextNode = &m_pathNodes[next];
bestDot = dot;
// direction is 0, 2, 4, 6 for north, east, south, west
// this could be done simpler...
if(nodeDir.x < 0.0f){
if(2.0f*Abs(nodeDir.y) < -nodeDir.x)
*nextDir = 6; // west
else if(-2.0f*nodeDir.x < nodeDir.y)
*nextDir = 0; // north
else if(2.0f*nodeDir.x > nodeDir.y)
*nextDir = 4; // south
else if(nodeDir.y > 0.0f)
*nextDir = 7; // north west
else
*nextDir = 5; // south west`
}else{
if(2.0f*Abs(nodeDir.y) < nodeDir.x)
*nextDir = 2; // east
else if(2.0f*nodeDir.x < nodeDir.y)
*nextDir = 0; // north
else if(-2.0f*nodeDir.x > nodeDir.y)
*nextDir = 4; // south
else if(nodeDir.y > 0.0f)
*nextDir = 1; // north east
else
*nextDir = 3; // south east`
}
}
}
if(*nextNode == nil){
*nextDir = 0;
*nextNode = node;
}
}
static CPathNode *apNodesToBeCleared[4995];
void
CPathFind::DoPathSearch(uint8 type, CVector start, int32 startNodeId, CVector target, CPathNode **nodes, int16 *pNumNodes, int16 maxNumNodes, CVehicle *vehicle, float *pDist, float distLimit, int32 targetNodeId)
{
int i, j;
// Find target
if(targetNodeId < 0)
targetNodeId = FindNodeClosestToCoors(target, type, distLimit);
if(targetNodeId < 0) {
*pNumNodes = 0;
if(pDist) *pDist = 100000.0f;
return;
}
// Find start
int numPathsToTry;
CTreadable *startObj;
if(startNodeId < 0){
if(vehicle == nil || (startObj = vehicle->m_treadable[type]) == nil)
startObj = FindRoadObjectClosestToCoors(start, type);
numPathsToTry = 0;
for(i = 0; i < 12; i++){
if(startObj->m_nodeIndices[type][i] < 0)
break;
if(m_pathNodes[startObj->m_nodeIndices[type][i]].group == m_pathNodes[targetNodeId].group)
numPathsToTry++;
}
}else{
numPathsToTry = 1;
startObj = m_mapObjects[m_pathNodes[startNodeId].objectIndex];
}
if(numPathsToTry == 0) {
*pNumNodes = 0;
if(pDist) *pDist = 100000.0f;
return;
}
if(startNodeId < 0){
// why only check node 0?
if(m_pathNodes[startObj->m_nodeIndices[type][0]].group !=
m_pathNodes[targetNodeId].group) {
*pNumNodes = 0;
if(pDist) *pDist = 100000.0f;
return;
}
}else{
if(m_pathNodes[startNodeId].group != m_pathNodes[targetNodeId].group) {
*pNumNodes = 0;
if(pDist) *pDist = 100000.0f;
return;
}
}
for(i = 0; i < ARRAY_SIZE(m_searchNodes); i++)
m_searchNodes[i].SetNext(nil);
AddNodeToList(&m_pathNodes[targetNodeId], 0);
int numNodesToBeCleared = 0;
apNodesToBeCleared[numNodesToBeCleared++] = &m_pathNodes[targetNodeId];
// Dijkstra's algorithm
// Find distances
int numPathsFound = 0;
if(startNodeId < 0 && m_mapObjects[m_pathNodes[targetNodeId].objectIndex] == startObj)
numPathsFound++;
for(i = 0; numPathsFound < numPathsToTry; i = (i+1) & 0x1FF){
CPathNode *node;
for(node = m_searchNodes[i].GetNext(); node; node = node->GetNext()){
if(m_mapObjects[node->objectIndex] == startObj &&
(startNodeId < 0 || node == &m_pathNodes[startNodeId]))
numPathsFound++;
for(j = 0; j < node->numLinks; j++){
int next = ConnectedNode(node->firstLink + j);
int dist = node->distance + m_distances[node->firstLink + j];
if(dist < m_pathNodes[next].distance){
if(m_pathNodes[next].distance != MAX_DIST)
RemoveNodeFromList(&m_pathNodes[next]);
if(m_pathNodes[next].distance == MAX_DIST)
apNodesToBeCleared[numNodesToBeCleared++] = &m_pathNodes[next];
AddNodeToList(&m_pathNodes[next], dist);
}
}
RemoveNodeFromList(node);
}
}
// Find out whence to start tracing back
CPathNode *curNode;
if(startNodeId < 0){
int minDist = MAX_DIST;
*pNumNodes = 1;
for(i = 0; i < 12; i++){
if(startObj->m_nodeIndices[type][i] < 0)
break;
int dist = (m_pathNodes[startObj->m_nodeIndices[type][i]].GetPosition() - start).Magnitude();
if(m_pathNodes[startObj->m_nodeIndices[type][i]].distance + dist < minDist){
minDist = m_pathNodes[startObj->m_nodeIndices[type][i]].distance + dist;
curNode = &m_pathNodes[startObj->m_nodeIndices[type][i]];
}
}
if(maxNumNodes == 0){
*pNumNodes = 0;
}else{
nodes[0] = curNode;
*pNumNodes = 1;
}
if(pDist)
*pDist = minDist;
}else
{
curNode = &m_pathNodes[startNodeId];
*pNumNodes = 0;
if(pDist)
*pDist = m_pathNodes[startNodeId].distance;
}
// Trace back to target and update list of nodes
while(*pNumNodes < maxNumNodes && curNode != &m_pathNodes[targetNodeId])
for(i = 0; i < curNode->numLinks; i++){
int next = ConnectedNode(curNode->firstLink + i);
if(curNode->distance - m_distances[curNode->firstLink + i] == m_pathNodes[next].distance){
curNode = &m_pathNodes[next];
nodes[(*pNumNodes)++] = curNode;
i = 29030; // could have used a break...
}
}
for(i = 0; i < numNodesToBeCleared; i++)
apNodesToBeCleared[i]->distance = MAX_DIST;
return;
}
static CPathNode *pNodeList[32];
static int16 DummyResult;
static int16 DummyResult2;
bool
CPathFind::TestCoorsCloseness(CVector target, uint8 type, CVector start)
{
float dist;
if(type == PATH_CAR)
DoPathSearch(type, start, -1, target, pNodeList, &DummyResult, 32, nil, &dist, 999999.88f, -1);
else
DoPathSearch(type, start, -1, target, nil, &DummyResult2, 0, nil, &dist, 50.0f, -1);
if(type == PATH_CAR)
return dist < 160.0f;
else
return dist < 100.0f;
}
void
CPathFind::Save(uint8 *buf, uint32 *size)
{
int i;
int n = m_numPathNodes/8 + 1;
*size = 2*n;
for(i = 0; i < m_numPathNodes; i++)
if(m_pathNodes[i].bDisabled)
buf[i/8] |= 1 << i%8;
else
buf[i/8] &= ~(1 << i%8);
for(i = 0; i < m_numPathNodes; i++)
if(m_pathNodes[i].bBetweenLevels)
buf[i/8 + n] |= 1 << i%8;
else
buf[i/8 + n] &= ~(1 << i%8);
}
void
CPathFind::Load(uint8 *buf, uint32 size)
{
int i;
int n = m_numPathNodes/8 + 1;
for(i = 0; i < m_numPathNodes; i++)
if(buf[i/8] & (1 << i%8))
m_pathNodes[i].bDisabled = true;
else
m_pathNodes[i].bDisabled = false;
for(i = 0; i < m_numPathNodes; i++)
if(buf[i/8 + n] & (1 << i%8))
m_pathNodes[i].bBetweenLevels = true;
else
m_pathNodes[i].bBetweenLevels = false;
}
void
CPathFind::DisplayPathData(void)
{
// Not the function from mobm_carPathLinksile but my own!
int i, j, k;
// Draw 50 units around camera
CVector pos = TheCamera.GetPosition();
const float maxDist = 50.0f;
// Render car path nodes
if(gbShowCarPaths)
for(i = 0; i < m_numCarPathNodes; i++){
if((m_pathNodes[i].GetPosition() - pos).MagnitudeSqr() > SQR(maxDist))
continue;
CVector n1 = m_pathNodes[i].GetPosition();
n1.z += 0.3f;
// Draw node itself
CLines::RenderLineWithClipping(n1.x, n1.y, n1.z,
n1.x, n1.y, n1.z + 1.0f,
0xFFFFFFFF, 0xFFFFFFFF);
for(j = 0; j < m_pathNodes[i].numLinks; j++){
k = ConnectedNode(m_pathNodes[i].firstLink + j);
CVector n2 = m_pathNodes[k].GetPosition();
n2.z += 0.3f;
// Draw links to neighbours
CLines::RenderLineWithClipping(n1.x, n1.y, n1.z,
n2.x, n2.y, n2.z,
0xFFFFFFFF, 0xFFFFFFFF);
}
}
// Render car path nodes
if(gbShowCarPathsLinks)
for(i = 0; i < m_numCarPathLinks; i++){
CVector2D n1_2d = m_carPathLinks[i].GetPosition();
if((n1_2d - pos).MagnitudeSqr() > SQR(maxDist))
continue;
int ni = m_carPathLinks[i].pathNodeIndex;
CVector pn1 = m_pathNodes[ni].GetPosition();
pn1.z += 0.3f;
CVector n1(n1_2d.x, n1_2d.y, pn1.z);
n1.z += 0.3f;
// Draw car node itself
CLines::RenderLineWithClipping(n1.x, n1.y, n1.z,
n1.x, n1.y, n1.z + 1.0f,
0xFFFFFFFF, 0xFFFFFFFF);
CLines::RenderLineWithClipping(n1.x, n1.y, n1.z + 0.5f,
n1.x+m_carPathLinks[i].GetDirX(), n1.y+m_carPathLinks[i].GetDirY(), n1.z + 0.5f,
0xFFFFFFFF, 0xFFFFFFFF);
// Draw connection to car path node
CLines::RenderLineWithClipping(n1.x, n1.y, n1.z,
pn1.x, pn1.y, pn1.z,
0xFF0000FF, 0xFFFFFFFF);
// traffic light type
uint32 col = 0xFF;
if((m_carPathLinks[i].trafficLightType&0x7F) == 1)
col += 0xFF000000;
if((m_carPathLinks[i].trafficLightType&0x7F) == 2)
col += 0x00FF0000;
if(m_carPathLinks[i].trafficLightType & 0x80)
col += 0x0000FF00;
CLines::RenderLineWithClipping(n1.x+0.2f, n1.y, n1.z,
n1.x+0.2f, n1.y, n1.z + 1.0f,
col, col);
for(j = 0; j < m_pathNodes[ni].numLinks; j++){
k = m_carPathConnections[m_pathNodes[ni].firstLink + j];
CVector2D n2_2d = m_carPathLinks[k].GetPosition();
int nk = m_carPathLinks[k].pathNodeIndex;
CVector pn2 = m_pathNodes[nk].GetPosition();
pn2.z += 0.3f;
CVector n2(n2_2d.x, n2_2d.y, pn2.z);
n2.z += 0.3f;
// Draw links to neighbours
CLines::RenderLineWithClipping(n1.x, n1.y, n1.z,
n2.x, n2.y, n2.z,
0xFF00FFFF, 0xFF00FFFF);
}
}
// Render ped path nodes
if(gbShowPedPaths)
for(i = m_numCarPathNodes; i < m_numPathNodes; i++){
if((m_pathNodes[i].GetPosition() - pos).MagnitudeSqr() > SQR(maxDist))
continue;
CVector n1 = m_pathNodes[i].GetPosition();
n1.z += 0.3f;
// Draw node itself
CLines::RenderLineWithClipping(n1.x, n1.y, n1.z,
n1.x, n1.y, n1.z + 1.0f,
0xFFFFFFFF, 0xFFFFFFFF);
for(j = 0; j < m_pathNodes[i].numLinks; j++){
k = ConnectedNode(m_pathNodes[i].firstLink + j);
CVector n2 = m_pathNodes[k].GetPosition();
n2.z += 0.3f;
// Draw links to neighbours
CLines::RenderLineWithClipping(n1.x, n1.y, n1.z,
n2.x, n2.y, n2.z,
0xFFFFFFFF, 0xFFFFFFFF);
// Draw connection flags
CVector mid = (n1+n2)/2.0f;
uint32 col = 0xFF;
if(ConnectionCrossesRoad(m_pathNodes[i].firstLink + j))
col += 0x00FF0000;
if(ConnectionHasTrafficLight(m_pathNodes[i].firstLink + j))
col += 0xFF000000;
CLines::RenderLineWithClipping(mid.x, mid.y, mid.z,
mid.x, mid.y, mid.z + 1.0f,
col, col);
}
}
}