//============================================================================= // 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 #include #include //======================================== // Project Includes //======================================== #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //****************************************************************************** // // 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; iGetCurrentVehicle(); 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; iGetIsActive() ) { 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( 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 ; iAddRef(); 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; iGetIsActive() ) { 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 orList; ::GetIntersectManager()->FindRoadSegmentElems( center, addRadius, orList ); bool noMoreFreeTrafficVehicles = false; for( int i=0; iGetNumLanes(); rAssert( numLanes >= 1 ); // loop through all the lanes for this segment for( unsigned int j=0; jGetRoad(); 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 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; iGetMaxActiveVehicles(); 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; iGetIsActive() ) { 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; iGetIsActive() ) { 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; iGetIsActive() ) { 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; imHuskPool.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; iRelease(); 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; iRelease(); 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; iGetVehicle(); // 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; jmTrafficVehicles.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; iRemoveAll( 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; iRelease(); mCharactersToStopFor[i] = NULL; mNumCharsToStopFor--; } } rAssert( mNumCharsToStopFor == 0 ); // Clean up swap array // mQueuedTrafficHorns.ClearUse(); // Clean up traffic vehicles // for( int i=0 ; iGetVehicle(); 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; jGetIsActive() == 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; imTrafficVehicles.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; iGetCurrentLevelIndex() ) { 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; iGetVehicle(); 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; iUpdate(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; iGetRoadSegment( 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; }