2170 lines
70 KiB
C++
2170 lines
70 KiB
C++
//=============================================================================
|
|
// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved.
|
|
//
|
|
// File: TrafficManager.cpp
|
|
//
|
|
// Description: Implement TrafficManager
|
|
//
|
|
// History: 09/09/2002 + Spawning/Removing vehicles -- Dusit Eakkachaichanvet
|
|
// 04/07/2002 + Created -- Cary Brisebois
|
|
//
|
|
//=============================================================================
|
|
|
|
//========================================
|
|
// System Includes
|
|
//========================================
|
|
// Foundation Tech
|
|
#include <raddebug.hpp>
|
|
#include <raddebugwatch.hpp>
|
|
#include <stdlib.h>
|
|
|
|
//========================================
|
|
// Project Includes
|
|
//========================================
|
|
#include <mission/gameplaymanager.h>
|
|
|
|
#include <camera/supercammanager.h>
|
|
#include <worldsim/traffic/TrafficManager.h>
|
|
#include <worldsim/redbrick/vehicle.h>
|
|
#include <worldsim/redbrick/trafficlocomotion.h>
|
|
#include <memory/srrmemory.h>
|
|
#include <debug/profiler.h>
|
|
#include <worldsim/character/character.h>
|
|
#include <worldsim/avatar.h>
|
|
#include <worldsim/avatarmanager.h>
|
|
#include <worldsim/redbrick/geometryvehicle.h>
|
|
#include <worldsim/vehiclecentral.h>
|
|
|
|
#include <ai/vehicle/trafficai.h>
|
|
|
|
#include <roads/roadmanager.h>
|
|
#include <roads/roadmanager.h>
|
|
#include <roads/road.h>
|
|
#include <roads/roadsegment.h>
|
|
#include <roads/roadsegmentdata.h>
|
|
#include <roads/lane.h>
|
|
#include <roads/intersection.h>
|
|
#include <roads/roadrendertest.h>
|
|
|
|
#include <render/Culling/ReserveArray.h>
|
|
#include <render/IntersectManager/IntersectManager.h>
|
|
|
|
//******************************************************************************
|
|
//
|
|
// Global Data, Local Data, Local Classes
|
|
//
|
|
//******************************************************************************
|
|
TrafficManager* TrafficManager::mInstance = NULL;
|
|
|
|
//#define TRAFFIC_TEST
|
|
#ifdef TRAFFIC_TEST
|
|
const float TrafficManager::FADE_RADIUS = 10.0f;
|
|
const float TrafficManager::ADD_RADIUS = 30.0f;
|
|
const float TrafficManager::CENTER_OFFSET = 20.0f;
|
|
const float TrafficManager::REMOVE_RADIUS = 35.0f;
|
|
const float TrafficManager::INITIAL_ADD_RADIUS = 20.0f;
|
|
#else
|
|
const float TrafficManager::FADE_RADIUS = 85.0;
|
|
const float TrafficManager::CENTER_OFFSET = 40.0f;
|
|
const float TrafficManager::ADD_RADIUS = 65.0f;
|
|
const float TrafficManager::REMOVE_RADIUS = 75.0f;
|
|
const float TrafficManager::INITIAL_ADD_RADIUS = 100.0f;
|
|
#endif
|
|
|
|
const unsigned int TrafficManager::MILLISECONDS_BETWEEN_REMOVE = 200;
|
|
const unsigned int TrafficManager::MILLISECONDS_BETWEEN_ADD = 200;
|
|
const unsigned int TrafficManager::MILLISECONDS_POPULATE_WORLD = 3000;
|
|
|
|
const unsigned int MILLISECONDS_STUNNED_AFTER_DEACTIVATED = 3000;
|
|
|
|
const int MAX_TRAFFIC = 5;
|
|
|
|
// Define all the swatch colours here...
|
|
TrafficManager::SwatchColour TrafficManager::sSwatchColours[] =
|
|
{
|
|
{148, 45, 12},
|
|
{133, 124, 4},
|
|
{110, 84, 145},
|
|
{170, 43, 74},
|
|
{48, 11, 102},
|
|
|
|
{110, 30, 32},
|
|
{140, 125, 207},
|
|
{195, 98, 31},
|
|
{122, 50, 69},
|
|
{148, 191, 229},
|
|
|
|
{0, 159, 123},
|
|
{0, 75, 132},
|
|
{43, 177, 166},
|
|
{250, 166, 23},
|
|
{172, 81, 127},
|
|
|
|
{185, 162, 232},
|
|
{229, 222, 33},
|
|
{163, 203, 60},
|
|
{213, 142, 210},
|
|
{255, 239, 158},
|
|
|
|
{122, 43, 103},
|
|
{181, 133, 70},
|
|
{68, 106, 171},
|
|
{59, 149, 36},
|
|
{205, 94, 0}
|
|
};
|
|
|
|
//******************************************************************************
|
|
//
|
|
// Public Member Functions
|
|
//
|
|
//******************************************************************************
|
|
void TrafficManager::InitDefaultModelGroups()
|
|
{
|
|
for( int i=0; i<TrafficManager::MAX_TRAFFIC_MODEL_GROUPS; i++ )
|
|
{
|
|
// WORKING ONES
|
|
//mTrafficModelGroups[i].AddTrafficModel( "minivanA", 5 );
|
|
/*
|
|
mTrafficModelGroups[i].AddTrafficModel( "compactA", 1 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "pickupA", 1 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "sportsA", 2 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "SUVA", 1 );
|
|
*/
|
|
/*
|
|
mTrafficModelGroups[i].AddTrafficModel( "coffin", 2 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "hallo",1 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "ship", 1 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "witchcar", 1 );
|
|
*/
|
|
|
|
/*
|
|
mTrafficModelGroups[i].AddTrafficModel( "sportsB", 1 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "wagonA", 1 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "minivanA", 1 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "taxiA", 2 );
|
|
*/
|
|
/*
|
|
mTrafficModelGroups[i].AddTrafficModel( "sedanA", 3 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "sedanB", 2 );
|
|
*/
|
|
//mTrafficModelGroups[i].AddTrafficModel( "ambul", 5 );
|
|
//mTrafficModelGroups[i].AddTrafficModel( "burnsarm", 5 );
|
|
//mTrafficModelGroups[i].AddTrafficModel( "fishtruc", 5 );
|
|
//mTrafficModelGroups[i].AddTrafficModel( "garbage", 5 );
|
|
//mTrafficModelGroups[i].AddTrafficModel( "icecream", 5 );
|
|
|
|
//mTrafficModelGroups[i].AddTrafficModel( "IStruck", 5 );
|
|
//mTrafficModelGroups[i].AddTrafficModel( "nuctruck", 5 );
|
|
//mTrafficModelGroups[i].AddTrafficModel( "pizza", 5 );
|
|
//mTrafficModelGroups[i].AddTrafficModel( "schoolbu", 5 );
|
|
//mTrafficModelGroups[i].AddTrafficModel( "votetruc", 5 );
|
|
|
|
// NOT TESTED
|
|
//mTrafficModelGroups[i].AddTrafficModel( "compactA", 5 );
|
|
|
|
// DEFAULTS
|
|
mTrafficModelGroups[i].AddTrafficModel( "compactA", 2 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "pickupA", 2 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "sportsA", 2 );
|
|
mTrafficModelGroups[i].AddTrafficModel( "SUVA", 2 );
|
|
}
|
|
mCurrTrafficModelGroup = 0;
|
|
}
|
|
|
|
|
|
ITrafficSpawnController* TrafficManager::GetSpawnController()
|
|
{
|
|
return (ITrafficSpawnController*) TrafficManager::GetInstance();
|
|
}
|
|
|
|
|
|
TrafficManager* TrafficManager::GetInstance()
|
|
{
|
|
MEMTRACK_PUSH_GROUP( "Traffic Manager" );
|
|
if ( !mInstance )
|
|
{
|
|
mInstance = new(GMA_LEVEL_OTHER) TrafficManager();
|
|
}
|
|
MEMTRACK_POP_GROUP( "Traffic Manager" );
|
|
return mInstance;
|
|
}
|
|
|
|
void TrafficManager::DestroyInstance()
|
|
{
|
|
delete mInstance;
|
|
mInstance = NULL;
|
|
}
|
|
|
|
void TrafficManager::HandleEvent( EventEnum id, void* pEventData )
|
|
{
|
|
if( id == EVENT_VEHICLE_DESTROYED )
|
|
{
|
|
// ignore it if it's not one of our vehicles...
|
|
Vehicle* v = (Vehicle*) pEventData;
|
|
SwapInTrafficHusk( v );
|
|
}
|
|
|
|
else if( id == EVENT_REPAIR_CAR )
|
|
{
|
|
//
|
|
// NOTE: When we want to support other cars than the user car
|
|
// picking up Wrench/repair icons in the main game, we should pass in
|
|
// the vehicle pointer when we trigger this event.
|
|
//
|
|
Vehicle* currVehicle = GetGameplayManager()->GetCurrentVehicle();
|
|
if( currVehicle == NULL )
|
|
{
|
|
return; // no current vehicle to repair.. oh well...
|
|
}
|
|
TrafficVehicle* tv = FindTrafficVehicle( currVehicle );
|
|
if( tv == NULL )
|
|
{
|
|
return; // not one of ours, don't worry...
|
|
}
|
|
|
|
|
|
// repair this vehicle..
|
|
if( currVehicle->mVehicleDestroyed )
|
|
{
|
|
bool usingHusk = false;
|
|
Vehicle* husk = tv->GetHusk();
|
|
if( husk )
|
|
{
|
|
usingHusk = true;
|
|
// damage is extensive... gotta replace husk with original vehicle
|
|
|
|
// obtain info from the husk
|
|
rmt::Vector initPos, initDir;
|
|
husk->GetPosition( &initPos );
|
|
initDir = husk->GetFacing();
|
|
rAssert( rmt::Epsilon( initDir.MagnitudeSqr(), 1.0f, 0.001f ) );
|
|
|
|
// remove husk
|
|
bool succeeded = GetVehicleCentral()->RemoveVehicleFromActiveList( husk );
|
|
rAssert( succeeded );
|
|
/*
|
|
GetVehicleCentral()->SetVehicleController( tv->mActiveListIndex, NULL );
|
|
tv->mActiveListIndex = -1;
|
|
*/
|
|
|
|
::GetVehicleCentral()->mHuskPool.FreeHusk( husk );
|
|
// restore fade alpha if we set it, so other vehicles don't get confused
|
|
husk->mGeometryVehicle->SetFadeAlpha( 255 );
|
|
husk->Release();
|
|
husk = NULL;
|
|
tv->SetHusk( NULL );
|
|
tv->SetHasHusk( false );
|
|
|
|
currVehicle->SetInitialPosition( &initPos );
|
|
float angle = GetRotationAboutY( initDir.x, initDir.z );
|
|
currVehicle->SetResetFacingInRadians( angle );
|
|
currVehicle->Reset();
|
|
//currVehicle->SetLocomotion( VL_PHYSICS );
|
|
|
|
}
|
|
|
|
// put in original vehicle
|
|
int res = GetVehicleCentral()->AddVehicleToActiveList( currVehicle );
|
|
if( res == -1 )
|
|
{
|
|
// not supposed to happen since the list can't be full!!!
|
|
// we TOOK something out of the list before adding something in
|
|
// If this assert happens, it is both fatal and strange
|
|
rAssert( false );
|
|
return;
|
|
}
|
|
//tv->mActiveListIndex = res;
|
|
//GetEventManager()->TriggerEvent( EVENT_TRAFFIC_SPAWN, currVehicle ); // tell sound
|
|
|
|
if( usingHusk )
|
|
{
|
|
Avatar* avatar = GetAvatarManager()->GetAvatarForPlayer( 0 );
|
|
rAssert( avatar );
|
|
|
|
// if the avatar is inside a vehicle the vehicle
|
|
// is probably a husk, update this vehicle to be the original
|
|
// vehicle and place the character in this new vehicle
|
|
//
|
|
if( avatar->IsInCar() )
|
|
{
|
|
rAssert( avatar->GetVehicle() );
|
|
rAssert( GetVehicleCentral()->mHuskPool.IsHuskType( avatar->GetVehicle()->mVehicleID ) );
|
|
|
|
avatar->SetVehicle( currVehicle );
|
|
|
|
Character* character = avatar->GetCharacter();
|
|
GetAvatarManager()->PutCharacterInCar( character, currVehicle );
|
|
}
|
|
}
|
|
|
|
// fire off event so Esan can know when we switch the vehicle on him.
|
|
GetEventManager()->TriggerEvent( EVENT_VEHICLE_DESTROYED_SYNC_SOUND, currVehicle );
|
|
|
|
}
|
|
// repair any damage to original vehicle
|
|
bool resetDamage = true;
|
|
currVehicle->ResetFlagsOnly( resetDamage );
|
|
}
|
|
|
|
// If the player honks his horn, we trigger horn honking too for nearby traffic
|
|
else if( id == EVENT_PLAYER_VEHICLE_HORN )
|
|
{
|
|
// if queue for the last time we honked horn hasn't been cleared yet,
|
|
// don't do more...
|
|
if( mQueuedTrafficHorns.mUseSize > 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vehicle* playerVehicle = (Vehicle*) pEventData;
|
|
rAssert( playerVehicle );
|
|
|
|
rmt::Vector playerPos;
|
|
playerVehicle->GetPosition( &playerPos );
|
|
|
|
for( int i=0; i<MAX_TRAFFIC; ++i )
|
|
{
|
|
TrafficVehicle* traffV = &mVehicles[i];
|
|
if( !traffV->GetIsActive() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( mQueuedTrafficHorns.mUseSize >= MAX_QUEUED_TRAFFIC_HORNS )
|
|
{
|
|
break;
|
|
}
|
|
|
|
rmt::Vector traffPos;
|
|
traffV->GetVehicle()->GetPosition( &traffPos );
|
|
|
|
// within n meters; make sure n is less than traffic remove radius
|
|
const float CLOSE_ENOUGH_TO_HONK_BACK_SQR = 625.0f;
|
|
|
|
float distSqr = (traffPos - playerPos).MagnitudeSqr();
|
|
if( distSqr < CLOSE_ENOUGH_TO_HONK_BACK_SQR )
|
|
{
|
|
int coinflip = rand() % 10; // 1 in n chance of honking back
|
|
if( coinflip == 0 )
|
|
{
|
|
int delayInMilliseconds = rand() % 300 + 500;
|
|
|
|
TrafficHornQueue tmp;
|
|
tmp.delayInMilliseconds = static_cast<unsigned int>( delayInMilliseconds );
|
|
tmp.vehicle = traffV->GetVehicle();
|
|
|
|
mQueuedTrafficHorns.Add( tmp );
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void TrafficManager::Init()
|
|
{
|
|
//////////////////////////////////////////
|
|
// Do normal cleaning up...
|
|
Cleanup();
|
|
|
|
//////////////////////////////////////////
|
|
// Initialize some members
|
|
mNumTraffic = 0;
|
|
mMillisecondsBetweenRemove = TrafficManager::MILLISECONDS_BETWEEN_REMOVE;
|
|
mMillisecondsBetweenAdd = 0;
|
|
mMillisecondsPopulateWorld = TrafficManager::MILLISECONDS_POPULATE_WORLD;
|
|
mDesiredTrafficSpeedKph = DetermineDesiredSpeedKph();
|
|
|
|
Vehicle* newV = NULL; // vehicle
|
|
Vehicle* newH = NULL; // vehicle husk
|
|
|
|
//////////////////////////////////////////
|
|
// Initialize Traffic cars
|
|
for( int i=0 ; i<MAX_TRAFFIC ; i++ )
|
|
{
|
|
TrafficVehicle* tv = &mVehicles[i];
|
|
rAssert( tv != NULL );
|
|
|
|
newV = InitRandomVehicle();
|
|
rAssert( newV != NULL );
|
|
newV->AddRef();
|
|
newV->SetLocomotion( VL_TRAFFIC );
|
|
newV->mTrafficVehicle = tv;
|
|
|
|
tv->SetVehicle( newV );
|
|
tv->SetHusk( NULL );
|
|
}
|
|
|
|
#ifdef DEBUGWATCH
|
|
char nameSpace[64];
|
|
sprintf( nameSpace, "Traffic Manager" );
|
|
|
|
radDbgWatchAddFloat( &mDesiredTrafficSpeedKph,
|
|
"Global Traffic Target Speed",
|
|
nameSpace,
|
|
NULL,
|
|
NULL,
|
|
10.0f,
|
|
200.0f );
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
void TrafficManager::ClearOutOfSightTraffic()
|
|
{
|
|
static const unsigned int MAX_MILLISECONDS_OUT_OF_SIGHT_BEFORE_REMOVAL = 5000;
|
|
|
|
Vehicle* playerVehicle = GetGameplayManager()->GetCurrentVehicle();
|
|
|
|
for( int i=0; i<MAX_TRAFFIC; ++i )
|
|
{
|
|
TrafficVehicle* traffV = &mVehicles[i];
|
|
if( !traffV->GetIsActive() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if the player is using this vehicle, goddammit don't remove the thing
|
|
if( traffV->GetVehicle() == playerVehicle )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( traffV->mOutOfSight &&
|
|
traffV->mMillisecondsOutOfSight > MAX_MILLISECONDS_OUT_OF_SIGHT_BEFORE_REMOVAL )
|
|
{
|
|
RemoveTraffic( i ); // remove this car
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void TrafficManager::UpdateQueuedTrafficHorns( unsigned int milliseconds )
|
|
{
|
|
// update our queue of traffic horns
|
|
int i=0;
|
|
while( i < mQueuedTrafficHorns.mUseSize )
|
|
{
|
|
if( mQueuedTrafficHorns[i].delayInMilliseconds <= milliseconds )
|
|
{
|
|
// trigger the event already, sheesh!
|
|
GetEventManager()->TriggerEvent( EVENT_TRAFFIC_HORN,
|
|
mQueuedTrafficHorns[i].vehicle );
|
|
mQueuedTrafficHorns.Remove( i );
|
|
}
|
|
else
|
|
{
|
|
mQueuedTrafficHorns[i].delayInMilliseconds -= milliseconds;
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ******************************
|
|
//
|
|
// BIG UPDATE METHOD
|
|
//
|
|
// ******************************
|
|
|
|
void TrafficManager::Update( unsigned int milliseconds )
|
|
{
|
|
rAssert( 0 <= mNumTraffic && mNumTraffic <= MAX_TRAFFIC );
|
|
|
|
BEGIN_PROFILE( "Traffic Man" );
|
|
|
|
if( mMillisecondsPopulateWorld > milliseconds )
|
|
{
|
|
mMillisecondsPopulateWorld -= milliseconds;
|
|
}
|
|
else
|
|
{
|
|
mMillisecondsPopulateWorld = 0;
|
|
}
|
|
|
|
// play the queued up traffic horn sounds, if the time is right
|
|
UpdateQueuedTrafficHorns( milliseconds );
|
|
|
|
//
|
|
// ====================================
|
|
// Getting information about the player
|
|
// ====================================
|
|
//
|
|
|
|
Vehicle* v = NULL;
|
|
|
|
rmt::Vector pPos, pVel;//, pDir;
|
|
float pSpeed;
|
|
|
|
Avatar* avatar = GetAvatarManager()->GetAvatarForPlayer( 0 );
|
|
rAssert( avatar != NULL );
|
|
// Avatar::GetPosition() will return position of vehicle if avatar inside
|
|
// vehicle. Same deal with Avatar::GetVelocity() & GetSpeedMps()
|
|
// Remember that we should use VELOCITY rather than facing because
|
|
// the player can face one way and travel in reverse.
|
|
//
|
|
avatar->GetPosition(pPos);
|
|
avatar->GetVelocity(pVel);
|
|
pSpeed = avatar->GetSpeedMps();
|
|
|
|
// Get the camera for Player 1.
|
|
// It's what we need to apply the center offset to our spawn & remove radii
|
|
SuperCam* pCam = GetSuperCamManager()->GetSCC(0)->GetActiveSuperCam();
|
|
rmt::Vector camTarget;
|
|
pCam->GetHeadingNormalized( &camTarget );
|
|
rAssert( rmt::Epsilon(camTarget.MagnitudeSqr(), 1.0f, 0.0005f) );
|
|
|
|
rmt::Vector center;
|
|
if( mMillisecondsPopulateWorld > 0 )
|
|
{
|
|
center = pPos;
|
|
}
|
|
else
|
|
{
|
|
center = pPos + camTarget * TrafficManager::CENTER_OFFSET;
|
|
}
|
|
|
|
/*
|
|
SuperCam* superCam = ::GetSuperCamManager()->GetSCC(0)->GetActiveSuperCam();
|
|
rAssert( superCam != NULL );
|
|
superCam->GetPosition( &pPos );
|
|
superCam->GetVelocity( &pVel );
|
|
pSpeed = pVel.Magnitude(); // *** SQUARE ROOT! ***
|
|
*/
|
|
/*
|
|
if( pSpeed > 0.001f )
|
|
{
|
|
pDir = pVel;
|
|
pDir.Scale(1.0f / pSpeed);
|
|
}
|
|
else
|
|
{
|
|
avatar->GetHeading( pDir );
|
|
}
|
|
rAssert( rmt::Epsilon( pDir.MagnitudeSqr(), 1.0f, 0.001f ) );
|
|
*/
|
|
|
|
// for sphere intersection/containment testing
|
|
float minDistSqr = 0.0f;
|
|
rmt::Vector distVec;
|
|
|
|
|
|
BEGIN_PROFILE( "Traffic Man: Remove" );
|
|
|
|
if( mMillisecondsBetweenRemove < milliseconds )
|
|
{
|
|
mMillisecondsBetweenRemove = MILLISECONDS_BETWEEN_REMOVE;
|
|
|
|
// don't remove if populating world
|
|
if( mMillisecondsPopulateWorld == 0 )
|
|
{
|
|
//
|
|
// ==================================
|
|
// Remove vehicles
|
|
// ===================================
|
|
// "Remove" from consideration vehicles that:
|
|
// - are no longer in the player's Traffic Radius, and/or
|
|
// - haven't been in player's view for some time
|
|
//
|
|
rmt::Sphere traffSphere;
|
|
traffSphere.Set( center, TrafficManager::REMOVE_RADIUS );
|
|
ClearTrafficOutsideSphere( traffSphere );
|
|
|
|
ClearOutOfSightTraffic();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mMillisecondsBetweenRemove -= milliseconds;
|
|
}
|
|
|
|
END_PROFILE( "Traffic Man: Remove" );
|
|
|
|
|
|
BEGIN_PROFILE( "Traffic Man: Add" );
|
|
|
|
//
|
|
// ==============================
|
|
// Adding cars to lanes as needed
|
|
// ==============================
|
|
// Imagine two overlapping circles. Circle1 is the traffic radius of your
|
|
// current position (center, radius), and Circle2 (center2, radius)
|
|
// is the traffic radius of your future position (based on velocity and
|
|
// assumption that it'll be just as long to reach the next loop as it took
|
|
// to reach the current loop).
|
|
//
|
|
if( mTrafficEnabled )
|
|
{
|
|
|
|
if( mMillisecondsBetweenAdd < milliseconds )
|
|
{
|
|
mMillisecondsBetweenAdd = MILLISECONDS_BETWEEN_ADD;
|
|
|
|
float addRadius = TrafficManager::ADD_RADIUS;
|
|
if( mMillisecondsPopulateWorld > 0 )
|
|
{
|
|
addRadius = TrafficManager::INITIAL_ADD_RADIUS;
|
|
}
|
|
|
|
rmt::Sphere pSphere( center, addRadius );
|
|
|
|
// FindRoadSegmentElems returns to us a list of RoadSegments whose bounding spheres
|
|
// come in contact with the player's traffic radius. These contact points give us
|
|
// the segments on the FRINGE of the traffic radius, where can we add cars.
|
|
//
|
|
// For each point of intersection, we only place down a car IF:
|
|
// a) the candidate car's predicted position in the NEXT frame lies within Circle 2
|
|
// (player's traffic zone in the NEXT frame)
|
|
// AND
|
|
// b) the lane containing this segment has fewer cars on it than the desired
|
|
// density value.
|
|
//
|
|
// The reason for FindRoadSegmentElems (which consequently forced us to build DSG
|
|
// Tree out of RoadSegments, involving a lot of work) is that a lane isn't a
|
|
// straight line. It's comprised of meandering segments. A lane can enter and exit
|
|
// the player's Traffic Radius as many times as it desires.
|
|
//
|
|
ReserveArray<RoadSegment*> orList;
|
|
::GetIntersectManager()->FindRoadSegmentElems( center, addRadius, orList );
|
|
|
|
bool noMoreFreeTrafficVehicles = false;
|
|
|
|
for( int i=0; i<orList.mUseSize; i++ )
|
|
{
|
|
RoadSegment* segment;
|
|
unsigned int numLanes;
|
|
|
|
segment = orList.mpData[i];
|
|
rAssert( segment != NULL );
|
|
|
|
numLanes = segment->GetNumLanes();
|
|
rAssert( numLanes >= 1 );
|
|
|
|
// loop through all the lanes for this segment
|
|
for( unsigned int j=0; j<numLanes; j++ )
|
|
{
|
|
|
|
Road* road;
|
|
Lane* lane;
|
|
unsigned int nDesiredDensity;
|
|
|
|
road = segment->GetRoad();
|
|
rAssert( road != NULL );
|
|
|
|
lane = road->GetLane( j );
|
|
nDesiredDensity = lane->GetDensity();
|
|
|
|
// HACK:
|
|
// We only have 5 traffic cars man c'mon...
|
|
if(nDesiredDensity > 2 )
|
|
{
|
|
nDesiredDensity = 2;
|
|
}
|
|
|
|
// we add vehicle only if number on the lane < expected density
|
|
// AND if projected next position of the car is in the projected
|
|
// next position of the player's Traffic Radius
|
|
|
|
if( lane->mTrafficVehicles.mUseSize < (int)(nDesiredDensity) )
|
|
{
|
|
// Here we're determining where in the world to place the car.
|
|
// We try to place it at the lane location where it intersects
|
|
// with our traffic zone (pSphere).
|
|
|
|
rmt::Vector startPos, startDir, endPos, endDir;
|
|
segment->GetLaneLocation( 0.0f, j, startPos, startDir );
|
|
segment->GetLaneLocation( 1.0f, j, endPos, endDir );
|
|
|
|
rmt::Vector intPts[2];
|
|
int numIntersections = IntersectLineSphere( startPos, endPos, pSphere, intPts );
|
|
if(numIntersections <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
rmt::Vector cPos, cDir;
|
|
cDir.Sub( endPos, startPos );
|
|
cDir.Normalize(); // *** SQUARE ROOT! ***
|
|
|
|
// for each intersection point found, plant a vehicle
|
|
for( int k=0; k<numIntersections; k++ )
|
|
{
|
|
// HACK:
|
|
// Designers want maxtraffic to be always 5 if you're on foot
|
|
// unless mMaxTraffic is actually 0, in which case, we leave at
|
|
// zero.
|
|
int maxTrafficToUse = mMaxTraffic;
|
|
if( mMaxTraffic > 0 && !avatar->IsInCar())
|
|
{
|
|
maxTrafficToUse = MAX_TRAFFIC_MODEL_GROUPS;
|
|
}
|
|
|
|
if( mNumTraffic < maxTrafficToUse )
|
|
{
|
|
// set cPos;
|
|
cPos = intPts[k];
|
|
|
|
// see if we got any more vehicles...
|
|
TrafficVehicle* tv = this->GetFreeTrafficVehicle();
|
|
if( tv == NULL )
|
|
{
|
|
noMoreFreeTrafficVehicles = true;
|
|
break;
|
|
}
|
|
v = tv->GetVehicle();
|
|
rAssert( v != NULL );
|
|
|
|
|
|
//
|
|
// We should detect if we're placing this car on top of another car
|
|
// We need to search the entire ActiveVehicles list, not just the
|
|
// traffic cars in our lane, to take into account vehicles that
|
|
// are lying around on the road in VL_PHYSICS
|
|
//
|
|
int nActiveVehicles = 0;
|
|
Vehicle** activeVehicles = NULL;
|
|
|
|
VehicleCentral* vc;
|
|
vc = ::GetVehicleCentral();
|
|
vc->GetActiveVehicleList( activeVehicles, nActiveVehicles );
|
|
|
|
|
|
rmt::Sphere cSphere;
|
|
v->GetBoundingSphere( &cSphere );
|
|
|
|
|
|
bool tryNextPoint = false;
|
|
int vCount = 0;
|
|
for( int i=0; i<vc->GetMaxActiveVehicles(); i++ )
|
|
{
|
|
if( vCount >= nActiveVehicles )
|
|
{
|
|
break;
|
|
}
|
|
|
|
Vehicle* aCar = activeVehicles[i];
|
|
if( aCar == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
vCount++;
|
|
|
|
rmt::Vector aPos;
|
|
rmt::Sphere aSphere;
|
|
aCar->GetPosition( &aPos );
|
|
aCar->GetBoundingSphere( &aSphere );
|
|
|
|
float distSqr = (aPos - cPos).MagnitudeSqr();
|
|
|
|
// if same as our lane, make sure they're at least some
|
|
// lookahead distance away...
|
|
float minDist = 5.0f; // initial buffer...
|
|
if( aCar->mTrafficLocomotion->GetAILane() == lane )
|
|
{
|
|
/*
|
|
float lookAhead = aCar->mTrafficLocomotion->GetAISpeed() *
|
|
TrafficAI::SECONDS_LOOKAHEAD;
|
|
*/
|
|
float lookAhead = aCar->mTrafficLocomotion->mActualSpeed *
|
|
TrafficAI::SECONDS_LOOKAHEAD;
|
|
if( lookAhead < TrafficAI::LOOKAHEAD_MIN )
|
|
{
|
|
lookAhead = TrafficAI::LOOKAHEAD_MIN;
|
|
}
|
|
minDist += lookAhead;
|
|
}
|
|
else
|
|
{
|
|
minDist += aSphere.radius + cSphere.radius;
|
|
}
|
|
|
|
float minDistSqr = minDist * minDist;
|
|
if( distSqr < minDistSqr )
|
|
{
|
|
// if we're too near another car, don't spawn here...
|
|
tryNextPoint = true;
|
|
break;
|
|
}
|
|
}
|
|
if( tryNextPoint )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// Check if in NEXT few seconds, vehicle will still be in
|
|
// player's traffic zone.
|
|
//
|
|
const float SECONDS_LOOK_AHEAD = 1.5f;
|
|
|
|
// next frame player pos & traffic zone
|
|
rmt::Vector center2 = center + pVel * SECONDS_LOOK_AHEAD;
|
|
|
|
float speedLimit = lane->GetSpeedLimit();
|
|
rmt::Vector cVel = cDir * speedLimit;
|
|
rmt::Vector cPos2 = cPos + cVel * SECONDS_LOOK_AHEAD;
|
|
|
|
minDistSqr = TrafficManager::REMOVE_RADIUS * TrafficManager::REMOVE_RADIUS;
|
|
distVec.Sub( cPos2, center2 );
|
|
|
|
|
|
if( distVec.MagnitudeSqr() < minDistSqr )
|
|
{
|
|
|
|
bool succeeded = this->AddTraffic(lane, tv);
|
|
if( !succeeded )
|
|
{
|
|
// can't add more traffic to this lane, do next lane
|
|
continue;
|
|
}
|
|
|
|
|
|
// Determine which point on the segment we are at...
|
|
// Since we know that cPos is on the line between startPos & endPos
|
|
// of the segment, we only need to test one coordinate (that didn't
|
|
// remain the same, of course)... This should give us a good enough
|
|
// approximation (within 0.00001)
|
|
//
|
|
float segmentT = GetLineSegmentT( startPos, endPos, cPos );
|
|
rAssert( 0.0f <= segmentT && segmentT <= 1.0f );
|
|
|
|
// INITIALIZE
|
|
|
|
// Get a random color for this vehicle
|
|
pddiColour randomColour;
|
|
GenerateRandomColour( randomColour );
|
|
v->mGeometryVehicle->SetTrafficBodyColour( randomColour );
|
|
|
|
// initialize vehicle's position & facing
|
|
v->SetInitialPosition( &cPos );
|
|
float angle = GetRotationAboutY( cDir.x, cDir.z );
|
|
v->SetResetFacingInRadians( angle );
|
|
v->Reset();
|
|
v->mHijackedByUser = false;
|
|
|
|
// set up TrafficLocomotion/TrafficAI info
|
|
v->mTrafficLocomotion->Init();
|
|
v->mTrafficLocomotion->InitPos( cPos );
|
|
v->mTrafficLocomotion->InitFacing( cDir );
|
|
v->mTrafficLocomotion->InitVehicleAI( v );
|
|
v->mTrafficLocomotion->InitLane( lane, j, lane->GetSpeedLimit() );
|
|
v->mTrafficLocomotion->InitSegment( segment, segment->GetSegmentIndex(), segmentT );
|
|
v->mTrafficLocomotion->SetActive( true );
|
|
|
|
// determine initial speed for traffic.
|
|
// if far from road's end, go at desired speed
|
|
float speed = 0.0f;
|
|
rmt::Vector segEnd, segFacing;
|
|
RoadSegment* lastSeg = road->GetRoadSegment( road->GetNumRoadSegments()-1 );
|
|
lastSeg->GetLaneLocation( 1.0f, 0, segEnd, segFacing );
|
|
const float MIN_DIST_FROM_ROAD_END_SQR = 100.0f;
|
|
if( (cPos-segEnd).MagnitudeSqr() > MIN_DIST_FROM_ROAD_END_SQR )
|
|
{
|
|
speed = GetDesiredTrafficSpeed();
|
|
}
|
|
v->mTrafficLocomotion->SetAISpeed( speed );
|
|
|
|
v->mVehicleType = VT_TRAFFIC;
|
|
v->SetLocomotion(VL_TRAFFIC);
|
|
|
|
} // end if vehicle's next pos lies in radius of player's next traffic zone
|
|
}
|
|
}
|
|
if( noMoreFreeTrafficVehicles )
|
|
{
|
|
break;
|
|
}
|
|
} // end if(lane density < desired density)
|
|
|
|
} // end for-loop through all the lanes in a particular segment
|
|
|
|
if( noMoreFreeTrafficVehicles )
|
|
{
|
|
break;
|
|
}
|
|
|
|
} // end for-loop through all segments returned by DSGFind
|
|
}
|
|
else
|
|
{
|
|
mMillisecondsBetweenAdd -= milliseconds;
|
|
}
|
|
}
|
|
|
|
END_PROFILE( "Traffic Man: Add" );
|
|
|
|
BEGIN_PROFILE( "Traffic Man: Update AI & Intersections" );
|
|
|
|
//
|
|
// ============================================================================
|
|
// Go through the vehicles, updating
|
|
// ============================================================================
|
|
//
|
|
|
|
// at first, no intersection has been updated in this update call.
|
|
for( int i=0; i<MAX_INTERSECTIONS; i++ )
|
|
{
|
|
mpIntersections[i] = NULL;
|
|
}
|
|
int nIntersectionsUpdated = 0;
|
|
|
|
for( int i=0; i<MAX_TRAFFIC; ++i )
|
|
{
|
|
TrafficVehicle* traffV = &mVehicles[i];
|
|
if( !traffV->GetIsActive() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( traffV->HasHusk() )
|
|
{
|
|
v = traffV->GetHusk();
|
|
}
|
|
else
|
|
{
|
|
v = traffV->GetVehicle();
|
|
}
|
|
rAssert( v );
|
|
|
|
// Test for out of sight of player 0... If so, increment timer, if not reset timer
|
|
rmt::Vector vPos;
|
|
v->GetPosition( &vPos );
|
|
traffV->mOutOfSight = !GetGameplayManager()->TestPosInFrustrumOfPlayer( vPos, 0 );
|
|
|
|
traffV->mMillisecondsOutOfSight += milliseconds;
|
|
if( !traffV->mOutOfSight )
|
|
{
|
|
traffV->mMillisecondsOutOfSight = 0;
|
|
}
|
|
|
|
// Determine fade alpha.. All active (in the world) traffic vehicles
|
|
// fade. Whether or not they're in VL_TRAFFIC or VL_PHYSICS is irrelevant
|
|
SetVehicleFadeAlpha( v, pPos );
|
|
|
|
// figure out if we should allow entering the traffic vehicle
|
|
const float GETIN_SPEED_THRESHOLD_MPS = 4.5f; // this is as fast as character can run
|
|
if( !avatar->IsInCar() && !v->mHijackedByUser )
|
|
{
|
|
rAssert( v->mVehicleType != VT_AI );
|
|
if( v->mSpeed > GETIN_SPEED_THRESHOLD_MPS )
|
|
{
|
|
v->ActivateTriggers( false );
|
|
}
|
|
else
|
|
{
|
|
v->ActivateTriggers( true );
|
|
}
|
|
}
|
|
|
|
if( v->GetLocomotionType() == VL_PHYSICS && !v->mHijackedByUser )
|
|
{
|
|
traffV->mMillisecondsDeactivated += milliseconds;
|
|
|
|
// Check through vehicles VL_PHYSICS to see if we can bring any of them
|
|
// back to life:
|
|
// - check health
|
|
// - check distance from last segment
|
|
// - check if we've been deactivated long enough
|
|
AttemptResurrection( traffV );
|
|
}
|
|
|
|
if( v->GetLocomotionType() == VL_TRAFFIC )
|
|
{
|
|
// Update Vehicle AI
|
|
//v->mTrafficLocomotion->UpdateAI(milliseconds);
|
|
|
|
// Update intersection
|
|
UpdateIntersection( milliseconds, v, nIntersectionsUpdated );
|
|
}
|
|
}
|
|
END_PROFILE( "Traffic Man: Update AI & Intersections" );
|
|
|
|
|
|
|
|
END_PROFILE( "Traffic Man" );
|
|
}
|
|
|
|
void TrafficManager::ClearTrafficOutsideSphere( const rmt::Sphere& s )
|
|
{
|
|
rmt::Vector distVec;
|
|
rmt::Vector vPos;
|
|
rmt::Sphere vSphere;
|
|
float minDistSqr = 0.0f;
|
|
|
|
Vehicle* playerVehicle = GetGameplayManager()->GetCurrentVehicle();//GetAvatarManager()->GetAvatarForPlayer(0)->GetVehicle();
|
|
|
|
for( int i=0; i<MAX_TRAFFIC; ++i )
|
|
{
|
|
TrafficVehicle* traffV = &mVehicles[i];
|
|
rAssert( traffV != NULL );
|
|
|
|
if( !traffV->GetIsActive() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Vehicle* v = NULL;
|
|
if( traffV->HasHusk() )
|
|
{
|
|
v = traffV->GetHusk();
|
|
}
|
|
else
|
|
{
|
|
v = traffV->GetVehicle();
|
|
}
|
|
rAssert( v != NULL );
|
|
|
|
// if the player is driving this traffic vehicle, don't take it away from him!
|
|
if( traffV->GetVehicle() == playerVehicle ) // get original vehicle to compare, not husk
|
|
{
|
|
continue;
|
|
}
|
|
|
|
v->GetPosition( &vPos );
|
|
|
|
v->GetBoundingSphere( &vSphere );
|
|
vSphere.centre = vPos;
|
|
|
|
minDistSqr = s.radius * s.radius;
|
|
distVec.Sub( vSphere.centre, s.centre );
|
|
if( distVec.MagnitudeSqr() < minDistSqr )
|
|
{
|
|
// BAH! vehicle still in traffic zone... leave it alone
|
|
continue;
|
|
}
|
|
|
|
// At this point, we want to kill the vehicle
|
|
// because it is now outside our traffic zone
|
|
RemoveTraffic( i );
|
|
}
|
|
}
|
|
|
|
void TrafficManager::ClearTrafficInSphere( const rmt::Sphere& s )
|
|
{
|
|
rmt::Vector distVec;
|
|
rmt::Vector vPos;
|
|
rmt::Sphere vSphere;
|
|
float minDistSqr = 0.0f;
|
|
|
|
Vehicle* playerVehicle = GetGameplayManager()->GetCurrentVehicle();//GetAvatarManager()->GetAvatarForPlayer(0)->GetVehicle();
|
|
|
|
|
|
for( int i=0; i<MAX_TRAFFIC; ++i )
|
|
{
|
|
TrafficVehicle* traffV = &mVehicles[i];
|
|
rAssert( traffV != NULL );
|
|
|
|
if( !traffV->GetIsActive() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Vehicle* v = NULL;
|
|
if( traffV->HasHusk() )
|
|
{
|
|
v = traffV->GetHusk();
|
|
}
|
|
else
|
|
{
|
|
v = traffV->GetVehicle();
|
|
}
|
|
rAssert( v != NULL );
|
|
|
|
// if the player is driving this traffic vehicle, don't take it away from him!
|
|
if( traffV->GetVehicle() == playerVehicle )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
v->GetPosition( &vPos );
|
|
|
|
v->GetBoundingSphere( &vSphere );
|
|
vSphere.centre = vPos;
|
|
|
|
minDistSqr = s.radius * s.radius;
|
|
distVec.Sub( vSphere.centre, s.centre );
|
|
|
|
// If vehicle still in sphere... kill it
|
|
if( distVec.MagnitudeSqr() < minDistSqr )
|
|
{
|
|
// At this point, we want to kill the vehicle
|
|
// because it is now outside our traffic zone
|
|
RemoveTraffic( i );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void TrafficManager::RemoveTraffic( Vehicle* vehicle )
|
|
{
|
|
rAssert( vehicle != NULL );
|
|
|
|
for( int i=0; i<MAX_TRAFFIC; ++i )
|
|
{
|
|
TrafficVehicle* tv = &mVehicles[i];
|
|
rAssert( tv != NULL );
|
|
|
|
Vehicle* v = NULL;
|
|
if( GetVehicleCentral()->mHuskPool.IsHuskType( vehicle->mVehicleID ) )
|
|
{
|
|
v = tv->GetHusk();
|
|
}
|
|
else
|
|
{
|
|
v = tv->GetVehicle();
|
|
}
|
|
|
|
// found it.
|
|
if( vehicle == v )
|
|
{
|
|
RemoveTrafficVehicle( tv );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void TrafficManager::EnableTraffic()
|
|
{
|
|
if( !CommandLineOptions::Get(CLO_NO_TRAFFIC) && !DISABLETRAFFIC )
|
|
{
|
|
mTrafficEnabled = true;
|
|
}
|
|
}
|
|
|
|
void TrafficManager::DisableTraffic()
|
|
{
|
|
mTrafficEnabled = false;
|
|
}
|
|
|
|
void TrafficManager::AddCharacterToStopFor( Character* character )
|
|
{
|
|
rAssert( character != NULL );
|
|
|
|
bool assigned = false;
|
|
|
|
for( int i=0; i<MAX_CHARS_TO_STOP_FOR; i++ )
|
|
{
|
|
if( mCharactersToStopFor[i] == character )
|
|
{
|
|
mCharactersToStopFor[i]->Release();
|
|
mCharactersToStopFor[i] = NULL;
|
|
}
|
|
if( !assigned && mCharactersToStopFor[i] == NULL )
|
|
{
|
|
tRefCounted::Assign( mCharactersToStopFor[i], character );
|
|
assigned = true;
|
|
mNumCharsToStopFor++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrafficManager::RemoveCharacterToStopFor( Character* character )
|
|
{
|
|
rAssert( character != NULL );
|
|
|
|
for( int i=0; i<MAX_CHARS_TO_STOP_FOR; i++ )
|
|
{
|
|
if( mCharactersToStopFor[i] == character )
|
|
{
|
|
mCharactersToStopFor[i]->Release();
|
|
mCharactersToStopFor[i] = NULL;
|
|
mNumCharsToStopFor--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrafficManager::GenerateRandomColour( pddiColour& colour )
|
|
{
|
|
int alpha = 255;
|
|
int red, green, blue;
|
|
|
|
int index = rand() % TrafficManager::NUM_SWATCH_COLOURS;
|
|
red = sSwatchColours[index].red;
|
|
green = sSwatchColours[index].green;
|
|
blue = sSwatchColours[index].blue;
|
|
/*
|
|
red = rand() % 256;
|
|
green = rand() % 256;
|
|
blue = rand() % 256;
|
|
*/
|
|
colour.Set( red, green, blue, alpha );
|
|
}
|
|
|
|
void TrafficManager::Deactivate( Vehicle* vehicle )
|
|
{
|
|
rAssert( vehicle != NULL );
|
|
|
|
for( int i=0; i<MAX_TRAFFIC; ++i )
|
|
{
|
|
TrafficVehicle* tv = &mVehicles[i];
|
|
rAssert( tv != NULL );
|
|
|
|
Vehicle* v = tv->GetVehicle();
|
|
|
|
// found it.
|
|
if( vehicle == v )
|
|
{
|
|
rAssert( v != NULL );
|
|
v->mTrafficLocomotion->SetActive( false );
|
|
//
|
|
// Remove the vehicle from the lane
|
|
Lane* lane = tv->GetLane();
|
|
if( lane )
|
|
{
|
|
for( int j=0; j<lane->mTrafficVehicles.mUseSize; j++ )
|
|
{
|
|
if( tv == lane->mTrafficVehicles[j] )
|
|
{
|
|
lane->mTrafficVehicles.Remove(j);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE:
|
|
// hold off NULLing out the lane variable.
|
|
// We could use it in case we want to resurrect it
|
|
//tv->SetLane( NULL );
|
|
|
|
// NOTE:
|
|
// DO NOT SET TrafficVehicle::mIsActive TO FALSE HERE,
|
|
// We want traffic manager to consider it as being
|
|
// active still (in the sense that it's still in the world and is
|
|
// subject to removal). We just want to remove it from the lane and
|
|
// deactivate its AI...
|
|
//tv->SetIsActive( false );
|
|
|
|
// start the deactivation timer
|
|
tv->mMillisecondsDeactivated = 0;
|
|
tv->mCanBeResurrected = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void TrafficManager::SwapInTrafficHusk( Vehicle* vehicle )
|
|
{
|
|
rAssert( vehicle );
|
|
|
|
TrafficVehicle* tv = FindTrafficVehicle( vehicle );
|
|
if( tv == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// obtain info from the vehicle
|
|
rmt::Vector initPos, initDir;
|
|
vehicle->GetPosition( &initPos );
|
|
initDir = vehicle->GetFacing();
|
|
rAssert( rmt::Epsilon( initDir.MagnitudeSqr(), 1.0f, 0.001f ) );
|
|
|
|
//
|
|
// Deactivate traffic vehicle, take it out of the world, swap in a free husk...
|
|
// that sort of thing
|
|
//
|
|
Deactivate( vehicle );
|
|
bool succeeded = ::GetVehicleCentral()->RemoveVehicleFromActiveList( vehicle );
|
|
rAssert( succeeded );
|
|
//GetVehicleCentral()->SetVehicleController( tv->mActiveListIndex, NULL );
|
|
GetEventManager()->TriggerEvent( EVENT_TRAFFIC_REMOVE, vehicle );
|
|
|
|
//
|
|
// Now we grab husk and put it in place of the original vehicle
|
|
//
|
|
Vehicle* husk = InitRandomHusk( vehicle );
|
|
if( husk == NULL )
|
|
{
|
|
// by returning here, we have removed the traffic car from the
|
|
// manager, the lane, and world scene... But not entirely! (RemoveTrafficVehicle
|
|
// will be called on it when it goes out of range later...) Good job!
|
|
return;
|
|
}
|
|
int res = ::GetVehicleCentral()->AddVehicleToActiveList( husk );
|
|
if( res == -1 )
|
|
{
|
|
// not supposed to happen since the list can't be full!!!
|
|
// we TOOK something out of the list before adding something in
|
|
// If this assert happens, it is both fatal and strange
|
|
rAssert( false );
|
|
return;
|
|
}
|
|
//tv->mActiveListIndex = res;
|
|
GetVehicleCentral()->SetVehicleController( res, husk->mTrafficLocomotion->GetAI() );
|
|
|
|
|
|
husk->AddRef();
|
|
husk->SetInitialPosition( &initPos );
|
|
float angle = GetRotationAboutY( initDir.x, initDir.z );
|
|
husk->SetResetFacingInRadians( angle );
|
|
husk->Reset();
|
|
husk->SetLocomotion( VL_PHYSICS );
|
|
|
|
/*
|
|
// tell the avatar that it's now in a husk
|
|
Avatar* avatar = GetAvatarManager()->GetAvatarForPlayer( 0 );
|
|
avatar->SetVehicle(husk);
|
|
*/
|
|
|
|
tv->SetHusk( husk );
|
|
|
|
// tell the TrafficVehicle that it is destroyed
|
|
tv->SetHasHusk( true );
|
|
|
|
}
|
|
|
|
void TrafficManager::SetMaxTraffic( int n )
|
|
{
|
|
if( n < 0 )
|
|
{
|
|
n = 0;
|
|
}
|
|
else if( n > MAX_TRAFFIC )
|
|
{
|
|
n = MAX_TRAFFIC;
|
|
}
|
|
|
|
mMaxTraffic = n;
|
|
}
|
|
|
|
int TrafficManager::GetMaxTraffic()
|
|
{
|
|
return mMaxTraffic;
|
|
}
|
|
|
|
//******************************************************************************
|
|
//
|
|
// Private Member Functions
|
|
//
|
|
//******************************************************************************
|
|
|
|
TrafficManager::TrafficManager()
|
|
{
|
|
// start listening for events
|
|
GetEventManager()->AddListener( this, EVENT_VEHICLE_DESTROYED );
|
|
GetEventManager()->AddListener( this, EVENT_REPAIR_CAR );
|
|
GetEventManager()->AddListener( this, EVENT_PLAYER_VEHICLE_HORN );
|
|
|
|
mVehicles = new TrafficVehicle[ MAX_TRAFFIC ];
|
|
|
|
mCurrTrafficModelGroup = -1;
|
|
mMaxTraffic = MAX_TRAFFIC;
|
|
|
|
// initialize NULL list.
|
|
mNumCharsToStopFor = 0;
|
|
for( int i=0; i<MAX_CHARS_TO_STOP_FOR; i++ )
|
|
{
|
|
mCharactersToStopFor[i] = NULL;
|
|
}
|
|
|
|
mTrafficEnabled = !DISABLETRAFFIC;
|
|
if( CommandLineOptions::Get(CLO_NO_TRAFFIC) )
|
|
{
|
|
mTrafficEnabled = false;
|
|
}
|
|
|
|
mQueuedTrafficHorns.Allocate( MAX_QUEUED_TRAFFIC_HORNS );
|
|
|
|
}
|
|
TrafficManager::~TrafficManager()
|
|
{
|
|
GetEventManager()->RemoveAll( this );
|
|
Cleanup();
|
|
|
|
mQueuedTrafficHorns.Clear();
|
|
delete[] mVehicles;
|
|
}
|
|
|
|
Vehicle* TrafficManager::InitRandomVehicle()
|
|
{
|
|
rAssert( mCurrTrafficModelGroup >= 0 );
|
|
|
|
Vehicle* newV = NULL;
|
|
|
|
// randomly choose a model index
|
|
int numModels = mTrafficModelGroups[mCurrTrafficModelGroup].GetNumModels();
|
|
int modelIndex = rand() % numModels;
|
|
|
|
TrafficModel* tm = NULL;
|
|
|
|
// if there are already too many instances of traffic cars under this model
|
|
// we go to the next model... and so on until we're out of models.
|
|
int count = 0;
|
|
while( count < numModels )
|
|
{
|
|
tm = mTrafficModelGroups[mCurrTrafficModelGroup].GetTrafficModel( modelIndex );
|
|
if( tm->mNumInstances >= tm->mMaxInstances )
|
|
{
|
|
modelIndex = (modelIndex+1) % numModels;
|
|
count++;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( count >= numModels )
|
|
{
|
|
char message[256];
|
|
sprintf( message, "DOH! Can't initialize a traffic car \"%s\" because\n"
|
|
"we already have %d of max %d instances allowed.\n"
|
|
"See leveli.mfk to increase the max allowed for this model\n",
|
|
tm->mModelName, tm->mNumInstances, tm->mMaxInstances );
|
|
|
|
rTuneAssertMsg( false, message );
|
|
return NULL;
|
|
}
|
|
|
|
newV = ::GetVehicleCentral()->InitVehicle( tm->mModelName, false, 0, VT_TRAFFIC );
|
|
rTuneAssert( newV != NULL );
|
|
tm->mNumInstances++;
|
|
|
|
return newV;
|
|
}
|
|
|
|
void TrafficManager::Cleanup()
|
|
{
|
|
// Clean up list of characters to stop for...
|
|
//
|
|
for( int i=0; i<MAX_CHARS_TO_STOP_FOR; i++ )
|
|
{
|
|
if( mCharactersToStopFor[i] != NULL )
|
|
{
|
|
mCharactersToStopFor[i]->Release();
|
|
mCharactersToStopFor[i] = NULL;
|
|
mNumCharsToStopFor--;
|
|
}
|
|
}
|
|
rAssert( mNumCharsToStopFor == 0 );
|
|
|
|
// Clean up swap array
|
|
//
|
|
mQueuedTrafficHorns.ClearUse();
|
|
|
|
// Clean up traffic vehicles
|
|
//
|
|
for( int i=0 ; i<MAX_TRAFFIC ; i++ )
|
|
{
|
|
TrafficVehicle* tv = &mVehicles[i];
|
|
rAssert( tv != NULL );
|
|
|
|
Vehicle* v = tv->GetVehicle();
|
|
if( tv->GetIsActive() )
|
|
{
|
|
// tell trafficlocomotion to be inactive,
|
|
// clear out traffic vehicle from a lane,
|
|
// reset trafficvehicle members (except vehicle pointer member) to indeterminate values,
|
|
// decrement numtraffic count by 1,
|
|
// remove from activelist
|
|
RemoveTraffic( i );
|
|
}
|
|
|
|
rAssert( tv->GetHusk() == NULL );
|
|
|
|
if( v != NULL )
|
|
{
|
|
v->ReleaseVerified();
|
|
tv->SetVehicle( NULL );
|
|
}
|
|
}
|
|
|
|
// clean up intersections
|
|
for( int j=0; j<MAX_INTERSECTIONS; j++ )
|
|
{
|
|
mpIntersections[j] = NULL;
|
|
}
|
|
|
|
#ifdef DEBUGWATCH
|
|
radDbgWatchDelete( &mDesiredTrafficSpeedKph );
|
|
#endif
|
|
|
|
}
|
|
|
|
bool TrafficManager::AddTraffic(Lane* lane, TrafficVehicle* tv)
|
|
{
|
|
rAssert( tv != NULL );
|
|
rAssert( tv->GetIsActive() == false );
|
|
rAssert( tv->GetLane() == NULL );
|
|
|
|
Vehicle* v = tv->GetVehicle();
|
|
rAssert( v != NULL );
|
|
rAssert( v->mTrafficLocomotion->GetActive() == false );
|
|
|
|
// add vehicle to the lane
|
|
// if there was some error in adding (say if list was full)
|
|
// then just abort
|
|
if( lane->mTrafficVehicles.mUseSize >= lane->mTrafficVehicles.mSize )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// add vehicle to ActiveList
|
|
if( ::GetVehicleCentral()->ActiveVehicleListIsFull() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
v->mVehicleType = VT_TRAFFIC;
|
|
|
|
int res = ::GetVehicleCentral()->AddVehicleToActiveList( v );
|
|
if( res == -1 )
|
|
{
|
|
// not supposed to happen since we already eliminated the
|
|
// safe failure condition (activelistisfull)
|
|
rAssert( false );
|
|
return false;
|
|
}
|
|
|
|
/////// NO MORE BAILING OUT AFTER THIS POINT /////////
|
|
|
|
lane->mTrafficVehicles.Add( tv );
|
|
|
|
// now add the AI to active vehicle controller list
|
|
GetVehicleCentral()->SetVehicleController( res, v->mTrafficLocomotion->GetAI() );
|
|
|
|
// set trafficVehicle fields
|
|
tv->SetLane( lane );
|
|
tv->SetIsActive( true );
|
|
tv->SetHasHusk( false );
|
|
//tv->mActiveListIndex = res;
|
|
tv->mMillisecondsDeactivated = 0;
|
|
tv->mMillisecondsOutOfSight = 0;
|
|
tv->mOutOfSight = true;
|
|
|
|
::GetEventManager()->TriggerEvent( EVENT_TRAFFIC_SPAWN, v );
|
|
|
|
mNumTraffic++;
|
|
return true;
|
|
}
|
|
|
|
void TrafficManager::RemoveTraffic( int vIndex )
|
|
{
|
|
int i = vIndex;
|
|
TrafficVehicle* tv = &mVehicles[i];
|
|
RemoveTrafficVehicle( tv );
|
|
}
|
|
|
|
void TrafficManager::RemoveTrafficVehicle( TrafficVehicle* tv )
|
|
{
|
|
rAssert( tv != NULL );
|
|
rAssert( tv->GetIsActive() == true );
|
|
|
|
Vehicle* v = tv->GetVehicle();
|
|
rAssert( v != NULL );
|
|
v->mTrafficLocomotion->SetActive( false );
|
|
|
|
// Remove the vehicle from the lane, if it hasn't been removed
|
|
// already (if the car was Deactivated, it will have already been
|
|
// removed from the lane.. the lane pointer will not be NULL cuz
|
|
// it's needed later for resurrection, but the traffic index will
|
|
// be -1.)
|
|
Lane* lane = tv->GetLane();
|
|
if( lane != NULL )
|
|
{
|
|
for( int i=0; i<lane->mTrafficVehicles.mUseSize; i++ )
|
|
{
|
|
if( tv == lane->mTrafficVehicles[i] )
|
|
{
|
|
lane->mTrafficVehicles.Remove(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove Vehicle (or its husk if it's destroyed) from ActiveList
|
|
if( tv->HasHusk() )
|
|
{
|
|
Vehicle* husk = tv->GetHusk();
|
|
rAssert( husk );
|
|
|
|
bool succeeded = ::GetVehicleCentral()->RemoveVehicleFromActiveList( husk );
|
|
rAssert( succeeded );
|
|
|
|
::GetVehicleCentral()->mHuskPool.FreeHusk( husk );
|
|
// restore fade alpha if we set it, so other vehicles don't get confused
|
|
husk->mGeometryVehicle->SetFadeAlpha( 255 );
|
|
husk->Release(); // don't verify destruction cuz huskpool has final ownership
|
|
tv->SetHusk( NULL );
|
|
}
|
|
else
|
|
{
|
|
Vehicle* v = tv->GetVehicle();
|
|
rAssert( v );
|
|
rAssert( tv->GetHusk() == NULL );
|
|
|
|
GetVehicleCentral()->RemoveVehicleFromActiveList( v );
|
|
GetEventManager()->TriggerEvent( EVENT_TRAFFIC_REMOVE, v );
|
|
}
|
|
|
|
// remove from our traffic system
|
|
//GetVehicleCentral()->SetVehicleController( tv->mActiveListIndex, NULL );
|
|
tv->SetLane( NULL );
|
|
tv->SetIsActive( false );
|
|
tv->SetHasHusk( false );
|
|
//tv->mActiveListIndex = -1;
|
|
tv->mMillisecondsDeactivated = 0;
|
|
tv->mMillisecondsOutOfSight = 0;
|
|
tv->mOutOfSight = true;
|
|
|
|
mNumTraffic--;
|
|
|
|
// search through queued traffic horns list and remove self...
|
|
Vehicle* traffVehicle = tv->GetVehicle();
|
|
for( int i=0; i<mQueuedTrafficHorns.mUseSize; i++ )
|
|
{
|
|
if( mQueuedTrafficHorns[i].vehicle == traffVehicle )
|
|
{
|
|
mQueuedTrafficHorns.Remove( i );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TrafficVehicle* TrafficManager::GetFreeTrafficVehicle()
|
|
{
|
|
// gotta start at some random index, so we don't recycle
|
|
// the same car (and thus same car model) over and over again
|
|
// when maxtraffic is set to a small number
|
|
|
|
int randIndex = rand() % MAX_TRAFFIC;
|
|
for( int i=0; i<MAX_TRAFFIC; ++i )
|
|
{
|
|
if( !mVehicles[randIndex].GetIsActive() )
|
|
{
|
|
return &mVehicles[randIndex];
|
|
}
|
|
randIndex = (randIndex + 1) % MAX_TRAFFIC;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
float TrafficManager::DetermineDesiredSpeedKph()
|
|
{
|
|
float speedKph = 0.0f;
|
|
switch( GetGameplayManager()->GetCurrentLevelIndex() )
|
|
{
|
|
case RenderEnums::L1:
|
|
speedKph = 60.0f;
|
|
break;
|
|
case RenderEnums::L2:
|
|
speedKph = 60.0f;
|
|
break;
|
|
case RenderEnums::L3:
|
|
speedKph = 60.0f;
|
|
break;
|
|
case RenderEnums::L4:
|
|
speedKph = 60.0f;
|
|
break;
|
|
case RenderEnums::L5:
|
|
speedKph = 60.0f;
|
|
break;
|
|
case RenderEnums::L6:
|
|
speedKph = 60.0f;
|
|
break;
|
|
case RenderEnums::L7:
|
|
speedKph = 60.0f;
|
|
break;
|
|
default:
|
|
rAssert( false );
|
|
break;
|
|
}
|
|
return speedKph;
|
|
}
|
|
|
|
TrafficVehicle* TrafficManager::FindTrafficVehicle( Vehicle* vehicle )
|
|
{
|
|
for( int i=0; i<MAX_TRAFFIC; ++i )
|
|
{
|
|
TrafficVehicle* tv = &mVehicles[i];
|
|
rAssert( tv != NULL );
|
|
|
|
Vehicle* v = tv->GetVehicle();
|
|
if( vehicle == v )
|
|
{
|
|
return tv;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool TrafficManager::IsVehicleTrafficVehicle( Vehicle* vehicle )
|
|
{
|
|
return FindTrafficVehicle( vehicle ) != NULL;
|
|
}
|
|
|
|
|
|
Vehicle* TrafficManager::InitRandomHusk( Vehicle* v )
|
|
{
|
|
Vehicle* husk = ::GetVehicleCentral()->mHuskPool.RequestHusk( VT_TRAFFIC, v );
|
|
return husk;
|
|
}
|
|
|
|
void TrafficManager::UpdateIntersection( unsigned int milliseconds, Vehicle* v, int& nIntersectionsUpdated )
|
|
{
|
|
rAssert( v->GetLocomotionType() == VL_TRAFFIC );
|
|
rAssert( v != NULL );
|
|
|
|
Intersection* intersection = NULL;
|
|
if( !v->mTrafficLocomotion->IsInIntersection() )
|
|
{
|
|
intersection = (Intersection*) v->mTrafficLocomotion->
|
|
GetAILane()->GetRoad()->GetDestinationIntersection();
|
|
}
|
|
else
|
|
{
|
|
intersection = (Intersection*) v->mTrafficLocomotion->
|
|
GetAILane()->GetRoad()->GetSourceIntersection();
|
|
}
|
|
|
|
// update the intersection if it hasn't already been updated this frame
|
|
bool foundIntersection = false;
|
|
for( int i=0; i<nIntersectionsUpdated; i++ )
|
|
{
|
|
if( mpIntersections[i] == intersection )
|
|
{
|
|
foundIntersection = true;
|
|
break;
|
|
}
|
|
}
|
|
if( !foundIntersection )
|
|
{
|
|
if( nIntersectionsUpdated < MAX_INTERSECTIONS )
|
|
{
|
|
mpIntersections[nIntersectionsUpdated] = intersection;
|
|
nIntersectionsUpdated++;
|
|
intersection->Update(milliseconds);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrafficManager::SetVehicleFadeAlpha( Vehicle* v, const rmt::Vector& pPos )
|
|
{
|
|
rAssert( v );
|
|
|
|
rmt::Vector vPos;
|
|
v->GetPosition( &vPos );
|
|
float distFromPlayer = (pPos - vPos).Length(); // *** SQUARE ROOT! ***
|
|
|
|
float fadeMinLimit = TrafficManager::FADE_RADIUS;
|
|
float fadeMaxLimit = TrafficManager::ADD_RADIUS + TrafficManager::CENTER_OFFSET;
|
|
int fadeAlpha = 255;
|
|
if( fadeMinLimit <= distFromPlayer && distFromPlayer <= fadeMaxLimit )
|
|
{
|
|
// if we're in the fading zone, gotta change fade alpha
|
|
float fadeRatio = (distFromPlayer - fadeMinLimit)/(fadeMaxLimit - fadeMinLimit);
|
|
fadeAlpha = (int) (255.0f * (1.0f - fadeRatio));
|
|
}
|
|
else if( distFromPlayer > fadeMaxLimit )
|
|
{
|
|
fadeAlpha = 0;
|
|
}
|
|
|
|
v->mGeometryVehicle->SetFadeAlpha( fadeAlpha );
|
|
}
|
|
|
|
bool TrafficManager::AttemptResurrection( TrafficVehicle* tv )
|
|
{
|
|
rAssert( tv );
|
|
rAssert( tv->GetIsActive() );
|
|
|
|
Vehicle* v = tv->GetVehicle();
|
|
rAssert( v );
|
|
rAssert( v->mTrafficLocomotion );
|
|
rAssert( v->mTrafficLocomotion->GetActive() == false );
|
|
|
|
// if has been hijacked by user, don't resurrect
|
|
if( v->mHijackedByUser )
|
|
{
|
|
tv->mCanBeResurrected = false;
|
|
return false;
|
|
}
|
|
|
|
// if we have determined that it can't be resurrect, no point trying
|
|
if( !tv->mCanBeResurrected )
|
|
{
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
// don't allow resurrection right away... cool down... cool down... cool down...
|
|
if( tv->mMillisecondsDeactivated < MILLISECONDS_STUNNED_AFTER_DEACTIVATED )
|
|
{
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
if( v->mVehicleDestroyed )
|
|
{
|
|
// reset it to 0 so we don't check again every frame
|
|
tv->mCanBeResurrected = false;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
// Check through vehicles VL_PHYSICS to see if we can bring any of them
|
|
// back to life:
|
|
// - check health
|
|
// - check distance from last segment
|
|
// - check if we've been deactivated long enough
|
|
|
|
// if not at rest yet, don't resurrect...
|
|
if( !v->IsAtRest() )
|
|
{
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
// If the lane won't support us anymore, wait for awhile
|
|
Lane* oldLane = tv->GetLane();
|
|
rAssert( oldLane != NULL );
|
|
if( oldLane->mTrafficVehicles.mUseSize >= oldLane->GetDensity() )
|
|
{
|
|
// reset it to 0 so there's a lull before we check again..
|
|
tv->mMillisecondsDeactivated = 0;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// check if there's anything ahead ...
|
|
//
|
|
TrafficAI::ObstacleType foundSOMETHING = TrafficAI::OT_NOTHING;
|
|
float distFromSOMETHINGSqr = 100000.0f;
|
|
void* SOMETHING = NULL;
|
|
bool SOMETHINGOnMyRight = false;
|
|
|
|
v->mTrafficLocomotion->GetAI()->CheckForObstacles(
|
|
foundSOMETHING, distFromSOMETHINGSqr, SOMETHING, SOMETHINGOnMyRight );
|
|
|
|
if( foundSOMETHING != TrafficAI::OT_NOTHING )
|
|
{
|
|
// reset it to 0 so there's a lull before we check again..
|
|
tv->mMillisecondsDeactivated = 0;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
rmt::Vector vPos, vDir, vUp;
|
|
v->GetPosition( &vPos );
|
|
|
|
vDir = v->GetFacing();
|
|
rAssert( rmt::Epsilon( vDir.MagnitudeSqr(), 1.0f, 0.001f ) );
|
|
|
|
v->GetVUP( &vUp );
|
|
rAssert( rmt::Epsilon( vUp.MagnitudeSqr(), 1.0f, 0.001f ) );
|
|
|
|
// ACTION:
|
|
// - if we were in an intersection, just build spline straight to the
|
|
// target segment (no problem!). Remain in DRIVING state.
|
|
// - else
|
|
// - Make sure we're not too far from closest point on last segment
|
|
// - Make sure the line from us to closest point on last segment does not
|
|
// cross over the segment (meaning that we were outside the segment...
|
|
// e.g. on the sidewalk... in which case we don't recover)
|
|
// - Build spline to a position 5 meters ahead of the closestPt on segment
|
|
// flowing onto a new segment as necessary... do not flow over into an
|
|
// intersection. Store out segment's t value and transit to RECOVERING
|
|
// state. When done RECOVERING state, add t value to the t.
|
|
|
|
TrafficAI* ai = v->mTrafficLocomotion->GetAI();
|
|
rAssert( ai );
|
|
|
|
RoadSegment* seg = ai->GetSegment();
|
|
rAssert( seg );
|
|
|
|
int laneIndex = (int)(ai->GetLaneIndex());
|
|
|
|
const float TOLERANCE_DIST_SQR = 64.0f;
|
|
const float UP_COSALPHA = 0.9848077f;
|
|
const float FACING_RESURRECT_COSAPLHA = 0.0f;
|
|
|
|
rmt::Vector targetPos, targetDir;
|
|
|
|
if( v->mTrafficLocomotion->IsInIntersection() ||
|
|
ai->GetState() == TrafficAI::LANE_CHANGING )
|
|
{
|
|
// TODO:
|
|
// The assumption that the intersection's UP vector is simply 0,1,0 is
|
|
// no longer valid since we don't enforce the intersection to be completely
|
|
// horizontal anymore. So... we have to determine the up vector ourselves...
|
|
// *shudder*....
|
|
|
|
// If vehicle's up vector isn't anywhere close to the horizontal up vector
|
|
// (0,1,0), don't resurrect (it might be tipped over or laying on its side)
|
|
rmt::Vector testUp( 0.0f, 1.0f, 0.0f );
|
|
if( vUp.Dot( testUp ) < UP_COSALPHA )
|
|
{
|
|
tv->mCanBeResurrected = false;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
// Just build a spline to the end of the intersection, if we're
|
|
// close enough to the original intersection spline
|
|
rmt::Vector* ways;
|
|
int nPts, currPt;
|
|
v->mTrafficLocomotion->GetSplineCurve( ways, nPts, currPt );
|
|
|
|
rAssert( ways ); // we should have mWays populated since we're in the intersection
|
|
|
|
rmt::Vector tmpStart, tmpEnd, tmpClosestPt;
|
|
tmpStart = ways[0];
|
|
tmpEnd = ways[nPts-1];
|
|
FindClosestPointOnLine( tmpStart, tmpEnd, vPos, tmpClosestPt );
|
|
|
|
const float CLOSE_ENOUGH_TO_ORIG_SPLINE_SQR = 16.0f;
|
|
float distSqr = (vPos - tmpClosestPt).MagnitudeSqr();
|
|
if( distSqr > CLOSE_ENOUGH_TO_ORIG_SPLINE_SQR )
|
|
{
|
|
tv->mCanBeResurrected = false;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
// now it's time to get the target of our new spline...
|
|
seg->GetLaneLocation( 0.0f, laneIndex, targetPos, targetDir );
|
|
|
|
// if our pos is too close to the end, don't do it;
|
|
// it will look too strange...
|
|
const float US_FAR_ENOUGH_FROM_TARGET_POS_SQR = 25.0f;
|
|
distSqr = (vPos - targetPos).MagnitudeSqr();
|
|
if( distSqr < US_FAR_ENOUGH_FROM_TARGET_POS_SQR )
|
|
{
|
|
tv->mCanBeResurrected = false;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
// if the closest point on original spline is too close to
|
|
// the end, don't do it; it will look too strange...
|
|
const float PROJ_FAR_ENOUGH_FROM_TARGET_POS_SQR = 25.0f;
|
|
distSqr = (tmpClosestPt - targetPos).MagnitudeSqr();
|
|
if( distSqr < PROJ_FAR_ENOUGH_FROM_TARGET_POS_SQR )
|
|
{
|
|
tv->mCanBeResurrected = false;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
if( !rmt::Epsilon( targetDir.MagnitudeSqr(), 1.0f, 0.001f ) )
|
|
{
|
|
targetDir.NormalizeSafe(); // *** SQUARE ROOT! ***
|
|
}
|
|
rAssert( rmt::Epsilon( targetDir.MagnitudeSqr(), 1.0f, 0.001f ) );
|
|
|
|
// If vehicle's facing is at too great an angle from target dir,
|
|
// and our lateral distance is not that far, don't do it...
|
|
// (it will look too strange)
|
|
if( targetDir.Dot( vDir ) < FACING_RESURRECT_COSAPLHA )
|
|
{
|
|
tv->mCanBeResurrected = false;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
////////////////////// OK, NO ABORTING NOW //////////////////////
|
|
if( ai->GetState() == TrafficAI::LANE_CHANGING )
|
|
{
|
|
ai->SetState( TrafficAI::SPLINING );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Here we're neither lane-changing nor in an intersection
|
|
|
|
// Find the closest seg along road (rather than use the last seg we were on)
|
|
const Road* road = seg->GetRoad();
|
|
rAssert( road );
|
|
|
|
float closestDistSqr = TOLERANCE_DIST_SQR;
|
|
RoadSegment* closestSeg = NULL;
|
|
rmt::Vector closestPt, start, end, closestPtOnSeg;
|
|
|
|
unsigned int numSegs = road->GetNumRoadSegments();
|
|
for( unsigned int i=0; i<numSegs; i++ )
|
|
{
|
|
RoadSegment* aSeg = road->GetRoadSegment( i );
|
|
rAssert( aSeg );
|
|
|
|
rmt::Vector tmpStart, tmpEnd, tmpDir;
|
|
aSeg->GetLaneLocation( 0.0f, laneIndex, tmpStart, tmpDir );
|
|
aSeg->GetLaneLocation( 1.0f, laneIndex, tmpEnd, tmpDir );
|
|
|
|
FindClosestPointOnLine( tmpStart, tmpEnd, vPos, closestPtOnSeg );
|
|
|
|
float distSqr = (vPos - closestPtOnSeg).MagnitudeSqr();
|
|
if( distSqr < closestDistSqr )
|
|
{
|
|
closestDistSqr = distSqr;
|
|
closestSeg = aSeg;
|
|
closestPt = closestPtOnSeg;
|
|
start = tmpStart;
|
|
end = tmpEnd;
|
|
}
|
|
}
|
|
if( closestSeg == NULL )
|
|
{
|
|
tv->mCanBeResurrected = false;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
seg = closestSeg;
|
|
|
|
// If vehicle's up vector isn't anywhere close to the segment's up vector,
|
|
// don't resurrect (it might be tipped over or laying on its side)
|
|
rmt::Vector testUp;
|
|
seg->GetSegmentNormal( testUp );
|
|
rAssert( rmt::Epsilon( testUp.MagnitudeSqr(), 1.0f, 0.001f ) );
|
|
if( vUp.Dot( testUp ) < UP_COSALPHA )
|
|
{
|
|
tv->mCanBeResurrected = false;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
|
|
// determine starting t
|
|
float startT = GetLineSegmentT( start, end, closestPt );
|
|
rAssert( 0.0f <= startT && startT <= 1.0f );
|
|
|
|
// TODO:
|
|
// Maybe make sure line from us to closestPt doesn't cross over a segment bound
|
|
// (test against line segments---v0,v1 and v2,v3---to make sure that
|
|
// closestPt and vPos are both on the left of v2,v3 and right of v0,v1)
|
|
|
|
// find a new lane position, 5 meters ahead...
|
|
unsigned int segmentIndex = seg->GetSegmentIndex();
|
|
float pathLength = seg->GetLaneLength( laneIndex );
|
|
float t = startT;
|
|
float distAhead = 10.0f;
|
|
t += distAhead / pathLength;
|
|
while( t > 1.0f )
|
|
{
|
|
t -= 1.0f;
|
|
|
|
if( segmentIndex < (numSegs-1) )
|
|
{
|
|
// move ahead a segment
|
|
segmentIndex++;
|
|
seg = road->GetRoadSegment( segmentIndex );
|
|
float newLength = seg->GetLaneLength( laneIndex );
|
|
t *= pathLength / newLength;
|
|
pathLength = newLength;
|
|
}
|
|
else // if we're out of segments.. we are at an intersection...
|
|
{
|
|
// TODO:
|
|
// Aborting here keeps us from being able to resurrect properly if
|
|
// we're closestDistSqr meters or less from the intersection (on approach).
|
|
// We need to be able to deal effectively with this case..
|
|
tv->mCanBeResurrected = false;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
rAssert( 0.0f <= t && t <= 1.0f );
|
|
|
|
seg->GetLaneLocation( t, laneIndex, targetPos, targetDir );
|
|
if( !rmt::Epsilon( targetDir.MagnitudeSqr(), 1.0f, 0.001f ) )
|
|
{
|
|
targetDir.NormalizeSafe(); // *** SQUARE ROOT! ***
|
|
}
|
|
rAssert( rmt::Epsilon( targetDir.MagnitudeSqr(), 1.0f, 0.001f ) );
|
|
|
|
// If vehicle's facing is at too great an angle from target dir,
|
|
// and our lateral distance is not that far, don't do it...
|
|
// (it will look too strange)
|
|
if( targetDir.Dot( vDir ) < FACING_RESURRECT_COSAPLHA )
|
|
{
|
|
tv->mCanBeResurrected = false;
|
|
tv->GetVehicle()->GetSimState()->SetControl( sim::simSimulationCtrl );
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////// OK NO ABORTING NOW ////////////////////////
|
|
// Update TrafficAI to look at the new segment, segIndex, and outT
|
|
ai->SetSegmentIndex( segmentIndex );
|
|
ai->SetState( TrafficAI::SPLINING );
|
|
|
|
v->mTrafficLocomotion->mOutLaneT = t;
|
|
}
|
|
|
|
float restHeightAboveGround = v->GetRestHeightAboveGround();
|
|
|
|
/////////////////////////////////////////////
|
|
// Adjust ground height (unfortunately we need to do this
|
|
// because we use pos averaging (so our y value is off)
|
|
rmt::Vector groundPosition, outnorm;
|
|
bool bFoundPlane = false;
|
|
|
|
groundPosition = vPos;
|
|
outnorm.Set( 0.0f, 1.0f, 0.0f );
|
|
|
|
GetIntersectManager()->FindIntersection(
|
|
groundPosition, // IN
|
|
bFoundPlane, // OUT
|
|
outnorm, // OUT
|
|
groundPosition // OUT
|
|
);
|
|
|
|
if( bFoundPlane )
|
|
{
|
|
vPos.y = groundPosition.y + restHeightAboveGround;
|
|
}
|
|
|
|
///////////////////////////////////
|
|
// build spline
|
|
vPos.y -= restHeightAboveGround;
|
|
//vDir.y = 0.0f;
|
|
if( !rmt::Epsilon( vDir.MagnitudeSqr(), 1.0f, 0.001f ) )
|
|
{
|
|
vDir.NormalizeSafe(); // *** SQUARE ROOT! ***
|
|
}
|
|
rAssert( rmt::Epsilon( vDir.MagnitudeSqr(), 1.0f, 0.001f ) );
|
|
|
|
v->mTrafficLocomotion->BuildArbitraryCurve( vPos, vDir, targetPos, targetDir );
|
|
|
|
|
|
// bring vehicle back into traffic:
|
|
// - set loco back to VL_TRAFFIC
|
|
// - put it back in the same lane, on the closest point on the closestSeg
|
|
// - TrafficLomotion::SetIsActive(true)
|
|
|
|
/* NOTE:
|
|
Don't need to do this sheeyatsu... the vehicle is already where
|
|
we want it to be and the simstate is up to date.
|
|
v->SetInitialPosition( &vPos );
|
|
float angle = GetRotationAboutY( vDir.x, vDir.z );
|
|
v->SetResetFacingInRadians( angle );
|
|
v->Reset();
|
|
*/
|
|
|
|
// Just a test to see if it's ever anything else... if it is, this could be
|
|
// a problem.. Please notify Dusit.
|
|
rAssert( v->mVehicleType == VT_TRAFFIC );
|
|
|
|
v->mVehicleType = VT_TRAFFIC;
|
|
v->SetLocomotion( VL_TRAFFIC );
|
|
v->mTrafficLocomotion->SetActive( true );
|
|
v->mTrafficLocomotion->SetAISpeed( 0.0f );
|
|
v->mTrafficLocomotion->InitPos( vPos );
|
|
v->mTrafficLocomotion->InitFacing( vDir );
|
|
|
|
// Readd the vehicle to the lane
|
|
oldLane->mTrafficVehicles.Add( tv );
|
|
|
|
return true;
|
|
}
|
|
|
|
|