diff options
Diffstat (limited to 'game/code/worldsim')
106 files changed, 58351 insertions, 0 deletions
diff --git a/game/code/worldsim/allworldsim.cpp b/game/code/worldsim/allworldsim.cpp new file mode 100644 index 0000000..1bc292d --- /dev/null +++ b/game/code/worldsim/allworldsim.cpp @@ -0,0 +1,9 @@ +#include <worldsim/avatar.cpp> +#include <worldsim/avatarmanager.cpp> +#include <worldsim/groundplanepool.cpp> +#include <worldsim/hitnrunmanager.cpp> +#include <worldsim/vehiclecentral.cpp> +#include <worldsim/worldcollisionsolveragent.cpp> +#include <worldsim/worldobject.cpp> +#include <worldsim/worldphysicsmanager.cpp> +#include <worldsim/huskpool.cpp> diff --git a/game/code/worldsim/avatar.cpp b/game/code/worldsim/avatar.cpp new file mode 100644 index 0000000..69e5f44 --- /dev/null +++ b/game/code/worldsim/avatar.cpp @@ -0,0 +1,1101 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: +// +// Description: Implementation of class Avatar +// +// History: 4/3/2002 + Created -- TBJ +// +//============================================================================= + +//======================================== +// System Includes +//======================================== +// Foundation Tech +#include <raddebug.hpp> + +//======================================== +// Project Includes +//======================================== +#include <worldsim/avatar.h> +#include <worldsim/character/character.h> +#include <worldsim/character/charactertarget.h> +#include <worldsim/character/charactermanager.h> +#include <worldsim/character/charactermappable.h> +#include <worldsim/character/charactercontroller.h> +#include <worldsim/vehiclecentral.h> +#include <worldsim/redbrick/geometryvehicle.h> +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/redbrick/vehiclecontroller/vehiclecontroller.h> +#include <worldsim/redbrick/vehiclecontroller/humanvehiclecontroller.h> +#include <worldsim/redbrick/vehiclecontroller/vehiclemappable.h> +#include <input/inputmanager.h> + +#include <ai/vehicle/vehicleai.h> + +#include <roads/geometry.h> +#include <roads/road.h> + +#include <camera/supercammanager.h> +#include <camera/supercamcentral.h> + +#include <roads/intersection.h> +#include <presentation/gui/ingame/guimanageringame.h> +#include <presentation/gui/ingame/guiscreenhud.h> +#include <memory/srrmemory.h> +#include <mission/gameplaymanager.h> + +#include <gameflow/gameflow.h> +#include <supersprint/supersprintmanager.h> +#include <worldsim/traffic/trafficmanager.h> +//****************************************************************************** +// +// Global Data, Local Data, Local Classes +// +//****************************************************************************** + +// +// If true, display avatar coordinates on screen +// +bool Avatar::s_displayCoordinates = false; + +struct AvatarInputManager +{ + AvatarInputManager( void ) + : + mVehicleMappableHandle( -1 ), +#ifdef RAD_PS2 + mVehicleMappableHandleWheel0( -1 ), + mVehicleMappableHandleWheel1( -1 ), +#endif + mCharacterMappableHandle( -1 ) + { + HeapMgr()->PushHeap (GMA_LEVEL_OTHER); + + mpVehicleMappable = new VehicleMappable; + mpVehicleMappable->AddRef(); + +#ifdef RAD_PS2 + mpVehicleMappableUSB0 = new VehicleMappable; + mpVehicleMappableUSB0->AddRef(); + + mpVehicleMappableUSB1 = new VehicleMappable; + mpVehicleMappableUSB1->AddRef(); +#endif + + mpHumanVehicleController = new HumanVehicleController; + mpHumanVehicleController->AddRef(); + + mpInCarCharacterMappable = new InCarCharacterMappable; + mpInCarCharacterMappable->AddRef(); + + mpBipedCharacterMappable = new BipedCharacterMappable; + mpBipedCharacterMappable->AddRef(); + + mpCameraRelativeCharacterController = new CameraRelativeCharacterController; + mpCameraRelativeCharacterController->AddRef(); + + HeapMgr()->PopHeap (GMA_LEVEL_OTHER); + } + ~AvatarInputManager( void ) + { + mpVehicleMappable->ReleaseController(); + mpVehicleMappable->Release(); + +#ifdef RAD_PS2 + mpVehicleMappableUSB0->Release(); + mpVehicleMappableUSB1->Release(); +#endif + + mpHumanVehicleController->ReleaseVehicleMappable(); + mpHumanVehicleController->Release(); + + mpInCarCharacterMappable->SetCharacterController( NULL ); + mpInCarCharacterMappable->Release(); + + mpBipedCharacterMappable->SetCharacterController( NULL ); + mpBipedCharacterMappable->Release(); + + mpCameraRelativeCharacterController->SetCamera (0); + mpCameraRelativeCharacterController->Release(); + + int x =5; + } + VehicleMappable* mpVehicleMappable; + VehicleMappable* mpVehicleMappableUSB0; + VehicleMappable* mpVehicleMappableUSB1; + HumanVehicleController* mpHumanVehicleController; + InCarCharacterMappable* mpInCarCharacterMappable; + BipedCharacterMappable* mpBipedCharacterMappable; + CameraRelativeCharacterController* mpCameraRelativeCharacterController; + int mVehicleMappableHandle; + int mVehicleMappableHandleWheel0; + int mVehicleMappableHandleWheel1; + int mCharacterMappableHandle; +}; + + +void UnRegisterMappableHandle( int controllerId, int& handle ) +{ + if ( handle > -1 ) + { + // Detach the mappable. + // + InputManager::GetInstance( )->UnregisterMappable( controllerId, handle ); + handle = -1; + } +} +//****************************************************************************** +// +// Public Member Functions +// +//****************************************************************************** + +//============================================================================== +// Avatar::Avatar +//============================================================================== +// Description: Constructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +Avatar::Avatar() +: +mHasBeenUpdatedThisFrame( false ), +mLastRoadSegment( NULL ), +mLastRoadSegmentT( 0.0f ), +mLastRoadT( 0.0f ), +mControllerId( INVALID_CONTROLLER ), +mPlayerId( 0 ), +mpCharacter( 0 ), +mpVehicle( 0 ), +mpAvatarInputManager( 0 ) +{ + mLastPathElement.elem = NULL; + + mpAvatarInputManager = new AvatarInputManager; + + GetCheatInputSystem()->RegisterCallback( this ); + + // + // Just in case the cheat is set before object creation + // + if( GetCheatInputSystem()->IsCheatEnabled( CHEAT_ID_SHOW_AVATAR_POSITION ) ) + { + s_displayCoordinates = true; + } +} + +//============================================================================== +// Avatar::~Avatar +//============================================================================== +// Description: Destructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +Avatar::~Avatar() +{ + Destroy( ); + + if ( mpAvatarInputManager ) + { + delete mpAvatarInputManager; + mpAvatarInputManager = 0; + } + + GetCheatInputSystem()->UnregisterCallback( this ); +} + +void Avatar::Destroy( void ) +{ + + if ( mpCharacter ) + { + mpCharacter->SetController( NULL ); + mpCharacter->Release( ); + mpCharacter = 0; + } + if ( mpVehicle ) + { + mpVehicle->Release( ); + mpVehicle = 0; + } + UnRegisterMappableHandle( mControllerId, mpAvatarInputManager->mCharacterMappableHandle ); + UnRegisterMappableHandle( mControllerId, mpAvatarInputManager->mVehicleMappableHandle ); +#ifdef RAD_PS2 + if ( mpAvatarInputManager->mVehicleMappableHandleWheel0 != -1 ) + { + UnRegisterMappableHandle( Input::USB0, mpAvatarInputManager->mVehicleMappableHandleWheel0 ); + } + + if ( mpAvatarInputManager->mVehicleMappableHandleWheel1 != -1 ) + { + UnRegisterMappableHandle( Input::USB1, mpAvatarInputManager->mVehicleMappableHandleWheel1 ); + } +#endif + mControllerId = INVALID_CONTROLLER; +} + +/* +============================================================================== +Avatar::GetControllerId +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: int + +============================================================================= +*/ +int Avatar::GetControllerId( void ) const +{ + return mControllerId; +} + +/* +============================================================================== +Avatar::SetControllerId +============================================================================== +Description: Comment + +Parameters: ( int id ) + +Return: void + +============================================================================= +*/ +void Avatar::SetControllerId( int id ) +{ + mControllerId = id; +} +/* +============================================================================== +Avatar::GetCharacter +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: const + +============================================================================= +*/ +Character* Avatar::GetCharacter( void ) const +{ + return mpCharacter; +} + +/* +============================================================================== +Avatar::SetCharacter +============================================================================== +Description: Comment + +Parameters: ( Character* pCharacter ) + +Return: void + +============================================================================= +*/ +void Avatar::SetCharacter( Character* pCharacter ) +{ + tRefCounted::Assign( mpCharacter, pCharacter ); + if( mpCharacter ) + { + tColour shadowColour = ::GetGameplayManager()->GetControllerColour( mControllerId ); + mpCharacter->SetShadowColour( shadowColour ); + } +} + +/* +============================================================================== +Avatar::GetVehicle +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: const + +============================================================================= +*/ +Vehicle* Avatar::GetVehicle( void ) const +{ + return mpVehicle; +} + +/* +============================================================================== +Avatar::SetVehicle +============================================================================== +Description: Comment + +Parameters: ( Vehicle* pVehicle ) + +Return: void + +============================================================================= +*/ +void Avatar::SetVehicle( Vehicle* pVehicle ) +{ + tRefCounted::Assign( mpVehicle, pVehicle ); + + if( pVehicle ) + { + int playerID = 0; // normal game defaults player to zero + if( GetGameFlow()->GetCurrentContext() == CONTEXT_LOADING_SUPERSPRINT ) + { + playerID = SuperSprintManager::GetInstance()->GetOnlyHumanPlayerID(); + if( this->mPlayerId != playerID ) + { + return; + } + } + + // can be -1 if in supersprint and more than 1 human is participating... + if( playerID == -1 ) + { + return; + } + + SuperCam* sc = GetSuperCamManager()->GetSCC( 0 )->GetActiveSuperCam(); + if ( sc && sc->GetType() == SuperCam::BUMPER_CAM ) + { + //Only stop rendering for bumpercams. + pVehicle->DrawVehicle( false ); + } + else + { + pVehicle->DrawVehicle( true ); + } + } +} + +/* +============================================================================== +Avatar::GetIntoVehicle +============================================================================== +Description: Pseudo code. + + 1. Get a "human" vehicle controller. + 2. Init HumanVehicleController with mControllerId. + 3. Register with ControllerSystem. + 4. Get pointer to Vehicle via VehicleManager using vehicleId. + 5. Set Avatar member data mpVehicle to Vehicle pointer from 4. + 6. Set Controller for Vehicle. + + +Parameters: ( Vehicle* pVehicle) + +Return: void + +============================================================================= +*/ +void Avatar::SetInCarController( void ) +{ + rAssert( mpVehicle ); + mpVehicle->mGeometryVehicle->FadeRoof( true ); + mpVehicle->mGeometryVehicle->EnableLights( true ); + mpVehicle->SetUserDrivingCar( true ); + mpVehicle->SetDisableGasAndBrake(false); + + // in case we just got into a traffi car, make sure brake lights are off + mpVehicle->mGeometryVehicle->HideBrakeLights(); + mpVehicle->mBrakeLightsOn = false; + mpVehicle->mGeometryVehicle->HideReverseLights(); + mpVehicle->mReverseLightsOn = false; + + int vehicleId = GetVehicleCentral( )->GetVehicleId( mpVehicle ); + // Detach the vehicle controller. + // + UnRegisterMappableHandle( mControllerId, mpAvatarInputManager->mVehicleMappableHandle ); + + + HumanVehicleController* pVehicleController = mpAvatarInputManager->mpHumanVehicleController; + + VehicleMappable* pVehicleMappable = mpAvatarInputManager->mpVehicleMappable; + pVehicleController->Create( mpVehicle, pVehicleMappable, mControllerId ); + pVehicleMappable->SetController( pVehicleController ); + + mpAvatarInputManager->mVehicleMappableHandle = InputManager::GetInstance( )->RegisterMappable( mControllerId, pVehicleMappable ); + + bool wheelAttached = false; +#ifdef RAD_PS2 + if ( GetGameplayManager()->GetGameType() != GameplayManager::GT_SUPERSPRINT ) + { + if ( mControllerId == Input::USB0 || + ( mControllerId != Input::USB0 && + mControllerId != Input::USB1 && + GetInputManager()->IsControllerInPort( Input::USB0 ) + ) + ) + { + mpAvatarInputManager->mVehicleMappableHandleWheel0 = InputManager::GetInstance( )->RegisterMappable( Input::USB0, mpAvatarInputManager->mpVehicleMappableUSB0 ); + pVehicleController->SetWheel( mpAvatarInputManager->mpVehicleMappableUSB0, 0 ); + mpAvatarInputManager->mpVehicleMappableUSB0->SetController( pVehicleController ); + GetInputManager()->SetRumbleForDevice( Input::USB0, GetInputManager()->IsRumbleEnabled() ); + wheelAttached = true; + } + else if ( mControllerId == Input::USB1 || + ( mControllerId != Input::USB0 && + mControllerId != Input::USB1 && + GetInputManager()->IsControllerInPort( Input::USB1 ) + ) + ) + { + mpAvatarInputManager->mVehicleMappableHandleWheel1 = InputManager::GetInstance( )->RegisterMappable( Input::USB1, mpAvatarInputManager->mpVehicleMappableUSB1 ); + pVehicleController->SetWheel( mpAvatarInputManager->mpVehicleMappableUSB1, 1 ); + mpAvatarInputManager->mpVehicleMappableUSB1->SetController( pVehicleController ); + GetInputManager()->SetRumbleForDevice( Input::USB1, GetInputManager()->IsRumbleEnabled() ); + wheelAttached = true; + } + } +#endif + + if ( !wheelAttached ) + { + GetInputManager()->SetRumbleForDevice( mControllerId, GetInputManager()->IsRumbleEnabled() ); + } + + if ( !GetGameplayManager()->mIsDemo ) + { + GetVehicleCentral( )->SetVehicleController( vehicleId, pVehicleController ); + } + + // First things, detach the in car character controller. + // + UnRegisterMappableHandle( mControllerId, mpAvatarInputManager->mCharacterMappableHandle ); + + CharacterMappable* pCharacterMappable = mpAvatarInputManager->mpInCarCharacterMappable; + CameraRelativeCharacterController* pCharacterController = mpAvatarInputManager->mpCameraRelativeCharacterController; + pCharacterController->Create( mpCharacter, pCharacterMappable ); + + mpAvatarInputManager->mCharacterMappableHandle = InputManager::GetInstance()->RegisterMappable( mControllerId, pCharacterMappable ); + mpCharacter->SetController( pCharacterController ); + + // new: + // for changing the physics/collision representation if applicabble + mpVehicle->SetInCarSimState(); + + mpVehicle->BeefUpHitPointsOnTrafficCarsWhenUserDriving(); + + +} + +void Avatar::SetCameraTargetToVehicle( bool cut ) +{ + // Set the camera target. + // + GetSuperCamManager()->GetSCC( mPlayerId )->SetTarget( mpVehicle ); + + // Select the follow cam. + // + GetSuperCamManager()->GetSCC( mPlayerId )->SelectSuperCam( SuperCam::FOLLOW_CAM, cut ? (SuperCamCentral::CUT|SuperCamCentral::FORCE ) : 0, cut ? 0 : 7000 ); +} + +void Avatar::GetIntoVehicleStart( Vehicle* pVehicle) +{ + SetVehicle( pVehicle ); + + // check to see if the target vehicle is currently controlled by traffic + // if it is, we need to work a little harder to carjack it :-) + if(pVehicle->GetLocomotionType() == VL_TRAFFIC) + { + pVehicle->SetLocomotion(VL_PHYSICS); + pVehicle->mHijackedByUser = true; + } + + if(GetSuperCamManager()->GetSCC( mPlayerId )->GetPreferredFollowCam() != SuperCam::BUMPER_CAM) + { + SetCameraTargetToVehicle( ); + } + + CGuiScreenMultiHud* currentHUD = GetCurrentHud(); + if( currentHUD != NULL ) + { + currentHUD->GetHudMap( mPlayerId )->UnregisterIcon( 0 ); + currentHUD->GetHudMap( mPlayerId )->RegisterIcon( HudMapIcon::ICON_PLAYER, + rmt::Vector( 0, 0, 0 ), + pVehicle ); + } + + UnRegisterMappableHandle( mControllerId, mpAvatarInputManager->mCharacterMappableHandle ); + + +} + +void Avatar::GetIntoVehicleEnd( Vehicle* pVehicle) +{ + SetCameraTargetToVehicle( ); + + SetInCarController( ); + // this call is to record the rest seating positions of the driver and passenger + pVehicle->RecordRestSeatingPositionsOnEntry(); +} + +/* +============================================================================== +Avatar::GetOutOfVehicle +============================================================================== +Description: Pseudo code + 2. Set Controller for Vehicle to NULL ( or AI controller ) + 3. Set Vehicle member data to NULL. + 4. Unregister controller with controller system. + +Parameters: ( Vehicle* pVehicle) + +Return: void + +============================================================================= +*/ +void Avatar::GetOutOfVehicleStart( Vehicle* pVehicle) +{ + // First things, detach the in car character controller. + // + UnRegisterMappableHandle( mControllerId, mpAvatarInputManager->mCharacterMappableHandle ); + + // Detach the vehicle controller. + UnRegisterMappableHandle( mControllerId, mpAvatarInputManager->mVehicleMappableHandle ); +#ifdef RAD_PS2 + if ( mpAvatarInputManager->mVehicleMappableHandleWheel0 != -1 ) + { + UnRegisterMappableHandle( Input::USB0, mpAvatarInputManager->mVehicleMappableHandleWheel0 ); + GetInputManager()->SetRumbleForDevice( Input::USB0, false ); + } + + if ( mpAvatarInputManager->mVehicleMappableHandleWheel1 != -1 ) + { + UnRegisterMappableHandle( Input::USB1, mpAvatarInputManager->mVehicleMappableHandleWheel1 ); + GetInputManager()->SetRumbleForDevice( Input::USB1, false ); + } +#endif + + // attach the on-foot controller + // we do this early (don't wait for end of get out of car) so that we can abort \ + // the door close if there is movement) + SetOutOfCarController(); + + int vehicleId = GetVehicleCentral()->GetVehicleId( pVehicle, false ); + + if ( !GetGameplayManager()->mIsDemo && (vehicleId != -1)) + { + GetVehicleCentral( )->SetVehicleController( vehicleId, 0 ); + } + + if(GetSuperCamManager()->GetSCC( mPlayerId )->GetPreferredFollowCam() == SuperCam::BUMPER_CAM) + { + SetCameraTargetToCharacter(); + } +} + +void Avatar::SetOutOfCarController( void ) +{ + if( GetVehicle() ) + { + GetVehicle()->mGeometryVehicle->FadeRoof( false ); + + if( !TrafficManager::GetInstance()->IsVehicleTrafficVehicle( GetVehicle() ) ) + { + GetVehicle()->mGeometryVehicle->EnableLights( false ); + } + + GetVehicle()->SetOutOfCarSimState(); + + GetVehicle()->SetUserDrivingCar( false ); + } + + // Then, make a new character controller. + // + CharacterMappable* pCharacterMappable = mpAvatarInputManager->mpBipedCharacterMappable; + CameraRelativeCharacterController* pCharacterController = mpAvatarInputManager->mpCameraRelativeCharacterController; + pCharacterController->Create( mpCharacter, pCharacterMappable ); + + // Register the new character controller. + // + mpAvatarInputManager->mCharacterMappableHandle = InputManager::GetInstance()->RegisterMappable( mControllerId, pCharacterMappable ); + mpCharacter->SetController( pCharacterController ); + + pCharacterController->SetCamera( GetSuperCamManager()->GetSCC( mPlayerId )->GetCamera() ); + + GetInputManager()->SetRumbleForDevice( mControllerId, false ); +} + +void Avatar::SetCameraTargetToCharacter( bool cut ) +{ + // Set the camera target. + // + GetSuperCamManager()->GetSCC( mPlayerId )->SetTarget( mpCharacter->GetTarget( ) ); + // Select the follow cam. + // +#ifdef RAD_WIN32 + GetSuperCamManager()->GetSCC( mPlayerId )->SelectSuperCam( SuperCam::ON_FOOT_CAM, cut ? SuperCamCentral::CUT : 0 ); +#else + GetSuperCamManager()->GetSCC( mPlayerId )->SelectSuperCam( SuperCam::WALKER_CAM, cut ? SuperCamCentral::CUT : 0 ); +#endif +} + +void Avatar::GetOutOfVehicleEnd( Vehicle* pVehicle) +{ + pVehicle->DrawVehicle(true); // just in case it was in bumper cam + + SetCameraTargetToCharacter(); + + SetVehicle( 0 ); + + CGuiScreenMultiHud* currentHUD = GetCurrentHud(); + if( currentHUD != NULL ) + { + currentHUD->GetHudMap( mPlayerId )->UnregisterIcon( 0 ); + currentHUD->GetHudMap( mPlayerId )->RegisterIcon( HudMapIcon::ICON_PLAYER, + rmt::Vector( 0, 0, 0 ), + mpCharacter->GetTarget() ); + } +} + /* +============================================================================== +Avatar::IsInCar +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: bool + +============================================================================= +*/ +bool Avatar::IsInCar( void ) const +{ + rAssert( mpCharacter ); + return mpCharacter->IsInCar( ) && (mpVehicle != NULL); +} + + +const void Avatar::GetPosition( rmt::Vector& pos ) const +{ + if( this->IsInCar() ) + { + mpVehicle->GetPosition( &pos ); + } + else + { + mpCharacter->GetPosition( pos ); + } +} + +const void Avatar::GetHeading( rmt::Vector& irHeading ) const +{ + if( this->IsInCar() ) + { + mpVehicle->GetHeading( &irHeading ); + } + else + { + mpCharacter->GetFacing( irHeading ); + } +} + +const void Avatar::GetNormalizedHeadingSafe( rmt::Vector& heading ) const +{ + if( this->IsInCar() ) + { + assert( mpVehicle != NULL ); + mpVehicle->GetVelocity( &heading ); + float speedMps = mpVehicle->GetSpeedKmh() * KPH_2_MPS; + if( rmt::Fabs(speedMps) < 0.00001f ) + { + heading.Set( 0.0f, 0.0f, 0.0f ); + } + else + { + heading.Scale( 1.0f/speedMps ); + } + } + else + { + // for characters, facing is same as heading (no skidding/sliding) + // I hope... + assert( mpCharacter != NULL ); + mpCharacter->GetFacing( heading ); + } + assert( rmt::Epsilon( heading.MagnitudeSqr(), 1.0f, 0.00001f ) || + rmt::Epsilon( heading.MagnitudeSqr(), 0.0f, 0.00001f ) ); +} + +const void Avatar::GetVelocity( rmt::Vector& vel ) const +{ + if( this->IsInCar() ) + { + assert( mpVehicle != NULL ); + mpVehicle->GetVelocity( &vel ); + } + else + { + // Not sure here if the Choreo puppet's velocity value + // (which is what Character::GetVelocity() retrieves) + // is a good one. So we just track our own. + assert( mpCharacter != NULL ); + mpCharacter->GetVelocity( vel ); + } +} + +const float Avatar::GetSpeedMps() const +{ + if( this->IsInCar() ) + { + assert( mpVehicle != NULL ); + return mpVehicle->GetSpeedKmh() * KPH_2_MPS; + } + else + { + assert( mpCharacter != NULL ); + return mpCharacter->GetSpeed(); + } + +} + +void Avatar::OnCheatEntered( eCheatID cheatID, bool isEnabled ) +{ + if( cheatID == CHEAT_ID_SHOW_AVATAR_POSITION ) + { + s_displayCoordinates = isEnabled; + } +} + +/* +============================================================================== +Avatar::Update +============================================================================== +Description: Comment + +Parameters: (float dt) + +Return: void + +============================================================================= +*/ +void Avatar::Update(float dt) +{ + mHasBeenUpdatedThisFrame = false; +#ifndef FINAL + char buffy[256]; + rmt::Vector posn; + const pddiColour YELLOW(255,255,0); + + // + // Coordinate display cheat + // + if( s_displayCoordinates ) + { + if( mpCharacter != NULL ) + { + GetPosition( posn ); + } + else + { + posn.Set( 0.0f, 0.0f, 0.0f ); + } + sprintf( buffy, "Avatar %d position: %f %f %f", mPlayerId, posn.x, posn.y, posn.z ); + p3d::pddi->DrawString( buffy, 40, 40 + ( mPlayerId * 20), YELLOW ); + } +#endif +} + +void Avatar::GetLastPathInfo( + RoadManager::PathElement& oElem, + RoadSegment*& oSeg, + float& oSegT, + float& oRoadT ) +{ + oElem.elem = NULL; + oSeg = NULL; + oSegT = 0.0f; + oRoadT = 0.0f; + + // don't do it again if already done + if( !mHasBeenUpdatedThisFrame ) + { + mHasBeenUpdatedThisFrame = true; + ////////////////////////////////////////////////////////////////////// + // Update the Avatar's closest path entities in the world. + + rmt::Vector myPos; + GetPosition( myPos ); + + + RoadSegment* seg = NULL; + float segT = 0.0f; + float roadT = 0.0f; + RoadManager::PathElement elem; + elem.elem = NULL; + + /* + RoadManager::PathfindingOptions options = RoadManager::PO_SEARCH_SHORTCUTS; + RoadManager::GetInstance()->FindClosestPathElement( + pos, 100.0f, options, tmpPathElem, tmpSeg, tmpSegT, tmpRoadT ); + */ + + // Special initial case: + // If my last path info is completely unset (started off the road) + // then we fetch the closest path element using Devin's thing + if( mLastPathElement.elem == NULL && mLastRoadSegment == NULL ) + { + RoadSegment* closestSeg = NULL; + float dummy; + GetIntersectManager()->FindClosestAnyRoad( myPos, 100.0f, closestSeg, dummy ); + + seg = (RoadSegment*) closestSeg; + segT = RoadManager::DetermineSegmentT( myPos, seg ); + roadT = RoadManager::DetermineRoadT( seg, segT ); + elem.elem = seg->GetRoad(); + elem.type = RoadManager::ET_NORMALROAD; + } + else + { + VehicleAI::FindClosestPathElement( myPos, elem, seg, segT, roadT, true ); + } + + if( elem.elem == NULL ) + { + GetEventManager()->TriggerEvent( EVENT_AVATAR_OFF_ROAD ); + + oElem = mLastPathElement; + oSeg = mLastRoadSegment; + oSegT = mLastRoadSegmentT; + oRoadT = mLastRoadT; + return; + } + else + { + GetEventManager()->TriggerEvent( EVENT_AVATAR_ON_ROAD ); + } + + rAssert( elem.elem != NULL ); + + mLastPathElement = elem; + if( seg ) + { + mLastRoadSegment = seg; + mLastRoadSegmentT = segT; + mLastRoadT = roadT; + } + + /* + // + // true means not to ignore the path element we just found + // default is true because we could have lastpathelement == elem + // but we could be on different segments... + // + bool OK = true; + + if( mLastPathElement != elem ) + { + if( mLastPathElement.elem == NULL ) + { + OK = true; + } + else + { + Road* lastRoad = NULL; + Intersection* lastInt = NULL; + Road* currRoad = NULL; + Intersection* currInt = NULL; + + // temporary type: 0 = intersection, 1 = road, 2 = shortcut + + // Figure out what type is LAST + int lastElemType = 0; + if( mLastPathElement.type == RoadManager::ET_NORMALROAD ) + { + lastRoad = (Road*) mLastPathElement.elem; + lastElemType++; + + if( lastRoad->GetShortCut() ) + { + lastElemType++; + } + } + else + { + lastInt = (Intersection*) mLastPathElement.elem; + } + + // Figure out what type is CURR + int currElemType = 0; + if( elem.type == RoadManager::ET_NORMALROAD ) + { + currRoad = (Road*) elem.elem; + currElemType++; + + if( currRoad->GetShortCut() ) + { + currElemType++; + } + } + else + { + currInt = (Intersection*) elem.elem; + } + + // + // Start resolving ... + /////////////// + // LAST: Intersection + if( lastElemType == 0 ) + { + rAssert( lastInt ); + if( currElemType == 2 )// CURR: shortcut + { + rAssert( currRoad ); + if( lastInt == (Intersection*) currRoad->GetSourceIntersection() || + lastInt == (Intersection*) currRoad->GetDestinationIntersection() ) + { + OK = true; + } + else + { + OK = false; + } + } + else + { + OK = true; + } + } + ///////////////// + // LAST: Normal road + else if( lastElemType == 1 ) + { + rAssert( lastRoad ); + + if( currElemType == 0 ) // CURR: intersection + { + rAssert( currInt ); + if( currInt == (Intersection*)lastRoad->GetSourceIntersection() || + currInt == (Intersection*)lastRoad->GetDestinationIntersection() ) + { + OK = true; + } + else + { + OK = false; + } + } + else if( currElemType == 1 ) // CURR: normal road + { + rAssert( currRoad ); + OK = true; + } + else // CURR: shortcut + { + rAssert( currRoad ); + OK = false; + } + } + //////////////// + // LAST: Shortcut + else + { + rAssert( lastRoad ); + + if( currElemType == 0 ) // CURR: intersection + { + rAssert( currInt ); + + // NOTE: + // This is the fix to Level 3 Mission 7 where the limo drives over the where the shortcuts + // and normal road segments merge near the squidport.... This was where its last good element + // was found... It then transitted onto the intersection, but rejected it because back then + // we only tested for the destination intersection... So it was stuck there for a long time + // but didn't realize it until it hit the next waypoint (wayy... over by the dam) and decided + // it needed to turn back. + if( currInt == (Intersection*)lastRoad->GetDestinationIntersection() || + currInt == (Intersection*)lastRoad->GetSourceIntersection() ) + { + OK = true; + } + else + { + OK = false; + } + } + else if( currElemType == 1 ) // CURR: normal road + { + rAssert( currRoad ); + OK = false; + } + else // CURR: shortcut + { + rAssert( currRoad ); + OK = false; + } + } + } + } + + if( OK ) + { + mLastPathElement = elem; + if( seg ) + { + if( mLastRoadSegment != seg ) + { + mLastRoadSegment = seg; + } + mLastRoadSegmentT = segT; + mLastRoadT = roadT; + } + } + */ + } + + oElem = mLastPathElement; + oSeg = (RoadSegment*) mLastRoadSegment; + oSegT = mLastRoadSegmentT; + oRoadT = mLastRoadT; +} + +void Avatar::GetRaceInfo( float& distToCurrCollectible, int& currCollectible, int& numLapsCompleted ) +{ + distToCurrCollectible = mDistToCurrentCollectible; + currCollectible = mCurrentCollectible; + numLapsCompleted = mNumLapsCompleted; +} + +void Avatar::SetRaceInfo( float distToCurrCollectible, int currCollectible, int numLapsCompleted ) +{ + mDistToCurrentCollectible = distToCurrCollectible; + mCurrentCollectible = currCollectible; + mNumLapsCompleted = numLapsCompleted; +} + +/* +============================================================================== +Avatar::SetCamera +============================================================================== +Description: Controller needs to be aware of camera changes so that + direction can be normalized. + +Parameters: ( tCamera* pCamera ) + +Return: void + +============================================================================= +*/ +void Avatar::SetCamera( tCamera* pCamera ) +{ + rAssert( pCamera != 0 ); + mpAvatarInputManager->mpCameraRelativeCharacterController->SetCamera( pCamera ); +} + +//****************************************************************************** +// +// Private Member Functions +// +//****************************************************************************** diff --git a/game/code/worldsim/avatar.h b/game/code/worldsim/avatar.h new file mode 100644 index 0000000..bf3fd05 --- /dev/null +++ b/game/code/worldsim/avatar.h @@ -0,0 +1,158 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: avatar.h +// +// Description: Avatar Class declaration. +// +// History: 4/3/2002 + Created -- TBJ +// +//============================================================================= + +#ifndef AVATAR_H +#define AVATAR_H + +//======================================== +// Nested Includes +//======================================== + +#include <radmath/radmath.hpp> +#include <cheats/cheatinputsystem.h> +#include <roads/roadmanager.h> + +//======================================== +// Forward References +//======================================== +class Character; +class Vehicle; +class VehicleController; +struct AvatarInputManager; +class tCamera; + +//============================================================================= +// +// Synopsis: The Avatar +// +//============================================================================= + +class Avatar : public ICheatEnteredCallback +{ +// METHODS: +public: + Avatar(); + ~Avatar(); + + void Destroy( void ); + + int GetControllerId( void ) const; + void SetControllerId ( int id ); + + void SetPlayerId( int id ) { mPlayerId = id; } + int GetPlayerId() const { return mPlayerId; } + + Character* GetCharacter( void ) const; + void SetCharacter( Character* pCharacter ); + + Vehicle* GetVehicle( void ) const; + void SetVehicle( Vehicle* pVehicle ); + + void GetIntoVehicleStart( Vehicle* pVehicle ); + void GetIntoVehicleEnd( Vehicle* pVehicle ); + void GetOutOfVehicleStart( Vehicle* pVehicle ); + void GetOutOfVehicleEnd( Vehicle* pVehicle ); + + void SetInCarController( void ); + void SetOutOfCarController( void ); + void SetCameraTargetToCharacter( bool cut = false ); + void SetCameraTargetToVehicle( bool cut = false ); + + void SetCamera( tCamera* pCamera ); + + const void GetPosition( rmt::Vector& pos ) const; + const void GetHeading( rmt::Vector& irHeading ) const; + + // *** // + const void GetNormalizedHeadingSafe( rmt::Vector& heading ) const; + const void GetVelocity( rmt::Vector& vel ) const; + const float GetSpeedMps() const; + // *** // + + bool IsInCar( void ) const; + + void PreSubstepUpdate(); + void Update(float dt); + void PreCollisionPrep(); + + void OnCheatEntered( eCheatID cheatID, bool isEnabled ); + + void GetLastPathInfo( + RoadManager::PathElement& elem, + RoadSegment*& seg, + float& segT, + float& roadT ); + + void GetRaceInfo( + float& distToCurrCollectible, + int& currCollectible, + int& numLapsCompleted ); + + void SetRaceInfo( + float distToCurrCollectible, + int currCollectible, + int numLapsCompleted ); + +//MEMBERS +public: + + +private: + + ///////////////// PATH INFO ///////////////////// + bool mHasBeenUpdatedThisFrame; + RoadManager::PathElement mLastPathElement; + RoadSegment* mLastRoadSegment; + float mLastRoadSegmentT; + float mLastRoadT; + + ///////////////// RACE DATA /////////////////// + // we keep pieces of the race data in this class + // because catch-up logic will need to use them + float mDistToCurrentCollectible; + int mCurrentCollectible; + int mNumLapsCompleted; + + + //Prevent wasteful constructor creation. + // + Avatar( const Avatar& avatar ); + Avatar& operator=( const Avatar& avatar ); + + // Data. + // + static const int INVALID_CONTROLLER = -1; + int mControllerId; + + int mPlayerId; + + Character* mpCharacter; + + // NULL if not in any vehicle, when IsInCar, this is set to that car + Vehicle* mpVehicle; + + AvatarInputManager* mpAvatarInputManager; + + static bool s_displayCoordinates; + + /* + // TRUE if neither directly on a NON-shortcut road segment nor + // in an intersection + bool mOffRoad; + + // number of seconds mOffRoad has been true or false + float mSecondsOffOrOnRoad; + */ +}; + + + +#endif //AVATAR_H diff --git a/game/code/worldsim/avatarmanager.cpp b/game/code/worldsim/avatarmanager.cpp new file mode 100644 index 0000000..3f8dcbe --- /dev/null +++ b/game/code/worldsim/avatarmanager.cpp @@ -0,0 +1,511 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: +// +// Description: Implementation of class AvatarManager +// +// History: 4/3/2002 + Created -- NAME +// +//============================================================================= + +//======================================== +// System Includes +//======================================== +// Foundation Tech +#include <raddebug.hpp> + +//======================================== +// Project Includes +//======================================== +#include <worldsim/avatarmanager.h> +#include <worldsim/avatar.h> +#include <worldsim/character/charactermanager.h> +#include <worldsim/character/character.h> +#include <memory/srrmemory.h> +#include <worldsim/vehiclecentral.h> +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/avatarmanager.h> +#include <worldsim/avatar.h> +#include <mission/gameplaymanager.h> +#include <events/eventenum.h> +#include <events/eventmanager.h> +#include <meta/locatorevents.h> +#include <meta/eventlocator.h> +#include <presentation/gui/guisystem.h> +#include <presentation/gui/guiwindow.h> +#include <presentation/gui/ingame/guiscreenhud.h> +#include <presentation/gui/ingame/guimanageringame.h> +#include <presentation/gui/ingame/guiscreeniriswipe.h> +#include <input/inputmanager.h> + +#include <camera/supercammanager.h> +#include <camera/supercamcentral.h> +#include <camera/supercam.h> + +#include <gameflow/gameflow.h> +#include <contexts/context.h> + +#include <ai/sequencer/actioncontroller.h> +#include <supersprint/supersprintmanager.h> + +//****************************************************************************** +// +// Global Data, Local Data, Local Classes +// +//****************************************************************************** +AvatarManager* AvatarManager::spAvatarManager = 0; + +//****************************************************************************** +// +// Public Member Functions +// +//****************************************************************************** + +AvatarManager* AvatarManager::CreateInstance( void ) +{ + rAssertMsg( spAvatarManager == 0, "AvatarManager already created.\n" ); + spAvatarManager = new ( GMA_PERSISTENT ) AvatarManager; + return AvatarManager::GetInstance(); +} + +AvatarManager* AvatarManager::GetInstance( void ) +{ + rAssertMsg( spAvatarManager != 0, "AvatarManager has not been created yet.\n" ); + return spAvatarManager; +} + + +void AvatarManager::DestroyInstance( void ) +{ + rAssertMsg( spAvatarManager != 0, "AvatarManager has not been created.\n" ); + delete ( GMA_PERSISTENT, spAvatarManager ); +} +//============================================================================== +// AvatarManager::AvatarManager +//============================================================================== +// Description: Constructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +AvatarManager::AvatarManager() +: +mNumAvatars( 0 ) +{ +} + +//============================================================================== +// AvatarManager::~AvatarManager +//============================================================================== +// Description: Destructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +AvatarManager::~AvatarManager() +{ + Destroy( ); +} + +void AvatarManager::Destroy( void ) +{ + int i; + for ( i = 0; i < mNumAvatars; i++ ) + { + mAvatarArray[ i ]->Destroy(); + delete mAvatarArray[i]; + } + mNumAvatars = 0; + + GetEventManager()->RemoveAll( this ); +} + + +/* +============================================================================== +AvatarManager::EnterGame +============================================================================== +Description: This will be called to create the avatars just before the + game session begins. + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void AvatarManager::EnterGame( void ) +{ + + HeapMgr()->PushHeap( GMA_LEVEL_OTHER ); + + GetEventManager()->AddListener( this, EVENT_GETINTOVEHICLE_START ); + GetEventManager()->AddListener( this, EVENT_GETINTOVEHICLE_END ); + GetEventManager()->AddListener( this, EVENT_GETOUTOFVEHICLE_START ); + GetEventManager()->AddListener( this, EVENT_GETOUTOFVEHICLE_END ); + GetEventManager()->AddListener( this, EVENT_CAMERA_CHANGE ); + + int i; + for (i = 0; i < MAX_AVATARS; i++) + { + mAvatarArray[i] = 0; + } + mNumAvatars = GetGameplayManager()->GetNumPlayers(); + + for ( i = 0; i < mNumAvatars; i++ ) + { + mAvatarArray[i] = new Avatar; + + Vehicle* pVehicle = GetVehicleCentral()->GetVehicle( i );// GetNewVehicle(); + int controllerID = GetInputManager()->GetControllerIDforPlayer( i ); + rWarningMsg( controllerID != -1, "No controller ID registered for player avatar!" ); + + mAvatarArray[ i ]->SetControllerId( controllerID ); + mAvatarArray[ i ]->SetPlayerId( i ); + Character* pCharacter = GetCharacterManager( )->GetCharacter( i ); + rAssert( pCharacter ); + mAvatarArray[ i ]->SetCharacter( pCharacter ); + rmt::Vector characterPosition; + + // Single player set up. + // + pCharacter->SetInCar( false ); + mAvatarArray[ i ]->SetVehicle( (Vehicle*)0 ); + mAvatarArray[ i ]->SetCameraTargetToCharacter( true ); + mAvatarArray[ i ]->SetOutOfCarController( ); + + //Chuck Adding support for loading of the last used skin for the level + + if ( pVehicle ) + { + // Hack. MS8. + // Position the character near the vehicle. + // + characterPosition.Set( 3.1f, 0.0f, 0.0f ); + + rmt::Matrix carToWorld = pVehicle->GetTransform(); + characterPosition.Transform( carToWorld ); + pCharacter->SetPosition( characterPosition ); + pCharacter->UpdateTransformToLoco(); + } + } + + HeapMgr()->PopHeap( GMA_LEVEL_OTHER ); +} + +void AvatarManager::ExitGame () +{ + Destroy (); +} + +void AvatarManager::HandleEvent( EventEnum id, void* pEventData ) +{ + switch ( id ) + { + case EVENT_GETINTOVEHICLE_START: + { + Character* pCharacter = static_cast<Character*>( pEventData ); + Avatar* pAvatar = FindAvatarForCharacter( pCharacter ); + if ( pAvatar ) + { + Vehicle* pVehicle = pCharacter->GetTargetVehicle(); + pAvatar->GetIntoVehicleStart( pVehicle ); + } + break; + } + case EVENT_GETINTOVEHICLE_END: + { + Character* pCharacter = static_cast<Character*>( pEventData ); + Avatar* pAvatar = FindAvatarForCharacter( pCharacter ); + if ( pAvatar ) + { + Vehicle* pVehicle = pCharacter->GetTargetVehicle(); + pAvatar->GetIntoVehicleEnd( pVehicle ); + } + break; + } + case EVENT_GETOUTOFVEHICLE_START: + { + Character* pCharacter = static_cast<Character*>( pEventData ); + Avatar* pAvatar = FindAvatarForCharacter( pCharacter ); + if ( pAvatar ) + { + Vehicle* pVehicle = pAvatar->GetVehicle(); + pAvatar->GetOutOfVehicleStart( pVehicle ); + } + break; + } + case EVENT_GETOUTOFVEHICLE_END: + { + Character* pCharacter = static_cast<Character*>( pEventData ); + Avatar* pAvatar = FindAvatarForCharacter( pCharacter ); + if ( pAvatar ) + { + Vehicle* pVehicle = pAvatar->GetVehicle(); + pAvatar->GetOutOfVehicleEnd( pVehicle ); + } + break; + } + case EVENT_CAMERA_CHANGE: + { + SuperCam* sc = static_cast<SuperCam*>(pEventData); + + int playerID = 0; // normal game defaults player to zero + if( GetGameFlow()->GetCurrentContext() == CONTEXT_LOADING_SUPERSPRINT || + GetGameFlow()->GetCurrentContext() == CONTEXT_SUPERSPRINT ) + { + playerID = SuperSprintManager::GetInstance()->GetOnlyHumanPlayerID(); + /* + if( GetAvatarForPlayer( playerID )->GetPlayerId() != playerID ) + { + break; + } + */ + } + + // can be -1 if in supersprint and more than 1 human is participating... + if( playerID == -1 ) + { + break; + } + + if ( sc == GetSuperCamManager()->GetSCC( 0 )->GetActiveSuperCam() ) + { + //Find the vehicle owned by this car. + //Vehicle* car = GetGameplayManager()->GetCurrentVehicle(); + Vehicle* car = GetAvatarForPlayer( playerID )->GetVehicle(); + + + if ( car ) + { + if ( sc->GetType() == SuperCam::BUMPER_CAM ) + { + //Only stop rendering for bumpercams. + car->DrawVehicle( false ); + } + else + { + car->DrawVehicle( true ); + } + } + } + + break; + } + default: + { + break; + } + } +} + +Avatar* AvatarManager::GetAvatarForPlayer( int playerId ) +{ + if( playerId < this->mNumAvatars ) + { + return mAvatarArray[ playerId ]; + } + + return( NULL ); +} + +//============================================================================= +// AvatarManager::PutCharacterInCar +//============================================================================= +// Description: Comment +// +// Parameters: ( Character* pCharacter, Vehicle* pVehicle ) +// +// Return: void +// +//============================================================================= +void AvatarManager::PutCharacterInCar( Character* pCharacter, Vehicle* pVehicle ) +{ + rAssert( pCharacter != NULL ); + rAssert( pVehicle != NULL ); + + int i; + for ( i = 0; i < mNumAvatars; i++ ) + { + if ( mAvatarArray[ i ]->GetCharacter( ) == pCharacter ) + { + if(pCharacter->IsInCar() && (pCharacter->GetTargetVehicle() == pVehicle)) + { + return; + } + + pCharacter->GetActionController()->Clear(); + + pCharacter->SetInCar( true ); + pCharacter->UpdateTransformToInCar( ); + pCharacter->SetTargetVehicle( pVehicle ); + + // transit the vehicle into VL_PHYSICS! + if(pVehicle->GetLocomotionType() == VL_TRAFFIC) + { + pVehicle->SetLocomotion(VL_PHYSICS); + pVehicle->mHijackedByUser = true; + } + + mAvatarArray[ i ]->SetVehicle( pVehicle ); + mAvatarArray[ i ]->SetCameraTargetToVehicle( true ); //CUT the camera + mAvatarArray[ i ]->SetInCarController( ); + + pCharacter->UpdateTransformToInCar(); + if(pCharacter->GetStateManager()->GetState() == CharacterAi::INCAR) + { + pCharacter->GetStateManager()->ResetState(); + } + else + { + pCharacter->GetStateManager()->SetState<CharacterAi::InCar>(); + //pCharacter->GetStateManager()->Update( 16 ); + } + + pCharacter->SetDesiredSpeed(0.0f); + + if ( GetCurrentHud() ) + { + GetCurrentHud()->GetHudMap( i )->UnregisterIcon( 0 ); + GetCurrentHud()->GetHudMap( i )->RegisterIcon( HudMapIcon::ICON_PLAYER, + rmt::Vector( 0, 0, 0 ), + pVehicle ); + } + + return; + } + } +} + +//============================================================================= +// AvatarManager::PutCharacterOnGround +//============================================================================= +// Description: Comment +// +// Parameters: ( Character* pCharacter, Vehicle* pVehicle ) +// +// Return: void +// +//============================================================================= +void AvatarManager::PutCharacterOnGround( Character* pCharacter, Vehicle* pVehicle ) +{ + rAssert( pCharacter != NULL ); + rAssert( pVehicle != NULL ); + + int i; + for ( i = 0; i < mNumAvatars; i++ ) + { + if ( mAvatarArray[ i ]->GetCharacter( ) == pCharacter ) + { + mAvatarArray[ i ]->GetOutOfVehicleStart( pVehicle ); + mAvatarArray[ i ]->GetOutOfVehicleEnd( pVehicle ); + + pCharacter->SetInCar( false ); + + pCharacter->UpdateTransformToLoco(); + pCharacter->GetStateManager()->SetState<CharacterAi::Loco>(); + pCharacter->GetStateManager()->Update( 16 ); + + return; + } + } +} + +/* +============================================================================== +AvatarManager::Update +============================================================================== +Description: Comment + +Parameters: (float dt) + +Return: void + +============================================================================= +*/ +void AvatarManager::Update(float dt) +{ + int i; + for ( i = 0; i < mNumAvatars; i++ ) + { + mAvatarArray[ i ]->Update( dt ); + } +} +//****************************************************************************** +// +// Private Member Functions +// +//****************************************************************************** + +Avatar* AvatarManager::FindAvatarForCharacter( Character* pCharacter ) +{ + int i; + for ( i = 0; i < mNumAvatars; i++ ) + { + if ( mAvatarArray[ i ]->GetCharacter( ) == pCharacter ) + { + return mAvatarArray[ i ]; + } + } + return 0; +} + + +//============================================================================= +// AvatarManager::GetAvatarForVehicle +//============================================================================= +// Description: Comment +// +// Parameters: (Vehicle* vehicle) +// +// Return: Avatar +// +//============================================================================= +Avatar* AvatarManager::GetAvatarForVehicle(Vehicle* vehicle) +{ + int i; + for ( i = 0; i < mNumAvatars; i++ ) + { + if (mAvatarArray[i]->GetVehicle() == vehicle) + { + return mAvatarArray[i]; + } + } + return 0; +} + +//============================================================================= +// AvatarManager::IsAvatarGettingInOrOutOfCar +//============================================================================= +// Description: returns true if the chacter for playerId is in a get into or get out of car transition state. +// +// Parameters: int playerId +// +// Return: bool +// +//============================================================================= +bool AvatarManager::IsAvatarGettingInOrOutOfCar(int playerId) +{ + if ( + GetAvatarForPlayer(0)->GetCharacter()->GetStateManager()->GetState() == CharacterAi::GET_IN + || + GetAvatarForPlayer(0)->GetCharacter()->GetStateManager()->GetState() == CharacterAi::GET_OUT + ) + + { + return true; + } + else + { + return false; + } +} + + + + diff --git a/game/code/worldsim/avatarmanager.h b/game/code/worldsim/avatarmanager.h new file mode 100644 index 0000000..c0230e7 --- /dev/null +++ b/game/code/worldsim/avatarmanager.h @@ -0,0 +1,87 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: avatarmanager.h +// +// Description: Blahblahblah +// +// History: 4/3/2002 + Created -- NAME +// +//============================================================================= + +#ifndef AVATARMANAGER_H +#define AVATARMANAGER_H + +//======================================== +// Nested Includes +//======================================== +#include <worldsim/avatar.h> +#include <events/eventlistener.h> + +#include <constants/maxplayers.h> + +//======================================== +// Forward References +//======================================== + +class Character; + +//============================================================================= +// +// Synopsis: Blahblahblah +// +//============================================================================= + +class AvatarManager +: +public EventListener +{ +public: + AvatarManager(); + ~AvatarManager(); + void Destroy( void ); + + static AvatarManager* GetInstance( ); + static AvatarManager* CreateInstance( ); + static void DestroyInstance( ); + + void EnterGame( void ); + void ExitGame( void ); + virtual void HandleEvent( EventEnum id, void* pEventData ); + + Avatar* GetAvatarForPlayer( int playerId ); + Avatar* GetAvatarForVehicleId( int vehicleId ); + + Avatar* GetAvatarForVehicle(Vehicle* vehicle); + + Avatar* FindAvatarForCharacter( Character* pCharacter ); + + void PutCharacterInCar( Character* pCharacter, Vehicle* pVehicle ); + void PutCharacterOnGround( Character* pCharacter, Vehicle* pVehicle ); + + //chuck returns a true if the players character is in get in car or get out of car transition state. + bool IsAvatarGettingInOrOutOfCar(int playerId); + + void Update(float dt); + + +private: + + //Prevent wasteful constructor creation. + AvatarManager( const AvatarManager& avatarmanager ); + AvatarManager& operator=( const AvatarManager& avatarmanager ); + + // Data. + // + static AvatarManager* spAvatarManager; + + static const int MAX_AVATARS = MAX_PLAYERS; + + Avatar* mAvatarArray[ MAX_AVATARS ]; + int mNumAvatars; +}; + +// A little syntactic sugar for getting at this singleton. +inline AvatarManager* GetAvatarManager() { return( AvatarManager::GetInstance() ); } + +#endif //AVATARMANAGER_H diff --git a/game/code/worldsim/character/aicharactercontroller.cpp b/game/code/worldsim/character/aicharactercontroller.cpp new file mode 100644 index 0000000..71c9cbb --- /dev/null +++ b/game/code/worldsim/character/aicharactercontroller.cpp @@ -0,0 +1,112 @@ +#include <worldsim/character/aicharactercontroller.h> +#include <worldsim/character/character.h> +#include <worldsim/character/charactermanager.h> +#include <main/game.h> + +class Behaviour +{ +public: + virtual rmt::Vector& Tick( Character& me ) = 0; +}; + +class Wander +: +public Behaviour +{ +public: + + Wander( void ); + virtual ~Wander( void ); + + rmt::Vector& GetTargetPoint( void ); + void SetTargetPoint( rmt::Vector& targetPoint ); + + rmt::Vector& GetPosition( void ); + void SetPosition( rmt::Vector& position ); + + float GetTargetRadius( void ) const; + void SetTargetRadius( float radius ); + + float GetSteeringRadius( void ) const; + void SetSteeringRadius( float radius ); + + rmt::Vector& Tick( Character& me ); +private: + rmt::Vector mTargetPoint; + rmt::Vector mPosition; + float mfTargetCircleRadius; + float mfSteeringCircleRadius; +}; + +Wander::Wander( void ) +: +mTargetPoint( 0.0f, 0.0f, -1.0f ), +mPosition( 0.0f, 0.0f, -1.0f ), +mfTargetCircleRadius( 1.0f ), +mfSteeringCircleRadius( 0.3f ) +{ + mTargetPoint.z = mfTargetCircleRadius; +} + +Wander::~Wander( void ) +{ +} + +rmt::Vector& Wander::Tick( Character& me ) +{ + static rmt::Randomizer r( Game::GetRandomSeed () ); + static rmt::Vector point( 0.0f, 0.0f, 0.0f ); + + point.x = r.FloatSigned( ); + point.y = 0.0f; + point.z = r.FloatSigned( ); + + // Make a point on the new circle. + // + point.Normalize( ); + point.Scale( mfSteeringCircleRadius ); + + // Add to point on circle. + // + mTargetPoint.Add( point ); + // Project back to unit circle. + // + mTargetPoint.Normalize( ); + // Scale to actual Target Circle. + // + mTargetPoint.Scale( mfTargetCircleRadius ); + + return mTargetPoint; +} + +AICharacterController::AICharacterController( Character* pCharacter, int index, tCamera* pCamera ) +: +mpBehaviour( 0 ) +{ +} + +void AICharacterController::Update( float timeins ) +{ + static Character* pCharacter = GetCharacterManager( )->GetCharacter( 0 ); + if ( !mpBehaviour ) + { + mpBehaviour = new Wander; + } + + mDirection = mpBehaviour->Tick( *pCharacter ); +} + +void AICharacterController::GetDirection( rmt::Vector& outDirection ) const +{ + outDirection = mDirection; +} + +float AICharacterController::GetValue( int buttonId ) const +{ + return 0.0f; +} + +bool AICharacterController::IsButtonDown( int buttonId ) const +{ + return false; +}
\ No newline at end of file diff --git a/game/code/worldsim/character/aicharactercontroller.h b/game/code/worldsim/character/aicharactercontroller.h new file mode 100644 index 0000000..af73274 --- /dev/null +++ b/game/code/worldsim/character/aicharactercontroller.h @@ -0,0 +1,27 @@ +#ifndef AICHARACTERCONTROLLER_H_ +#define AICHARACTERCONTROLLER_H_ + +#include <worldsim/character/charactercontroller.h> +#include <radmath/radmath.hpp> + +class Behaviour; +class Character; +class tCamera; + +class AICharacterController +: +public CharacterController +{ +public: + AICharacterController( Character* pCharacter, int index, tCamera* pCamera ); + void Update( float timeins ); + void GetDirection( rmt::Vector& outDirection ) const; + float GetValue( int buttonId ) const; + bool IsButtonDown( int buttonId ) const; +protected: +private: + rmt::Vector mDirection; + Behaviour* mpBehaviour; +}; + +#endif // AICHARACTERCONTROLLER_H_
\ No newline at end of file diff --git a/game/code/worldsim/character/allcharacter.cpp b/game/code/worldsim/character/allcharacter.cpp new file mode 100644 index 0000000..2fc662d --- /dev/null +++ b/game/code/worldsim/character/allcharacter.cpp @@ -0,0 +1,7 @@ +#include <worldsim/character/aicharactercontroller.cpp> +#include <worldsim/character/character.cpp> +#include <worldsim/character/charactercontroller.cpp> +#include <worldsim/character/charactermanager.cpp> +#include <worldsim/character/charactermappable.cpp> +#include <worldsim/character/characterrenderable.cpp> +#include <worldsim/character/charactertarget.cpp> diff --git a/game/code/worldsim/character/character.cpp b/game/code/worldsim/character/character.cpp new file mode 100644 index 0000000..61f82ae --- /dev/null +++ b/game/code/worldsim/character/character.cpp @@ -0,0 +1,5171 @@ +#include <worldsim/character/character.h> + +#include <choreo/animation.hpp> +#include <p3d/anim/skeleton.hpp> +#include <p3d/matrixstack.hpp> +#include <simcommon/simstatearticulated.hpp> +#include <simcollision/collisionvolume.hpp> + +#include <worldsim/character/charactercontroller.h> +#include <worldsim/character/charactermappable.h> +#include <worldsim/character/aicharactercontroller.h> +#include <worldsim/character/characterrenderable.h> +#include <worldsim/character/charactertarget.h> +#include <worldsim/character/charactermanager.h> + +#include <worldsim/coins/sparkle.h> +#include <worldsim/avatarmanager.h> +#include <worldsim/worldphysicsmanager.h> +#include <roads/geometry.h> +#include <memory/srrmemory.h> +#include <ai/sequencer/actioncontroller.h> +#include <ai/sequencer/action.h> +#include <ai/statemanager.h> +#include <ai/actionbuttonhandler.h> +#include <ai/actionbuttonmanager.h> +#include <render/RenderManager/WorldRenderLayer.h> +#include <render/RenderManager/RenderManager.h> +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/redbrick/trafficlocomotion.h> +#include <worldsim/character/footprint/footprintmanager.h> +#include <worldsim/harass/chasemanager.h> +#include <ai/actor/intersectionlist.h> + +#include <render/DSG/StatePropDSG.h> +#include <render/DSG/CollisionEntityDSG.h> + +#include <events/eventenum.h> +#include <events/eventmanager.h> + +#include <render/IntersectManager/IntersectManager.h> + +#include <camera/supercammanager.h> + +#include <mission/gameplaymanager.h> + +#include <meta/locator.h> +#include <meta/spheretriggervolume.h> +#include <meta/triggervolumetracker.h> +#include <meta/eventlocator.h> + +#include <main/game.h> + +#include <sound/soundmanager.h> + +#include <interiors/interiormanager.h> + +// Sim includes. +// +#include <simcommon/simstate.hpp> +#include <simcommon/physicsproperties.hpp> +#include <simcommon/simulatedobject.hpp> +#include <simcollision/collisionobject.hpp> +#include <simcollision/collisionvolume.hpp> +#include <simcollision/collisionmanager.hpp> +#include <simcollision/proximitydetection.hpp> +#include <simcommon/simmath.hpp> + +#include <render/particles/particlemanager.h> +#include <render/RenderManager/RenderManager.h> +#include <render/RenderManager/RenderLayer.h> +#include <render/Culling/WorldScene.h> +#include <render/DSG/StaticPhysDSG.h> + +//#ifdef RAD_DEBUG + #define DRAW_CHARACTER_COLLISION +//#endif // RAD_DEBUG + +#ifdef DRAW_CHARACTER_COLLISION + #include <main/commandlineoptions.h> + #include <simcollision/collisiondisplay.hpp> + #include <simcommon/simutility.hpp> + #include <simcollision/collisionvolume.hpp> + #include <simcollision/collisionobject.hpp> +#endif //DRAW_CHARACTER_COLLISION + +// NPC includes +// +#include <input/controller.h> +#include <input/inputmanager.h> +#include <worldsim/vehiclecentral.h> + +#include <debug/profiler.h> + +#include <presentation/presentation.h> +#include <presentation/presentationanimator.h> +#include <presentation/gui/ingame/guimanageringame.h> +#include <presentation/gui/ingame/guiscreenhud.h> + +#ifdef RAD_WIN32 +#include <camera/pccam.h> +#include <input/usercontrollerwin32.h> +#include <input/mouse.h> +#include <input/realcontroller.h> +#include <camera/supercam.h> +#include <camera/supercamcontroller.h> +#endif + +const static rmt::Vector vUp( 0.0f, 1.0f, 0.0f ); + +const float KICK_ANGLE = 45; + +static int s_IntersectFrame; + +class AmbientDialogueButton : public ActionButton::ButtonHandler +{ +public: + AmbientDialogueButton(Character* c) : mpCharacter(c) + { + } + + bool OnButtonPressed( Character* pCharacter, Sequencer* pSeq ) + { + // + // Send a couple of sound events for dialog. + // + if( SoundManager::IsFoodCharacter( mpCharacter ) ) + { + GetEventManager()->TriggerEvent( EVENT_AMBIENT_ASKFOOD ); + GetEventManager()->TriggerEvent( EVENT_AMBIENT_FOODREPLY, mpCharacter ); + } + else + { + GetEventManager()->TriggerEvent( EVENT_AMBIENT_GREETING ); + GetEventManager()->TriggerEvent( EVENT_AMBIENT_RESPONSE, mpCharacter ); + } + return true; + } + +protected: + Character* mpCharacter; +}; + +class AmbientDialogueTrigger : public SphereTriggerVolume +{ +public: + + AmbientDialogueTrigger(Character* c, float radius) : SphereTriggerVolume(rmt::Vector(0,0,0), radius), mpCharacter(c) + { + SetLocator(new TriggerLocator); + GetLocator()->SetNumTriggers(1); + GetLocator()->AddTriggerVolume(this); + GetLocator()->AddRef(); + mButton = new AmbientDialogueButton(c); + mButton->AddRef(); + } + + ~AmbientDialogueTrigger() + { + tRefCounted::Release(mButton); + } + + void ClearLocator(void) + { + GetLocator()->Release(); + } + void Trigger( unsigned int playerID, bool bActive ) + { + if(bActive) + { + Character* character = GetAvatarManager()->GetAvatarForPlayer(playerID)->GetCharacter(); + if ( character ) + { + character->AddActionButtonHandler(mButton); + } + } + else + { + Character* character = GetAvatarManager()->GetAvatarForPlayer(playerID)->GetCharacter(); + if ( character ) + { + character->RemoveActionButtonHandler(mButton); + } + } + } + +protected: + Character* mpCharacter; + AmbientDialogueButton* mButton; +}; + +/* +============================================================================== +NPCharacter::NPCharacter +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: NPCharacter + +============================================================================= +*/ +NPCharacter::NPCharacter( void ) +: +Character( ), +mMappableHandle( Input::INVALID_CONTROLLERID ) +{ +MEMTRACK_PUSH_GROUP( "NPCCharacter" ); + + mIsNPC = true; + SetInSubstep( false ); // initially false + + /*** + CharacterMappable* pCharacterMappable = new (GMA_PERSISTENT) BipedCharacterMappable; + + PhysicalController* pController = new (GMA_PERSISTENT) PhysicalController; + this->SetController( pController ); + + pController->SetCharacterMappable( pCharacterMappable ); + pCharacterMappable->SetCharacterController( pController ); + + mMappableHandle = InputManager::GetInstance()->RegisterMappable( 1, pCharacterMappable ); + ***/ + this->SetController( new(GMA_LEVEL_OTHER) NPCController ); + this->GetController()->SetCharacter( this ); +MEMTRACK_POP_GROUP( "NPCCharacter" ); +} +/* +============================================================================== +NPCharacter::~NPCharacter +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: NPCharacter + +============================================================================= +*/ +NPCharacter::~NPCharacter( void ) +{ + // if we don't clear this here, there may be actions cued that + // will screw up once we've been destructed + if(GetActionController()) + { + GetActionController()->Clear(); + } + + /*** + InputManager::GetInstance()->UnregisterMappable( 1, mMappableHandle ); + ***/ + mMappableHandle = Input::INVALID_CONTROLLERID; +} + +#ifdef RAD_DEBUG + static float timeScale = 1.00f; +#endif + +/* +============================================================================== +NPCharacter::OnUpdateRoot +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void NPCharacter::OnUpdateRoot( float timeins ) +{ + // Intentionall left blank. + // +} + +/* +============================================================================== +NPCharacter::OnPostSimUpdate +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void NPCharacter::OnPostSimUpdate( float timeins ) +{ + UpdatePuppet( timeins ); +} +/* +============================================================================== +Character::Character +============================================================================== +Description: Constructor + +Parameters: ( ) + +Return: na + +============================================================================= +*/ +sim::TArray< sim::RayIntersectionInfo > Character::msIntersectInfo( 64 ); //.ResizeArray( 2 ); + +Character::Character( ) +: +mbCollidedWithVehicle( false ), +mbInAnyonesFrustrum( false ), +mbSurfing(false), +mbAllowUnload(true), +mbIsPlayingIdleAnim(false), + +#ifdef RAD_WIN32 +mPCCamFacing( 0 ), +#endif + +mIsNPC( false ), + +mGroundPlaneSimState( 0 ), +mGroundPlaneWallVolume( 0 ), +mCollisionAreaIndex( WorldPhysicsManager::INVALID_COLLISION_AREA ), + +mpController( 0 ), +mpCharacterRenderable( 0 ), +mpPuppet( 0 ), +mfFacingDir( 0.0f ), +mfDesiredDir( 0.0f ), +mfSpeed( 0.0f ), +mVelocity (0.0f, 0.0f, 0.0f), +mfDesiredSpeed( 0.0f ), +mbInCar( false ), +mpCharacterTarget( 0 ), +mpActionController( 0 ), +mpCurrentActionButtonHandler( 0 ), +mpTargetVehicle( 0 ), +mTerrainType( TT_Road ), +mInteriorTerrain( false ), +mpStateManager( 0 ), +mfRadius( 0.35f ), +mbCollided( false ), +mCurrentCollision( 0 ), +mbIsStanding( true ), +mpWalkerLocomotion( 0 ), +mpJumpLocomotion( 0 ), +mpStandingCollisionVolume( 0 ), +mpStandingJoint( 0 ), +mfGroundVerticalVelocity( 0.0f ), +mfGroundVerticalPosition( 0.0f ), +mbTurbo( false ), +mbIsJump( false ), +mbSolveCollisions( true ), +mpPropHandler( 0 ), +mPropJoint( -1 ), +mVisible( false ), +mpWorldScene( 0 ), +m_IsSimpleShadow( true ), +mYAdjust( 0.0f ), +mbBusy(false), +mbSimpleLoco( false ), +m_TimeLeftToShock( 0 ), +m_IsBeingShocked( false ), +mDoKickwave(false), +mKickwave(NULL), +mKickwaveController(NULL), +mAmbient(false), +mAmbientLocator(0), +mAmbientTrigger(NULL), +mLastFramePos(0.0f,0.0f,0.0f), +mbDoGroundIntersect(true), +mIntersectFrame(s_IntersectFrame++), +mAllowRockin(false), +mHasBeenHit(false), +mbSnapToGround(false), +mSecondsSinceActionControllerUpdate( 0.0f ), +mTooFarToUpdate(false), +mSecondsSinceOnPostSimUpdate( 0.0f ), +mRole(ROLE_UNKNOWN), +mScale(1.0f), +mIsInSubstep(true), +mLean(0.0f, 1.0f, 0.0f), +mIsLisa(false), +mIsMarge(false), +mManaged(false) +{ + + mLastInteriorLoadCheck = radTimeGetMicroseconds64(); + + mPrevSimTransform.Identity(); + + mTranslucent = true; + + mShadowColour.Set( 0, 0, 0 ); + + mGroundNormal.Set( 0.0f, 0.0f, 0.0f ); + mRealGroundPos.Set( 0.0f, 0.0f, 0.0f ); + mRealGroundNormal.Set( 0.0f, 0.0f, 0.0f ); + + mLastGoodPosOverStatic.Set( 0.0f, 0.0f, 0.0f ); + + unsigned int i; + for ( i = 0; i < MAX_ACTION_BUTTON_HANDLERS; ++i ) + { + mpActionButtonHandlers[ i ] = NULL; + } + +} +/* +============================================================================== +Character::Init +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void Character::Init( void ) +{ + +//#ifdef RAD_GAMECUBE +// HeapMgr()->PushHeap(GMA_GC_VMM); +//#else + HeapMgr()->PushHeap(GMA_LEVEL_OTHER); +//#endif + + mpCharacterTarget = new CharacterTarget( this ); + + mbWasFootPlanted.resize(2, false); + + mpActionController = new ActionController; + mpActionController->Clear( ); + + mpStateManager = new CharacterAi::StateManager( this ); + + { + // Manually create the physics objects for now. + // Set inventory section to "Default" because the sim library will store stuff + // + p3d::inventory->PushSection( ); + p3d::inventory->SelectSection( "Default" ); + + // Manually create the physics objects for now. + // + sim::SymMatrix identity; + identity.Identity(); + rmt::Vector offset( 0.0f, 0.9f, -0.05f ); + + sim::CylinderVolume* pCollisionVolume = new sim::CylinderVolume( offset, vUp, 0.55f, mfRadius ); + sim::CollisionObject* pCollisionObject = new sim::CollisionObject( pCollisionVolume ); + sim::PhysicsProperties* pPhysicsProperties = sim::PhysicsProperties::HardWoodProperties(p3d::inventory); + sim::PhysicsObject* pSimulatedObject = new sim::PhysicsObject( pPhysicsProperties, offset, identity, 2.0f ); + pSimulatedObject->SetSimEnvironment( GetWorldPhysicsManager()->mSimEnvironment ); + sim::SimState* pSimState = sim::SimState::CreateSimState( pCollisionObject, pSimulatedObject ); + rAssert( pSimState ); + SetSimState( pSimState ); + + pCollisionObject->SetCollisionEnabled(false); + SetSolveCollisions(false); + + p3d::inventory->PopSection( ); + } + + int i; + for ( i = 0; i < CollisionData::MAX_COLLISIONS; i++ ) + { + mCollisionData[ i ].mCollisionNormal.Set( 0.0f, 0.0f, 0.0f ); + mCollisionData[ i ].mCollisionDistance = 0.0f; + mCollisionData[ i ].mpCollisionVolume = (sim::CollisionVolume*)0; + } + mbCollidedWithVehicle = false; + + mpWalkerLocomotion = new WalkerLocomotionAction( this ); + mpWalkerLocomotion->AddRef(); + + mpJumpLocomotion = new JumpAction( this, "jump_idle", Character::GetJumpHeight() ); + mpJumpLocomotion->AddRef(); + + rmt::Matrix mat; + mat.Identity( ); + SetParentTransform( mat ); + +/* mpPropHandler = new ActionButton::AttachProp; + mpPropHandler->AddRef( ); + */ + mPropJoint = 0; + + if( !IsNPC() ) + { + InitGroundPlane(); + } + //AssignCollisionAreaIndex( ); + + ////////////////////////////// + // Update Simstate transform + rmt::Vector position; + GetPosition( position ); + rmt::Vector facing; + GetFacing( facing ); + + mat.Identity( ); + mat.Row( 2 ) = facing; + mat.FillTranslate( position ); + + mpSimStateObj->SetTransform( mat ); + + if( !mIsNPC ) + { + // If you're a player character + // Add ourself, our groundplane, and our self-groundplane pair + // to collision manager + AddToPhysics(); + } + + // Initialize position at origin + rmt::Vector zero( 0.0f, 0.0f, 0.0f ); + SetPosition( zero ); + + UpdateTransformToLoco( ); + + // Dusit [Nov 13,2002]: + // Need to initialize out the garbage values. The problem is that + // the garbage values happen to be quite large... In a later call to + // UpdateSimState, we overwrite the currently quite nice simstate & + // collisionvolume transforms with the puppet's garbage value (still + // garbage because cloned NPCs such as pedestrians are not given a valid + // position by locator, but by arbitrary spawning). So now simstate & + // collisionvolume have large garbage transforms. Later on when + // this cloned NPC gets spawned (e.g. in Pedestrian::Activate()) with + // a valid position, the collisionvolume tries to update itself + // by calculating the difference between LARGE garbage value and a + // comparatively smaller position value. The floating point accuracy + // favors the large value and instead of moving to the new smaller + // position value, we move to (0,0,0). At this point, simstate's + // transform and simcollisionvolume's transform are out of synch. + // + SetPosition( position ); + +//#ifdef RAD_GAMECUBE +// HeapMgr()->PopHeap( GMA_GC_VMM ); +//#else + HeapMgr()->PopHeap( GMA_LEVEL_OTHER ); +//#endif +} + + +void Character::InitGroundPlane() +{ + MEMTRACK_PUSH_GROUP( "Character" ); + + HeapMgr()->PushHeap (GMA_LEVEL_OTHER); + + rmt::Vector p(0.0f, 0.0f, 0.0f); + rmt::Vector n(0.0f, 1.0f, 0.0f); + + mGroundPlaneWallVolume = new sim::WallVolume(p, n); + mGroundPlaneWallVolume->AddRef(); + + mGroundPlaneSimState = (sim::ManualSimState*)( + sim::SimState::CreateManualSimState(mGroundPlaneWallVolume)); + mGroundPlaneSimState->AddRef(); + + mGroundPlaneSimState->GetCollisionObject()->SetManualUpdate(true); + mGroundPlaneSimState->GetCollisionObject()->SetAutoPair(false); + mGroundPlaneSimState->GetCollisionObject()->SetIsStatic(true); + mGroundPlaneSimState->GetCollisionObject()->SetCollisionEnabled(false); + + char buffy[128]; + sprintf( buffy, "player_character_groundplane" ); + mGroundPlaneSimState->GetCollisionObject()->SetName( buffy ); + + mGroundPlaneSimState->mAIRefIndex = PhysicsAIRef::redBrickPhizMoveableGroundPlane; + mGroundPlaneSimState->mAIRefPointer = (void*)this; + + /* + mGroundPlanePhysicsProperties = new PhysicsProperties; + mGroundPlanePhysicsProperties->AddRef(); + + mGroundPlanePhysicsProperties->SetFrictCoeffCGS(0.8f); + mGroundPlanePhysicsProperties->SetRestCoeffCGS(1.15f); + mGroundPlanePhysicsProperties->SetTangRestCoeffCGS(0.0f); + + mGroundPlaneSimState->SetPhysicsProperties(mGroundPlanePhysicsProperties); + */ + + HeapMgr()->PopHeap (GMA_LEVEL_OTHER); + MEMTRACK_POP_GROUP( "Character" ); +} + +bool Character::IsInCarOrGettingInOut( void ) +{ + if(mbInCar || + GetStateManager()->GetState() == CharacterAi::GET_IN || + GetStateManager()->GetState() == CharacterAi::GET_OUT) + { + return true; + } + return false; +} + +/* +============================================================================== +Character::SetPuppet +============================================================================== +Description: Comment + +Parameters: ( choreo::Puppet* pPuppet ) + +Return: void + +============================================================================= +*/ +void Character::SetPuppet( choreo::Puppet* pPuppet ) +{ + rAssert( pPuppet ); + + rmt::Vector position; + float facing, desiredFacing; + GetPosition( position ); + facing = GetFacingDir(); + desiredFacing = GetDesiredDir(); + tRefCounted::Assign( mpPuppet, pPuppet ); + SetPosition( position ); + SetFacingDir(facing); + SetDesiredDir(desiredFacing); + + if(mpWalkerLocomotion) + { + mpWalkerLocomotion->SwitchLocomotion( ); + } + + mbWasFootPlanted.resize( pPuppet->GetLegCount(), false ); +/* + if ( pPuppet ) + { + mPropJoint = pPuppet->GetP3DPose( )->FindJointIndex( "Wrist_R" ); + } + if ( mPropJoint == -1 ) + { + // Safe value. + // + mPropJoint = 0; + } +*/ + // The feet collider doesn't work as well as the ray intersection. + // It isn't any cheaper in the grand scheme of things either. + // + //mpFeetCollider = new CharacterFeetCollider( this ); + int legs = mpPuppet->GetLegCount( ); + for ( int i = 0; i < legs; i++ ) + { + GetPuppet( )->SetIsLegIKEnabled( i, false ); + } + + if(GetActionController()) + { + GetActionController()->Clear(); + } + + if(mpStateManager) + { + SetInCar(false); + + if(mpStateManager->GetState() != CharacterAi::NOSTATE) + { + mpStateManager->ResetState(); + } + } + + pPuppet->GetEngine()->GetPoseEngine()->Begin(true); +} + + +void Character::SetYAdjust( float yOffset ) +{ + mYAdjust = yOffset; +} + +float Character::GetYAdjust() +{ + return mYAdjust; +} + +/* +============================================================================== +Character::ResetSpeed +============================================================================== +*/ +void Character::ResetSpeed( void ) +{ + mpSimStateObj->ResetVelocities( ); + mpWalkerLocomotion->SetDesiredSpeed( 0.0f ); +} +static rmt::Vector dstart,dend; +void Character::Kick() +{ + if(GetInteriorManager()->IsInside()) + { + return; + } + + // Fetch the current character position + rmt::Vector position; + GetPosition( &position ); + + // Fetch character facing vector + rmt::Vector facing; + GetFacing( facing ); + + + // We want to rotate the facing vector upwards by N degrees + // Find the vector thats orthogonal to the facing vector and the world up axis + // Lets make sure that the facing vector isn't parallel with the up vector + // first + rmt::Vector kickdir; + const rmt::Vector WORLD_UP( 0, 1.0f, 0 ); + const float DOT_PRODUCT_PARALLEL_EPSILON = 0.99f; + if ( facing.Dot( WORLD_UP ) < DOT_PRODUCT_PARALLEL_EPSILON ) + { + // Not parallel, take the crossproduct between world and up + rmt::Vector right; + right.CrossProduct( WORLD_UP, facing ); + rmt::Matrix rotation; + rotation.Identity(); + rotation.FillRotation( right, rmt::DegToRadian( KICK_ANGLE )); + kickdir.Rotate( facing, rotation ); + rAssert( kickdir.y > 0 ); + } + else + { + // They are parallel, just use the facing vector as the kicking direction + kickdir = facing; + } + + + + + const float KICK_EFFECT_RADIUS = 5.0f; + WorldPhysicsManager::NumObjectsHit numObjectsHit; + + // Use the intersection list for querying and performing sim collision testing + IntersectionList intersectList; + intersectList.FillIntersectionListDynamics( position, KICK_EFFECT_RADIUS, true, this ); + DynaPhysDSG* objectHit = NULL; + const float KICK_RAY_LEN = 2.0f; + + + rmt::Vector kickDest = position + kickdir; + rmt::Vector kickStart = position - kickdir; + + + rmt::Vector kickIntersect; + if ( intersectList.TestIntersectionDynamics( kickStart, kickDest, &kickIntersect, &objectHit ) ) + { + switch ( objectHit->GetAIRef() ) + { + case PhysicsAIRef::NPCharacter: + { + // We kicked an NPC. Send some events + NPCharacter* ch = (NPCharacter*)objectHit; + GetEventManager()->TriggerEvent( EVENT_KICK_NPC, ch ); + GetEventManager()->TriggerEvent( EVENT_KICK_NPC_SOUND, ch ); + GetEventManager()->TriggerEvent( EVENT_PEDESTRIAN_SMACKDOWN ); + ch->SetHasBeenHit(true); + + // Make the object that got hit fly + CharacterAi::CharacterState state = ch->GetStateManager()->GetState(); + if( state == CharacterAi::LOCO ) + { + // call a special kick that will reset the pastangular and pastlinear + // histories... + ch->ApplyKickForce( kickdir, CharacterTune::sfKickingForce ); + } + else + { + ch->ApplyForce( kickdir, CharacterTune::sfKickingForce ); + } + GetEventManager()->TriggerEvent( EVENT_OBJECT_KICKED, objectHit ); + } + break; + + case PhysicsAIRef::redBrickVehicle: + GetEventManager()->TriggerEvent( EVENT_OBJECT_KICKED, objectHit ); + break; + + case PhysicsAIRef::StateProp: + { + rAssert( dynamic_cast< StatePropDSG* >( objectHit ) != NULL ); + StatePropDSG* stateprop = static_cast< StatePropDSG* >( objectHit ); + // Lets not send EVENT_OBJECT_KICKED when the object has no collision volume + if ( stateprop->IsCollisionEnabled() ) + { + objectHit->ApplyForce( kickdir, CharacterTune::sfKickingForce ); + GetEventManager()->TriggerEvent( EVENT_OBJECT_KICKED, objectHit ); + } + } + break; + + default: + // Make the object that got hit fly + objectHit->ApplyForce( kickdir, CharacterTune::sfKickingForce ); + InstDynaPhysDSG* toBreak = dynamic_cast<InstDynaPhysDSG*>(objectHit); + + if(toBreak) + { + toBreak->Break(); + } + + GetEventManager()->TriggerEvent( EVENT_OBJECT_KICKED, objectHit ); + break; + } + } + GetEventManager()->TriggerEvent( EVENT_KICK, this ); + +} + +void Character::Slam() +{ + int i; + WorldPhysicsManager::CollisionEntityDSGList dsgList; + + rmt::Vector position; + GetPosition( &position ); + + int numObjectsKicked = GetWorldPhysicsManager()->ApplyForceToDynamicsSpherical( mCollisionAreaIndex, + position, + 2, + CharacterTune::sfSlamForce, + &dsgList ); + + if ( numObjectsKicked > 0 ) + { + for( i = 0; i < WorldPhysicsManager::CollisionEntityDSGList::NUM_COLLISION_LIST_ENTITIES; i++ ) + { + CollisionEntityDSG* collObject = dsgList.collisionEntity[i]; + + if( collObject != NULL ) + { + switch ( collObject->GetAIRef() ) + { + case PhysicsAIRef::NPCharacter: + { + Character* ch = static_cast<Character*>(dsgList.collisionEntity[i]); + GetEventManager()->TriggerEvent( EVENT_KICK_NPC, ch ); + GetEventManager()->TriggerEvent( EVENT_KICK_NPC_SOUND, ch ); + GetEventManager()->TriggerEvent( EVENT_PEDESTRIAN_SMACKDOWN ); + ch->SetHasBeenHit(true); + GetEventManager()->TriggerEvent( EVENT_OBJECT_KICKED, collObject ); + } + break; + case PhysicsAIRef::StateProp: + { + rAssert( dynamic_cast< StatePropDSG* >( collObject ) != NULL ); + StatePropDSG* stateprop = static_cast< StatePropDSG* >( collObject ); + // Lets not send EVENT_OBJECT_KICKED when the object has no collision volume + if ( stateprop->IsCollisionEnabled() ) + { + GetEventManager()->TriggerEvent( EVENT_OBJECT_KICKED, collObject ); + } + break; + } + default: + GetEventManager()->TriggerEvent( EVENT_OBJECT_KICKED, collObject ); + InstDynaPhysDSG* toBreak = dynamic_cast<InstDynaPhysDSG*>(collObject); + + if(toBreak) + { + toBreak->Break(); + } + break; + } + + } + } + } + GetEventManager()->TriggerEvent( EVENT_STOMP, this ); +} + +void Character::RelocateAndReset( const rmt::Vector& position, float facing, bool resetMe, bool snapToGround ) +{ + // TODO: Not done yet... + // What else need we do to relocate character & reset its states??? + mLastFramePos = position; + mVelocity.Set(0.0f, 0.0f, 0.0f); + + // Switch to AI control if we need to... + sim::SimState* simState = mpSimStateObj; + if( simState != NULL ) + { + simState->SetControl( sim::simAICtrl ); + } + + // Update transforms for PoseJointZero, Puppet, and SimStateObj + SetPosition( position ); + + // update Puppet's root transform with facing + SetFacingDir( facing ); + SetDesiredDir( facing ); + ResetSpeed(); + + if( mpWalkerLocomotion != NULL ) + { + // Gotta do this so facing is not overwritten by blend priorities + // when the player's character is involved... + // Without this call here, the player's character will gets its + // new facing value clobbered by old values stored in blend priorities + // when we go into UpdateTransformToLoco() + choreo::LocomotionDriver* locomod = mpWalkerLocomotion->GetDriver(); + if( locomod != NULL ) + { + locomod->SetActualFacingAngle( facing ); + } + } + + SetStandingJoint(NULL); + + UpdateTransformToLoco(); + + // By this point, our position should be finalized and no UpdateBBox + // has been called (for moveinworldscene to do) + + // Don't call UpdatePuppet() because it resets our puppet->engine->rootblender's + // and joints' transforms back to previous one due to blend priorities in UpdateRoot() + //UpdatePuppet( 0.0f ); + choreo::Puppet* pPuppet = GetPuppet( ); + rAssert( pPuppet ); + pPuppet->UpdatePose(); +// pPuppet->UpdateEnd(); + + // update jump's root transform after Puppet's transform is set + if( mpJumpLocomotion != NULL ) + { + // Copies transform from Puppet->Engine->RootBlender. + // If that is correct, so will this be. + mpJumpLocomotion->SetRootTransform(); + } + if( mpWalkerLocomotion != NULL ) + { + // Characters obtained a new locomotion driver in previous call to + // UpdateTransformToLoco, so we have to reset that driver's + // actual facing angle again... + // Without this call, NPCs won't face the correct way... + // Player character can get away with it cuz the driver gets updated + // again later. + choreo::LocomotionDriver* locomod = mpWalkerLocomotion->GetDriver(); + if( locomod != NULL ) + { + locomod->SetActualFacingAngle( facing ); + } + } + + + //////////////////////////////////////////////////////////////////// + // + // Force NPC to submit statics and whatever's below them & + // update their terrain intersect info. We should actually do this + // for EVERY character, but dammit we can't afford to. + // + if( mIsNPC && mRole != ROLE_PEDESTRIAN ) + { + // temporarily transit the controller to STOPPED state so we force submit statics + // to actually submit statics... + NPCController* npcController = (NPCController*) mpController; + NPCController::State oldState = npcController->GetState(); + npcController->TransitToState( NPCController::STOPPED ); + + // temporarily obtain collision area index if we don't got one... + int oldArea = mCollisionAreaIndex; + if( oldArea == WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + AddToPhysics(); + } + + SubmitStatics(); + + rmt::Vector prevPosition = position; + rmt::Vector groundPosition = position; + rmt::Vector outnorm( 0.0f, 1.0f, 0.0f ); + bool bFoundPlane = false; + + mTerrainType = static_cast<eTerrainType>( GetIntersectManager()->FindIntersection( + groundPosition, // IN + bFoundPlane, // OUT + outnorm, // OUT + groundPosition ) ); // OUT + mInteriorTerrain = ( (int)mTerrainType & 0x80 ) == 0x80; + mTerrainType = static_cast<eTerrainType>( ( (int)mTerrainType & ~0x80 ) ); + + if( bFoundPlane ) + { + mRealGroundPos = groundPosition; + mRealGroundNormal = outnorm; + float tooHigh = 100000.0f; + rAssert( -tooHigh <= mRealGroundNormal.x && mRealGroundNormal.x <= tooHigh ); + rAssert( -tooHigh <= mRealGroundNormal.y && mRealGroundNormal.y <= tooHigh ); + rAssert( -tooHigh <= mRealGroundNormal.z && mRealGroundNormal.z <= tooHigh ); + } + else + { + // + // If this assert goes off for the player charater when he's hit by traffic + // it means that Physics has placed him at some location other than his + // present location. This is a bad thing... possibly related to + // collisions with traffic cars. + //rAssertMsg( false, "We're SOOO far from the ground, we're not intersecting" ); + } + rmt::Vector collisionPosition; + rmt::Vector collisionNormal; + + collisionNormal.Clear( ); + collisionPosition.Clear( ); + + bool bOverStatic = GetCollisionHeight( prevPosition, position, collisionPosition, collisionNormal ); + if ( bOverStatic ) + { + if ( !bFoundPlane || collisionPosition.y > groundPosition.y ) + { + mGroundY = collisionPosition.y; + mGroundNormal = collisionNormal; + + mLastGoodPosOverStatic = position; + mLastGoodPosOverStatic.y = mGroundY; + } + else + { + rAssert( bFoundPlane ); + mGroundY = groundPosition.y; + mGroundNormal = outnorm; + } + } + else if ( bFoundPlane ) + { + mGroundY = groundPosition.y; + mGroundNormal = outnorm; + } + else + { + mGroundY = position.y; + mGroundNormal.Set( 0.0f, 1.0f, 0.0f ); + } + + if( snapToGround ) + { + rmt::Vector groundPos, groundNormal; + GetTerrainIntersect( groundPos, groundNormal ); + SetPosition( groundPos ); + SetGroundPoint( groundPos ); + mpJumpLocomotion->SetRootPosition( WorldToLocal(groundPos) ); + snapToGround = false; + } + + if( oldArea == WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + RemoveFromPhysics(); + } + npcController->TransitToState( oldState ); + } + ////////////////////////////////////////////////////////////////////////// + + + + MoveInWorldScene(); + if( GetCharacterManager()->GetCharacter(0) == this ) + GetTriggerVolumeTracker()->ResetDynaloadZones(); + + mbSnapToGround = snapToGround; + mTooFarToUpdate = false; + mSecondsSinceActionControllerUpdate = 0.0f; + mSecondsSinceOnPostSimUpdate = 0.0f; + + + if( resetMe ) + { + // Reset collisions (otherwise the collisions carry on from last placement) + ResetCollisions(); + + // do SimState reset + if( mpSimStateObj != NULL ) + { + mpSimStateObj->ResetVelocities(); + } + + // Reset action button so we don't get blinking "y" after reset + ClearAllActionButtonHandlers(); + + mbTurbo = false; + + // Clear props?? +// mpPropHandler->Reset(); + + GetStateManager()->SetState<CharacterAi::Loco>(); + + mbIsJump = false; + + mpActionController->Clear(); + + /* + // TODO: + // Do more resetting here... If it gets to be a lot, gotta put it in a + // separate ResetStates() function + + // Clear actions & priorities?? + + // TODO: + // How do we halt the jump sequence & reset back to standing? + // Actually. We need a general "HaltAnimations" function that stops + // whatever you're doing and reset to standing & idle animation... + // This includes stopping: jumping, opening doors, turboing, dashing, etc... + + if( mbIsJump ) + { + mpJumpLocomotion->End(); + mpJumpLocomotion->Done(); + mbIsJump = false; + } + + // TODO: + // DO we need to do these things? + if( mpLocomotion != NULL ) + { + mpLocomotion->Clear(); + } + + if( mpActionController != NULL ) + { + mpActionController->Clear(); + } + + if( mpController != NULL ) + { + mpController->ClearIntention(); + } + + if( mpStateManager != NULL ) + { + mpStateManager->ResetState( CharacterAi::LOCO, this ); + } + + + //mbWasFootPlanted.clear(); + mbIsStanding = true; + mbSolveCollisions = false; + + */ + } +} + + + +void Character::AddToPhysics( void ) +{ + if( !mManaged ) + { + return; + } + + if( mCollisionAreaIndex != WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + return; + } + + sim::CollisionObject* myColObj = mpSimStateObj->GetCollisionObject(); + sim::CollisionObject* groundPlaneColObj = mGroundPlaneSimState->GetCollisionObject(); + + AssignCollisionAreaIndex(); + + GetWorldPhysicsManager()->mCollisionManager->AddCollisionObject( + myColObj, mCollisionAreaIndex ); + GetWorldPhysicsManager()->mCollisionManager->AddCollisionObject( + groundPlaneColObj, mCollisionAreaIndex); + GetWorldPhysicsManager()->mCollisionManager->AddPair( + groundPlaneColObj, myColObj, mCollisionAreaIndex); + + myColObj->SetCollisionEnabled( true ); + + // disable groundplane collision till we need it + groundPlaneColObj->SetCollisionEnabled( false ); + + SetSolveCollisions( true ); +} + +void Character::RemoveFromPhysics( void ) +{ + if( mCollisionAreaIndex == WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + return; + } + + sim::CollisionObject* myColObj = mpSimStateObj->GetCollisionObject(); + sim::CollisionObject* groundPlaneColObj = mGroundPlaneSimState->GetCollisionObject(); + + myColObj->SetCollisionEnabled( false ); + groundPlaneColObj->SetCollisionEnabled( false ); + + // Freeing a collision area will also empty it out, so no remove calls + // necessary here. + GetWorldPhysicsManager()->FreeCollisionAreaIndex( mCollisionAreaIndex ); + + mCollisionAreaIndex = WorldPhysicsManager::INVALID_COLLISION_AREA; + + SetSolveCollisions( false ); +} + +void NPCharacter::AddToPhysics( void ) +{ + // NPCs don't keep track of their own groundplane... When they go into sim + // they are submitted by some other entity and get temp groundplanes + // assigned to them. + // So we only add ourselves to collisions + + /* +#ifdef RAD_DEBUG + rDebugPrintf( "+++++++++ Adding to Physics: %s, index %d\n", GetName(), mCollisionAreaIndex ); +#endif + */ + + if( mCollisionAreaIndex != WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + return; + } + + sim::CollisionObject* myColObj = mpSimStateObj->GetCollisionObject(); + + AssignCollisionAreaIndex(); + + GetWorldPhysicsManager()->mCollisionManager->AddCollisionObject( + myColObj, mCollisionAreaIndex ); + + myColObj->SetCollisionEnabled( true ); + + SetSolveCollisions( true ); +} + +void NPCharacter::RemoveFromPhysics( void ) +{ + /* +#ifdef RAD_DEBUG + rDebugPrintf( "-------- Removing from Physics: %s, index %d\n", GetName(), mCollisionAreaIndex ); +#endif + */ + + if( mCollisionAreaIndex == WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + return; + } + + sim::CollisionObject* myColObj = mpSimStateObj->GetCollisionObject(); + + myColObj->SetCollisionEnabled( false ); + + // Freeing a collision area will also empty it out, so no remove calls + // necessary here. + + GetWorldPhysicsManager()->FreeCollisionAreaIndex( mCollisionAreaIndex ); + + mCollisionAreaIndex = WorldPhysicsManager::INVALID_COLLISION_AREA; + + SetSolveCollisions( false ); + +} + + + + +/* +============================================================================== +Character::~Character +============================================================================== +Description: Destructor + +Parameters: ( void ) + +Return: na + +============================================================================= +*/ +Character::~Character( void ) +{ + tRefCounted::Release(mpStandingCollisionVolume); + + if(GetActionController()) + { + GetActionController()->Clear(); + } + + tRefCounted::Release(mKickwave); + tRefCounted::Release(mKickwaveController); + + if( mGroundPlaneSimState ) + { + if(mCollisionAreaIndex != WorldPhysicsManager::INVALID_COLLISION_AREA) + { + GetWorldPhysicsManager()->mCollisionManager->RemoveCollisionObject( + mGroundPlaneSimState->GetCollisionObject(), + mCollisionAreaIndex); + } + mGroundPlaneSimState->Release(); + mGroundPlaneSimState = NULL; + } + if( mGroundPlaneWallVolume ) + { + mGroundPlaneWallVolume->Release(); + mGroundPlaneWallVolume = NULL; + } + + // NOTE: + // Do the same RemoveCollisionObject call for our simstate? + // No need. CharacterManager's destroy will call + // RemoveFromAllDynamicBlahblahblah for us. + + if( WorldPhysicsManager::INVALID_COLLISION_AREA != mCollisionAreaIndex ) + { + GetWorldPhysicsManager()->FreeCollisionAreaIndex( mCollisionAreaIndex ); + } + + if( mpController ) + { + mpController->Release( ); + mpController = 0; + } + if( mpCharacterRenderable ) + { + delete mpCharacterRenderable; + } + if ( mpCharacterTarget ) + { + delete mpCharacterTarget; + mpCharacterTarget = 0; + } + if ( mpActionController ) + { + delete mpActionController; + mpActionController = 0; + } + if ( mpStateManager ) + { + delete mpStateManager; + mpStateManager = 0; + } + if ( mpWalkerLocomotion ) + { + mpWalkerLocomotion->Release( ); + mpWalkerLocomotion = 0; + } + if ( mpJumpLocomotion ) + { + mpJumpLocomotion->Release( ); + mpJumpLocomotion = 0; + } + if ( mbWasFootPlanted.empty() == false ) + { + mbWasFootPlanted.clear(); + } + + SetTargetVehicle(NULL); + ClearAllActionButtonHandlers(); +// tRefCounted::Release( mpPropHandler ); + if(mAmbientTrigger) + { + mAmbientTrigger->ClearLocator(); + } + tRefCounted::Release( mAmbientTrigger ); + + // + // Delete the puppet last. I moved this down to the bottom because of an occasional + // non-reproducible crash on gameplay exit where the ActionController destruction above + // ended up referencing the puppet which had already been cleaned up. Other desctructors + // above (e.g. locomotion objects) may also end up referencing the puppet. Putting it + // at the bottom should avoid that. -- jdy + // + if ( mpPuppet ) + { + mpPuppet->ReleaseVerified(); + mpPuppet = 0; + } +} +/* +============================================================================== +Character::UpdateParentTransform +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void Character::UpdateParentTransform( float timeins ) +{ + // Do this before UpdateRoot() + // + + rmt::Matrix transform; + transform.Identity( ); + if ( IsInCar() && GetTargetVehicle() ) + { + transform = GetTargetVehicle()->GetTransform(); + } + else if ( mpStandingJoint ) + { + transform = mpStandingJoint->GetWorldMatrix( ); + } + SetParentTransform( transform, timeins ); + +} + + +/* +============================================================================== +Character::SetParentTransform +============================================================================== +Description: Comment + +Parameters: ( const rmt::Matrix& mat, float timeins ) + +Return: void + +============================================================================= +*/ +void Character::SetParentTransform( const rmt::Matrix& newTransform, float timeins ) +{ + if ( !rmt::Epsilon( timeins, 0.0f ) ) + { + float invDeltaTime = 1.0f / timeins; + mfGroundVerticalVelocity = newTransform.Row( 3 ).y - mfGroundVerticalPosition; + mfGroundVerticalVelocity *= invDeltaTime; + } + else + { + mfGroundVerticalVelocity = 0.0f; + } + mParentTransform = newTransform; + mfGroundVerticalPosition = mParentTransform.Row( 3 ).y; + + //rAssertMsg( mParentTransform.IsOrthoNormal( ), "Your parent transform node is screwed!\n" ); + + if ( !IsInCar( ) ) + { + // If we are not in the car, we always want to keep the character up + // at 0,1,0. + // + rmt::Vector facing = mParentTransform.Row( 2 ); + mParentTransform.FillHeadingXZ( facing ); + + // We don't detect vertical motion with the transform. + // so we will zero it out. + // + mParentTransform.Row( 3 ).y = 0.0f; + } + mInvParentTransform.InvertOrtho( mParentTransform ); + if ( mpPuppet ) + { + poser::Transform transform; + transform.Identity( ); + transform.SetMatrix( mParentTransform ); + mpPuppet->SetParentTransform( transform ); + } +} + + +void Character::UpdateGroundPlane( float timeins ) +{ + // new approach with manual sim state + rAssert( mGroundPlaneWallVolume ); + + float tooHigh = 100000.0f; + rAssert( -tooHigh <= mRealGroundNormal.x && mRealGroundNormal.x <= tooHigh ); + rAssert( -tooHigh <= mRealGroundNormal.y && mRealGroundNormal.y <= tooHigh ); + rAssert( -tooHigh <= mRealGroundNormal.z && mRealGroundNormal.z <= tooHigh ); + + mGroundPlaneWallVolume->mPosition = mRealGroundPos; //p; + mGroundPlaneWallVolume->mNormal = mRealGroundNormal; //n; + + rAssert( mGroundPlaneSimState ); + sim::CollisionObject* co = mGroundPlaneSimState->GetCollisionObject(); + co->PostManualUpdate(); +} + + + +/* +============================================================================== +Character::PreSimUpdate +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void Character::PreSimUpdate( float timeins ) +{ +#ifdef RAD_DEBUG + timeins *= timeScale; +#endif + + if( !IsNPC() ) // ok, we're player character + { + UpdateGroundPlane( timeins ); + } + + ///////////////////////////////////////////////// + // Test if we're too far from the camera for some updates + mTooFarToUpdate = false; + + // if we're in First Person Cam, this is bad news... They can ZOOM! + // So treat it as though we're ALWAYS not too far to update. + if( GetInputManager()->GetGameState() != Input::ACTIVE_FIRST_PERSON ) + { + rmt::Vector myPos; + GetPosition( myPos ); + + rmt::Vector testPos; + GetAvatarManager()->GetAvatarForPlayer( 0 )->GetPosition( testPos ); + /* + // NOTE: + // Can't use the camera pos because we could get quite far from the + // cam.. use the avatar's pos + GetSuperCamManager()->GetSCC(0)->GetCamera()->GetPosition( &testPos ); + */ + float fov, aspect; + GetSuperCamManager()->GetSCC(0)->GetCamera()->GetFOV(&fov, &aspect); + static float fovCutoff = 1.0f; + + const float DIST_TOO_FAR_TO_UPDATE_SQR = 625.0f; + + float distSqr = (myPos - testPos).MagnitudeSqr(); + if( distSqr > DIST_TOO_FAR_TO_UPDATE_SQR && (fov > fovCutoff)) + { + mTooFarToUpdate = true; + } + } + + ////////////////////////////////////////////////////// + // Sense and think. + // + rmt::Vector direction; + UpdateController( direction, timeins ); + if ( IsMovementLocked() == false ) + { + UpdateDesiredDirAndSpeed( direction ); + } + else + { + UpdateDesiredDirAndSpeed( rmt::Vector(0,0,0) ); + } + + + + if( mpSimStateObj->GetControl() == sim::simSimulationCtrl && + GetStateManager()->GetState() != CharacterAi::INSIM ) + { + // this will cause flail & get-up anims to be sequenced + GetStateManager()->SetState<CharacterAi::InSim>(); + } + + + //////////////////////////////////////////////////////// + // Selectively update action controller every n frames if: + // - not in view, or + // - too far away + + unsigned int modulo = 0x7; + unsigned int frameCount = GetGame()->GetFrameCount(); + + mSecondsSinceActionControllerUpdate += timeins; + + //chuck: changed the herusitic to also include drivers in cars + //since we dont want driver pop a mission restarts + if( + !mIsNPC + || + (mbInAnyonesFrustrum) + || + ((frameCount & modulo) == (mIntersectFrame & modulo) + || + (mpTargetVehicle != NULL) + + ) + + + + ) + { + BEGIN_PROFILE("ActionController()->Update") + GetActionController()->Update( mSecondsSinceActionControllerUpdate ); + END_PROFILE("ActionController()->Update") + + // Execute intentions based on current state + mpStateManager->Update( mSecondsSinceActionControllerUpdate ); + + // Do simulation + GetActionController()->WakeUp( mSecondsSinceActionControllerUpdate ); + GetActionController()->DoSimulation( mSecondsSinceActionControllerUpdate ); + + mSecondsSinceActionControllerUpdate = 0.0f; + } + //////////////////////////////////////////////////////// + + + + // Reset last frame's collisions in preparation for new collision data + // (given unto me by WorldPhysMan's substep) + ResetCollisions( ); + + mpPuppet->UpdateBegin(); + + // Zero out the prophandler. If it is still valid it will get set + // during CollisioDetect. + // +// mpPropHandler->SetProp( 0 ); +} + +/* +============================================================================== +Character::ResetCollisions +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void Character::ResetCollisions( void ) +{ + // If the thing has moved, then there will be new collision state info. + // If NOT then we don't reset our collision state info, we will reuse it. + // + // if ( mpSimStateObj->GetCollisionObject( )->HasMoved() ) + { + // Reset everything after this code, and before PostSimUpdate + // is when we do the collision detection. + // + mbCollided = false; + int i; + for ( i = 0; i < CollisionData::MAX_COLLISIONS; i++ ) + { + mCollisionData[ i ].mCollisionNormal.Set( 0.0f, 0.0f, 0.0f ); + mCollisionData[ i ].mCollisionDistance = 0.0f; + mCollisionData[ i ].mpCollisionVolume = (sim::CollisionVolume*)0; + } + mCurrentCollision = 0; + } + + mbCollidedWithVehicle = false; + +} +/* +============================================================================== +Character::UpdateController +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector& direction, float timeins ) + +Return: void + +============================================================================= +*/ +void Character::UpdateController( rmt::Vector& direction, float timeins ) +{ + + if ( GetController( ) ) + { + GetController( )->Update( timeins ); + GetController( )->GetDirection( direction ); + } + else + { + direction.Set( 0.0f, 0.0f, 0.0f ); + } +} +/* +============================================================================== +Character::UpdateDesiredDirAndSpeed +============================================================================== +Description: Comment + +Parameters: ( const rmt::Vector& dir ) + +Return: void + +============================================================================= +*/ +void Character::UpdateDesiredDirAndSpeed( const rmt::Vector& dir ) +{ + rmt::Vector direction = dir; + +#ifdef RAD_WIN32 + SuperCam* cam = GetSuperCamManager()->GetSCC(0)->GetActiveSuperCam(); + PCCam* pPCCam = NULL; + if( !mIsNPC && cam->GetType() == SuperCam::PC_CAM ) + { + pPCCam = static_cast<PCCam*>(cam); + } +#endif + +#ifndef RAD_WIN32 + if ( direction.DotProduct( direction ) > 0.001f ) + { + SetDesiredDir( choreo::GetWorldAngle( direction.x, direction.z ) ); + } +#else + if( !mIsNPC && pPCCam ) + { + pPCCam->SetPitchVelocity( direction.x ); + + // if input is given in either z-axis (forward and back) or x-axis (left and right) + // + rAssert( dynamic_cast<CameraRelativeCharacterController*>( GetController() ) ); + CharacterMappable* map = ((CameraRelativeCharacterController*)this->GetController())->GetCharacterMappable(); + + float dirPadZ = map->GetValue( CharacterController::DPadUp ) - map->GetValue( CharacterController::DPadDown ); + float dirAnalogZ = map->GetValue( CharacterController::LeftStickY ); + float dirZ = rmt::Fabs( dirPadZ ) > rmt::Fabs( dirAnalogZ ) ? dirPadZ : dirAnalogZ; + + float dirPadX = map->GetValue( CharacterController::DPadRight ) - map->GetValue( CharacterController::DPadLeft ); + float dirAnalogX = map->GetValue( CharacterController::LeftStickX ); + float dirX = rmt::Fabs( dirPadX ) > rmt::Fabs( dirAnalogX ) ? dirPadX : dirAnalogX; + + /* + float currAngle = GetFacingDir(); + + if ( !rmt::Epsilon( dirX, 0, 0.01f ) || !rmt::Epsilon( dirZ, 0, 0.01f ) ) + { + rmt::Vector camHeading; + cam->GetHeading( &camHeading ); + camHeading.y = 0.0f; + + rmt::Vector tmpHeading; + + rmt::Matrix mat; + mat.Identity(); + mat.FillHeading( camHeading, rmt::Vector( 0.0f, 1.0f, 0.0f ) ); + tmpHeading.Set( dirX, 0.0f, dirZ ); + tmpHeading.Transform( mat ); + + if( rmt::Epsilon( dirX, 0.0f ) ) + { + if ( dirZ > 0.0f ) + { + mPCCamFacing = 0; + } + else + { + mPCCamFacing = 1; + } + } + else + { + mPCCamFacing = 2; + } + + mInvParentTransform.RotateVector( tmpHeading, &tmpHeading ); + currAngle = choreo::GetWorldAngle( tmpHeading.x, tmpHeading.z ); + SetFacingDir( currAngle ); + + direction.x = dirX; + } + else + { + // Upon no input, face the character in the direction of cam + //rmt::Vector camHeading; + //cam->GetHeading( &camHeading ); + //camHeading.y = 0.0f; + + // reset currAngle and modify the character's facing directly. + //currAngle = choreo::GetWorldAngle( camHeading.x, camHeading.z ); + //SetFacingDir( currAngle ); + + //direction.Set( 0.0f, 0.0f, 0.0f ); + + //mPCCamFacing = 0; + } + + static float X_SENSE_MOD = 0.01f; + float mouseSense = static_cast<Mouse*>(GetInputManager()->GetController( 0 )->GetRealController( MOUSE ))->getSensitivityX(); + + static float ROT_DIR = 4.0f; + + float fPitchVelocity = pPCCam->GetPitchVelocity(); + float fScaleFactor = 1.0f; + if( mbTurbo ) fScaleFactor *= mVelocity.Magnitude(); + + currAngle += ROT_DIR * mouseSense * X_SENSE_MOD * pPCCam->GetAngularSpeed() * pPCCam->GetPitchVelocity(); + + SetDesiredDir( currAngle ); + */ + + if ( !rmt::Epsilon( dirX, 0, 0.01f ) || !rmt::Epsilon( dirZ, 0, 0.01f ) ) + { + rmt::Vector camHeading; + cam->GetHeading( &camHeading ); + camHeading.y = 0.0f; + + rmt::Vector tmpHeading; + + rmt::Matrix mat; + mat.Identity(); + mat.FillHeading( camHeading, rmt::Vector( 0.0f, 1.0f, 0.0f ) ); + tmpHeading.Set( dirX, 0.0f, dirZ ); + tmpHeading.Transform( mat ); + + if( rmt::Epsilon( dirX, 0.0f ) ) + { + if ( dirZ > 0.0f ) + { + mPCCamFacing = 0; + } + else + { + mPCCamFacing = 1; + } + } + else + { + mPCCamFacing = 2; + } + + mInvParentTransform.RotateVector( tmpHeading, &tmpHeading ); + float currAngle = choreo::GetWorldAngle( tmpHeading.x, tmpHeading.z ); + SetDesiredDir( currAngle ); + + direction.x = dirX; + } + } + else // We reached here cuz we're either an NPC or we're not in PC_CAM + { + if ( direction.DotProduct( direction ) > 0.001f ) + { + SetDesiredDir( choreo::GetWorldAngle( direction.x, direction.z ) ); + } + } +#endif + + float fDesiredSpeed = direction.Magnitude(); + + // apply non-linear response to input to Player Character only.. + // the NPCs rely on desired speed to remain unaltered + // for the intended speed to be reached. + if( !mIsNPC ) + { + fDesiredSpeed *= fDesiredSpeed; + } + + // Now normalize the speed to a smaller range than 1.0, probably something like 0.84f + // + float fMaxScale = GetInputScale( ); + fDesiredSpeed /= fMaxScale; + if ( fDesiredSpeed > 1.0f ) + { + fDesiredSpeed = 1.0f; + } + SetDesiredSpeed( fDesiredSpeed * GetMaxSpeed( ) ); +} +/* +============================================================================== +Character::UpdateRoot +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void Character::UpdateRoot( float timeins ) +{ + OnUpdateRoot( timeins ); +} + +/* +============================================================================== +Character::Update +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void Character::OnUpdateRoot( float timeins ) +{ +#ifdef RAD_DEBUG + timeins *= timeScale; +#endif + + UpdatePuppet( timeins ); +} +/* +============================================================================== +Character::UpdatePuppet +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void Character::UpdatePuppet( float timeins ) +{ + choreo::Puppet* pPuppet = GetPuppet( ); + rAssert( pPuppet ); + // post sim inserted here + UpdateParentTransform( timeins ); + + mbNeedChoreoUpdate = !( + (pPuppet->GetEngine()->GetRootBlender()->GetRootDriverCount() <= 1) + && (!mVisible || !mbInAnyonesFrustrum) + && mbSimpleLoco); + + if(pPuppet->GetDriverCount() == 0) + { +// rDebugPrintf("WARNING : Character '%s' has no pose drivers, holding last pose\n", GetName()); + mbNeedChoreoUpdate = false; + } + + if(!mbNeedChoreoUpdate && mbSimpleLoco) + { + pPuppet->GetEngine()->GetRootBlender()->ClearRootDrivers(); + + rmt::Vector position = pPuppet->GetPosition(); + rAssert( mpWalkerLocomotion != NULL ); + choreo::LocomotionDriver* locoDriver = mpWalkerLocomotion->GetDriver(); + rAssert( locoDriver != NULL ); + float velocity = locoDriver->GetDesiredVelocity(); + + rmt::Vector facing; + this->GetFacing(facing); + + facing *= velocity * timeins; + pPuppet->SetPosition(position + facing); + + pPuppet->UpdateRoot(); + } + else + { +// if(pPuppet->GetDriverCount() != 0) + { + // Update choreo. + // + // Push() all drivers before call to choreo::Puppet::Begin() + // + pPuppet->Advance( timeins ); + + // choreo::Puppet::UpdateRoot() will change choreo::Puppet Position and Orientation + // + pPuppet->UpdateRoot(); + } + } +} + +bool Character::TestInAnyonesFrustrum() +{ + //////////////////////////////////////////////// + // Test if we're in anyone's frustrum + // + mbInAnyonesFrustrum = false; + int playerCount = 0; + int numPlayers = ::GetGameplayManager()->GetNumPlayers(); + while( !mbInAnyonesFrustrum && playerCount < numPlayers ) + { + TestInFrustrumOfPlayer(playerCount); + playerCount++; + } + if( mpCharacterRenderable != NULL ) + { + // characterrenderable doesn't have pointer to character, so + // store result there too... + mpCharacterRenderable->SetInAnyonesFrustrum( mbInAnyonesFrustrum ); + } + return mbInAnyonesFrustrum; +} + + +/* +============================================================================== +Character::PostSimUpdate +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void Character::PostSimUpdate( float timeins ) +{ + TestInAnyonesFrustrum(); + +#ifdef RAD_DEBUG + timeins *= timeScale; +#endif + + // determine how frequently we need to make some of the expensive calls + unsigned int modulo = 0x7; + unsigned int frameCount = GetGame()->GetFrameCount(); + bool shouldUpdate = !mIsNPC || // update the PC at full rate + IsInCar() || // or parent transform will get screwed + (mbInAnyonesFrustrum) || // update if we are visible + ((frameCount & modulo) == (mIntersectFrame & modulo)); + +BEGIN_PROFILE("OnPostSimUpdate") + mSecondsSinceOnPostSimUpdate += timeins; + + if( shouldUpdate ) + { + OnPostSimUpdate( mSecondsSinceOnPostSimUpdate ); + mSecondsSinceOnPostSimUpdate = 0.0f; + } + + mbNeedChoreoUpdate = mbNeedChoreoUpdate && shouldUpdate && (!mTooFarToUpdate || ((frameCount & modulo) == (mIntersectFrame & modulo))); + +END_PROFILE("OnPostSimUpdate") + + // Call physics update. + // Needs to be called before UpdatePose in case we're in SimulationCtrl +BEGIN_PROFILE("UpdateSimState") + UpdateSimState( timeins ); +END_PROFILE("UpdateSimState") + + choreo::Puppet* pPuppet = GetPuppet( ); + rAssert( pPuppet ); + + ResolveCollisions(); + + BEGIN_PROFILE("UpdateGroundHeight") + if ( !IsInCar( ) ) + { + UpdateGroundHeight(); + } +END_PROFILE("UpdateGroundHeight") + + // Update the puppet with the physics transform? + // +/* +BEGIN_PROFILE("UpdateProps") + UpdateProps( timeins ); +END_PROFILE("UpdateProps") +*/ + + if ( GetController() ) + { + GetController()->ClearIntention(); + } + + if(IsNPC()) + { + if(mAmbientTrigger) + { + rmt::Vector pos; + GetPosition(pos); + mAmbientTrigger->SetPosition(pos); + } + } + else + { + /* + // Clear the action handler when we are set to handle a prop + // that has been previously cleared. + // +BEGIN_PROFILE("GetActionButtonHandler") + if( mpPropHandler->GetProp() == 0 ) + { + mpPropHandler->Exit( this ); + RemoveActionButtonHandler( mpPropHandler ); + } +END_PROFILE("GetActionButtonHandler") + */ + +BEGIN_PROFILE("UpdateFootPlant") + if( !IsInCar() ) + { + if( radTimeGetMicroseconds64()-mLastInteriorLoadCheck > 3000000 ) //(amortise/3s) if it's been more than 3 sec's since we last checked for volumes + { + rmt::Vector posn; + GetPosition(&posn); + GetTriggerVolumeTracker()->ActivateNearestInteriorLoadZone(0, posn, 40.0f); + mLastInteriorLoadCheck = radTimeGetMicroseconds64(); + } + + UpdateFootPlant( ); + } +END_PROFILE("UpdateFootPlant") + + UpdateShock( timeins ); + + if(mDoKickwave && mKickwaveController) + { + mKickwaveController->Advance(timeins * 1000.0f); + if(mKickwaveController->LastFrameReached()) + { + mDoKickwave = false; + } + } + } + + rmt::Vector tmp, tmpDir; + GetPosition(tmp); + tmpDir.Sub(tmp, mLastFramePos); + mVelocity = tmpDir / timeins; + + // if movement is too great while not in car, reset velocity & reset position to last frame's pos + // this is to stop the player character from going out of world or warping too high in the sky, + // or too low below ground, etc... + + const float DIFF_SQR_IN_POS_TOO_HIGH_RESET = 2500.0f; + + CharacterAi::CharacterState state = GetStateManager()->GetState(); + + if( !mIsNPC && + ( state == CharacterAi::LOCO || state == CharacterAi::INSIM )&& + ( tmpDir.MagnitudeSqr() > DIFF_SQR_IN_POS_TOO_HIGH_RESET ) ) + { + //rAssertMsg( false, "Player character got moved REALLY far in one frame... Using last frame pos!\n" ); + mVelocity.Set( 0.0f, 0.0f, 0.0f ); + SetPosition( mLastFramePos ); + mpJumpLocomotion->SetRootPosition( WorldToLocal( mLastFramePos ) ); + this->SetGroundPoint( mLastFramePos ); + } + else + { + if(mCollisionAreaIndex != -1) + { + if(GetWorldPhysicsManager()->FenceSanityCheck(mCollisionAreaIndex, mLastFramePos, tmp, &tmp)) + { + SetPosition( tmp ); + mpJumpLocomotion->SetRootPosition( WorldToLocal( tmp ) ); + this->SetGroundPoint( tmp ); + } + } + + mLastFramePos = tmp; + } + +BEGIN_PROFILE("MoveInWorldScene") + MoveInWorldScene(); +END_PROFILE("MoveInWorldScene") +} + +void Character::ResolveCollisions(void) +{ + mCollidedThisFrame = false; + sim::SimState* simState = mpSimStateObj; //GetSimState(); + if( simState != NULL ) + { + if( simState->GetControl() == sim::simAICtrl ) + { + bool inSR1Or2 = false; + Mission* m = GetGameplayManager()->GetCurrentMission(); + if( m ) + { + inSR1Or2 = m->mIsStreetRace1Or2; + } + bool shouldSolveCollisions = !IsInCar() && + (!mIsNPC || (mIsNPC && !inSR1Or2)); + + + if( shouldSolveCollisions ) + { + rmt::Vector desiredPos = LocalToWorld( mpPuppet->GetPosition( ) ); + rmt::Vector outPos; +BEGIN_PROFILE("SolveCollisionWithStatic") + Character::eCollisionType collisionType = SolveCollisionWithStatic( desiredPos, outPos ); +END_PROFILE("SolveCollisionWithStatic") + + if( collisionType & Character::HitHead ) + { + if(mpJumpLocomotion->IsJumpState(JumpAction::Jump)) + { + GetEventManager()->TriggerEvent(EVENT_HIT_HEAD, this); + + if(!mbIsStanding && (mVelocity.y > 0.0f)) + { + mpJumpLocomotion->Reset(0.0f, true); + + if(!IsNPC()) + { + outPos.y -= 0.01f; + } + } + } + } + + if ( collisionType & ( Character::HitWall | Character::HitHead) ) + { + // Fix the character position. + // + SetPosition( outPos ); + mpJumpLocomotion->SetRootPosition( WorldToLocal( outPos ) ); + this->SetGroundPoint( outPos); + mCollidedThisFrame = true; + } + + } + } + } +} + +/* +============================================================================== +Character::UpdateFootPlant +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void Character::UpdateFootPlant( void ) +{ + int legs = mpPuppet->GetLegCount( ); + for ( int i = 0; i < legs; i++ ) + { + bool bFootPlanted = mpPuppet->IsFootPlanted( i ); + if ( bFootPlanted ) + { + if ( mbWasFootPlanted[ i ] == false ) + { + // foot planted. + // + + // If were are dashing around (turbo) and the foot just made + // contact with the ground, kick up a dust cloud + // also make sure the user isnt just holding in the turbo button while + // the character is just standing still + // + + + if ( GetDesiredSpeed() > 1.0f ) + { + /* footprints + rmt::Matrix transform; + transform.Identity(); + transform.FillTranslate( mpPuppet->GetFootPosition( i ) ); + + GetFootprintManager()->CreateFootprint( transform, FootprintManager::eSquishies ); + */ + + // no puffs in interior + if(!GetInteriorManager()->IsInside()) + { + rmt::Vector facing; + this->GetFacing( facing ); + GetSparkleManager()->AddDash( mpPuppet->GetFootPosition( i ), facing, GetDesiredSpeed() * 0.125f ); + } + + if(this == GetCharacterManager()->GetCharacter(0)) + { + GetEventManager()->TriggerEvent( EVENT_FOOTSTEP, this ); + } + } + } + } + mbWasFootPlanted[ i ] = bFootPlanted; + } +} +/* +============================================================================== +Character::OnPostSimUpdate +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void Character::OnPostSimUpdate( float timeins ) +{ +} + +/* +============================================================================== +Character::UpdateShock +============================================================================== +Description: Comment + +Parameters: ( float delta time in seconds ) + +Return: void + +============================================================================= +*/ +void Character::UpdateShock( float timeins ) +{ + if ( m_IsBeingShocked ) + { + // UpdateDesiredDirAndSpeed( rmt::Vector( 0,0,0 ) ); + m_TimeLeftToShock -= timeins; + + if ( IsNPC() == false ) + { + float blur = m_TimeLeftToShock * 2.0f; + if ( blur > 1.0f ) blur = 1.0f; + GetRenderManager()->SetBlurAlpha( blur ); + } + if ( m_TimeLeftToShock <= 0 ) + { + mpCharacterRenderable->SetShocked( false ); + m_IsBeingShocked = false; + } + } + else + { + if ( IsNPC() == false ) + GetRenderManager()->SetBlurAlpha( 0 ); + } +} + + +/* +============================================================================== +Character::AddToWorldScene +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void Character::AddToWorldScene( void ) +{ + if( !mVisible && mManaged ) + { + UpdatePuppet( 0.0f ); + + choreo::Puppet* pPuppet = GetPuppet( ); + rAssert( pPuppet ); + + pPuppet->UpdatePose(); + + if(mpCharacterRenderable->GetDrawable()) + { + mpPuppet->GetP3DPose()->SetSkeleton(mpCharacterRenderable->GetDrawable()->GetSkeleton()); + pPuppet->UpdateEnd(); + } + + UpdateSimState( 0.0f ); + + // Call updateBBox so the DSG knows where to put this guy at the start. + rmt::Box3D dummyBox; + UpdateBBox( dummyBox ); + + rAssert( mpWorldScene == 0 ); + mpWorldScene = ((WorldRenderLayer*)GetRenderManager()->mpLayer(RenderEnums::LevelSlot))->pWorldScene(); + mpWorldScene->Add( this ); + + //UpdateProps( 0.0f ); + + mVisible = true; + } +} +/* +============================================================================== +Character::RemoveFromWorldScene +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: + +============================================================================= +*/ +void Character::RemoveFromWorldScene( void ) +{ + if( mVisible ) + { + mpWorldScene->Remove( this ); + mpWorldScene = 0; + mVisible = false; + } +} + + + +void Character::MoveInWorldScene( void ) +{ + if( mVisible ) + { + rmt::Box3D oldBox; + UpdateBBox( oldBox ); + // now move! + // + mpWorldScene->Move(oldBox, (IEntityDSG*)this); + } +} + +/* +============================================================================== +Character::UpdateSimState +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void Character::UpdateSimState( float timeins ) +{ + sim::SimState* simState = mpSimStateObj; //GetSimState(); + if( simState != 0 ) + { + if( simState->GetControl() == sim::simAICtrl ) + { + /* + poser::Transform poserTrans = mpPuppet->GetRootTransform(); + rmt::Matrix matrix = poserTrans.GetMatrix(); + simState->SetTransform( matrix, timeins ); + */ + + rmt::Matrix matrix; + rmt::Vector facing; + rmt::Vector side; + rmt::Vector position; + + GetFacing(facing); + GetPosition( position ); + + side.CrossProduct(facing, rmt::Vector(0.0f, 1.0f, 0.0f)); + + // leaning while in jump or when doing idle is *very* likely to push us through stuff + bool dontLean = GetJumpLocomotionAction()->IsInJump() || mbIsPlayingIdleAnim; + + if(!dontLean) + { + dontLean = mLean.Dot(rmt::Vector(0.0f, 1.0f, 0.0f)) > 0.925; + } + + matrix.Identity(); + matrix.Row(0) = side; + matrix.Row(1) = dontLean ? rmt::Vector(0.0f, 1.0f, 0.0f) : mLean; + matrix.Row(2).CrossProduct(side, mLean); + matrix.Row(3) = position; + + rmt::Matrix oldMat = simState->GetTransform( ); + simState->SetTransform( matrix, timeins ); + simState->GetCollisionObject()->Update(); + + // TODO: + // Do we really need to update velocity here?? + rmt::Vector velXZ = simState->VelocityState().mLinear; + velXZ.y = 0.0f; + mfSpeed = velXZ.Magnitude( ); + } + else if( simState->GetControl() == sim::simSimulationCtrl ) + { + // If simstate velocities are too great, fudge them here. We don't want characters flying too far away... + rmt::Vector vel = simState->VelocityState().mLinear; + float limit = 15.0f; + rAssert( limit >= 0.0f ); + float linearSpdMps = vel.Length(); // *** SQUARE ROOT! *** + if( linearSpdMps > limit ) + { + simState->VelocityState().mLinear.Scale( limit/linearSpdMps ); + } + + // Update Character's speed + rmt::Vector velXZ = simState->VelocityState().mLinear; + velXZ.y = 0.0f; + mfSpeed = velXZ.Magnitude( ); + + // Update Character's facing and position + rmt::Matrix matrix = (rmt::Matrix) simState->GetTransform(); + rmt::Vector negZed( 0.0f, 0.0f, -1.0f ); + + poser::Transform transform( matrix ); + mpPuppet->SetRootTransform( transform ); + + poser::Pose* pPose = mpPuppet->GetPose( ); + // stuff fixed up root transform into joint + poser::Joint* joint = pPose->GetJoint( 0 ); + joint->SetWorldTransform( transform ); + joint->SetObjectTransform( transform ); + } + } +} + +/* +============================================================================== +Character::UpdateBBox +============================================================================== +Description: Comment + +Parameters: ( rmt::Box3D& oldBox ) + +Return: void + +============================================================================= +*/ +void Character::UpdateBBox( rmt::Box3D& oldBox ) +{ + oldBox = mBBox; + + GetPosition( mPosn ); + + mBBox.low = mPosn; + mBBox.high = mPosn; + + mBBox.high += mpSimStateObj->GetCollisionObject()->GetCollisionVolume()->mBoxSize; + mBBox.low -= mpSimStateObj->GetCollisionObject()->GetCollisionVolume()->mBoxSize; + + mSphere.centre.Sub(mBBox.high,mBBox.low); + mSphere.centre *= 0.5f; + mSphere.centre.Add(mBBox.low); + mSphere.radius = mpSimStateObj->GetCollisionObject()->GetCollisionVolume()->mSphereRadius; +} +// Implements CollisionEntityDSG +// +/* +============================================================================== +Character::PreReactToCollision +============================================================================== +Description: Comment + +Parameters: ( sim::SimState* pCollidedObj, sim::Collision& inCollision ) + +Return: bool + +============================================================================= +*/ +sim::Solving_Answer Character::PreReactToCollision( sim::SimState* pCollidedObj, sim::Collision& inCollision ) +{ + if((inCollision.mCollisionVolumeA->Type() == sim::BBoxVolumeType) || + (inCollision.mCollisionVolumeB->Type() == sim::BBoxVolumeType)) + { + return sim::Solving_Continue; + } + + //////////////////////////////////////////// + // Deal properly with ignoring targetvehicle pointer + // + if( this->mpTargetVehicle && (pCollidedObj->mAIRefPointer == this->mpTargetVehicle)) + { + return sim::Solving_Aborted; + } + + if( this->mpTargetVehicle && this->mpTargetVehicle->mVehicleDestroyed) + { + if(pCollidedObj->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + Vehicle* v = (Vehicle*)pCollidedObj->mAIRefPointer; + if(v->mVehicleID == VehicleEnum::HUSKA) + { + Vehicle* ov = GetVehicleCentral()->mHuskPool.FindOriginalVehicleGivenHusk(v); + if(ov && (ov == this->mpTargetVehicle)) + { + return sim::Solving_Aborted; + } + } + } + } + + if( this->mpTargetVehicle && this->mpTargetVehicle->GetDriver() && + (pCollidedObj->mAIRefPointer == this->mpTargetVehicle->GetDriver())) + { + return sim::Solving_Aborted; + } + ///////////////////////////////////////// + + // Remember that we've legitimately collided with a vehicle + if( pCollidedObj->mAIRefIndex == PhysicsAIRef::redBrickVehicle ) + { + mbCollidedWithVehicle = true; + } + + // Don't store collision data if we're under simulation control + if( mpSimStateObj->GetControl() == sim::simSimulationCtrl ) + { + // When a traffic vehicle hits an NPC (or vice versa) while in simulation control... + if( (pCollidedObj->mAIRefIndex == PhysicsAIRef::redBrickVehicle) && + (mpSimStateObj->mAIRefIndex == PhysicsAIRef::NPCharacter) && + (GetStateManager()->GetState() == CharacterAi::LOCO || GetStateManager()->GetState() == CharacterAi::INSIM) ) + { + + sim::Collision theCollision = inCollision; + sim::SimState* pSimState = theCollision.mCollisionObjectB->GetSimState(); + if( pSimState->mAIRefPointer == this && + (pSimState->mAIRefIndex == PhysicsAIRef::NPCharacter || + pSimState->mAIRefIndex == PhysicsAIRef::PlayerCharacter) ) + { + theCollision.mNormal.Scale( -1.0f ); + if(theCollision.GetPositionB().y - mGroundY < 0.05f) + { + return sim::Solving_Continue; + } + } + else + { + if(theCollision.GetPositionA().y - mGroundY < 0.05f) + { + return sim::Solving_Continue; + } + } + + // Grab vehicle's speed and if it's greater than threshold, + // transit to simulation control. + Vehicle* v = (Vehicle*) pCollidedObj->mAIRefPointer; + rAssert( v ); + + float vSpeedMps = v->mSpeed; + float speedThreshold = 1.0f; + if( vSpeedMps > speedThreshold && v->mVehicleType == VT_TRAFFIC ) + { + /* + const int MAX_RAND_MODULUS = 3; + const float THEORETICAL_MAX_SPEED_MPS = 28.0f; + + float speedRatio = v->mSpeed / THEORETICAL_MAX_SPEED_MPS; + + const float BASE_SPEED_COMPONENT_MOD_MPS = 2.0f; + + // Impart immediate vertical velocity! YA! + float randX, randY, randZ; + randX = ((float)(rand()%MAX_RAND_MODULUS) + BASE_SPEED_COMPONENT_MOD_MPS) * speedRatio; + randY = ((float)(rand()%MAX_RAND_MODULUS) + BASE_SPEED_COMPONENT_MOD_MPS) * speedRatio; + randZ = ((float)(rand()%MAX_RAND_MODULUS) + BASE_SPEED_COMPONENT_MOD_MPS) * speedRatio; + + rmt::Vector impactLinearVel( 0.0f, randY, 0.0f ); + rmt::Vector impactAngularVel( randX, randY, randZ); + + impactLinearVel += v->GetFacing() + theCollision.mNormal; + */ + rmt::Vector impactLinearVel = theCollision.mNormal * 1.4f; + + + rmt::Vector& linearVel = mpSimStateObj->GetLinearVelocity(); + linearVel.Add( impactLinearVel ); + //rmt::Vector& angularVel = mpSimStateObj->GetAngularVelocity(); + //angularVel.Add( impactAngularVel ); + } + } + return sim::Solving_Continue; + } + + //rAssert( mCurrentCollision < CollisionData::MAX_COLLISIONS ); + if( mCurrentCollision >= CollisionData::MAX_COLLISIONS ) + { + return sim::Solving_Continue;//false; + } + + if( //mpSimStateObj->mAIRefIndex == PhysicsAIRef::PlayerCharacter && + pCollidedObj->mAIRefIndex == PhysicsAIRef::redBrickPhizMoveableGroundPlane ) + { + return sim::Solving_Aborted; + } + + // + // store some value for when player character (me) collides with another character + // + if( mpSimStateObj->mAIRefIndex == PhysicsAIRef::PlayerCharacter && + pCollidedObj->mAIRefIndex == PhysicsAIRef::NPCharacter ) + { + GetEventManager()->TriggerEvent( EVENT_PC_NPC_COLLISION, pCollidedObj->mAIRefPointer ); + } + static bool bTestNormals = true; + static float sfEdgeTune = 0.6f; + sim::Collision theCollision = inCollision; + // recall: + // mPositionA = mPositionB + mNormal * mDistance + // We want the normal to point from Object to Homer. + // Normals always point from B to A. + // So, if homer is B, then flip the normal. + // + + sim::SimState* pSimState = theCollision.mCollisionObjectB->GetSimState(); + if( pSimState->mAIRefPointer == this && + (pSimState->mAIRefIndex == PhysicsAIRef::NPCharacter || + pSimState->mAIRefIndex == PhysicsAIRef::PlayerCharacter) ) + { + theCollision.mNormal.Scale( -1.0f ); + if(theCollision.GetPositionB().y - mGroundY < 0.05f) + { + return sim::Solving_Continue; + } + } + else + { + if(theCollision.GetPositionA().y - mGroundY < 0.05f) + { + return sim::Solving_Continue; + } + } + + if ( theCollision.mCollisionVolumeA == mpStandingCollisionVolume + || theCollision.mCollisionVolumeB == mpStandingCollisionVolume ) + { + return sim::Solving_Continue;//false; + } + + // When a vehicle hits us (or vice versa), transit to simulation control + // so physics take over if we are in locomiotion (for now, don't do this + // if we are not in loco (i.e. getting in or out of car) or state can get + // badly screwed + if( (pCollidedObj->mAIRefIndex == PhysicsAIRef::redBrickVehicle) && + (GetStateManager()->GetState() == CharacterAi::LOCO || + GetStateManager()->GetState() == CharacterAi::INSIM) ) + { + // Grab vehicle's speed and if it's greater than threshold, + // transit to simulation control. + Vehicle* v = (Vehicle*) pCollidedObj->mAIRefPointer; + rAssert( v ); + + float vSpeedMps = v->mSpeed; + + float speedThreshold = 1.0f; + if( vSpeedMps > speedThreshold ) + { + if( mpSimStateObj->mAIRefIndex == PhysicsAIRef::NPCharacter ) + { + if( v == GetAvatarManager()->GetAvatarForPlayer( 0 )->GetVehicle() ) + { + GetEventManager()->TriggerEvent( EVENT_PLAYER_MAKES_LIGHT_OF_CAR_HITTING_NPC ); + GetEventManager()->TriggerEvent( EVENT_PLAYER_CAR_HIT_NPC, this ); + mHasBeenHit = true; + } + + ///////////////////////////// + // We're entering simulation + ///////////////////////////// + mpSimStateObj->SetControl( sim::simSimulationCtrl ); + GetWorldPhysicsManager()->EnableGroundPlaneCollision(mGroundPlaneIndex); + + + // If a traffic vehicle hit us, its simstate will have zero velocity, so + // it won't be imparting anything upon us... So we fake it... + if( v->mVehicleType == VT_TRAFFIC ) + { + + //const int MIN_RAND_MODULUS = 1; + const int MAX_RAND_MODULUS = 6; + const float THEORETICAL_MAX_SPEED_MPS = 28.0f; + + float speedRatio = v->mSpeed / THEORETICAL_MAX_SPEED_MPS; + + //int delta = MAX_RAND_MODULUS - MIN_RAND_MODULUS; + //int randMod = (int)(speedRatio * delta) + MIN_RAND_MODULUS; + //rAssert( randMod >= MIN_RAND_MODULUS ); + + const float BASE_SPEED_COMPONENT_MOD_MPS = 6.0f; + + // Impart immediate vertical velocity! YA! + float randX, randY, randZ; + randX = ((float)(rand()%MAX_RAND_MODULUS) + BASE_SPEED_COMPONENT_MOD_MPS) * speedRatio; + randY = ((float)(rand()%MAX_RAND_MODULUS) + BASE_SPEED_COMPONENT_MOD_MPS) * speedRatio; + randZ = ((float)(rand()%MAX_RAND_MODULUS) + BASE_SPEED_COMPONENT_MOD_MPS) * speedRatio; + + rmt::Vector impactLinearVel( 0.0f, randY, 0.0f ); + rmt::Vector impactAngularVel( randX, randY, randZ); + + impactLinearVel += v->GetFacing() + theCollision.mNormal; + + + rmt::Vector& linearVel = mpSimStateObj->GetLinearVelocity(); + linearVel.Add( impactLinearVel ); + rmt::Vector& angularVel = mpSimStateObj->GetAngularVelocity(); + angularVel.Add( impactAngularVel ); + } + + } + else // it's the player character that's getting hit by a vehicle + { + // Ignore all other cars except VT_AI cars + // need to break out and keep solving though + if(((Vehicle*)pCollidedObj->mAIRefPointer)->mVehicleType != VT_AI ) + { + goto KeepSolving; + } + + if( !IsInCar() ) + { + + // ignore "up" collision normals (presumably we're standing on top + // of a traffic car) + const float TOP_OF_VEHICLE_COS_ALPHA = 0.9848077f; // approx 10 degrees + float dp = theCollision.mNormal.Dot( mRealGroundNormal ); + + // if deviate by > 10 degrees from ground normal, then we're + // probably not standing on it. + if( dp < TOP_OF_VEHICLE_COS_ALPHA ) + { + + ///////////////////////////// + // We're entering simulation + ///////////////////////////// + + // Transit to Simulation control and set up its ground plane... + // Didn't need to be done for NPCs cuz they would have been + // submitted by other dynamics when hit (and would have obtained + // their ground plane at that point) + AddToSimulation(); + + rAssert( mGroundPlaneSimState ); + mGroundPlaneSimState->GetCollisionObject()->SetCollisionEnabled( true ); + + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + if(v->mName) + { + if(chaseManager->IsModelRegistered(v->mName)) + { + GetEventManager()->TriggerEvent( EVENT_HIT_AND_RUN_CAUGHT, NULL ); + } + } + } + } + } + } + } +// return sim::Solving_Continue; + } + } + +KeepSolving: + + if ( bTestNormals ) + { + for ( int i = 0; i < mCurrentCollision; i++ ) + { + // Test each collision normal to see if it is a duplicate of a collision we + // have already stored. + // + if( ( mCollisionData[ i ].mpCollisionVolume == theCollision.mCollisionVolumeA || + mCollisionData[ i ].mpCollisionVolume == theCollision.mCollisionVolumeB ) ) + { + rAssert( mbCollided ); + + rmt::Vector facing; + GetFacing(facing); + + bool store = ( mCollisionData[ i ].mCollisionNormal.DotProduct(facing) > theCollision.mNormal.DotProduct(facing)); + + if(store) + { + mCollisionData[ i ].mCollisionDistance = theCollision.mDistance; + GetPosition( mCollisionData[ i ].mCollisionPosition ); + mCollisionData[ i ].mCollisionNormal = theCollision.mNormal; + mCollisionData[ i ].mpCollisionVolume = theCollision.mCollisionVolumeA == mpSimStateObj->GetCollisionObject()->GetCollisionVolume() ? theCollision.mCollisionVolumeB : theCollision.mCollisionVolumeA; + } + + + return sim::Solving_Continue;//true; + } + } + } + + + // Tests to see if we should skip this collision. + // + mbCollided = true; + + mCollisionData[ mCurrentCollision ].mCollisionDistance = theCollision.mDistance; + GetPosition( mCollisionData[ mCurrentCollision ].mCollisionPosition ); + mCollisionData[ mCurrentCollision ].mCollisionNormal = theCollision.mNormal; + mCollisionData[ mCurrentCollision ].mpCollisionVolume = theCollision.mCollisionVolumeA == mpSimStateObj->GetCollisionObject()->GetCollisionVolume() ? theCollision.mCollisionVolumeB : theCollision.mCollisionVolumeA; + + mCurrentCollision++; + + return sim::Solving_Continue;//true; + +} + + +//============================================================================= +// Character::PostReactToCollision +//============================================================================= +// Description: Comment +// +// Parameters: ( sim::SimState* pCollidedObj, sim::Collision& inCollision ) +// +// Return: sim +// +//============================================================================= +sim::Solving_Answer Character::PostReactToCollision(rmt::Vector& impulse, sim::Collision& inCollision) +{ + return sim::Solving_Continue; +} + + +/* +============================================================================== +Character::SolveCollisionWithStatic +============================================================================== +Description: desiredPos is the new position of the character after the + choreo::UpdateRoot( ) call. + the position of the character at the time of collision + was stored in the mCollisionData array. + +Parameters: ( const rmt::Vector& desiredPos ) + +Return: rmt + +============================================================================= +*/ +Character::eCollisionType Character::SolveCollisionWithStatic( const rmt::Vector& desiredPos, rmt::Vector& outPos ) +{ + static bool sbOutput = false; + + outPos = desiredPos; //.Set( 0.0f, 0.0f, 0.0f ); + if ( !mbSolveCollisions ) + { + return NoCollision; + } + eCollisionType collisionType = NoCollision; + int intCollisionType = collisionType; + int i = 0; + + if(mCurrentCollision > 0) + { + if ( sbOutput ) rDebugPrintf( "solving %d\n", mCurrentCollision); + } + + // unsightly hack to try and deal with multiple collisions trying + // to push you through walls + if(mCurrentCollision > 1) + { + bool lock = true; + + if(mCurrentCollision == 2) + { + if( mCollisionData[ 0 ].mCollisionNormal.Dot(mCollisionData[ 1 ].mCollisionNormal) > 0.5f) + { + lock = false; + } + } + + int nAway = 0; + rmt::Vector facing; + GetFacing(facing); + + for(int i = 0; i < mCurrentCollision; i++) + { + if( mCollisionData[ i ].mCollisionNormal.Dot(facing) > 0.0f) + { + nAway++; + } + } + + if(nAway == mCurrentCollision) + { + lock = false; + } + + for(int i = 0; i < mCurrentCollision; i++) + { + if( mCollisionData[ i ].mCollisionNormal.y > 0.1f) + { + lock = false; + break; + } + } + + if(lock) + { + if ( sbOutput ) rDebugPrintf( "locking\n", mCurrentCollision); + outPos = mLastFramePos; + if(this->GetJumpLocomotionAction()->IsInJump()) + { + outPos.y = desiredPos.y; + + for(int i = 0; i < mCurrentCollision; i++) + { + if ( mCollisionData[ i ].mCollisionNormal.y <= -0.5f ) + { + return HitHead; + } + } + } + return HitWall; + } + } + + int numSlides = 0; + + for(i = 0; i < mCurrentCollision; i++) + { + if( mCollisionData[ i ].mCollisionNormal.y > 0.1f) + { + numSlides += 1; + } + } + + for ( i = 0; i < mCurrentCollision; i++ ) + { + if( !CanStandOnCollisionNormal( mCollisionData[ i ].mCollisionNormal ) ) + { + + // Desired motion vector. + // + rmt::Vector diffVect; + + // outPos is the solved position. So it will change each time + // through the loop. + // + static bool sbUseOutpos = true; + rmt::Vector prevPos = mCollisionData[ i ].mCollisionPosition; //GetPuppet( )->GetPrevPosition( ); + if ( sbUseOutpos ) + diffVect.Sub( outPos, prevPos ); + else + diffVect.Sub( desiredPos, prevPos ); + + // dampen the y a little, to avoid multiple sliding surfaces + // actually holding us in the air + if( mCollisionData[ i ].mCollisionNormal.y > 0.1f) + { + diffVect.y *= 1.0f / float(numSlides); + } + + // Normalized desired motion vector. + // + rmt::Vector normalizeDiff = diffVect; + + // The distance we want to travel this frame. + // + float dist = normalizeDiff.NormalizeSafe(); + + // handle the case when the character is not moving, + // but an animated collision volume has collided with it. + // + if ( dist == 0.0f ) + { + // Possible solution. Take the collision normal, + // and use that for 'normalizeDiff'. ie assume the character + // is moving (relatively) in the direction of the normal. + // + normalizeDiff = mCollisionData[ i ].mCollisionNormal; + normalizeDiff.Scale(-1.0f); + dist = 0.0f; + } + // The dot will tell us if we are moving into ( dot < 0 ) + // the collision or away from the collision (dot >= 0 ).. + // + float dot = normalizeDiff.DotProduct( mCollisionData[ i ].mCollisionNormal ); + rmt::Vector adjPos = mCollisionData[ i ].mCollisionNormal; + bool bSolve = true; + if ( bSolve ) + { + // Scale the distance against the collision normal. + // + float tempDist = dist; + tempDist *= -dot; + + // We only want to add the collision distance to + // tempDist (the distance we will scale the motion vector) + // when the collDist is gt zero, ie NOT interpentrating. + // If we are interpenetrating, NOT adding the collDist + // will snap us to the surface of the collision. + // + static bool sbPositive = true; + static bool sbNegative = true; + if ( mCollisionData[ i ].mCollisionDistance < 0.0f ) + { + if ( sbNegative ) + { + tempDist -= mCollisionData[ i ].mCollisionDistance; + if ( sbOutput ) rDebugPrintf( "collision %.4f (%.2f, %.2f, %.2f) %s\n", + mCollisionData[ i ].mCollisionDistance, + mCollisionData[ i ].mCollisionNormal.x, + mCollisionData[ i ].mCollisionNormal.y, + mCollisionData[ i ].mCollisionNormal.z, + mCollisionData[ i ].mpCollisionVolume->GetCollisionObject( )->GetName( ) ); + } + } + else if ( mCollisionData[ i ].mCollisionDistance > 0.0f ) + { + if( sbPositive ) + { + tempDist += mCollisionData[ i ].mCollisionDistance; + if ( sbOutput ) rDebugPrintf( "collision %.4f (%.2f, %.2f, %.2f) %s\n", + mCollisionData[ i ].mCollisionDistance, + mCollisionData[ i ].mCollisionNormal.x, + mCollisionData[ i ].mCollisionNormal.y, + mCollisionData[ i ].mCollisionNormal.z, + mCollisionData[ i ].mpCollisionVolume->GetCollisionObject( )->GetName( ) ); + } + } + adjPos.Scale( tempDist ); + + rmt::Vector tmpOutPos = outPos; + outPos.Add( adjPos ); + + // if the collisionNormal is straight down. + // + if ( mCollisionData[ i ].mCollisionNormal.y <= -0.5f ) + { + // Ouch, you hit your head! + // + intCollisionType |= HitHead; + if(!IsNPC()) + { + outPos.y -= 0.1f; + } + } + else + { + + // D'oh. You ran into a wall. + // + // if we're surfing and the wall belongs to the car we're + // on, then we can ignore it. Physics gives us weird collisions + // from time to time, causing us to get shoved off the car + // we're surfing on. + // + bool ignoreThisOne = false; + + if( mbSurfing && (this->GetLocoVelocity().MagnitudeSqr() < 0.001f) && mpStandingCollisionVolume) + { + sim::SimState* surfSimState = mpStandingCollisionVolume->GetCollisionObject( )->GetSimState(); + + rAssert( surfSimState->mAIRefIndex == PhysicsAIRef::redBrickVehicle ); + + sim::SimState* objSimState = mCollisionData[ i ].mpCollisionVolume->GetCollisionObject()->GetSimState(); + + if( objSimState->mAIRefIndex == PhysicsAIRef::redBrickVehicle && + (Vehicle*)(objSimState->mAIRefPointer) == (Vehicle*)(surfSimState->mAIRefPointer) ) + { + ignoreThisOne = true; + } + } + + if( ignoreThisOne ) + { + outPos = tmpOutPos; + } + else + { + intCollisionType |= HitWall; + } + } + + if( intCollisionType != NoCollision ) + { + // if we hit a static or a fence piece + sim::SimState* simState = mCollisionData[ i ].mpCollisionVolume-> + GetCollisionObject()->GetSimState(); + + if( mIsNPC && + (simState->mAIRefIndex == PhysicsAIRef::redBrickPhizFence || + simState->mAIRefIndex == PhysicsAIRef::redBrickPhizStatic) ) + { + // if we're not panicking, this call will have no effect + ((NPCController*)GetController())->QuellPanic(); + } + + } + } + else + { + if ( sbOutput ) rDebugPrintf( "Dot product reject. dot = %.2f, dist = %.6f, %s\n", + dot, + mCollisionData[ i ].mCollisionDistance, + mCollisionData[ i ].mpCollisionVolume->GetCollisionObject( )->GetName( ) + ); + } + } + else + { + if ( sbOutput ) rDebugPrintf( "CollisionNormal reject: %.2f, %.2f, %.2f, %s\n", + mCollisionData[ i ].mCollisionNormal.x, + mCollisionData[ i ].mCollisionNormal.y, + mCollisionData[ i ].mCollisionNormal.z, + mCollisionData[ i ].mpCollisionVolume->GetCollisionObject( )->GetName( ) + ); + } + } + collisionType = (eCollisionType)intCollisionType; + return collisionType; +} +/* +============================================================================== +Character::GetMaxSpeed +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: float + +============================================================================= +*/ +float Character::GetMaxSpeed( void ) const +{ + float fTurbo = 0.0f; + if ( IsTurbo( ) ) + { + fTurbo = CharacterTune::sfDashBurstMax; + } + return CharacterTune::sfMaxSpeed + fTurbo; +} + +/* +============================================================================== +Character::GetTerrainIntersect +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector& pos, rmt::Vector& normal ) + +Return: void + +============================================================================= +*/ +void Character::GetTerrainIntersect( rmt::Vector& pos, rmt::Vector& normal ) const +{ + GetPosition( pos ); + pos.y = mGroundY; + normal = mGroundNormal; +} + +void Character::GetTerrainType( eTerrainType& TerrainType, bool& Interior ) const +{ + TerrainType = mTerrainType; + Interior = mInteriorTerrain; +} + +void Character::SnapToGround(void) +{ + mbSnapToGround = true; + UpdateGroundHeight(); +} + +void Character::UpdateGroundHeight( void ) +{ + unsigned int modulo = mbInAnyonesFrustrum ? 0x3 : 0x7; + if( !mbSnapToGround && IsNPC() && + ((GetGame()->GetFrameCount() & modulo) != (mIntersectFrame & modulo))) + { + return; + } + +BEGIN_PROFILE("Character::UpdateGroundHeight") + + choreo::Puppet* pPuppet = GetPuppet( ); + rAssert( pPuppet ); + + // Before + // + rmt::Vector prevPosition = mLastFramePos;//LocalToWorld( pPuppet->GetPrevPosition( ) ); + + // And after! + // + rmt::Vector position; + GetPosition( position ); + // Updates the cached value. + // +// UpdateGroundHeight( prevPosition, position, mGroundY, mGroundNormal ); + + rmt::Vector groundPosition = position; + rmt::Vector outnorm( 0.0f, 1.0f, 0.0f ); + + // I don't understand these ones. + // + rmt::Vector intersectPos = position; + rmt::Vector intersectNorm( 0.0f, 1.0f, 0.0f ); + bool bFoundIntersect = false; + bool bFoundPlane = false; + + mTerrainType = static_cast<eTerrainType>( GetIntersectManager()->FindIntersection( + groundPosition, // IN + bFoundPlane, // OUT + outnorm, // OUT + groundPosition ) ); // OUT + mInteriorTerrain = ( (int)mTerrainType & 0x80 ) == 0x80; + mTerrainType = static_cast<eTerrainType>( ( (int)mTerrainType & ~0x80 ) ); + + if( bFoundPlane ) + { + mRealGroundPos = groundPosition; + mRealGroundNormal = outnorm; + float tooHigh = 100000.0f; + rAssert( -tooHigh <= mRealGroundNormal.x && mRealGroundNormal.x <= tooHigh ); + rAssert( -tooHigh <= mRealGroundNormal.y && mRealGroundNormal.y <= tooHigh ); + rAssert( -tooHigh <= mRealGroundNormal.z && mRealGroundNormal.z <= tooHigh ); + } + else + { + // + // If this assert goes off for the player charater when he's hit by traffic + // it means that Physics has placed him at some location other than his + // present location. This is a bad thing... possibly related to + // collisions with traffic cars. + //rAssertMsg( false, "We're SOOO far from the ground, we're not intersecting" ); + } + rmt::Vector collisionPosition; + rmt::Vector collisionNormal; + + collisionNormal.Clear( ); + collisionPosition.Clear( ); + + + rmt::Vector playerPos; + GetAvatarManager()->GetAvatarForPlayer(0)->GetPosition( playerPos ); + + rmt::Vector distVecToPlayer = playerPos - position; + distVecToPlayer.y = 0.0f; + float distSqrFromPlayer = distVecToPlayer.MagnitudeSqr(); + + float delta = position.y - groundPosition.y; + + const float TOO_FAR_FROM_GROUND_DIST = 2.0f; // 2 meters? Maybe that's good enough + const float VISIBLE_TO_PLAYER_DIST_SQR = 90000.0f; + const float CLOSE_ENOUGH_TO_PLAYER_DIST_SQR = 400.0f; // always check if within 20 meters + + bool bOverStatic = false; + if(IsNPC()) + { + // discretionally ray-test against objects + if( delta > TOO_FAR_FROM_GROUND_DIST || + (!bFoundPlane && distSqrFromPlayer <= VISIBLE_TO_PLAYER_DIST_SQR) || + distSqrFromPlayer < CLOSE_ENOUGH_TO_PLAYER_DIST_SQR ) + { + //rDebugPrintf( "**** NPC %s calling getCollisionHeight *****\n", GetName() ); + bOverStatic = GetCollisionHeight( prevPosition, position, collisionPosition, collisionNormal ); + } + /* + else if( distSqrFromPlayer >= CLOSE_ENOUGH_TO_PLAYER_DIST_SQR ) + { + if( strcmp( GetName(), "b_ralph" ) == 0 ) + { + rDebugPrintf( "%s not within 20 meters of player\n", GetName() ); + } + } + */ + } + else + { + bOverStatic = GetCollisionHeight( prevPosition, position, collisionPosition, collisionNormal ); + } + + bool bNpcShouldNotMove = false; + + if ( bOverStatic ) + { + if ( !bFoundPlane || collisionPosition.y > groundPosition.y ) + { + mGroundY = collisionPosition.y; + mGroundNormal = collisionNormal; + + mLastGoodPosOverStatic = position; + mLastGoodPosOverStatic.y = mGroundY; + } + else + { + rAssert( bFoundPlane ); + mGroundY = groundPosition.y; + mGroundNormal = outnorm; + } + } + else if ( bFoundPlane ) + { + if( IsNPC() && mLastGoodPosOverStatic.Equals( position, 0.5f ) ) + { + if( ((NPCController*)GetController())->GetState() == NPCController::TALKING_WITH_PLAYER ) + { + bNpcShouldNotMove = false; // don't transit out of TALKING_WITH_PLAYER state + } + else + { + bNpcShouldNotMove = true; + } + mGroundY = position.y; + mGroundNormal.Set( 0.0f, 1.0f, 0.0f ); + } + else + { + mGroundY = groundPosition.y; + mGroundNormal = outnorm; + } + } + else + { + bNpcShouldNotMove = true; + mGroundY = position.y; + mGroundNormal.Set( 0.0f, 1.0f, 0.0f ); + } + + if(mpSimStateObj->GetControl() == sim::simSimulationCtrl) + { + return; + } + + if( mIsNPC ) + { + // if we're dropping more than N meters, we transit to sim, + // but only if we have a valid ground plane (otherwise we could + // fall through the world!) + delta = position.y - mGroundY; + if( delta > TOO_FAR_FROM_GROUND_DIST && + distSqrFromPlayer <= VISIBLE_TO_PLAYER_DIST_SQR && + mGroundPlaneIndex != WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + // transit to sim here so we "fall" to the ground + mpSimStateObj->SetControl( sim::simSimulationCtrl ); + GetWorldPhysicsManager()->EnableGroundPlaneCollision(mGroundPlaneIndex); + } + } + + + static float sfFallingTolerance = 0.5f; + + rmt::Vector pos; + GetPosition( pos ); + + rmt::Vector dummy, groundPos; + GetTerrainIntersect( groundPos, dummy ); + + delta = pos.y - groundPos.y; + + JumpAction* pJumpAction = GetJumpLocomotionAction(); + bool inJump = pJumpAction->IsInJump(); + + if (( mVelocity.y <= mfGroundVerticalVelocity && (delta < 0.0f )) || + ( !inJump && (delta < sfFallingTolerance)) || + ( IsNPC() && mpSimStateObj->GetControl() == sim::simAICtrl ) || + mbSnapToGround) + { + mbIsStanding = true; + + if(mbDoGroundIntersect || mbSnapToGround) + { + mbSnapToGround = false; + + if( IsNPC() ) + { + NPCController* npcController = (NPCController*) GetController(); + if( bNpcShouldNotMove ) + { + npcController->TransitToState( NPCController::NONE ); + } + else + { + if( npcController->GetState() == NPCController::NONE ) + { + npcController->TransitToState( NPCController::FOLLOWING_PATH ); + } + } + } + SetPosition(groundPos); + this->SetGroundPoint(groundPos); + mpJumpLocomotion->SetRootPosition( WorldToLocal(groundPos) ); + } + } + else + { + mbIsStanding = false; + + if ( !mbIsStanding && mbDoGroundIntersect && !inJump && (this->mpSimStateObj->GetControl() == sim::simAICtrl)) + { + //falling. + // + pJumpAction->Reset( 0.0f, true ); + GetActionController()->Clear();; + Sequencer* seq = GetActionController()->GetNextSequencer(); + seq->BeginSequence(); + seq->AddAction(pJumpAction); + seq->EndSequence(); + } + } + +END_PROFILE("Character::UpdateGroundHeight") +} +/* +============================================================================== +Character::pPosition +============================================================================== +Description: Comment + +Parameters: () + +Return: rmt + +============================================================================= +*/ +rmt::Vector* Character::pPosition() +{ + rAssert( 0 ); + return (Vector*)0; +} + +/* +============================================================================== +Character::rPosition +============================================================================== +Description: Comment + +Parameters: () + +Return: const + +============================================================================= +*/ +const rmt::Vector& Character::rPosition() +{ + GetPosition(lameAssPosition); + return lameAssPosition; +} + +/* +============================================================================== +Character::GetPosition +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector* ipPosn ) + +Return: void + +============================================================================= +*/ +void Character::GetPosition( rmt::Vector* ipPosn ) +{ + GetPosition(*ipPosn); +} + +////////////////////////////////////////////////////////////////////////// + +void Character::SetFadeAlpha( int fadeAlpha ) +{ + if( mpCharacterRenderable != NULL ) + { + mpCharacterRenderable->SetFadeAlpha( fadeAlpha ); + } +} + +int Character::CastsShadow() +{ + return mpCharacterRenderable->CastsShadow(); +} +/* +============================================================================== +Character::DisplayShadow +============================================================================== +Description: Comment + +Parameters: () + +Return: void + +============================================================================= +*/ +void Character::DisplayShadow() +{ + /* + if( !IsInCar() && !IsSimpleShadow() ) + { + mpCharacterRenderable->DisplayShadow( mpPuppet->GetP3DPose() ); + } + */ +} + +/* +============================================================================== +Character::DisplaySimpleShadow +============================================================================== +Description: Draw the simple shadow during the simple shadow pass. + +Parameters: () + +Return: void + +============================================================================= +*/ +void Character::DisplaySimpleShadow( void ) +{ +} + + +//============================================================================= +//Character::DisplaySimpleShadow +//============================================================================= +//Description: Draw the simple shadow during the simple shadow pass. +// +//Parameters: () +// +//Return: void +//============================================================================= +bool Character::CanPlayAnimation( const tName& name ) const +{ + choreo::Bank* bank = mpPuppet->GetBank(); + choreo::Animation* anim = choreo::find<choreo::Animation>( bank, name.GetUID() ); + if( anim == NULL ) + { + PrintAnimations(); + } + return ( anim != NULL ); +} + +/* +============================================================================== +Character::CanStandOnCollisionVolume +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: bool + +============================================================================= +*/ +bool Character::CanStandOnCollisionVolume( void ) const +{ + int i; + + for ( i = 0; i < mCurrentCollision; i++ ) + { + bool bCanStand = CanStandOnCollisionNormal( mCollisionData[ i ].mCollisionNormal ); + if ( bCanStand ) + { + return true; + } + } + return false; +} + +/* +============================================================================== +Character::CanStandOnCollisionNormal +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector& normal ) + +Return: bool + +============================================================================= +*/ +bool Character::CanStandOnCollisionNormal( const rmt::Vector& normal ) const +{ + float dot = normal.DotProduct( vUp ); + const float cos30 = 0.86602540378443864676372317075294f; + static float sfStandTolerance = cos30; + if ( dot > sfStandTolerance ) + { + return true; + } + return false; +} + +/* +============================================================================== +Character::CanStaggerCollision +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: bool + +============================================================================= +*/ +bool Character::CanStaggerCollision( void ) const +{ + int i; + + for ( i = 0; i < mCurrentCollision; i++ ) + { + if ( this->mpStandingCollisionVolume != mCollisionData[ i ].mpCollisionVolume ) + { + bool bCanStagger = CanStaggerCollisionNormal( mCollisionData[ i ].mCollisionNormal ); + if ( bCanStagger ) + { + return true; + } + } + } + return false; +} + +/* +============================================================================== +Character::CanStaggerCollisionNormal +============================================================================== +Description: Comment + +Parameters: ( const rmt::Vector& normal ) + +Return: bool + +============================================================================= +*/ +bool Character::CanStaggerCollisionNormal( const rmt::Vector& normal ) const +{ + + static rmt::Vector facing; + GetFacing( facing ); + + float dot = normal.DotProduct( facing ); + static float sfStaggerTolerance = -0.9f; + if ( dot < sfStaggerTolerance ) + { + return true; + } + return false; +} +/* +============================================================================== +Character::FindStandingVolume +============================================================================== +Description: Comment + +Parameters: ( const rmt::Vector& inPos, sim::CollisionVolume* inVolume, rmt::Vector& outNormal, float& outDist) + +Return: sim + +============================================================================= +*/ +sim::CollisionVolume* Character::FindStandingVolume( const rmt::Vector& inPos, sim::CollisionVolume* inVolume, rmt::Vector& outNormal, float& outDist ) +{ + sim::CollisionVolume* pOutVolume = 0; + + switch (inVolume->Type()) + { + case sim::SphereVolumeType: + case sim::CylinderVolumeType: + case sim::OBBoxVolumeType: + case sim::WallVolumeType: + { + float currentDist = VERY_LARGE; + rmt::Vector currentNormal; + + // trivial reject stuff that is actually no where near us (probably a subvolume + // of a large volume we did intersect with) + if((rmt::Fabs(inVolume->mPosition.x - inPos.x) > (inVolume->mBoxSize.x + 1.0f)) || + (rmt::Fabs(inVolume->mPosition.z - inPos.z) > (inVolume->mBoxSize.z + 1.0f))) + { + break; + } + + sim::CollisionVolume* pOutSubVolume = sim::FindClosestPointOnVolume( inPos, inVolume, currentNormal, currentDist); + if ( pOutSubVolume && pOutSubVolume->GetCollisionObject()->GetCollisionEnabled()) + { + if ( CanStandOnCollisionNormal( currentNormal ) ) + { + // We must be above the object. + // + if ( currentDist >= 0.0f ) + { + pOutVolume = pOutSubVolume; + outDist = currentDist; + outNormal = currentNormal; + } + } + } + break; + } + case sim::BBoxVolumeType: + { + float currentDist = VERY_LARGE; + rmt::Vector currentNormal; + for (int i=0; i<inVolume->SubVolumeList()->GetSize(); i++) + { + sim::CollisionVolume* pOutSubVolume = FindStandingVolume(inPos, inVolume->SubVolumeList()->GetAt(i), currentNormal, currentDist ); + if ( pOutSubVolume && pOutSubVolume->GetCollisionObject()->GetCollisionEnabled()) + { + // For each volume returned, keep the closest one only. + // + if ( currentDist < outDist ) + { + outDist = currentDist; + outNormal = currentNormal; + pOutVolume = pOutSubVolume; + } + } + } + } + break; + case sim::MaxCollisionVolumeEnum: + case sim::CollisionVolumeType: + break; + } + return pOutVolume; +} +/* +============================================================================== +Character::GetCollisionHeight +============================================================================== +Description: Comment + +Parameters: ( const rmt::Vector& prevPosition, const rmt::Vector& position, rmt::Vector& outPosition, rmt::Vector& collisionNormal ) + +Return: bool + +============================================================================= +*/ +bool Character::GetCollisionHeight( const rmt::Vector& prevPosition, const rmt::Vector& position, rmt::Vector& outPosition, rmt::Vector& collisionNormal ) +{ +BEGIN_PROFILE( "GetCollisionHeight" ); + + msIntersectInfo.Clear(); + + float fOldRayThickness = sim::RayIntersectionInfo::sRayThickness; + static float sfCharacterRayThickness = 0.3f; + sim::RayIntersectionInfo::sRayThickness = sfCharacterRayThickness; + const poser::Joint* pStandingJoint = 0; + bool bFoundIntersect = false; + sim::CollisionVolume* oldStanding = NULL; + tRefCounted::Assign(oldStanding, mpStandingCollisionVolume); + tRefCounted::Release(mpStandingCollisionVolume); + + // Had to increase the fudge when I moved character::update calls outside + // of the physics substep. + // + static float sfFudge = 0.6f; + rmt::Vector testPosition; + testPosition = position; + testPosition.y = prevPosition.y; + testPosition.y += sfFudge; + + float outDist = VERY_LARGE; + float currentDist = outDist; + + HeapMgr()->PushHeap( GMA_TEMP ); + + // adds in the list the collision object interfering with the ray and ordered according to their distance to the source. + // use sim::RayIntersectionInfo::SetMethod(method) to set the method + // use sim::RayIntersectionInfo::SetReturnClosestOnly(true/false) if you need only the closest object + // nb. if SetReturnClosestOnly(true) is used, the previous returned list can be used as a cache. + sim::RayIntersectionInfo::SetReturnClosestOnly( false ); + sim::RayIntersectionInfo::SetMethod( sim::RayIntersectionBBox ); + rmt::Vector rayOrg( 0.0f, 0.0f, 0.0f ); + rmt::Vector rayEnd( 0.0f, -100.0f, 0.0f ); + rayOrg.Add( testPosition ); + rayEnd.Add( testPosition ); + + HeapMgr()->PopHeap( GMA_TEMP ); + + // Do not allow ray test against the character collision object + // Otherwise, rayintersection will detect against player volume. + // + bool bRayRestore = mpSimStateObj->GetCollisionObject()->GetRayCastingEnabled( ); + bool bCollRestore = mpSimStateObj->GetCollisionObject()->GetCollisionEnabled( ); + mpSimStateObj->GetCollisionObject( )->SetRayCastingEnabled( false ); + mpSimStateObj->GetCollisionObject( )->SetCollisionEnabled( false ); + + bool bGroundPlaneRestore = false; + if( mGroundPlaneSimState ) + { + bGroundPlaneRestore = mGroundPlaneSimState->GetCollisionObject( )->GetRayCastingEnabled( ); + mGroundPlaneSimState->GetCollisionObject( )->SetRayCastingEnabled( false ); + } + + // Test ray against remaining collision objects. + // + GetWorldPhysicsManager()->mCollisionManager->DetectRayIntersection(msIntersectInfo, rayOrg, rayEnd, false, this->GetCollisionAreaIndex() ); + //GetWorldPhysicsManager()->mCollisionManager->DetectRayIntersection(msIntersectInfo, rayOrg, rayEnd, false, + // GetAvatarManager()->GetAvatarForPlayer(0)->GetCharacter()->GetCollisionAreaIndex() ); + + // Restore the state. + // + mpSimStateObj->GetCollisionObject( )->SetRayCastingEnabled( bRayRestore ); + mpSimStateObj->GetCollisionObject( )->SetCollisionEnabled( bCollRestore ); + if( mGroundPlaneSimState ) + { + mGroundPlaneSimState->GetCollisionObject( )->SetRayCastingEnabled( bGroundPlaneRestore ); + } + + + // Iterate through the entire list because of way DetectRayIntersection works. + // It checks the top level hierarchy of an object, so it will return bad values + // if the hierarchy is large, and you are standing on top of something in a different + // (smaller) hierarchy. see Level 9, duffTruck BV vs L9 BV. + // + int i; + for ( i = 0; i < msIntersectInfo.GetSize( ); i++ ) + { + if ( msIntersectInfo.GetSize() > 0 && msIntersectInfo[ i ].mCollisionVolume ) + { + rmt::Vector outNormal; + float prevOut = outDist; + sim::CollisionVolume* pOutVolume = FindStandingVolume( testPosition, msIntersectInfo[ i ].mCollisionVolume, outNormal, outDist ); + + bool vehicleIgnore = false; + if(this->mpTargetVehicle && pOutVolume) + { + if(pOutVolume->GetCollisionObject()->GetSimState()->mAIRefPointer == this->mpTargetVehicle) + { + if(oldStanding != pOutVolume) + { + vehicleIgnore = true; + outDist = prevOut; + } + } + } + + if ( pOutVolume && + pOutVolume->GetCollisionObject()->GetCollisionEnabled() && + (pOutVolume->GetCollisionObject()->GetSimState()->mAIRefIndex != PhysicsAIRef::redBrickPhizVehicleGroundPlane) && + (pOutVolume->GetCollisionObject()->GetSimState()->mAIRefIndex != PhysicsAIRef::redBrickPhizMoveableGroundPlane) && + (!vehicleIgnore)) + { + if ( outDist < currentDist ) + { + currentDist = outDist; + tRefCounted::Assign(mpStandingCollisionVolume, pOutVolume); + collisionNormal = outNormal; + } + } + } + } + + mbSurfing = false; + + if ( mpStandingCollisionVolume ) + { + rmt::Vector newPos = collisionNormal; + newPos.Scale( -currentDist ); + testPosition.Add( newPos ); + outPosition = testPosition; + + // Test to see if we are standing on the collision volume. + // + if ( position.y - outPosition.y <= 0.1f ) + { + // We are standing. + // + // If this is an animated object, find the transform to parent the character. + // + sim::SimState* pSimState = mpStandingCollisionVolume->GetCollisionObject( )->GetSimState(); + + if(pSimState->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + mbSurfing = true; + } + + if( !( PhysicsAIRef::redBrickPhizStatic & pSimState->mAIRefIndex ) ) + { + if ( pSimState->IsArticulated( ) ) + { + // Find joint based on mpStandingCollisionVolume->ObjRefIndex(). + // + sim::SimStateArticulated* pSimStateArticulated = static_cast<sim::SimStateArticulated*>( pSimState ); + + // Michael - this will assert on articulated objects that were converted to rigid bodies + // (these always have their volume::objrefindex set to -1) + // commenting out, it doesn't appear to have any problems + + // rAssert(mpStandingCollisionVolume->ObjRefIndex() != -1); + if(mpStandingCollisionVolume->ObjRefIndex() != -1) + { + const poser::Pose* pPose = pSimStateArticulated->GetPose( ); + rAssert( pPose ); + pStandingJoint = pPose->GetJoint( mpStandingCollisionVolume->ObjRefIndex() ); + rAssert( pStandingJoint ); + } + } + + } + } + bFoundIntersect = true; + } + + SetStandingJoint( pStandingJoint ); + sim::RayIntersectionInfo::sRayThickness = fOldRayThickness; +END_PROFILE( "GetCollisionHeight" ); + tRefCounted::Release(oldStanding); + return bFoundIntersect; +} + +#ifdef RAD_DEBUG + +void Character::PrintAnimations() const +{ + choreo::Bank* bank = mpPuppet->GetBank(); + choreo::BaseBank::RawIterator* it = bank->NewRawIterator(); + it->AddRef(); + + IRefCount* obj = it->First(); + while( obj != NULL ) + { + choreo::Animation* anim = dynamic_cast< choreo::Animation* >( obj ); + if( anim != NULL ) + { + tAnimation* tAnim = anim->GetP3DAnimation(); + const char* name = tAnim->GetName(); + rDebugPrintf( "animationName '%s'\n", name ); + } + obj = it->Next(); + } + it->Release(); +} +#endif + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +void Character::SetStandingJoint( const poser::Joint* pJoint ) +{ + if ( pJoint != mpStandingJoint ) + { + rmt::Vector position; + GetPosition( position ); + + rmt::Vector desiredFacing; + GetDesiredFacing( desiredFacing ); + rmt::Vector facingVector = mpPuppet->GetFacingVector( ); + + mParentTransform.RotateVector( desiredFacing, &desiredFacing ); + mParentTransform.RotateVector( facingVector, &facingVector ); + + rmt::Matrix transform; + if(pJoint) + { + transform = pJoint->GetWorldMatrix( ); + } + else + { + transform.Identity(); + } + + SetParentTransform( transform ); + + SetPosition( position ); + mInvParentTransform.RotateVector( desiredFacing, &desiredFacing ); + mInvParentTransform.RotateVector( facingVector, &facingVector ); + mpPuppet->SetFacingVector( facingVector ); + SetDesiredDir( choreo::GetWorldAngle( desiredFacing.x, desiredFacing.z ) ); + mpJumpLocomotion->SetRootTransform( ); + mpStandingJoint = pJoint; + } +} + +/* +============================================================================== +Character::SetGroundPoint +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector& groundPoint ) + +Return: void + +============================================================================= +*/ +void Character::SetGroundPoint( const rmt::Vector& groundPoint ) +{ + //choreo::Puppet* pPuppet = GetPuppet( ); + //if ( pPuppet ) + //{ + // pPuppet->SetGroundPoint( groundPoint ); + //} + //SetPosition( groundPoint ); + if ( mpPuppet ) + { + // Transform from world to object space. + // + rmt::Vector transformedPos = groundPoint; + transformedPos.Transform( mInvParentTransform ); + + mpPuppet->SetPosition( transformedPos ); + + poser::Pose* pPose = mpPuppet->GetPose( ); + // stuff fixed up root transform into joint + poser::Joint* joint = pPose->GetJoint( 0 ); + joint->SetObjectTranslation( groundPoint ); + joint->SetWorldTranslation( groundPoint ); + } +} +//============================================================================= +// Character::UpdateTransformToLoco +//============================================================================= +// Description: Comment +// +// Parameters: ( void ) +// +// Return: void +// +//============================================================================= +void Character::UpdateTransformToLoco( void ) +{ + // This will get us the world space position and facing. + // + rmt::Vector position; + GetPosition( position ); + rmt::Vector facing; + GetFacing( facing ); + mParentTransform.RotateVector( facing, &facing ); + + tRefCounted::Release(mpStandingCollisionVolume); + mbSurfing = false; + + // Go from the car space back to world space. + // + UpdateParentTransform( 0.0f ); + + SetDesiredDir( choreo::GetWorldAngle( facing.x, facing.z ) ); + SetFacingDir( choreo::GetWorldAngle( facing.x, facing.z ) ); + SetPosition( position ); + + SetInCar(false); + + //AssignCollisionAreaIndex( ); + //mpSimStateObj->GetCollisionObject()->SetCollisionEnabled( true ); +} +/* +============================================================================== +Character::AssignCollisionAreaIndex +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void Character::AssignCollisionAreaIndex( void ) +{ + if ( WorldPhysicsManager::INVALID_COLLISION_AREA == mCollisionAreaIndex ) + { + mCollisionAreaIndex = GetWorldPhysicsManager()->GetCharacterCollisionAreaIndex(); + } + rTuneAssert( mCollisionAreaIndex != -1 ); +} +//============================================================================= +// Character::UpdateTransformToInCar +//============================================================================= +// Description: Comment +// +// Parameters: ( void ) +// +// Return: void +// +//============================================================================= +void Character::UpdateTransformToInCar( void ) +{ + if( mbIsJump ) + { + mpJumpLocomotion->Done(); + mbIsJump = false; + } + + rmt::Vector pos; + GetPosition(pos); + + // Update the character parent transform to vehicleToWorld. + // + UpdateParentTransform( 0.0f ); + + SetStandingJoint(NULL); + tRefCounted::Release(mpStandingCollisionVolume); + mbSurfing = false; + + SetPosition(pos); + SetDesiredDir( rmt::PI ); + SetFacingDir( rmt::PI ); +} + +/* +============================================================================== +Character::SetInCar +============================================================================== +Description: Comment + +Parameters: ( bool bInCar ) + +Return: void + +============================================================================= +*/ +void Character::SetInCar( bool bInCar ) +{ + mbInCar = bInCar; + + mTranslucent = !mbInCar; + + if(this == GetCharacterManager()->GetCharacter(0)) + { + CGuiScreenHud* currentHud = GetCurrentHud(); + if( !mbInCar ) + { + if( radTimeGetMicroseconds64()-mLastInteriorLoadCheck > 3000000 ) //(amortise/3s) if it's been more than 3 sec's since we last checked for volumes + { + rmt::Vector posn; + GetPosition(&posn); + GetTriggerVolumeTracker()->ActivateNearestInteriorLoadZone(0, posn, 40.0f); + mLastInteriorLoadCheck = radTimeGetMicroseconds64(); + } + + if(mpCurrentActionButtonHandler) + { + if ( mpCurrentActionButtonHandler->IsInstanceEnabled() ) + { + if( currentHud != NULL ) + { + currentHud->HandleMessage( GUI_MSG_SHOW_HUD_OVERLAY, HUD_ACTION_BUTTON ); + } + } + } + } + else if(mbInCar && mpCurrentActionButtonHandler) + { + if( currentHud != NULL ) + { + currentHud->HandleMessage( GUI_MSG_HIDE_HUD_OVERLAY, HUD_ACTION_BUTTON ); + } + } + } +} + +/* +============================================================================== +Character::GetActionButtonHandler +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: ActionButton + +============================================================================= +*/ +ActionButton::ButtonHandler* Character::GetActionButtonHandler( void ) const +{ + return mpCurrentActionButtonHandler; +} + +/* +============================================================================== +Character::AddActionButtonHandler +============================================================================== +Description: Comment + +Parameters: ( ActionButton::ButtonHandler* pActionButtonHandler ) + +Return: void + +============================================================================= +*/ +void Character::AddActionButtonHandler( ActionButton::ButtonHandler* pActionButtonHandler ) +{ + if ( !IsNPC() ) + { + unsigned int i; +#ifdef RAD_DEBUG + //Make sure this is only added once. + for ( i = 0; i < MAX_ACTION_BUTTON_HANDLERS; ++i ) + { + rAssert( mpActionButtonHandlers[ i ] != pActionButtonHandler ); + } +#endif + for ( i = 0; i < MAX_ACTION_BUTTON_HANDLERS; ++i ) + { + if ( mpActionButtonHandlers[ i ] == NULL ) + { + //Add here. + mpActionButtonHandlers[ i ] = pActionButtonHandler; + mpActionButtonHandlers[ i ]->AddRef(); + break; + } + } + + rAssertMsg( i < MAX_ACTION_BUTTON_HANDLERS, "Need to increase the size of MAX_ACTION_BUTTON_HANDLERS" ); + if ( i == MAX_ACTION_BUTTON_HANDLERS ) + { + return; + } + + ActionButton::ButtonHandler* newButton = TestPriority( pActionButtonHandler, mpCurrentActionButtonHandler ); + + if ( newButton != mpCurrentActionButtonHandler ) + { + //This is a new action button of highest priority. + + mpCurrentActionButtonHandler = pActionButtonHandler; + + if ( !IsInCar() ) + { + if ( mpCurrentActionButtonHandler->IsInstanceEnabled() ) + { + CGuiScreenHud* currentHud = GetCurrentHud(); + if( currentHud != NULL ) + { + currentHud->HandleMessage( GUI_MSG_SHOW_HUD_OVERLAY, HUD_ACTION_BUTTON ); + } + } + } + } + } +} + +//============================================================================= +// Character::RemoveActionButtonHandler +//============================================================================= +// Description: Comment +// +// Parameters: ( ActionButtonHandler::ButtonHandler* pActionButtonHandler ) +// +// Return: void +// +//============================================================================= +void Character::RemoveActionButtonHandler( ActionButton::ButtonHandler* pActionButtonHandler ) +{ + if ( !IsNPC() ) + { + unsigned int i; + for ( i = 0; i < MAX_ACTION_BUTTON_HANDLERS; ++i ) + { + if ( mpActionButtonHandlers[ i ] == pActionButtonHandler ) + { + //This is the one to remove. + mpActionButtonHandlers[ i ]->Release(); + mpActionButtonHandlers[ i ] = NULL; + + if ( mpCurrentActionButtonHandler == pActionButtonHandler ) + { + mpCurrentActionButtonHandler = NULL; + CGuiScreenHud* currentHud = GetCurrentHud(); + if( currentHud != NULL ) + { + currentHud->HandleMessage( GUI_MSG_HIDE_HUD_OVERLAY, HUD_ACTION_BUTTON ); + } + } + + break; + } + } + + if ( mpCurrentActionButtonHandler == NULL ) + { + //Find a new one. + for ( i = 0; i < MAX_ACTION_BUTTON_HANDLERS; ++i ) + { + if ( mpActionButtonHandlers[ i ] != NULL ) + { + mpCurrentActionButtonHandler = TestPriority( mpCurrentActionButtonHandler, mpActionButtonHandlers[ i ] ); + } + } + + if ( mpCurrentActionButtonHandler != NULL ) + { + if ( mpCurrentActionButtonHandler->IsInstanceEnabled() ) + { + CGuiScreenHud* currentHud = GetCurrentHud(); + if( currentHud != NULL ) + { + currentHud->HandleMessage( GUI_MSG_SHOW_HUD_OVERLAY, HUD_ACTION_BUTTON ); + } + } + } + } + } +} + +//============================================================================= +// Character::TestPriority +//============================================================================= +// Description: Comment +// +// Parameters: ( ActionButton::ButtonHandler* bA, ActionButton::ButtonHandler* bB ) +// +// Return: ActionButton +// +//============================================================================= +ActionButton::ButtonHandler* Character::TestPriority( ActionButton::ButtonHandler* bA, ActionButton::ButtonHandler* bB ) +{ + if ( NULL == bA ) + { + return bB; + } + else if ( NULL == bB ) + { + return bA; + } + + ActionButton::ButtonHandler::Type bAType = bA->GetType(); + ActionButton::ButtonHandler::Type bBType = bB->GetType(); + + if ( bAType == ActionButton::ButtonHandler::GET_IN_CAR || bBType == ActionButton::ButtonHandler::GET_IN_CAR ) + { + //Is this the players car? + Avatar* myAvatar = GetAvatarManager()->FindAvatarForCharacter( this ); + + if ( myAvatar ) + { + int id = myAvatar->GetPlayerId(); + rAssert( id == 0 ); //ONly works in single player. + + Vehicle* myVehicle = GetGameplayManager()->GetCurrentVehicle(); + if ( myVehicle ) + { + int actionId = (int)myVehicle->mpEventLocator->GetData( ); + ActionButton::ButtonHandler* pActionButtonHandler = GetActionButtonManager()->GetActionByIndex( actionId ); + rAssert( pActionButtonHandler ); + + if ( bA == pActionButtonHandler ) + { + bAType = ActionButton::ButtonHandler::GET_IN_USER_CAR; + } + + if ( bB == pActionButtonHandler ) + { + bBType = ActionButton::ButtonHandler::GET_IN_USER_CAR; + } + } + } + } + + return bAType < bBType ? bA : bB; +} + +//============================================================================= +// Character::ClearAllActionButtonHandlers +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Character::ClearAllActionButtonHandlers() +{ + unsigned int i; + for ( i = 0; i < MAX_ACTION_BUTTON_HANDLERS; ++i ) + { + if ( mpActionButtonHandlers[ i ] != NULL ) + { + mpActionButtonHandlers[ i ]->Exit( this ); + mpActionButtonHandlers[ i ]->Release(); + mpActionButtonHandlers[ i ] = NULL; + } + } + + mpCurrentActionButtonHandler = NULL; + if(this == GetCharacterManager()->GetCharacter(0)) + { + CGuiScreenHud* currentHud = GetCurrentHud(); + if( currentHud != NULL ) + { + currentHud->HandleMessage( GUI_MSG_HIDE_HUD_OVERLAY, HUD_ACTION_BUTTON ); + } + } +} + +tSkeleton* Character::Prop::spSkeleton = 0; +int Character::Prop::sSkelRefs = 0; + +Character::Prop::Prop( void ) + : +mpProp( 0 ), +mpPose( 0 ) +{ + if ( !spSkeleton ) + { + spSkeleton = new tSkeleton( 1 ); + rmt::Matrix mat; + mat.Identity( ); + spSkeleton->GetJoint( 0 )->worldMatrix = spSkeleton->GetJoint( 0 )->restPose = mat; + spSkeleton->GetJoint( 0 )->inverseWorldMatrix = spSkeleton->GetJoint( 0 )->worldMatrix; + spSkeleton->GetJoint( 0 )->inverseWorldMatrix.InvertOrtho( ); + } + spSkeleton->AddRef( ); + sSkelRefs++; + tRefCounted::Assign( mpPose, spSkeleton->NewPose( ) ); +} + +Character::Prop::~Prop( void ) +{ + tRefCounted::Release( mpProp ); + tRefCounted::Release( mpPose ); + spSkeleton->Release (); + sSkelRefs--; + if (sSkelRefs < 1) + { + spSkeleton = 0; + } +} +/* +============================================================================== +Character::TouchProp +============================================================================== +Description: Comment + +Parameters: ( InstDynaPhysDSG* pProp ) + +Return: void + +============================================================================= +*/ +void Character::TouchProp( InstDynaPhysDSG* pProp ) +{ + /* + bool sbPickUp = false; + if ( sbPickUp ) + { + mpPropHandler->SetProp( pProp ); + AddActionButtonHandler( mpPropHandler ); + mpPropHandler->Enter( this ); + } + */ +} +/* +============================================================================== +Character::AttachProp +============================================================================== +Description: Comment + +Parameters: ( InstDynaPhysDSG* pProp ) + +Return: void + +============================================================================= +*/ +void Character::AttachProp( InstDynaPhysDSG* pProp ) +{ + /* + int i; + for ( i = 0; i < MAX_PROPS; i++ ) + { + if ( mPropList[ i ].mpProp == 0 ) + { + tRefCounted::Assign( mPropList[ i ].mpProp, pProp ); + GetPuppet( )->AttachProp( mPropJoint, mPropList[ i ].mpPose ); + + mPropList[ i ].mpProp->GetSimState()->GetCollisionObject()->SetCollisionEnabled( false ); + mPropList[ i ].mpProp->GetSimState()->SetControl( sim::simAICtrl ); + break; + } + } + */ +} +/* +============================================================================== +Character::RemoveProp +============================================================================== +Description: Comment + +Parameters: ( InstDynaPhysDSG* pProp ) + +Return: void + +============================================================================= +*/ +void Character::RemoveProp( InstDynaPhysDSG* pProp ) +{ + /* + int i; + for ( i = 0; i < MAX_PROPS; i++ ) + { + if ( mPropList[ i ].mpProp == pProp ) + { + tRefCounted::Release( mPropList[ i ].mpProp ); + GetPuppet( )->RemoveAttachedProp( mPropList[ i ].mpPose ); + break; + } + } + */ +} +/* +============================================================================== +Character::UpdateProps +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void Character::UpdateProps( float timeins ) +{ + /* + int i; + for ( i = 0; i < MAX_PROPS; i++ ) + { + if ( mPropList[ i ].mpProp != 0 ) + { + mPropList[ i ].mpProp->GetSimState()->SetTransform( mPropList[ i ].mpPose->GetJoint( 0 )->worldMatrix, timeins ); + mPropList[ i ].mpProp->Update( timeins ); + + CharacterController::eIntention theIntention = GetController()->GetIntention(); + if( CharacterController::DoAction == theIntention ) + { + // Throw the sum'bitch. + // + sim::SimState* pSimState = mPropList[ i ].mpProp->GetSimState(); + rAssert( pSimState ); + mPropList[ i ].mpProp->AddToSimulation(); + GetFacing( pSimState->VelocityState( ).mLinear ); + static float sfUpVelocity = 3.0f; + pSimState->VelocityState( ).mLinear.y = sfUpVelocity; + pSimState->GetCollisionObject()->SetCollisionEnabled( true ); + + // Not the most efficient. + // + RemoveProp( mPropList[ i ].mpProp ); + } + } + } + */ +} + + + +void Character::UpdatePhysicsObjects( float timeins, int area ) +{ + rAssert( area != -1 ); + RestTest(); + GetWorldPhysicsManager()->UpdateDynamicObjects(timeins, area ); + GetWorldPhysicsManager()->UpdateAnimCollisions(timeins, area ); +} + +/* +============================================================================== +Character::SubmitStatics +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void Character::SubmitStatics( void ) +{ + BEGIN_PROFILE( "Per Character Submit STatics" ); + if ( GetRenderLayer() == GetRenderManager()->rCurWorldRenderLayer() ) + { + // Always test because we need to dump stuff while we are in the car. + // Dynaimc loading rides again + // + // TBJ [8/14/2002] + // + if( true ) //!(IsInCar())) + { + int collisionAreaIndex = GetCollisionAreaIndex(); + if ( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + rmt::Vector position; + GetPosition( position ); + static float sfCollisionRadius = 5.0f; + GetWorldPhysicsManager()->SubmitStaticsPseudoCallback(position, sfCollisionRadius, collisionAreaIndex, GetSimState(), true); + + GetWorldPhysicsManager()->SubmitFencePiecesPseudoCallback(position, sfCollisionRadius, collisionAreaIndex, GetSimState(), true); + } + } + } + END_PROFILE( "Per Character Submit STatics" ); +} + +/* +============================================================================== +Character::SubmitAnimCollisions +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void Character::SubmitAnimCollisions( void ) +{ + BEGIN_PROFILE( "Per Character Submit Anims" ); + if ( GetRenderLayer() == GetRenderManager()->rCurWorldRenderLayer() ) + { + // Always test because we need to dump stuff while we are in the car. + // Dynaimc loading rides again + // + // This is also nice because objects will animate while we are in the car. + // + // TBJ [8/14/2002] + // + if( true ) //!(IsInCar())) + { + int collisionAreaIndex = GetCollisionAreaIndex(); + if ( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + rmt::Vector position; + GetPosition( position ); + + static float sfCollisionRadius = 1.5f; + GetWorldPhysicsManager()->SubmitAnimCollisionsPseudoCallback(position, sfCollisionRadius, collisionAreaIndex, GetSimState()); + static float sfUpdateRadius = 30.0f;//0.0f; + GetWorldPhysicsManager()->SubmitAnimCollisionsForUpdateOnly( position, sfUpdateRadius, collisionAreaIndex ); + } + } + } + END_PROFILE( "Per Character Submit Anims" ); +} + +/* +============================================================================== +Character::SubmitDynamics +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void Character::SubmitDynamics( void ) +{ + BEGIN_PROFILE( "Per Character Submit Dyn" ); + if ( GetRenderLayer() == GetRenderManager()->rCurWorldRenderLayer() ) + { + // Always test because we need to dump stuff while we are in the car. + // Dynaimc loading rides again + // + // This is also nice because objects will animate while we are in the car. + // + // TBJ [8/14/2002] + // + if( true ) //!(IsInCar())) + { + int collisionAreaIndex = GetCollisionAreaIndex(); + if ( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + rmt::Vector position; + GetPosition( position ); + + static float sfCollisionRadius = 10.5f; + GetWorldPhysicsManager()->SubmitDynamicsPseudoCallback(position, sfCollisionRadius, collisionAreaIndex, GetSimState(), true); + } + } + } + END_PROFILE( "Per Character Submit Dyn" ); +} + +bool Character::PosInFrustrumOfPlayer( const rmt::Vector& pos, int playerID ) +{ + return GetGameplayManager()->TestPosInFrustrumOfPlayer( pos, playerID ); +} + +void Character::TestInFrustrumOfPlayer( int playerID ) +{ + if( PosInFrustrumOfPlayer( mSphere.centre, playerID ) ) + { + mbInAnyonesFrustrum = true; + } +} + +void Character::SetShadowColour( tColour shadowColour ) +{ + mShadowColour = shadowColour; + if( mpCharacterRenderable ) + { + mpCharacterRenderable->SetShadowColour( shadowColour ); + } +} + +tColour Character::GetShadowColour() +{ + return mShadowColour; +} + +void Character::SetSwatch( int swatchNum ) +{ + rAssert( mpCharacterRenderable != NULL ); + mpCharacterRenderable->SetSwatch( swatchNum ); +} + +void Character::SetDrawable( CharacterRenderable* pDrawable ) +{ + if(mpCharacterRenderable) + { + delete mpCharacterRenderable; + } + mpCharacterRenderable = pDrawable; +} + +void Character::Shock( float timeInSeconds ) +{ + if ( mpCharacterRenderable ) + { + m_TimeLeftToShock = timeInSeconds; + m_IsBeingShocked = true; + + mpCharacterRenderable->SetShocked( true ); + + GetEventManager()->TriggerEvent( EVENT_WASP_BULLET_HIT_CHARACTER_STYLIZED_VIOLENCE_FOLLOWS ); + + // + // Throw KICK_NPC_SOUND to trigger the HitByW dialogue that Chris and Cory want -- Esan + // + GetEventManager()->TriggerEvent( EVENT_KICK_NPC_SOUND, this ); + } +} + +void Character::DoKickwave(void) +{ + if(!mKickwave) + { + tRefCounted::Assign(mKickwave, p3d::find<tDrawable>("kickwave")); + tRefCounted::Assign(mKickwaveController, p3d::find<tFrameController>("kickwave")); + } + + if(mKickwave) + { + mDoKickwave = true; + mKickwaveController->SetFrame(0); + } +} + +void Character::OnTransitToAICtrl() +{ + mPrevSimTransform = GetSimState()->GetTransform(); + mGroundPlaneSimState->GetCollisionObject()->SetCollisionEnabled( false ); + + // get "up" out of sim control + rmt::Vector up, right, forward; + //right = mpCharacter->mPrevSimTransform.Row(0); + up = mPrevSimTransform.Row(1); + //forward = mpCharacter->mPrevSimTransform.Row(2); + + float dir = choreo::GetWorldAngle( up.x, up.z ); + RelocateAndReset( mPrevSimTransform.Row(3), dir ); +} + +void Character::Display(void) +{ + if(IS_DRAW_LONG) return; + DSG_BEGIN_PROFILE(" Character::Display") + if(!mpCharacterRenderable->GetDrawable()) + { + return; + } + + if( mbNeedChoreoUpdate) + { + mpPuppet->UpdatePose( ); + mbNeedChoreoUpdate = false; + } + + if(IsMarge() && + ((GetStateManager()->GetState() == CharacterAi::INCAR) || + (GetStateManager()->GetState() == CharacterAi::GET_IN) || + (GetStateManager()->GetState() == CharacterAi::GET_OUT)) && + GetTargetVehicle() && + GetTargetVehicle()->mHighRoof) + { + poser::Pose* p = mpPuppet->GetPose(); + int numJoints = p->GetJointCount(); + if( numJoints >= 36 ) + { + mpPuppet->GetPose()->GetJoint(33)->SetObjectMatrix(mpCharacterRenderable->GetDrawable()->GetSkeleton()->GetJoint(33)->restPose); + mpPuppet->GetPose()->GetJoint(34)->SetObjectMatrix(mpCharacterRenderable->GetDrawable()->GetSkeleton()->GetJoint(34)->restPose); + mpPuppet->GetPose()->GetJoint(35)->SetObjectMatrix(mpCharacterRenderable->GetDrawable()->GetSkeleton()->GetJoint(35)->restPose); + } + } + + mpPuppet->GetP3DPose()->SetSkeleton(mpCharacterRenderable->GetDrawable()->GetSkeleton()); + mpPuppet->UpdateEnd( ); + + static float JumpRatio = 0.0f; + if( JumpRatio == 0.0f && ( GetJumpHeight() != 0.0f ) ) + { + JumpRatio = 0.5f / GetJumpHeight(); + } + if( !IsInCar() && IsSimpleShadow() ) + { + rmt::Vector groundPos; + rmt::Vector groundNormal; + rmt::Vector characterFacing; + GetTerrainIntersect( groundPos, groundNormal ); + GetFacing( characterFacing ); + struct BlobShadowParams p( groundPos, groundNormal, characterFacing ); + const rmt::Vector& pos = rPosition(); + p.ShadowScale = 1.0f - ( ( pos.y - groundPos.y ) * JumpRatio ); + p.ShadowAlpha = p.ShadowScale * ( mInteriorTerrain ? 0.5f : 1.0f ); + p3d::pddi->SetZWrite(false); + mpCharacterRenderable->DisplayShadow( mpPuppet->GetP3DPose(), &p ); + p3d::pddi->SetZWrite( true ); + } + + tPose* pose = mpPuppet->GetP3DPose(); + + mLean = pose->GetJoint(17)->worldMatrix.Row(3); + mLean.Sub(pose->GetJoint(0)->worldMatrix.Row(3)); + mLean.NormalizeSafe(); + + rmt::Matrix backToTheOrigin; + + backToTheOrigin.Identity(); + backToTheOrigin.Row(3) = pose->GetJoint(0)->worldMatrix.Row(3); + backToTheOrigin.InvertOrtho(); + + rmt::Vector rootPos = pose->GetJoint(0)->worldMatrix.Row(3); + + bool shouldScale = mScale != 1.0f; + + for(int i = 0; i < pose->GetNumJoint(); i++) + { + rmt::Matrix tmp; + tmp.Mult(pose->GetJoint(i)->worldMatrix, backToTheOrigin); + if(shouldScale) + { + rmt::Matrix scale; + scale.Identity(); + scale.FillScale(mScale, mScale, mScale); + pose->GetJoint(i)->worldMatrix.Mult(tmp, scale); + } + else + { + pose->GetJoint(i)->worldMatrix = tmp; + } + } + + p3d::stack->Push(); + p3d::stack->Translate(0.0f, mYAdjust, 0.0f); + + p3d::stack->Push(); + p3d::stack->Translate(rootPos); + + mpCharacterRenderable->Display( mSphere.centre, pose ); + + p3d::stack->Pop(); + p3d::stack->Pop(); + + if(mDoKickwave && mKickwave) + { + p3d::stack->Push(); + p3d::stack->Translate(rootPos); + p3d::stack->Multiply(pose->GetJoint(0)->worldMatrix); + mKickwave->Display(); + p3d::stack->Pop(); + } +#ifdef DRAW_CHARACTER_COLLISION +#ifdef RAD_RELEASE + if ( CommandLineOptions::Get( CLO_DEBUGBV ) ) +#else + if ( !mpCharacterRenderable->GetDrawable() || CommandLineOptions::Get( CLO_DEBUGBV ) ) +#endif + { + // The sim library allocates shaders and stuff for this, so we should ensure they're on the temp heap + // + HeapMgr()->PushHeap (GMA_TEMP); + + sim::CollisionVolume* pVolume = GetSimState( )->GetCollisionObject()->GetCollisionVolume( ); + sim::DrawCollisionVolume( pVolume ); + if ( mpStandingCollisionVolume ) + sim::DrawCollisionVolume( mpStandingCollisionVolume ); + int i; + for ( i = 0; i < mCurrentCollision; i++ ) + { + sim::CollisionVolume* pVolume = mCollisionData[ i ].mpCollisionVolume; + if ( pVolume ) + sim::DrawCollisionVolume(pVolume); + } + + HeapMgr()->PopHeap( GMA_TEMP ); + } +#endif // DRAW_CHARACTER_COLLISION + DSG_END_PROFILE(" Character::Display") +} + +void Character::SetAmbient(const char* location, float radius) +{ + mAmbient = true; + mAmbientLocator = tEntity::MakeUID(location); + + if((mRole != ROLE_REWARD) && (radius != 0.0f)) + { + tRefCounted::Assign(mAmbientTrigger, new AmbientDialogueTrigger(this, radius)); + EnableAmbientDialogue(true); + } +} + +void Character::EnableAmbientDialogue(bool e) +{ + if(!mAmbientTrigger) + return; + + if(e) + { + GetTriggerVolumeTracker()->AddTrigger(mAmbientTrigger); + } + else + { + GetTriggerVolumeTracker()->RemoveTrigger(mAmbientTrigger); + } +} + +void Character::ResetAmbientPosition(void) +{ + Locator* l = p3d::find<Locator>(mAmbientLocator); + if(l) + { + rmt::Vector pos; + l->GetPosition(&pos); + RelocateAndReset(pos, 0.0f); + } + AddToWorldScene(); + + static_cast<NPCController*>(GetController())->ClearTempWaypoint(); +} + +/////////////////////////// NPC STUFF //////////////////////////////// + +void NPCharacter::UpdatePhysicsObjects( float timeins, int area ) +{ + // NPCs shouldn't be submitting to physics stuff around it. + // + RestTest(); +} + +void NPCharacter::AssignCollisionAreaIndex( void ) +{ + Character::AssignCollisionAreaIndex(); +} + + +void NPCharacter::SubmitStatics( void ) +{ + BEGIN_PROFILE( "Per NPCharacter Submit Statics" ); + + // DUSIT [Oct 29,2002]: + // HACK: + // When should we submit statics around ourselves? + // characters too far away from player shouldn't be submitting statics + // characters standing still shouldn't submit. + // characters not "off path" shouldn't submit. + // characters in street races 1 & 2 shouldn't submit (or they'll pop out side + // the race props onto the race track). + + NPCController* npcController = (NPCController*) GetController(); + if( npcController != NULL ) + { + NPCController::State state = npcController->GetState(); + bool npcStateNeedsToSubmit = + (GetStateManager()->GetState() == CharacterAi::INSIM) || + ( + (GetStateManager()->GetState() != CharacterAi::INSIM) && + ( + (npcController->mOffPath && state == NPCController::FOLLOWING_PATH) || + (state == NPCController::STOPPED ) || + (state == NPCController::DODGING) || + (state == NPCController::PANICKING) || + (state == NPCController::TALKING_WITH_PLAYER) + ) + ); + + if( npcStateNeedsToSubmit ) + { + // We COULD do this to prevent code duplication. But a virtual function call + // is quite expensive. + //Character::SubmitStatics(); + + if ( GetRenderLayer() == GetRenderManager()->rCurWorldRenderLayer() ) + { + // Always test because we need to dump stuff while we are in the car. + // Dynaimc loading rides again + // + // TBJ [8/14/2002] + // + int collisionAreaIndex = GetCollisionAreaIndex(); + if( collisionAreaIndex == WorldPhysicsManager::INVALID_COLLISION_AREA && + GetRole() != ROLE_PEDESTRIAN ) + { + if(!IsInCar()) + { + AddToPhysics(); + collisionAreaIndex = GetCollisionAreaIndex(); + } + } + + if( collisionAreaIndex != WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + if ( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + rmt::Vector position; + GetPosition( position ); + static float sfCollisionRadius = 1.0f; + GetWorldPhysicsManager()->SubmitStaticsPseudoCallback(position, sfCollisionRadius, collisionAreaIndex, GetSimState()); + + GetWorldPhysicsManager()->SubmitFencePiecesPseudoCallback(position, sfCollisionRadius, collisionAreaIndex, GetSimState()); + } + } + } + } + else + { + int collisionAreaIndex = GetCollisionAreaIndex(); + if( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + // + // Empty the collision area index of all submissions... + // unfortunately, this takes US and our GROUNDPLANE out of the list too + // so we gotta re-add and re-pair. + // + GetWorldPhysicsManager()->EmptyCollisionAreaIndex( collisionAreaIndex ); + GetWorldPhysicsManager()->mCollisionManager->AddCollisionObject( + mpSimStateObj->GetCollisionObject(), mCollisionAreaIndex ); + //AddToPhysics(); + } + } + } + + + END_PROFILE( "Per NPCharacter Submit Statics" ); +} + +void NPCharacter::SubmitDynamics( void ) +{ +} + +void NPCharacter::OnTransitToAICtrl() +{ + // do here what we need to do at the very moment we transit + // back from simulation control to AI control + mPrevSimTransform = GetSimState()->GetTransform(); + +} + +void NPCharacter::ApplyForce( const rmt::Vector& direction, float force ) +{ + if(!IsInCar()) + { + DynaPhysDSG::ApplyForce( direction, force ); + } +} + + +void NPCharacter::ApplyKickForce( const rmt::Vector& direction, float force ) +{ + if(!IsInCar()) + { + DynaPhysDSG::ApplyForce( direction, force ); + + /* + rmt::Vector& rAngular = mpSimStateObj->GetAngularVelocity(); + float deltaV = force / 100.0f; + rAngular += (direction * deltaV); + */ + + rmt::Vector linVel = mpSimStateObj->GetLinearVelocity(); + mPastLinear.SetAverage( linVel.Magnitude() ); + + rmt::Vector angVel = mpSimStateObj->GetAngularVelocity(); + mPastAngular.SetAverage( angVel.Magnitude() ); + + } +} + + diff --git a/game/code/worldsim/character/character.h b/game/code/worldsim/character/character.h new file mode 100644 index 0000000..b87dcf5 --- /dev/null +++ b/game/code/worldsim/character/character.h @@ -0,0 +1,1428 @@ +#ifndef CHARACTER_H_ +#define CHARACTER_H_ + +#include <radmath/radmath.hpp> +#include <radtime.hpp> + +#include <render/DSG/InstDynaPhysDSG.h> +#include <ai/state.h> +#include <ai/statemanager.h> +//#include <ai/sequencer/action.h> +#include <memory/memorypool.h> +#include <p3d/memory.hpp> + +#include <ai/sequencer/task.h> +#include <p3d/p3dtypes.hpp> +#include <events/eventenum.h> +#include <events/eventmanager.h> +#include <radmath/radmath.hpp> +#include <worldsim/redbrick/vehicle.h> + +#include <worldsim/character/charactercontroller.h> + +#include <choreo/puppet.hpp> +#include <simcollision/proximitydetection.hpp> + +class tDrawablePose; +class tCamera; +class CharacterTarget; +class ActionController; + +namespace ActionButton +{ + class ButtonHandler; + class AttachProp; +} +class Vehicle; + +namespace CharacterAi +{ + class StateManager; +}; + +namespace sim +{ + class CollisionVolume; +}; + +namespace poser +{ + class Joint; + class Transform; +}; + +struct CharacterTune +{ + static float sfLocoRotateRate; + static float sfLocoAcceleration; + static float sfLocoDecceleration; + static bool bLocoTest; + static float sfAirRotateRate; + static float sfAirAccelScale; + static float sfAirGravity; + static float sfStompGravityScale; + static float sfDashBurstMax; + static float sfDashAcceleration; + static float sfDashDeceleration; + static float sfJumpHeight; + static float sfDoubleJumpHeight; + static float sfDoubleJumpAllowUp; + static float sfDoubleJumpAllowDown; + static float sfHighJumpHeight; + static float sfMaxSpeed; + + static rmt::Vector sGetInPosition; + static float sGetInHeightThreshold; + static float sGetInOpenDelay; + static float sGetInOpenSpeed; + static float sGetInCloseDelay; + static float sGetInCloseSpeed; + static float sGetOutOpenDelay; + static float sGetOutOpenSpeed; + static float sGetOutCloseDelay; + static float sGetOutCloseSpeed; + + static float sfTurboRotateRate; + + static float sfGetInOutOfCarAnimSpeed; + static float sfKickingForce; + static float sfSlamForce; + static float sfShockTime; +}; +class tPose; +class tSkeleton; +class InstDynaPhysDSG; +class WorldScene; +class AmbientDialogueTrigger; +class CharacterRenderable; +class JumpAction; +class WalkerLocomotionAction; + +class Character +: +public DynaPhysDSG +{ +public: + enum Role { + ROLE_UNKNOWN, + ROLE_DRIVER, + ROLE_REWARD, + ROLE_ACTIVE_BONUS, + ROLE_COMPLETED_BONUS, + ROLE_PEDESTRIAN, + ROLE_MISSION + }; + +public: // MEMBERS + Character( void ); + virtual ~Character( void ); + void Init( void ); + + virtual void OnTransitToAICtrl(); + virtual int GetAIRef() { return PhysicsAIRef::PlayerCharacter;} + + float GetFacingDir( void ) const; + void SetFacingDir( float fFacingDir ); + + float GetDesiredDir( void ) const; + void GetDesiredFacing( rmt::Vector& facing ) const; + //ICameraTarget. + // + void GetFacing( rmt::Vector& facing ) const; + void SetFacing( rmt::Vector& facing ); + + // Call this to place character at a particular place in the world + // and perhaps reset its states. + void RelocateAndReset( const rmt::Vector& position, float facing, bool resetMe=true, bool snapToGround = true ); + + void SetPosition( const rmt::Vector& position ); + void GetPosition( rmt::Vector& position ) const; + + void SetVelocity( rmt::Vector& velocity ); + void GetVelocity( rmt::Vector& velocity ) const; + const rmt::Vector& GetLocoVelocity() const; + + float GetSpeed( void ) const; + void SetSpeed( float fSpeed ); + void ResetSpeed( void ); + float GetDesiredSpeed( void ) const; + + + void StickReleased( void ); + void StickPressed( void ); + + void SetPuppet( choreo::Puppet* pPuppet ); + choreo::Puppet* GetPuppet( void ) const; + + void SetDrawable( CharacterRenderable* pDrawablePose ); + + virtual void PreSimUpdate(float timeins); + virtual void UpdateRoot( float timeins ); + virtual void ResolveCollisions(void); + virtual void PostSimUpdate(float timeins); + + // Just use this to set a flag. + virtual void Update( float timeins ) {}; + + void UpdateSimState( float timeins ); + void UpdateBBox( rmt::Box3D& oldBox ); + + bool IsInCar( void ) const; + void SetInCar( bool bInCar ); + bool IsInCarOrGettingInOut( void ); + + CharacterTarget* GetTarget( void ) const; + + void SetController( CharacterController* pController ); + CharacterController* GetController( void ) const; + + void SetDesiredDir( float fDesiredDir ); + void SetDesiredSpeed( float fDesiredSpeed ); + + ActionButton::ButtonHandler* GetActionButtonHandler( void ) const; + void AddActionButtonHandler( ActionButton::ButtonHandler* pActionButtonHandler ); + void RemoveActionButtonHandler( ActionButton::ButtonHandler* pActionButtonHandler ); + void ClearAllActionButtonHandlers(); + + void SetTargetVehicle( Vehicle* pVehicle ); + Vehicle* GetTargetVehicle( ) const; + float GetMaxSpeed( void ) const; + + CharacterAi::StateManager* GetStateManager( void ) const; + + ActionController* GetActionController( void ) const; + + static float GetJumpHeight( void ); + void GetTerrainIntersect( rmt::Vector& pos, rmt::Vector& normal ) const; + void GetTerrainType( eTerrainType& TerrainType, bool& Interior ) const; + + virtual void SubmitStatics( void ); + virtual void SubmitAnimCollisions( void ); + virtual void SubmitDynamics( void ); + + // Implements CollisionEntityDSG + // + virtual void Display(void); + virtual rmt::Vector* pPosition() ; + virtual const rmt::Vector& rPosition() ; + virtual void GetPosition( rmt::Vector* ipPosn ) ; + + void DisplayShadow(); + void DisplaySimpleShadow( void ); + int CastsShadow(); + bool IsSimpleShadow( void ); + void SetSimpleShadow( bool IsSimpleShadow ); + + virtual sim::Solving_Answer PreReactToCollision( sim::SimState* pCollidedObj, sim::Collision& inCollision ); + virtual sim::Solving_Answer PostReactToCollision(rmt::Vector& impulse, sim::Collision& inCollision); + + + bool IsInCollision( void ) const; + void ResetCollisions( void ); + bool CanStandOnCollisionVolume( void ) const; + bool CanStaggerCollision( void ) const; + + enum eCollisionType + { + NoCollision = 0, + HitWall = 1 << 0, + HitHead = 1 << 1, + DangerousMultiple = 1 << 3 + }; + + eCollisionType SolveCollisionWithStatic( const rmt::Vector& desiredPos, rmt::Vector& outPos ); + + bool IsStanding( void ) const; + void SetGroundPoint( const rmt::Vector& groundPoint ); + + WalkerLocomotionAction* GetWalkerLocomotionAction( void ) const; + JumpAction* GetJumpLocomotionAction( void ) const; + + void UpdateParentTransform( float timeins ); + void GetRootTransform( poser::Transform& out ) const; + void SetStandingJoint( const poser::Joint* pJoint ); + const rmt::Matrix& GetParentTransform( void ) const; + const rmt::Matrix& GetInverseParentTransform( void ) const; + float GetGroundVerticalVelocity( void ) const; + + void AddToWorldScene( void ); + void RemoveFromWorldScene( void ); + void MoveInWorldScene( void ); + virtual void InitGroundPlane( void ); + virtual void AddToPhysics( void ); + virtual void RemoveFromPhysics( void ); + + int GetCollisionAreaIndex( void ) const; + + rmt::Vector WorldToLocal( const rmt::Vector& world ) const; + rmt::Vector LocalToWorld( const rmt::Vector& local ) const; + void UpdateTransformToLoco( void ); + void UpdateTransformToInCar( void ); + + bool IsJumping( void ) const; + void SetJumping( bool bIsJumping ); + + void SetTurbo( bool bTurbo ); + bool IsTurbo( void ) const; + + bool IsVisible( void ) const { return mVisible;} + + void SetSolveCollisions( bool bCollide ) + { + mbSolveCollisions = bCollide; + } + + bool GetSolveCollisions( void ) const + { + return mbSolveCollisions; + } + + struct Prop + { + Prop( void ); + ~Prop( void ); + InstDynaPhysDSG* mpProp; + tPose* mpPose; + static tSkeleton* spSkeleton; + static int sSkelRefs; + static int mPropCount; + }; + void TouchProp( InstDynaPhysDSG* pProp ); + void AttachProp( InstDynaPhysDSG* pProp ); + void RemoveProp( InstDynaPhysDSG* pProp ); + void UpdateProps( float timeins ); + + virtual void UpdatePhysicsObjects( float timeins, int area ); + bool CanPlayAnimation( const tName& name ) const; + #ifdef RAD_DEBUG + void PrintAnimations() const; + #else + void PrintAnimations() const{}; + #endif + + void SetSwatch( int swatchNum ); + bool PosInFrustrumOfPlayer( const rmt::Vector& pos, int playerID ); + + void SetYAdjust( float yOffset ); + float GetYAdjust(); + + void Kick(); + void Slam(); + + void SetFadeAlpha( int fadeAlpha ); + + bool IsBusy(void) {return mbBusy;} + void SetBusy(bool b) {mbBusy = b;} + + bool IsSimpleLoco(void) {return mbSimpleLoco;} + void SetSimpleLoco(bool b) {mbSimpleLoco = b;} + + void SetShadowColour( tColour shadowColour ); + tColour GetShadowColour(); + + void Shock( float timeInSeconds ); + + void DoKickwave(void); + + // Don't apply any velocity change to the player character + // There appear to be bugs when this happens ( character getting stuck in the ground ) + virtual void ApplyForce( const rmt::Vector& direction, float force ){} + + void SetAmbient(const char* location, float radius); + bool IsAmbient(void) { return mAmbient;} + void EnableAmbientDialogue(bool); + void ResetAmbientPosition(void); + + bool IsNPC(); + + enum { MAX_ACTION_BUTTON_HANDLERS = 5 }; + + void DoGroundIntersect(bool b) { mbDoGroundIntersect = b;} + + bool GetRockinIdle(void) { return mAllowRockin; } + void SetRockinIdle(bool b) { mAllowRockin = b; } + + bool HasBeenHit() { return mHasBeenHit; } + void SetHasBeenHit( bool tf ) { mHasBeenHit = tf; } + + void SetRole(Role r) { mRole = r;} + Role GetRole(void) { return mRole;} + + void SetScale(float f) { mScale = f;} + + bool CollidedThisFrame(void) { return mCollidedThisFrame; } + + bool IsInSubstep() { return mIsInSubstep; } + void SetInSubstep( bool in ) { mIsInSubstep = in; } + + bool TestInAnyonesFrustrum(); + + const rmt::Vector& GetLean(void) { return mLean; } + + bool IsLisa(void) { return mIsLisa; } + bool IsMarge(void) { return mIsMarge; } + void SetIsLisa(bool b) { mIsLisa = b; } + void SetIsMarge(bool b) { mIsMarge = b; } + + void SnapToGround(void); + + unsigned GetActiveFrame(void) { return mIntersectFrame; } + + void SetManaged(bool b) { mManaged = b;} + bool IsManaged(void) { return mManaged; } + +public: // MEMBERS + bool mbCollidedWithVehicle; + bool mbInAnyonesFrustrum; //if we have array of MAX_PLAYERS, we can distinguish betw each player's frustrum + bool mbSurfing; + bool mbAllowUnload; + bool mbIsPlayingIdleAnim; // shuffling feet, scratching bum, etc. +#ifdef RAD_WIN32 + int mPCCamFacing; // 0 = cam direction, 1 = cam's right, 2 = facing cam, 3 = cam's left +#endif + + + rmt::Matrix mPrevSimTransform; + +protected: // METHODS + void TestInFrustrumOfPlayer( int playerID ); + + void UpdateGroundHeight(void); + + void SetParentTransform( const rmt::Matrix& mat, float timeins = 0.0f ); + sim::CollisionVolume* FindStandingVolume( const rmt::Vector& inPos, sim::CollisionVolume* inVolume, rmt::Vector& outNormal, float& outDist ); + bool GetCollisionHeight( const rmt::Vector& prevPosition, const rmt::Vector& position, rmt::Vector& outPosition, rmt::Vector& collisionNormal ) ; + bool CanStandOnCollisionNormal( const rmt::Vector& normal ) const; + bool CanStaggerCollisionNormal( const rmt::Vector& normal ) const; + + void UpdateController( rmt::Vector& direction, float timeins ); + virtual void UpdateDesiredDirAndSpeed( const rmt::Vector& dir ); + virtual void UpdateFootPlant( void ); + void UpdatePuppet( float timeins ); + void UpdateGroundPlane( float timeins ); + // Is the character allowed to move? + bool IsMovementLocked() { return m_IsBeingShocked; } + + + virtual void AssignCollisionAreaIndex( void ); + virtual float GetInputScale( void ) + { + // Number arrived at via experimentation. + // + return 0.69f; + } + +protected: // MEMBERS + bool mIsNPC; + + // ground plane stuff for player character only + sim::ManualSimState* mGroundPlaneSimState; + sim::WallVolume* mGroundPlaneWallVolume; + + int mCollisionAreaIndex; + + static sim::TArray<sim::RayIntersectionInfo> msIntersectInfo; + + radTime64 mLastInteriorLoadCheck; + +private: // METHODS + virtual void OnUpdateRoot( float timeins ); + virtual void OnPostSimUpdate(float timeins); + void UpdateShock( float timeins ); + ActionButton::ButtonHandler* TestPriority( ActionButton::ButtonHandler* bA, ActionButton::ButtonHandler* bB ); + + +private: // MEMBERS + // The abstract controller object. + // + CharacterController* mpController; + + // A pointer to the renderable object. + // + CharacterRenderable* mpCharacterRenderable; + + // A pointer to the choreo::Puppet + // + choreo::Puppet* mpPuppet; + + // The facing angle, in radians. 0 = ( 0, 0, -1 ) + // + float mfFacingDir; + + // The desired facing angle. The character will attempt to converge on this angle. + // + float mfDesiredDir; + + // Scalar velocity along the facing angle. + // + float mfSpeed; + + rmt::Vector mVelocity; + + // The desired speed. The character will accel or decel towards this speed. + // + float mfDesiredSpeed; + + // Is in car. + // + bool mbInCar; + + // Whether the characters feet were planted last update + // + std::vector< bool, s2alloc<bool> > mbWasFootPlanted; + + // For camera tracking. + // + CharacterTarget* mpCharacterTarget; + + // For ai and animation. + // + ActionController* mpActionController; + + + //To control action button priority, we do the following. + ActionButton::ButtonHandler* mpActionButtonHandlers[ MAX_ACTION_BUTTON_HANDLERS ]; + ActionButton::ButtonHandler* mpCurrentActionButtonHandler; + + Vehicle* mpTargetVehicle; + + static float sfMaxSpeed; + + // Terrain positioning. + // + float mGroundY; + rmt::Vector mGroundNormal; + eTerrainType mTerrainType; // What type of terrain is the character on. + bool mInteriorTerrain; // The terrain is covered/inside. Such as a tunnel or building interior. + + rmt::Vector mRealGroundPos; // ground, excluding statics we may be on. + rmt::Vector mRealGroundNormal; // ground, excluding statics we may be on. + + CharacterAi::StateManager* mpStateManager; + + float mfRadius; + +// Hack. + friend class CharacterRenderable; + // Collision detection and response. + // + bool mbCollided; + int mCurrentCollision; + struct CollisionData + { + CollisionData( void ) + : + mCollisionDistance( 0.0f ), + mpCollisionVolume( 0 ) + { + } + static const int MAX_COLLISIONS = 8; + rmt::Vector mCollisionPosition; + rmt::Vector mCollisionNormal; + float mCollisionDistance; + sim::CollisionVolume* mpCollisionVolume; + }; + CollisionData mCollisionData[ CollisionData::MAX_COLLISIONS ]; + + bool mbIsStanding; + WalkerLocomotionAction* mpWalkerLocomotion; + JumpAction* mpJumpLocomotion; + + + sim::CollisionVolume* mpStandingCollisionVolume; + const poser::Joint* mpStandingJoint; + rmt::Matrix mParentTransform; + rmt::Matrix mInvParentTransform; + float mfGroundVerticalVelocity; + float mfGroundVerticalPosition; + + bool mbTurbo; + bool mbIsJump; + bool mbSolveCollisions; + + static const int MAX_PROPS = 1; + + Prop mPropList[ MAX_PROPS ]; + + ActionButton::AttachProp* mpPropHandler; + int mPropJoint; + + bool mVisible; + + WorldScene* mpWorldScene; + bool m_IsSimpleShadow; + + // when we apply choreo puppet skeleton over top of differently + // scaled character model, this value is to adjust the difference between + // the character model root and the npd skeleton root. Can be +ve or -ve + float mYAdjust; + + bool mbBusy; + + bool mbSimpleLoco; + bool mbNeedChoreoUpdate; + + tColour mShadowColour; + + float m_TimeLeftToShock; + bool m_IsBeingShocked; + + bool mDoKickwave; + tDrawable* mKickwave; + tFrameController* mKickwaveController; + + bool mAmbient; + tUID mAmbientLocator; + AmbientDialogueTrigger* mAmbientTrigger; + + rmt::Vector mLastFramePos; + + bool mbDoGroundIntersect; + + unsigned mIntersectFrame; + + bool mAllowRockin; + + bool mHasBeenHit; + + bool mbSnapToGround; + + float mSecondsSinceActionControllerUpdate; + + bool mTooFarToUpdate; + + float mSecondsSinceOnPostSimUpdate; + + Role mRole; + + float mScale; + + bool mCollidedThisFrame; + + bool mIsInSubstep; + + rmt::Vector mLean; + + bool mIsLisa; + bool mIsMarge; + + rmt::Vector mLastGoodPosOverStatic; + + rmt::Vector lameAssPosition; + + bool mManaged; +}; + +/* +============================================================================== +Character::GetFacing +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector& facingVector ) + +Return: void + +============================================================================= +*/ +inline void Character::GetFacing( rmt::Vector& facingVector ) const +{ + if ( mpPuppet ) + { + // Transform from object to world space. + // + facingVector = mpPuppet->GetFacingVector( ); + // Transform from object to world space. + // + //mInvParentTransform.RotateVector( facingVector, &facingVector ); + return; + } + facingVector.Set( 0.0f, 0.0f, -1.0f ); +} +/* +============================================================================== +Character::GetFacingDir +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: float + +============================================================================= +*/ +inline float Character::GetFacingDir( void ) const +{ + rmt::Vector facing; + GetFacing( facing ); + return choreo::GetWorldAngle( facing.x, facing.z ); +} + +/* +============================================================================== +Character::SetFacing +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector& facingVector ) + +Return: inline + +============================================================================= +*/ +inline void Character::SetFacing( rmt::Vector& facingVector ) +{ + if ( mpPuppet ) + { + mpPuppet->SetFacingVector( facingVector ); + } +} +/* +============================================================================== +Character::SetFacingDir +============================================================================== +Description: Comment + +Parameters: ( float fDir ) + +Return: void + +============================================================================= +*/ +inline void Character::SetFacingDir( float fDir ) +{ + rmt::Vector facing; + facing = choreo::DEFAULT_FACING_VECTOR; + choreo::RotateYVector( fDir, facing ); + SetFacing( facing ); +} +/* +============================================================================== +Character::GetDesiredFacing +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector& facingVector ) + +Return: void + +============================================================================= +*/ +inline void Character::GetDesiredFacing( rmt::Vector& facingVector ) const +{ + facingVector = choreo::DEFAULT_FACING_VECTOR; + choreo::RotateYVector( mfDesiredDir, facingVector ); + // Transform from object to world space. + // + //mInvParentTransform.RotateVector( facingVector, &facingVector ); +} +/* +============================================================================== +Character::GetDesiredDir +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: float + +============================================================================= +*/ +inline float Character::GetDesiredDir( void ) const +{ + rmt::Vector facing; + GetDesiredFacing( facing ); + return choreo::GetWorldAngle( facing.x, facing.z ); +} + +/* +============================================================================== +Character::SetPosition +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector& position ) + +Return: void + +============================================================================= +*/ +inline void Character::SetPosition( const rmt::Vector& position ) +{ + if ( mpPuppet ) + { + // Transform from world to object space. + // + rmt::Vector transformedPos = position; + transformedPos.Transform( mInvParentTransform ); + + mpPuppet->SetPosition( transformedPos ); + } + + sim::SimState* simState = mpSimStateObj; //GetSimState(); + if( simState != NULL && simState->GetControl() == sim::simAICtrl ) + { + UpdateSimState( 0.0f ); + } +} + +/* +============================================================================== +Character::GetPosition +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector& position ) + +Return: void + +============================================================================= +*/ +inline void Character::GetPosition( rmt::Vector& position ) const +{ + if ( mpPuppet ) + { + position = mpPuppet->GetPosition( ); + position.Transform( mParentTransform ); + } + else + { + position.Set(0.0f, 0.0f, 0.0f); + } +} +/* +============================================================================== +Character::GetVelocity +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector& velocity ) + +Return: void + +============================================================================= +*/ +inline void Character::GetVelocity( rmt::Vector& velocity ) const +{ + velocity = mVelocity; +} +/* +============================================================================== +Character::GetLocoVelocity +============================================================================== +Description: How fast is the character running/walking (locomoting) + +Parameters: () + +Return: inline + +============================================================================= +*/ +inline const rmt::Vector& Character::GetLocoVelocity() const +{ + return mpPuppet->GetVelocity( ); +} +/* +============================================================================== +Character::GetSpeed +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: float + +============================================================================= +*/ +inline float Character::GetSpeed( void ) const +{ + return mfSpeed; +} +/* +============================================================================== +Character::SetSpeed +============================================================================== +Description: Comment + +Parameters: ( float fSpeed ) + +Return: void + +============================================================================= +*/ +inline void Character::SetSpeed( float fSpeed ) +{ + rmt::Vector facing; + GetFacing( facing ); + facing.Scale( fSpeed ); + mParentTransform.RotateVector( facing, &facing ); + mpSimStateObj->VelocityState( ).mLinear = facing; +} +/* +============================================================================== +Character::GetDesiredSpeed +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: float + +============================================================================= +*/ +inline float Character::GetDesiredSpeed( void ) const +{ + return mfDesiredSpeed; +} +/* + +/* +============================================================================== +Character::SetDesiredDir +============================================================================== +Description: Comment + +Parameters: ( float fDesiredDir ) + +Return: void + +============================================================================= +*/ +inline void Character::SetDesiredDir( float fDesiredDir ) +{ + mfDesiredDir = fDesiredDir; +} +/* +============================================================================== +Character::SetDesiredSpeed +============================================================================== +Description: Comment + +Parameters: ( float fDesiredSpeed ) + +Return: void + +============================================================================= +*/ +inline void Character::SetDesiredSpeed( float fDesiredSpeed ) +{ + if ( fDesiredSpeed > GetMaxSpeed( ) ) + { + fDesiredSpeed = GetMaxSpeed( ); + } + mfDesiredSpeed = fDesiredSpeed; +} + +/* +============================================================================== +Character::GetPuppet +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: choreo + +============================================================================= +*/ +inline choreo::Puppet* Character::GetPuppet( void ) const +{ + return mpPuppet; +} + +/* +============================================================================== +Character::IsInCar +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: bool + +============================================================================= +*/ +inline bool Character::IsInCar( void ) const +{ + return mbInCar; +} + + +/* +============================================================================== +Character::SetController +============================================================================== +Description: Comment + +Parameters: ( CharacterController* pController ) + +Return: void + +============================================================================= +*/ +inline void Character::SetController( CharacterController* pController ) +{ + tRefCounted::Assign( mpController, pController ); +} +/* +============================================================================== +Character::GetController +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterController + +============================================================================= +*/ +inline CharacterController* Character::GetController( void ) const +{ + return mpController; +} + +/* +============================================================================== +Character::GetTarget +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterTarget + +============================================================================= +*/ +inline CharacterTarget* Character::GetTarget( void ) const +{ + rAssert( mpCharacterTarget ); + return mpCharacterTarget; +} + +/* +============================================================================== +Character::SetTargetVehicle +============================================================================== +Description: Comment + +Parameters: ( Vehicle* pVehicle ) + +Return: void + +============================================================================= +*/ +inline void Character::SetTargetVehicle( Vehicle* pVehicle ) +{ + mpTargetVehicle = pVehicle; +} +/* +============================================================================== +Character::GetTargetVehicle +============================================================================== +Description: Comment + +Parameters: ( ) + +Return: Vehicle + +============================================================================= +*/ +inline Vehicle* Character::GetTargetVehicle( ) const +{ + return mpTargetVehicle; +} +/* +============================================================================== +Character::GetStateManager +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterAi + +============================================================================= +*/ +inline CharacterAi::StateManager* Character::GetStateManager( void ) const +{ + return mpStateManager; +} + +/* +============================================================================== +Character::GetActionController +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: ActionController + +============================================================================= +*/ +inline ActionController* Character::GetActionController( void ) const +{ + return mpActionController; +} +/* +============================================================================== +Character::GetJumpHeight +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: float + +============================================================================= +*/ +inline float Character::GetJumpHeight( void ) +{ + return CharacterTune::sfJumpHeight; +} +/* +============================================================================== +Character::GetRootTransform +============================================================================== +Description: Comment + +Parameters: ( poser::Transform& out ) + +Return: void + +============================================================================= +*/ +inline void Character::GetRootTransform( poser::Transform& out ) const +{ + if ( mpPuppet ) + { + out = mpPuppet->GetRootTransform( ); + poser::Transform parentTransform; + parentTransform.SetMatrix( mParentTransform ); + out.Mult( parentTransform ); + } + else + { + out.Identity( ); + } +} +/* +============================================================================== +Character::WorldToLocal +============================================================================== +Description: Comment + +Parameters: ( const rmt::Vector& world ) + +Return: rmt + +============================================================================= +*/ +inline rmt::Vector Character::WorldToLocal( const rmt::Vector& world ) const +{ + rmt::Vector local = world; + local.Transform( mInvParentTransform ); + return local; +} +/* +============================================================================== +Character::LocalToWorld +============================================================================== +Description: Comment + +Parameters: ( const rmt::Vector& local ) + +Return: rmt + +============================================================================= +*/ +inline rmt::Vector Character::LocalToWorld( const rmt::Vector& local ) const +{ + rmt::Vector world = local; + world.Transform( mParentTransform ); + return world; +} +/* +============================================================================== +Character::GetCollisionAreaIndex +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: int + +============================================================================= +*/ +inline int Character::GetCollisionAreaIndex( void ) const +{ + return mCollisionAreaIndex; +} +/* +============================================================================== +Character::IsInCollision +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: bool + +============================================================================= +*/ +inline bool Character::IsInCollision( void ) const +{ + return mbCollided; +} +/* +============================================================================== +Character::IsStanding +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: bool + +============================================================================= +*/ +inline bool Character::IsStanding( void ) const +{ + return mbIsStanding; +} +/* +============================================================================== +Character::GetWalkerLocomotionAction +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: WalkerLocomotionAction + +============================================================================= +*/ +inline WalkerLocomotionAction* Character::GetWalkerLocomotionAction( void ) const +{ + return mpWalkerLocomotion; +} +/* +============================================================================== +Character::GetJumpLocomotionAction +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: JumpAction + +============================================================================= +*/ +inline JumpAction* Character::GetJumpLocomotionAction( void ) const +{ + return mpJumpLocomotion; +} +/* +============================================================================== +Character::GetParentTransform +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: const + +============================================================================= +*/ +inline const rmt::Matrix& Character::GetParentTransform( void ) const +{ + return mParentTransform; +} +/* +============================================================================== +Character::GetInverseParentTransform +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: const + +============================================================================= +*/ +inline const rmt::Matrix& Character::GetInverseParentTransform( void ) const +{ + return mInvParentTransform; +} +/* +============================================================================== +Character::GetGroundVerticalVelocity +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: float + +============================================================================= +*/ +inline float Character::GetGroundVerticalVelocity( void ) const +{ + return mfGroundVerticalVelocity; +} + +/* +============================================================================== +Character::IsJumping +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: bool + +============================================================================= +*/ +inline bool Character::IsJumping( void ) const +{ + return mbIsJump; +} +/* +============================================================================== +Character::SetJumping +============================================================================== +Description: Comment + +Parameters: ( bool bIsJumping ) + +Return: void + +============================================================================= +*/ +inline void Character::SetJumping( bool bIsJumping ) +{ + mbIsJump = bIsJumping; +} + +/* +============================================================================== +Character::SetTurbo +============================================================================== +Description: Comment + +Parameters: ( bool bTurbo ) + +Return: void + +============================================================================= +*/ +inline void Character::SetTurbo( bool bTurbo ) +{ + if( bTurbo != mbTurbo ) + { + if( bTurbo ) + { + GetEventManager()->TriggerEvent( EVENT_TURBO_START, this ); + } + mbTurbo = bTurbo; + } +} +/* +============================================================================== +Character::IsTurbo +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: bool + +============================================================================= +*/ +inline bool Character::IsTurbo( void ) const +{ + return mbTurbo; +} + +/* +============================================================================== +Character::IsSimpleShadow +============================================================================== +Description: Is the shadow a blobby shadow or a volume shadow? + +Parameters: ( void ) + +Return: bool + +============================================================================= +*/ +inline bool Character::IsSimpleShadow( void ) +{ + return m_IsSimpleShadow; +} +/* +============================================================================== +Character::SetSimpleShadow +============================================================================== +Description: Set if the character's shadow is a blobby, in apposed to the + volumetric. + +Parameters: ( bool ) + +Return: void + +============================================================================= +*/ +inline void Character::SetSimpleShadow( bool IsSimpleShadow ) +{ + m_IsSimpleShadow = IsSimpleShadow; +} + +inline bool Character::IsNPC() +{ + return mIsNPC; +} + +class NPCharacter +: +public Character +{ +public: + NPCharacter( void ); + ~NPCharacter( void ); + + virtual int GetAIRef() { return PhysicsAIRef::NPCharacter;} + + virtual void AddToPhysics( void ); + virtual void RemoveFromPhysics( void ); + + virtual void SubmitStatics( void ); + virtual void SubmitAnimCollisions( void ) {}; + virtual void SubmitDynamics( void ); + + virtual void UpdatePhysicsObjects( float timeins, int area ); + virtual void OnTransitToAICtrl(); + + virtual void ApplyKickForce( const rmt::Vector& direction, float force ); + virtual void ApplyForce( const rmt::Vector& direction, float force ); +protected: + + virtual void AssignCollisionAreaIndex( void ); + virtual float GetInputScale( void ) + { + // Scale 1.0f does nothing. + return 1.0f; + } +private: + virtual void OnUpdateRoot( float timeins ); + virtual void OnPostSimUpdate(float timeins); + virtual void UpdateFootPlant( void ) + { + } + int mMappableHandle; +}; + + + +#endif // CHARACTER_H_ + diff --git a/game/code/worldsim/character/charactercontroller.cpp b/game/code/worldsim/character/charactercontroller.cpp new file mode 100644 index 0000000..0b41b77 --- /dev/null +++ b/game/code/worldsim/character/charactercontroller.cpp @@ -0,0 +1,1668 @@ +#include <ai/sequencer/actioncontroller.h> +#include <worldsim/character/charactercontroller.h> +#include <worldsim/character/controllereventhandler.h> +#include <worldsim/character/charactermappable.h> +#include <worldsim/character/character.h> +#include <input/inputmanager.h> +#include <mission/gameplaymanager.h> + +#include <camera/supercammanager.h> +#include <camera/supercamcentral.h> +#include <camera/supercam.h> + +#include <p3d/camera.hpp> +#include <p3d/pointcamera.hpp> + +#include <events/eventmanager.h> + +#include <worldsim/avatar.h> +#include <worldsim/avatarmanager.h> + +#include <roads/geometry.h> + +#include <worldsim/character/charactermanager.h> + +#include <ai/sequencer/action.h> +#include <presentation/mouthflapper.h> +#include <interiors/interiormanager.h> +//#include <presentation/presentation.h> +//#include <presentation/presentationanimator.h> + +/* +============================================================================== +CharacterController::CharacterController +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterController + +============================================================================= +*/ +CharacterController::CharacterController( void ) +: +mpCharacter( 0 ), +mIntention( NONE ), +mPreserveIntention( NONE ), +mActive( true ) +{ +} +/* +============================================================================== +CharacterController::~CharacterControlller +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterController + +============================================================================= +*/ +CharacterController::~CharacterController( void ) +{ + if ( mpCharacter ) + { + mpCharacter = 0; + } +} +/* +============================================================================== +CharacterController::GetCharacter +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: Character + +============================================================================= +*/ +Character* CharacterController::GetCharacter( void ) const +{ + return mpCharacter; +} +/* +============================================================================== +CharacterController::SetCharacter +============================================================================== +Description: Comment + +Parameters: ( Character* pCharacter ) + +Return: void + +============================================================================= +*/ +void CharacterController::SetCharacter( Character* pCharacter ) +{ + mpCharacter = pCharacter; +} +/* +============================================================================== +PhysicalController::PhysicalController +============================================================================== +Description: Constructor + +Parameters: ( void ) + +Return: n/a + +============================================================================= +*/ +PhysicalController::PhysicalController( void ) +: +mpCharacterMappable( 0 ) +{ +} +/* +============================================================================== +PhysicalController::~PhysicalController +============================================================================== +Description: Destructor + +Parameters: ( void ) + +Return: n/a + +============================================================================= +*/ +PhysicalController::~PhysicalController( void ) +{ + // This should release and set mpCharacterMappable = 0; + // + mpCharacterMappable->Release( ); + mpCharacterMappable = 0; +} +/* +============================================================================== +PhysicalController::SetCharacterMappable +============================================================================== +Description: Comment + +Parameters: ( CharacterMappable* pMappable ) + +Return: void + +============================================================================= +*/ +void PhysicalController::SetCharacterMappable( CharacterMappable* pMappable ) +{ + tRefCounted::Assign( mpCharacterMappable, pMappable ); +} +/* +============================================================================== +PhysicalController::GetDirection +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector& outDirection ) + +Return: void + +============================================================================= +*/ +void PhysicalController::GetDirection( rmt::Vector& outDirection ) +{ + if(mActive) + { + mpCharacterMappable->GetDirection( outDirection ); + } + else + { + outDirection.Set(0.0f, 0.0f, 0.0f); + } +} +/* +============================================================================== +PhysicalController::GetValue +============================================================================== +Description: Comment + +Parameters: ( int buttonId ) + +Return: float + +============================================================================= +*/ +float PhysicalController::GetValue( int buttonId ) const +{ + if(mActive) + { + return GetCharacterMappable( )->GetValue( buttonId ); + } + else + { + return 0.0f; + } +} +/* +============================================================================== +PhysicalController::IsButtonDown +============================================================================== +Description: Comment + +Parameters: ( int buttonId ) + +Return: bool + +============================================================================= +*/ +bool PhysicalController::IsButtonDown( int buttonId ) const +{ + if(mActive) + { + return GetCharacterMappable( )->IsButtonDown( buttonId ) || GetIntention( ) == (eIntention)buttonId; + } + else + { + return false; + } +} + + +int PhysicalController::TimeSinceChange( int buttonId ) const +{ + return IsButtonDown(buttonId) ? 0 : GetCharacterMappable( )->GetButton( buttonId )->TimeSinceChange(); +}; + +/* +============================================================================== +PhysicalController::GetCharacterMappable +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterMappable + +============================================================================= +*/ +CharacterMappable* PhysicalController::GetCharacterMappable( void ) const +{ + return mpCharacterMappable; +} +/* +============================================================================== +CameraRelativeCharacterController::CameraRelativeCharacterController +============================================================================== +Description: Constructor + +Parameters: ( int index, tCamera* pCamera ) + +Return: n/a + +============================================================================= +*/ +CameraRelativeCharacterController::CameraRelativeCharacterController( void ) +: +mpCamera( 0 ), +mbCameraChange( false ) +{ + mpEventHandler = new CameraRelativeCharacterControllerEventHandler( this ); + GetEventManager()->AddListener( mpEventHandler, EVENT_CAMERA_CHANGE ); +} +void CameraRelativeCharacterController::Create( Character* pCharacter, + CharacterMappable* pCharacterMappable ) +{ +MEMTRACK_PUSH_GROUP( "Character Controller" ); + SetCharacter( pCharacter ); + SetCharacterMappable( pCharacterMappable ); + pCharacterMappable->SetCharacterController( this ); +MEMTRACK_POP_GROUP( "Character Controller" ); +} +/* +============================================================================== +CameraRelativeCharacterController::~CameraRelativeCharacterController +============================================================================== +Description: Destructor + +Parameters: ( void ) + +Return: n/a + +============================================================================= +*/ +CameraRelativeCharacterController::~CameraRelativeCharacterController( void ) +{ + if ( mpCamera ) + { + mpCamera->Release( ); + mpCamera = 0; + } + if ( mpEventHandler ) + { + GetEventManager()->RemoveListener( mpEventHandler, EVENT_CAMERA_CHANGE ); + delete mpEventHandler; + mpEventHandler = 0; + } +} + + +void CameraRelativeCharacterController::SetIntention( eIntention intention ) +{ + if(!mActive) + { + return; + } + + // ignore input if we're not in locomotion state + if( mpCharacter->GetStateManager()->GetState() == CharacterAi::INSIM ) + { + // TODO: + // Should clear the intention here? or maybe just let the + // old intention linger? Hmm... + mIntention = CharacterController::NONE; + } + else + { + mIntention = intention; + } + +} + + +/* +============================================================================== +CameraRelativeCharacterController::GetDirection +============================================================================== +Description: Transforms Controller relative inputs into camera relative inputs. + The character will carry on moving the same world direction independent + of camera position until the controller direction changes. + +Parameters: ( rmt::Vector& outDirection ) + +Return: void + +============================================================================= +*/ +void CameraRelativeCharacterController::GetDirection( rmt::Vector& outDirection ) +{ + if(!mActive) + { + outDirection.Set(0.0f,0.0f,0.0f); + return; + } + + if( mpCharacter->GetStateManager()->GetState() == CharacterAi::INSIM ) + { + outDirection.Set( 0.0f, 0.0f, 0.0f ); // feed zero, so no walk anim + return; + } + + GetCharacterMappable( )->GetDirection( outDirection ); + +#ifdef RAD_WIN32 + SuperCam* cam = GetSuperCamManager()->GetSCC( 0 )->GetActiveSuperCam(); + if( cam->GetType() == SuperCam::PC_CAM ) + { + //We don't do anything to modify the direction. + return; + } + +#endif + + if ( mbCameraChange ) + { + float cos15 = 0.9659258262890682867497431997289f; + static float sfAngleTolerance = cos15; + + // Compare the old direction with the new direction transformed + // by the old matrix. + // + outDirection.Transform( mLastCameraMatrix ); + + rmt::Vector normDir = outDirection; + normDir.Normalize(); + + rmt::Vector normLast = mLastDirection; + normLast.Normalize(); + + float dot = normDir.DotProduct( normLast ); + + if ( dot > sfAngleTolerance ) + { + // Direction is the "same" within given tolerance. + // + outDirection = mLastDirection; + } + else + { + mbCameraChange = false; + } + } + else if ( mpCamera != (tCamera*)0 ) + { + rmt::Matrix mat = mpCamera->GetCameraToWorldMatrix(); + mat.IdentityTranslation( ); + outDirection.Transform( mat ); + } + + // Rotate by the inv transform of whatever the character is + // standing on. + // + rmt::Matrix invMat = mpCharacter->GetInverseParentTransform(); + invMat.IdentityTranslation( ); + outDirection.Transform( invMat ); + + // This is designed to address analog stick hysteresis. + // If you hold the stick in a direction and release it, often it + // will snap back and momentarily read a value >90 deg from the intended direction. + // this will address those momentary values. + // + Character* pCharacter = GetCharacter(); + rmt::Vector facing; + rmt::Vector normDir = outDirection; + normDir.Normalize(); + pCharacter->GetFacing( facing ); + float dot = facing.DotProduct( normDir ); + static float sfDot = 0.000001f; + if ( dot <= -sfDot ) + { + //rReleasePrintf( "%f dot\n", dot ); + // gt 90 deg. + // + static float toleranceSqr = rmt::Sqr( 0.60f ); + //rReleasePrintf( "%f MagnitudeSqr\n", outDirection.MagnitudeSqr( ) ); + if ( outDirection.MagnitudeSqr( ) < toleranceSqr ) + { + // Small value. + // + static float sfScale = 0.01f; + outDirection.Scale( sfScale ); + } + } + +} +/* +============================================================================== +CameraRelativeCharacterController::SetCamera +============================================================================== +Description: Comment + +Parameters: ( tCamera* pCamera ) + +Return: void + +============================================================================= +*/ +void CameraRelativeCharacterController::SetCamera( tCamera* pCamera ) +{ + tRefCounted::Assign( mpCamera, pCamera ); +} + +/* +============================================================================== +CameraRelativeCharacterController::HandleEvent +============================================================================== +Description: Implements Devil May Cry style controls when the camera view switches. + The character will carry on moving the same world direction independent + of camera position until the controller direction changes. + +Parameters: ( EventEnum id, void* pEventData ) + +Return: void + +============================================================================= +*/ +void CameraRelativeCharacterController::HandleEvent( int id, void* pEventData ) +{ + EventEnum eventId = (EventEnum)id; + + if ( EVENT_CAMERA_CHANGE == eventId ) + { + bool interior = GetInteriorManager()->IsEntering() || GetInteriorManager()->IsExiting() || GetInteriorManager()->IsInside(); + + if (mpCamera && !mbCameraChange && !interior) + { + // Get absolute direction we'll transform it down below. + // + GetCharacterMappable( )->GetDirection( mLastDirection ); + + // Get the camera matrix to store while in transition. + // + mLastCameraMatrix = mpCamera->GetCameraToWorldMatrix(); + mLastCameraMatrix.IdentityTranslation( ); + + // Now transform mLastDirection by the camera matrix. + // We store it to maintain the CameraRelative controls to the previous + // camera. + // + // NB: We don't just call CameraRelativeCharacterController::GetDirection() + // because that will take into account the parentTransform as well. + // + mLastDirection.Transform( mLastCameraMatrix ); + + // Tell the controller that we want to hold onto mLastDirection. + // + mbCameraChange = true; + + // Update the new camera. + // + SuperCam* pSuperCam = static_cast<SuperCam*>( pEventData ); + tCamera* pCamera = pSuperCam->GetCamera(); + SetCamera( pCamera ); + } + } + else + { + rAssert( false ); + } +} + +////////////////////////////////////////////////// NPC CONTROLLER /////////////////////////////////////////// + +static const float SECONDS_BACK_TO_FOLLOW_PATH = 3.0f; +static const float SECONDS_TRACKING_PLAYER = 0.3f; +static const float NPC_FOLLOW_PATH_SPEED_MPS = 1.0f; + +static const float SECONDS_BETW_PANIC_CHANGE_DIRECTION = 2.0f; +static const float NPC_PANIC_RUN_SPEED_MPS = 3.0f; + +static const int TALK_TIME_MILLISECONDS = 3000; +static const int STOP_TALK_CHECK_INTERVAL_MILLISECONDS = 1000; + +static const float SECONDS_BETW_PLAYING_TURN_ANIMS = 0.5f; + +NPCController::NPCController() +: +mOffPath( false ), +mMillisecondsInTalk( 0 ), +mTalkTarget( NULL ), +mListening( true ), +mState( FOLLOWING_PATH ), +mSecondsInStopped( 0.0f ), +mNumNPCWaypoints( 0 ), +mCurrNPCWaypoint( -1 ), +mStartPanicking( false ), +mSecondsChangePanicDir( 0.0f ), +mSecondsSinceLastTurnAnim( SECONDS_BETW_PLAYING_TURN_ANIMS ), +mUseTempWaypoint(false), +mTempWaypoint(0.0f, 0.0f , 0.0f) +{ + mSpeedMps = GetFollowPathSpeedMps(); + mDirection.Set( 0.0f, 0.0f, 1.0f ); + mNormalizedDodgeDir.Set( 0.0f, 0.0f, 1.0f ); + mMouthFlapper = new MouthFlapper(); + mMouthFlapper->AddRef(); + mMouthFlapper->SetIsEnabled( false ); + + + mNormalizedPanicDir.Set( 0.0f, 0.0f, 1.0f ); +} + + +NPCController::~NPCController( void ) +{ + if( mMouthFlapper != NULL ) + { + mMouthFlapper->Release(); + } +} + +void NPCController::SetCharacter( Character *pCharacter ) +{ + CharacterController::SetCharacter( pCharacter ); +} + +bool NPCController::AddNPCWaypoint( const rmt::Vector& pt ) +{ + // can't add another point + if( mNumNPCWaypoints >= MAX_NPC_WAYPOINTS ) + { + return false; + } + + if( mNumNPCWaypoints == 0 ) + { + // set our sights on the first waypoint + mCurrNPCWaypoint = 0; + } + mNPCWaypoints[ mNumNPCWaypoints ] = pt; + mNumNPCWaypoints++; + + return true; +} +void NPCController::IncitePanic() +{ + // he's gonna run, so let him RUN .... into things... + //GetCharacter()->SetSolveCollisions( true ); + + // flag this guy to start panicking, if he can't panick right now.. + mStartPanicking = true; + + // don't panick if already panicking or in limbo (deliberately set there) + if( mState == NPCController::PANICKING || mState == NPCController::NONE ) + { + return; + } + /* + // if not in an ideal state for panicking... + if( mState != NPCController::FOLLOWING_PATH && + mState != NPCController::STANDING && + mState != NPCController::STOPPED ) + { + return; + } + */ + + TransitToState( NPCController::PANICKING ); + +} +void NPCController::QuellPanic() +{ + mStartPanicking = false; + TransitToState( NPCController::STOPPED ); +} + + + +void NPCController::Update( float seconds ) +{ + + if( mpCharacter == NULL ) + { + return; + } + + // if character in simulation, no point + if( mpCharacter->GetSimState()->GetControl() == sim::simSimulationCtrl ) + { + return; + } + + // if character is not in locomotion state, no point... + if( mpCharacter->GetStateManager()->GetState() != CharacterAi::LOCO ) + { + return; + } + + //BEGIN_PROFILE( "NPCController::Update" ); + + // Grab the simstate control. we'll be checking this sporadically. + int control = mpCharacter->GetSimState()->GetControl(); + + if( mStartPanicking ) + { + IncitePanic(); + } + + switch( mState ) + { + case FOLLOWING_PATH: + { + //rAssert( control == sim::simAICtrl ); + FollowPath( seconds ); + DetectAndDodge( seconds ); + } + break; + case STOPPED: + { + //rAssert( control == sim::simAICtrl ); + mSecondsInStopped += seconds; + mSecondsSinceLastTurnAnim += seconds; + + // set to 1.5f and they'll follow you terminator style! + mSpeedMps = 0.0f; + mpCharacter->SetSpeed( mSpeedMps ); + + // + // Track player for awhile then we can start deciding + // whether or not we want/need to dodge... + // + rmt::Vector playerPos, myPos, myFacing; + + Avatar* avatar = ::GetAvatarManager()->GetAvatarForPlayer(0); + avatar->GetPosition( playerPos ); + + mpCharacter->GetPosition( &myPos ); + + mpCharacter->GetFacing( myFacing ); + //rAssert( rmt::Epsilon( myFacing.MagnitudeSqr(), 1.0f, 0.001f ) ); + + rmt::Vector toPlayer = playerPos - myPos; + toPlayer.NormalizeSafe(); + rAssert( rmt::Epsilon( toPlayer.MagnitudeSqr(), 1.0f, 0.001f ) ); + + //cosN for N being > half the animation turn angle + const float COSALPHA_TURN_TOLERANCE = 0.8387f; + + // cosN, so anything > than N we just set the direction directly rather than play anim + const float COSALPHA_QUICKTURN_TOLERANCE = 0.7071f; + + float cosAlpha = myFacing.Dot( toPlayer ); + if( cosAlpha < COSALPHA_QUICKTURN_TOLERANCE ) + { + // just turn (no anim) + SetDirection( toPlayer ); + float dir = choreo::GetWorldAngle( toPlayer.x, toPlayer.z ); + mpCharacter->SetDesiredDir( dir ); + + // reset so we can do turn anim right away after this + mSecondsSinceLastTurnAnim = SECONDS_BETW_PLAYING_TURN_ANIMS; + } + else if( COSALPHA_QUICKTURN_TOLERANCE <= cosAlpha && cosAlpha <= COSALPHA_TURN_TOLERANCE ) + { + if( mSecondsSinceLastTurnAnim >= SECONDS_BETW_PLAYING_TURN_ANIMS ) + { + // figure out whether to turn left or right + rmt::Vector myRight = Get90DegreeRightTurn( myFacing ); + if( myRight.Dot( toPlayer ) > 0.0f ) + { + // player's on my right, turn right + SetIntention( TurnRight ); + } + else + { + // player's on my left, turn left + SetIntention( TurnLeft ); + } + mSecondsSinceLastTurnAnim = 0.0f; // back to zero! + } + } + + if( mSecondsInStopped > SECONDS_BACK_TO_FOLLOW_PATH ) + { + + // if we are going to be too close to other characters, transit + // to stopped here. + CharacterManager* cm = GetCharacterManager(); + + const float TOO_CLOSE_TO_NPC_DIST_SQR = 2.0f; + + // + // if I'm a ped, I can ignore other peds (in THIS check only) cuz I already + // deal with them safely elsewhere... If I'm a non-ped NPC, I would + // want to check for peds, so I don't walk through them. + // + bool ignorePeds = (mpCharacter->GetRole() == Character::ROLE_PEDESTRIAN)? true : false; + + int maxChars = cm->GetMaxCharacters(); + + bool gonnaHit = false; + + for( int i=0; i<maxChars; i++ ) + { + Character* c = cm->GetCharacter( i ); + if( c && c != mpCharacter ) + { + if( ignorePeds && c->GetRole() == Character::ROLE_PEDESTRIAN ) + { + continue; + } + rmt::Vector cPos; + c->GetPosition( cPos ); + + float distSqr = (cPos - myPos).MagnitudeSqr(); + if( distSqr <= TOO_CLOSE_TO_NPC_DIST_SQR ) + { + gonnaHit = true; + break; + } + } + } + + if( gonnaHit ) + { + mSecondsInStopped = 0.0f; + } + else + { + TransitToState(FOLLOWING_PATH); + } + } + // + // If we've been tracking player long enough, + // time to dodge!!! + // + if( mSecondsInStopped > SECONDS_TRACKING_PLAYER ) + { + DetectAndDodge( seconds ); + } + + } + break; + case DODGING: + { + //rAssert( control == sim::simAICtrl ); + // so NPCs don't dodge into someplace indeterminate + //mpCharacter->SetSolveCollisions( true ); + + // sets NeedsUpdate flag for character (so it doens't pause + // in midmotion if player moves out of physics simming radius) + mpCharacter->Update( seconds ); + } + break; + case CRINGING: + { + //rAssert( control == sim::simAICtrl ); + } + break; + case TALKING: + { + rAssert( mTalkTarget != NULL ); + //rAssert( control == sim::simAICtrl ); + + int coinflip = 0; + + // Direction is set for us when we transited to this state + // it never needs changing. + //SetDirection( ); + + // if target is no longer in TALKING state, we stop too... + if( mTalkTarget->GetState() != TALKING ) + { + TransitToState(FOLLOWING_PATH); + break; + } + + if( mListening ) + { + rAssert( mTalkTarget != NULL ); + rAssert( !mMouthFlapper->IsEnabled() ); + rAssert( !mTalkTarget->mListening ); // one of us needs to do the talking! geez... + rAssert( mTalkTarget->mMouthFlapper->IsEnabled() ); + + mMillisecondsInTalk = 0; + } + else + { + rAssert( mTalkTarget != NULL ); + rAssert( mMouthFlapper->IsEnabled() ); + rAssert( mTalkTarget->mListening ); // only one of us can talk at once! geez... + rAssert( !mTalkTarget->mMouthFlapper->IsEnabled() ); + + mMillisecondsInTalk += (int)(seconds*1000); + if( mMillisecondsInTalk >= TALK_TIME_MILLISECONDS ) + { + // ok, by now we've talked for longer than alotted time, + // so every check interval, we have a chance to + // stop talking and let the other person start + // talking. + + if( (mMillisecondsInTalk - TALK_TIME_MILLISECONDS) >= + STOP_TALK_CHECK_INTERVAL_MILLISECONDS ) + { + mMillisecondsInTalk = TALK_TIME_MILLISECONDS; + + coinflip = rand() % 3; + // 2 in 3 chance in favor of stopping our yammer + if( coinflip != 2 ) + { + StopTalking(); + mTalkTarget->StartTalking(); + } + } + } + } + + /* + // there's a good chance we won't bother to detect cars while talking... uh oh! + coinflip = rand() % 1000; + if( coinflip == 0 ) + { + DetectAndDodge( seconds ); + } + */ + } + break; + case STANDING: + { + //rAssert( control == sim::simAICtrl ); + mSecondsInStopped += seconds; + if( mSecondsInStopped > SECONDS_BACK_TO_FOLLOW_PATH ) + { + TransitToState(FOLLOWING_PATH); + break; + } + DetectAndDodge( seconds ); + } + break; + case PANICKING: + { + //rAssert( control == sim::simAICtrl ); + + rmt::Vector myPos; + mpCharacter->GetPosition( myPos ); + + mSecondsChangePanicDir += seconds; + if( mSecondsChangePanicDir > SECONDS_BETW_PANIC_CHANGE_DIRECTION ) + { + Avatar* player = GetAvatarManager()->GetAvatarForPlayer( 0 ); + rAssert( player ); + + rmt::Vector playerPos; + player->GetPosition( playerPos ); + + rmt::Vector awayFromPlayer = myPos - playerPos; + + // vary direction of panic run by modifying at random .x & .z + // of the awayFromPlayer vector... + + float randomXVariance = 0.0f; + float randomZVariance = 0.0f; + const float FAR_ENOUGH_TO_CHANGE_DIR_SQR = 36.0f; + if( awayFromPlayer.MagnitudeSqr() > FAR_ENOUGH_TO_CHANGE_DIR_SQR ) + { + randomXVariance = (float)(rand()%500) / 100.0f; + randomXVariance *= (rand() % 2)? 1.0f : -1.0f; + randomZVariance = (float)(rand()%500) / 100.0f; + randomZVariance *= (rand() % 2)? 1.0f : -1.0f; + } + awayFromPlayer.x += randomXVariance; + awayFromPlayer.z += randomZVariance; + + awayFromPlayer.NormalizeSafe(); // *** SQUARE ROOT! *** + + mNormalizedPanicDir = awayFromPlayer; + mSecondsChangePanicDir = 0.0f; + } + SetDirection( mNormalizedPanicDir ); + + } + break; + case TALKING_WITH_PLAYER: // fall thru + case NONE: + break; // do nothing + default: + { + // bad state + rAssert( false ); + } + } + //END_PROFILE( "NPCController::Update" ); +} + +float NPCController::GetFollowPathSpeedMps() const +{ + if( mOffPath ) + { + return NPC_FOLLOW_PATH_SPEED_MPS; + } + if( mNumNPCWaypoints > 1 ) + { + return NPC_FOLLOW_PATH_SPEED_MPS; + } + return 0.0f; +} + + +void NPCController::FollowPath( float seconds ) +{ + // if we're in collision, we should transit to STOPPED. + if( mpCharacter->mbCollidedWithVehicle ) + { + TransitToState( STOPPED ); + return; + } + + // get our pos + rmt::Vector currPos; + mpCharacter->GetPosition( &currPos ); + + + // if we are going to be too close to other characters, transit + // to stopped here. + CharacterManager* cm = GetCharacterManager(); + + const float TOO_CLOSE_TO_NPC_DIST_SQR = 2.0f; + + // + // if I'm a ped, I can ignore other peds (in THIS check only) cuz I already + // deal with them safely elsewhere... If I'm a non-ped NPC, I would + // want to check for peds, so I don't walk through them. + // + bool ignorePeds = (mpCharacter->GetRole() == Character::ROLE_PEDESTRIAN)? true : false; + + int maxChars = cm->GetMaxCharacters(); + for( int i=0; i<maxChars; i++ ) + { + Character* c = cm->GetCharacter( i ); + if( c && c != mpCharacter ) + { + if( ignorePeds && c->GetRole() == Character::ROLE_PEDESTRIAN ) + { + continue; + } + rmt::Vector cPos; + c->GetPosition( cPos ); + + float distSqr = (cPos - currPos).MagnitudeSqr(); + if( distSqr <= TOO_CLOSE_TO_NPC_DIST_SQR ) + { + TransitToState( STOPPED ); + return; + } + } + } + + + //mpCharacter->SetSolveCollisions( true ); + + mSpeedMps = GetFollowPathSpeedMps(); + mpCharacter->SetSpeed( mSpeedMps ); + + // If we're off our path, pathfind way back on path + rmt::Vector lastPos; + + //rAssert( 1 <= mNumNPCWaypoints && mNumNPCWaypoints < MAX_NPC_WAYPOINTS ); + //rAssert( 0 <= mCurrNPCWaypoint && mCurrNPCWaypoint < mNumNPCWaypoints ); + + // Find the good pos on path that we want to be at, or the point + // itself if no waypoints, ok? + if(mUseTempWaypoint) + { + lastPos = mTempWaypoint; + if((lastPos - mNPCWaypoints[0]).Magnitude() < 20.0f) // were close to our path again, clear the temp waypoint + { + lastPos = mNPCWaypoints[0]; + mUseTempWaypoint = false; + } + } + else if( mNumNPCWaypoints == 1 ) + { + lastPos = mNPCWaypoints[0]; // only one waypoint, so this is it. + } + else if( mNumNPCWaypoints > 1 ) + { + rmt::Vector start, end; + end = mNPCWaypoints[ mCurrNPCWaypoint ]; + + int lastPt = mCurrNPCWaypoint - 1; + if( lastPt < 0 ) + { + lastPt = mNumNPCWaypoints - 1; + } + rAssert( 0 <= lastPt && lastPt < mNumNPCWaypoints ); + start = mNPCWaypoints[ lastPt ]; + + end.y = start.y = currPos.y; // ignore heights + + FindClosestPointOnLine( start, end, currPos, lastPos ); + } + else + { + // do nothing! + lastPos = currPos; + } + + rmt::Vector epsilon; + GetAllowedPathOffset( epsilon ); + + bool same = false; + if( rmt::Epsilon( currPos.x, lastPos.x, epsilon.x ) && + //rmt::Epsilon( currPos.y, lastPos.y, epsilon.y ) && // IGNORE y VALUE + rmt::Epsilon( currPos.z, lastPos.z, epsilon.z ) ) + { + same = true; + } + + if( !mOffPath && same ) + { + // we were on path, and we're still on path + return; + } + else if( mOffPath && same ) + { + // we were off path, now we're on path + mOffPath = false; + return; + } + else if( !same ) + { + // we are not on path... + mOffPath = true; + + bool tooFar = (lastPos - currPos).Magnitude() > 100.0f; + + // If no one is looking, might as well warp baby! + if( !mpCharacter->mbInAnyonesFrustrum || tooFar) + { + // Make sure where we're warping to is also not in anyone's frustrum... + bool bLastPosInAnyonesFrustrum = false; + for( int i=0; i<GetGameplayManager()->GetNumPlayers(); i++ ) + { + if( mpCharacter->PosInFrustrumOfPlayer( lastPos, i ) ) + { + bLastPosInAnyonesFrustrum = true; + break; + } + } + if( !bLastPosInAnyonesFrustrum || tooFar) + { + float facing = mpCharacter->GetFacingDir(); + mpCharacter->RelocateAndReset( lastPos, facing, false ); + mOffPath = false; + return; + } + } + + //mpCharacter->SetSolveCollisions( true ); + OnOffPath( lastPos, currPos ); + } +} + +void NPCController::TeleportToPath( void ) +{ + float facing = mpCharacter->GetFacingDir(); + mpCharacter->RelocateAndReset( mNPCWaypoints[0], facing, false ); + mOffPath = false; + ClearTempWaypoint(); +} + +void NPCController::DetectAndDodge( float seconds ) +{ + // if we're in a street race, don't do any detecting or dodging + // cuz it could put us outside the race props boundaries. + Mission* m = GetGameplayManager()->GetCurrentMission(); + if( m && m->mIsStreetRace1Or2 ) + { + rmt::Vector dir( 0.0f, 0.0f, 0.0f ); + SetDirection( dir ); + mSpeedMps = 0.0f; + mpCharacter->SetSpeed( mSpeedMps ); + return; + } + + + float hisVel = 0.0f; + float epsilon = 0.001f; + + bool willGetHit = Detect( seconds, hisVel ); + + if( willGetHit ) + { + //If we haven't tried stopping... + if( mState != STOPPED ) + { + TransitToState( STOPPED ); + } + // ok, we're stopped, but are they're still coming? + else + { + Avatar* avatar = GetAvatarManager()->GetAvatarForPlayer( 0 ); + rAssert( avatar != NULL ); + + if( hisVel > epsilon ) // we're already stopped, but he's still coming + { + // Well if they're not in a vehicle, who cares! + if( !avatar->IsInCar() ) + { + return; + } + + int coinflip = rand() % 2; + if( coinflip == 0 ) + { + //TransitToState(CRINGING); + PerformCringe(); + return; + } + else + { + //TransitToState(DODGING); + PerformDodge( ); + return; + } + } + } + } + + // + // The reason we do this after is that we may have needed to transit + // back to STOPPED (in the code above) after we've just transitted + // from there to FOLLOWING_PATH because we might hit the player. + // So if the state is still FOLLOWING_PATH despite detecting and + // dodging, we'll go ahead and traverse the pedpathsegments + // + if( mState == FOLLOWING_PATH ) + { + TraversePath( seconds ); + } +} + + +void NPCController::TraversePath( float seconds ) +{ + rmt::Vector dir( 0.0f, 0.0f, 0.0f ); + if( mOffPath ) + { + // allow pathfinding back (don't set speed to zero or set direction to zero) + return; + } + + if( mCurrNPCWaypoint == -1 ) + { + SetDirection( dir ); + mSpeedMps = 0.0f; + mpCharacter->SetSpeed( mSpeedMps ); + return; + } + + if( (mNumNPCWaypoints < 2) || mUseTempWaypoint) + { + SetDirection( dir ); + mSpeedMps = 0.0f; + mpCharacter->SetSpeed( mSpeedMps ); + return; + } + + // loop back to the first waypoint + if( mCurrNPCWaypoint >= mNumNPCWaypoints ) + { + mCurrNPCWaypoint = 0; + } + + rAssert( 0 <= mCurrNPCWaypoint && mCurrNPCWaypoint < mNumNPCWaypoints ); + rmt::Vector wayPos = mNPCWaypoints[ mCurrNPCWaypoint ]; + + rmt::Vector myPos; + GetCharacter()->GetPosition( myPos ); + + // if player not close enough, don't bother to do this... + rmt::Vector playerPos; + GetAvatarManager()->GetAvatarForPlayer( 0 )->GetPosition( playerPos ); + + const float DIST_PLAYER_TOO_FAR_TO_BOTHER_SQR = 22500.0f; + if( (playerPos-myPos).MagnitudeSqr() >= DIST_PLAYER_TOO_FAR_TO_BOTHER_SQR ) + { + SetDirection( dir ); + mSpeedMps = 0.0f; + mpCharacter->SetSpeed( mSpeedMps ); + return; + } + + // ignore height differences + wayPos.y = myPos.y; + dir = wayPos - myPos; + + float distToWaypoint = dir.Magnitude(); // *** SQUARE ROOT! *** + const float DIST_CLOSE_ENOUGH_TO_NPC_WAYPOINT = 0.5f; + if( distToWaypoint < DIST_CLOSE_ENOUGH_TO_NPC_WAYPOINT ) + { + // increment curr waypoint + mCurrNPCWaypoint++; + if( mCurrNPCWaypoint >= mNumNPCWaypoints ) + { + mCurrNPCWaypoint = 0; // increment for next round + } + + // Non-ped NPCs will want to do some idle anims... Peds + // will just keep on going... + OnReachedWaypoint(); + } + + if( distToWaypoint > 0.0f ) + { + SetDirection( dir / distToWaypoint ); + mSpeedMps = GetFollowPathSpeedMps(); + mpCharacter->SetSpeed( mSpeedMps ); + } +} + +void NPCController::OnReachedWaypoint() +{ + /* + // Reached a waypoint, so do some anim here if we're in the player's + // frustrum + if( mpCharacter->IsVisible() && mpCharacter->mbInAnyonesFrustrum ) + { + PresentationAnimator* npcAnimator = GetPresentationManager()->GetAnimatorNpc(); + Character* oldCharacter = npcAnimator->GetCharacter(); + const bool oldRandomSelection = npcAnimator->GetRandomSelection(); + + npcAnimator->SetCharacter( mpCharacter ); + npcAnimator->SetRandomSelection( true ); + npcAnimator->PlaySpecialAmbientAnimation(); + npcAnimator->SetCharacter( oldCharacter ); + npcAnimator->SetRandomSelection( oldRandomSelection ); + } + */ +} + + + +static const float DODGE_SPEED_MPS = 1.0f; // doesn't seem to matter as long as > 0.0f +static const float CRINGE_SPEED_MPS = 0.0f; + +static const float VEHICLE_SPAN_METERS = 1.5f; +static const float CHARACTER_SPAN_METERS = 0.5f; +static const float COLLISION_LOOK_AHEAD_SECONDS = 1.0f; +static const float COLLISION_DISTANCE_SQUARED = 400.0f; // must be within this to calc collisions + +bool NPCController::Detect( float seconds, float& hisVel ) +{ + hisVel = 0.0f; // init return value to something + + // Clear out previous use of mDodgeInfo + mDodgeInfo.wasSetThisFrame = false; + + + // Things we use over and over again in this function + rmt::Vector playerPos, playerHeading, playerSide, playerUp, playerVel; + rmt::Vector myPos, myHeading, mySide, myUp, myVel; + + + ////////////////////////////////////// + // Information about player + ////////////////////////////////////// + Avatar* avatar = GetAvatarManager()->GetAvatarForPlayer( 0 ); + rAssert( avatar != NULL ); + avatar->GetPosition( playerPos ); + float playerCollisionRadius = + (avatar->IsInCar())? VEHICLE_SPAN_METERS : CHARACTER_SPAN_METERS; + + + ////////////////////////////////////// + // Information about self + ////////////////////////////////////// + mpCharacter->GetPosition( myPos ); + float myCollisionRadius = CHARACTER_SPAN_METERS; + + + ////////////////////////////////////// + // Detect if player will hit us + ////////////////////////////////////// + + // + // CRUDE TEST + // ========== + // Test against hard-coded distanceSquared to see if we're + // close enough to player to even bother to proceed... + // + rmt::Vector myPosToPlayerPos = playerPos - myPos; + if( myPosToPlayerPos.MagnitudeSqr() > COLLISION_DISTANCE_SQUARED ) + { + return false; + } + + // + // MORE DETAILED TEST + // =================== + // Treat everything only on x-z plane. In other words, we won't + // detect a vehicle coming at us from above or below. + // + + // Get more info about player and self + float epsilon = 0.005f; + bool gonnaHit = false; + float spanDistSum = playerCollisionRadius + myCollisionRadius; + + myPos.y = 0.0f; + mpCharacter->GetFacing( myHeading ); + rAssert( rmt::Epsilon(myHeading.MagnitudeSqr(), 1.0f, epsilon) ); + myVel = myHeading * GetSpeedMps(); + myVel.y = 0.0f; + myUp.Set( 0.0f, 1.0f, 0.0f ); + float mySpeedMps = myVel.Length(); // *** SQUARE ROOT! *** + + + playerPos.y = 0.0f; + avatar->GetVelocity( playerVel ); + playerVel.y = 0.0f; + playerUp.Set( 0.0f, 1.0f, 0.0f ); + float playerSpeedMps = playerVel.Length(); // *** SQUARE ROOT! *** + + hisVel = playerSpeedMps; + + ///////////////////////////////////////////////////////// + // If player is stationary + // + if( playerSpeedMps <= epsilon || avatar->GetCharacter()->mbIsPlayingIdleAnim ) + { + mySide.CrossProduct( myUp, myHeading ); + mySide.Normalize(); + + // sanity check lengths to 1.0f. myHeading should not be 0.0f since + // we're in the case where mySpeedMps > 0.0f. mySide shouldn't be 0.0f + // since we made myUp and myHeading orthonormal + + rAssert( rmt::Epsilon( myHeading.MagnitudeSqr(), 1.0f, epsilon ) ); + rAssert( rmt::Epsilon( mySide.MagnitudeSqr(), 1.0f, epsilon ) ); + + float lookAheadDist = GetFollowPathSpeedMps() * COLLISION_LOOK_AHEAD_SECONDS + spanDistSum; + + //float lookAheadDist = mySpeedMps * COLLISION_LOOK_AHEAD_SECONDS + spanDistSum; + bool playerPosLiesOnMyRight; + + gonnaHit = WillCollide( myPos, + myHeading, // Must be normalized to 1 + mySide, // Must be normalized to 1 + spanDistSum, + lookAheadDist, + playerPos, + playerPosLiesOnMyRight ); + } + ////////////////////////////////////////////////////////////// + // If player is not stationary, but I am... + // + else if( playerSpeedMps > epsilon && mySpeedMps <= epsilon ) + { + playerHeading = playerVel / playerSpeedMps; + playerSide.CrossProduct( playerUp, playerHeading ); + playerSide.Normalize(); + + rAssert( rmt::Epsilon( playerHeading.MagnitudeSqr(), 1.0f, epsilon ) ); + rAssert( rmt::Epsilon( playerSide.MagnitudeSqr(), 1.0f, epsilon ) ); + + float lookAheadDist = playerSpeedMps * COLLISION_LOOK_AHEAD_SECONDS + spanDistSum; + bool myPosLiesOnPlayersRight; + + gonnaHit = WillCollide( playerPos, + playerHeading, // Must be normalized to 1 + playerSide, // Must be normalized to 1 + spanDistSum, + lookAheadDist, + myPos, + myPosLiesOnPlayersRight ); + + // + // Store Dodge info to be used in PerformDodge + // because this should be the only control flow that allows you + // to call PerformDodge() later. + // + if( gonnaHit ) + { + mDodgeInfo.wasSetThisFrame = true; + mDodgeInfo.myPosIsOnPlayersRightSide = myPosLiesOnPlayersRight; + mDodgeInfo.playerRightSide = playerSide; + } + } + ///////////////////////////////////////////////////////////// + // If both moving, gotta do full collision test + // + else + { + rAssert( playerSpeedMps > epsilon && mySpeedMps > epsilon ); + // + // Easiest, Crudest way to go about this is to do sphere-sphere + // collision on the 2 motion vectors. We don't need sophisticated + // check because this only provides an early warning about nearby + // activities... + // + + // + // Compute future positions + // + rmt::Vector playerPos2, myPos2; + playerPos2 = playerPos + playerVel * COLLISION_LOOK_AHEAD_SECONDS; + myPos2 = myPos + myVel * COLLISION_LOOK_AHEAD_SECONDS; + + // + // Find midpoints & radii + // + rmt::Vector playerMid, myMid; + playerMid = ( playerPos2 + playerPos ) * 0.5f; + myMid = (myPos2 + myPos ) * 0.5f; + + float playerMotionRadius, myMotionRadius; + playerMotionRadius = playerSpeedMps * COLLISION_LOOK_AHEAD_SECONDS * 0.5f; + myMotionRadius = mySpeedMps * COLLISION_LOOK_AHEAD_SECONDS * 0.5f; + + // + // Test for sphere-sphere collision + // + float minDist = myMotionRadius + playerMotionRadius; + float minDistSqr = minDist * minDist; + float actualDistSqr = (playerMid - myMid).MagnitudeSqr(); + + if( actualDistSqr < minDistSqr ) + { + gonnaHit = true; + } + else + { + gonnaHit = false; + } + + } + return gonnaHit; + +} + + +void NPCController::PerformCringe( ) +{ + SetIntention( Cringe ); + SetSpeedMps( CRINGE_SPEED_MPS ); + + // Determine direction... + rmt::Vector myPos, playerPos; + mpCharacter->GetPosition( myPos ); + + Avatar* avatar = GetAvatarManager()->GetAvatarForPlayer( 0 ); + rAssert( avatar != NULL ); + avatar->GetPosition( playerPos ); + + rmt::Vector toPlayer = playerPos - myPos; + toPlayer.Normalize(); // *** SQUARE ROOT! *** + + SetDirection( toPlayer ); +} + +void NPCController::PerformDodge( ) +{ + if( !mDodgeInfo.wasSetThisFrame ) + { + return; + } + + // NOTE: + // I wouldn't be here unless I transited from STOPPED state, + // meaning my speed was 0.0f when Detect() was called just before + // this method was called, meaning that mDodgeInfo should + // have been set by the control flow through Detect() that + // happens when I am stationary and player is not. + + // Set Controller information + SetIntention( Dodge ); + SetSpeedMps( DODGE_SPEED_MPS ); + + // Determine which way to dodge... + if( mDodgeInfo.myPosIsOnPlayersRightSide ) + { + mNormalizedDodgeDir = mDodgeInfo.playerRightSide; + } + else + { + mNormalizedDodgeDir = mDodgeInfo.playerRightSide * -1; + } + SetDirection( mNormalizedDodgeDir ); + + // + // Say funny ha-ha line + // + GetEventManager()->TriggerEvent( EVENT_PEDESTRIAN_DODGE, mpCharacter ); +} + +void NPCController::GetDirection( rmt::Vector& outDirection ) +{ + // mDirection initialized and kept at 0,0,0 + rAssert( mpCharacter->GetMaxSpeed() > 0.0f ); + outDirection = mDirection * (mSpeedMps/mpCharacter->GetMaxSpeed()); +} + +void NPCController::TransitToState( State state ) +{ + // If in TALKING but transiting to state other than TALKING, + // must StopTalking(). + if( mState == TALKING && state != TALKING ) + { + StopTalking(); + mTalkTarget = NULL; + } + + mpCharacter->SetInSubstep( false ); + + switch( state ) + { + case TALKING: + { + // initially, tell anyone who transits to TALKING to start + // by listening. It's up to the function that sets them TALKING + // to call StartTalking() on one of the parties... + mListening = true; + mSpeedMps = 0.0f; + mpCharacter->SetSpeed( mSpeedMps ); + } + break; + case TALKING_WITH_PLAYER: // fall thru + case STOPPED: // fall thru + case STANDING: // fall thru + case NONE: + { + mSpeedMps = 0.0f; + mpCharacter->SetSpeed( mSpeedMps ); + mSecondsInStopped = 0.0f; + mSecondsSinceLastTurnAnim = 0.0f; + } + break; + case PANICKING: + { + // test to see if we were ever told to transit to panic + // while already in panic. This isn't anything fatal, it's just + // more work than necessary + rAssert( mState != PANICKING ); + + mSpeedMps = NPC_PANIC_RUN_SPEED_MPS; + mpCharacter->SetSpeed( mSpeedMps ); + mSecondsChangePanicDir = 0.0f; + + Avatar* player = GetAvatarManager()->GetAvatarForPlayer( 0 ); + rAssert( player ); + + rmt::Vector myPos, playerPos; + mpCharacter->GetPosition( myPos ); + player->GetPosition( playerPos ); + + rmt::Vector awayFromPlayer = myPos - playerPos; + awayFromPlayer.NormalizeSafe(); // *** SQUARE ROOT! *** + + mNormalizedPanicDir = awayFromPlayer; + } + break; + case DODGING: + { + mpCharacter->SetInSubstep( true ); + } + break; + default: + { + //nothing + } + // Put other cases here if they need specific operations... + } + + mState = state; +} +void NPCController::GetAllowedPathOffset( rmt::Vector& offset ) +{ + offset.Set( 1.0f, 1.0f, 1.0f ); +} + +void NPCController::OnOffPath( rmt::Vector lastPos, rmt::Vector currPos ) +{ + rAssert( mOffPath ); + + // set direction... + rmt::Vector dirBackToPath = lastPos - currPos; + dirBackToPath.y = 0.0f; + + dirBackToPath.Normalize(); // *** SQUARE ROOT! *** + SetDirection( dirBackToPath ); +} + + +void NPCController::StartTalking() +{ + if( mListening && mState == TALKING ) + { + mMouthFlapper->SetCharacter( mpCharacter ); + mMillisecondsInTalk = 0; + mListening = false; + mpCharacter->GetPuppet()->GetEngine()->GetPoseEngine()->AddPoseDriver( 2, mMouthFlapper ); + mMouthFlapper->SetIsEnabled( true ); + } +} +void NPCController::StopTalking() +{ + if( !mListening && mState == TALKING ) + { + mMillisecondsInTalk = 0; + mListening = true; + mMouthFlapper->SetIsEnabled( false ); + mpCharacter->GetPuppet()->GetEngine()->GetPoseEngine()->RemovePoseDriver( 2, mMouthFlapper ); + } +} + +void NPCController::SetTempWaypont(const rmt::Vector& w) +{ + mTempWaypoint = w; + mUseTempWaypoint = true; +} + +void NPCController::ClearTempWaypoint(void) +{ + mUseTempWaypoint = false; +} diff --git a/game/code/worldsim/character/charactercontroller.h b/game/code/worldsim/character/charactercontroller.h new file mode 100644 index 0000000..85431a8 --- /dev/null +++ b/game/code/worldsim/character/charactercontroller.h @@ -0,0 +1,308 @@ +#ifndef CHARACTERCONTROLLER_H_ +#define CHARACTERCONTROLLER_H_ + +#include <p3d/refcounted.hpp> +#include <radmath/radmath.hpp> + +//////// FORWARD DECLARATIONS ///////// +class Character; +class MouthFlapper; +/////////////////////////////////////// + +class CharacterController +: +public tRefCounted +{ +public: + enum eIntention + { + NONE, + LeftStickX, + LeftStickY, + DoAction, // DUSIT [Nov 6,2002]: "Action" conflicts with class name + Jump, + Dash, + Attack, + DPadUp, + DPadDown, + DPadLeft, + DPadRight, +#ifdef RAD_WIN32 + GetOutCar, + MouseLookLeft, + MouseLookRight, +#endif + NUM_INPUTS, // "real" inputs (stuff with buttons) goes + // before this, things that are only triggerd by AI go after + Dodge, + Cringe, + TurnRight, + TurnLeft, + CelebrateSmall, + CelebrateBig, + WaveHello, + WaveGoodbye + }; + + CharacterController( void ); + virtual ~CharacterController( void ); + + virtual void Update( float timeins ) {}; + virtual void GetDirection( rmt::Vector& outDirection ) = 0; + virtual float GetValue( int buttonId ) const = 0; + virtual bool IsButtonDown( int buttonId ) const = 0; + virtual int TimeSinceChange( int buttonId ) const { return IsButtonDown(buttonId) ? 0 : 5000; }; + + Character* GetCharacter( ) const; + virtual void SetCharacter( Character* pCharacter ); + + virtual void SetIntention( eIntention intention ) + { + if( mActive ) + { + mIntention = intention; + } + } + eIntention GetIntention( void ) const + { + return mIntention; + } + + void PreserveIntention( eIntention intention ) + { + mPreserveIntention = intention; + } + + void ClearIntention( void ) + { + mIntention = mPreserveIntention; + mPreserveIntention = NONE; + } + + void SetActive( bool active ) { mActive = active; } + +protected: + Character* mpCharacter; + eIntention mIntention; + eIntention mPreserveIntention; + bool mActive; +}; + + +//////////////////////////////////////////////////////////// +// Dusit +//////////////////////////////////////////////////////////// + +class NPCController +: +public CharacterController +{ +public: // METHODS + NPCController( void ); + virtual ~NPCController( void ); + + virtual void Update( float seconds ); + virtual float GetSpeedMps() const; + virtual void SetSpeedMps( float fSpeedMps ); + virtual void SetDirection( const rmt::Vector& inDirection ); + virtual void GetDirection( rmt::Vector& outDirection ); + virtual float GetValue( int buttonId ) const; + virtual bool IsButtonDown( int buttonId ) const; + + void SetCharacter( Character *pCharacter ); + + enum State + { + // We enumerate here, all the different states specific to + // NPC behavior controller... which are essentially substates + // of the character's locomotion state (in character's statemanager) + + FOLLOWING_PATH, // following predescribed path + STOPPED, // not moving, turns to track player, will resume following path + DODGING, // uh... dodging + CRINGING, // if we're not dodging, we're cringing... + TALKING, // when peds want to talk to one another! + STANDING, // just not moving, will resume following path + PANICKING, // running in random direction, more or less away from player + TALKING_WITH_PLAYER, // just for conversations.. + NONE, // limbo... do nothing... stand there... don't resume following path + NUM_STATES + }; + virtual State GetState() const + { + return mState; + } + void TransitToState( State state ); + + bool AddNPCWaypoint( const rmt::Vector& pt ); + void ClearNPCWaypoints(); + + void StartTalking(); + void StopTalking(); + void IncitePanic(); + void QuellPanic(); + + void TeleportToPath(void); + void SetTempWaypont(const rmt::Vector&); + void ClearTempWaypoint(void); + +public: // MEMBERS + + enum { MAX_NPC_WAYPOINTS = 32 }; + + bool mOffPath; + MouthFlapper* mMouthFlapper; + int mMillisecondsInTalk; + NPCController* mTalkTarget; + bool mListening; // if in talking state but mouth not flapping + +protected: // METHODS + + virtual void OnOffPath( rmt::Vector lastPos, rmt::Vector currPos ); + virtual void GetAllowedPathOffset( rmt::Vector& offset ); + virtual float GetFollowPathSpeedMps() const; + virtual void FollowPath( float seconds ); + virtual void OnReachedWaypoint(); + + // detect collision with player vehicle and set dodge intention. + // returns true if need to dodge, false otherwise... + virtual void DetectAndDodge( float seconds ); + virtual void TraversePath( float seconds ); + + bool Detect( float seconds, float& hisVel ); + void PerformDodge(); + void PerformCringe(); + +protected: // MEMBERS + + // This struct is filled in the Detect call before PerformDodge is called + // and used from within PerformDodge to determine direction of dodge. + struct DodgeInfo + { + bool wasSetThisFrame; + rmt::Vector playerRightSide; + bool myPosIsOnPlayersRightSide; + }; + DodgeInfo mDodgeInfo; + + State mState; + float mSecondsInStopped; + float mSpeedMps; + rmt::Vector mNormalizedDodgeDir; // Normalized + + rmt::Vector mNPCWaypoints[ MAX_NPC_WAYPOINTS ]; + int mNumNPCWaypoints; + int mCurrNPCWaypoint; + + bool mStartPanicking; + float mSecondsChangePanicDir; + rmt::Vector mNormalizedPanicDir; + + float mSecondsSinceLastTurnAnim; // while in STOPPED state, we sometimes play turn animations + + bool mUseTempWaypoint; + rmt::Vector mTempWaypoint; + +private: // METHODS + +private: // MEMBERS + rmt::Vector mDirection; +}; + +inline void NPCController::ClearNPCWaypoints() +{ + mNumNPCWaypoints = 0; + mCurrNPCWaypoint = -1; +} + +inline float NPCController::GetSpeedMps() const +{ + return mSpeedMps; +} +inline void NPCController::SetSpeedMps( float fSpeedMps ) +{ + mSpeedMps = fSpeedMps; +} +inline void NPCController::SetDirection( const rmt::Vector& inDirection ) +{ + mDirection = inDirection; +} +inline float NPCController::GetValue( int buttonId ) const +{ + return 0.0f; +} +inline bool NPCController::IsButtonDown( int buttonId ) const +{ + return false; +} + +///////////////////////////////////////////////////////////// + + + + +class CharacterMappable; +class tCamera; + +class PhysicalController +: +public CharacterController +{ +public: + PhysicalController( void ); + virtual ~PhysicalController( void ); + + virtual void GetDirection( rmt::Vector& outDirection ); + virtual float GetValue( int buttonId ) const; + virtual bool IsButtonDown( int buttonId ) const; + virtual int TimeSinceChange( int buttonId ) const; + virtual void SetIntention( eIntention intention ) {}; + + CharacterMappable* GetCharacterMappable( void ) const; + void SetCharacterMappable( CharacterMappable* pMappable ); +private: + CharacterMappable* mpCharacterMappable; +}; + +////////////////////////////////////////////////////////////////////////// +// +// +// +// +// TBJ [8/9/2002] +// +////////////////////////////////////////////////////////////////////////// + +class CameraRelativeCharacterControllerEventHandler; + +class CameraRelativeCharacterController +: +public PhysicalController +{ +public: + + CameraRelativeCharacterController( void ); + virtual ~CameraRelativeCharacterController( void ); + + void Create( Character* pCharacter, CharacterMappable* pCharacterMappable ); + void GetDirection( rmt::Vector& outDirection ); + virtual void SetIntention( eIntention intention ); + + void SetCamera( tCamera* pCamera ); + + +protected: + friend class CameraRelativeCharacterControllerEventHandler; + void HandleEvent( int id, void* pEventData ); +private: + + // Don't much like this pointer here. + // + tCamera* mpCamera; + CameraRelativeCharacterControllerEventHandler* mpEventHandler; + bool mbCameraChange; + rmt::Vector mLastDirection; + rmt::Matrix mLastCameraMatrix; +}; + +#endif // CHARACTERCONTROLLER_H_ diff --git a/game/code/worldsim/character/charactermanager.cpp b/game/code/worldsim/character/charactermanager.cpp new file mode 100644 index 0000000..e5f003b --- /dev/null +++ b/game/code/worldsim/character/charactermanager.cpp @@ -0,0 +1,2488 @@ +#include <worldsim/character/charactermanager.h> +#include <worldsim/character/character.h> +#include <worldsim/character/characterrenderable.h> +#include <worldsim/worldphysicsmanager.h> +#include <worldsim/vehiclecentral.h> +#include <worldsim/redbrick/vehicle.h> + +#include <ai/actionbuttonmanager.h> +#include <ai/actionbuttonhandler.h> +#include <events/eventenum.h> +#include <events/eventmanager.h> +#include <meta/locatorevents.h> +#include <meta/eventlocator.h> +#include <meta/triggervolume.h> +#include <meta/carstartlocator.h> + +#include <camera/supercamcentral.h> +#include <camera/supercammanager.h> + +#include <memory/srrmemory.h> + +#include <raddebug.hpp> +#include <raddebugwatch.hpp> + +#include <p3d/anim/drawablepose.hpp> +#include <p3d/shader.hpp> +#include <p3d/shadow.hpp> + +// Choreo includes. +// +#include <p3d/anim/pose.hpp> +#include <choreo/bank.hpp> +#include <choreo/load.hpp> +#include <choreo/puppet.hpp> +#include <choreo/utility.hpp> +#include <simcommon/simstate.hpp> +#include <simcommon/simulatedobject.hpp> +#include <simcollision/collisionobject.hpp> + +#include <console/console.h> + +#include <render/RenderManager/RenderManager.h> +#include <render/RenderManager/RenderLayer.h> +// Temp hack to get NPCs in. +// TBJ [9/3/2002] +// +#include <mission/gameplaymanager.h> +#include <constants/maxplayers.h> + +#include <stdlib.h> +#include <debug/profiler.h> +#include <worldsim/avatarmanager.h> + +#include <radtime.hpp> +#include <p3d/anim/skeleton.hpp> + +#include <ai/sequencer/actioncontroller.h> +#include <ai/sequencer/action.h> +#include <ai/sequencer/sequencer.h> +#include <presentation/blinker.h> + +#include <presentation/gui/ingame/guimanageringame.h> +#include <presentation/gui/ingame/guiscreenhud.h> +#include <mission/charactersheet/charactersheetmanager.h> + +CharacterManager* CharacterManager::spCharacterManager = 0; + +/************ OUTPUT_TIMES FOR DLOP **********/ +//#define OUTPUT_TIMES +/**********************************************/ + +// Init the character tunables. +// +// Statics. +// +float CharacterTune::sfLocoRotateRate; +float CharacterTune::sfLocoAcceleration; +float CharacterTune::sfLocoDecceleration; +bool CharacterTune::bLocoTest; +float CharacterTune::sfAirRotateRate; +float CharacterTune::sfAirAccelScale; +float CharacterTune::sfAirGravity; +float CharacterTune::sfStompGravityScale; +float CharacterTune::sfDashBurstMax; +float CharacterTune::sfDashAcceleration; +float CharacterTune::sfDashDeceleration; +float CharacterTune::sfJumpHeight; +float CharacterTune::sfDoubleJumpHeight; +float CharacterTune::sfDoubleJumpAllowUp; +float CharacterTune::sfDoubleJumpAllowDown; + +float CharacterTune::sfHighJumpHeight; +float CharacterTune::sfMaxSpeed; +float CharacterTune::sfTurboRotateRate; + +float CharacterTune::sfGetInOutOfCarAnimSpeed; +rmt::Vector CharacterTune::sGetInPosition; +float CharacterTune::sGetInHeightThreshold; +float CharacterTune::sGetInOpenDelay; +float CharacterTune::sGetInOpenSpeed; +float CharacterTune::sGetInCloseDelay; +float CharacterTune::sGetInCloseSpeed; +float CharacterTune::sGetOutOpenDelay; +float CharacterTune::sGetOutOpenSpeed; +float CharacterTune::sGetOutCloseDelay; +float CharacterTune::sGetOutCloseSpeed; +float CharacterTune::sfKickingForce; +float CharacterTune::sfSlamForce; +// How long in seconds that the character will get shocked for +float CharacterTune::sfShockTime; + +bool CharacterManager::sbFixedSimRate; + +static const unsigned INVALID_LOAD = 0xffffffff; + +Blinker g_Blinkers[ 64 ]; //should match max_characters + +/* +============================================================================== +CharacterManager::CreateInstance +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterManager + +============================================================================= +*/ +CharacterManager* CharacterManager::CreateInstance( void ) +{ + rAssertMsg( spCharacterManager == 0, "CharacterManager already created.\n" ); +#ifdef RAD_GAMECUBE + spCharacterManager = new ( GMA_GC_VMM ) CharacterManager; +#else + spCharacterManager = new ( GMA_PERSISTENT ) CharacterManager; +#endif + + return( spCharacterManager ); +} +/* +============================================================================== +CharacterManager::GetInstance +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterManager + +============================================================================= +*/ +CharacterManager* CharacterManager::GetInstance( void ) +{ + rAssertMsg( spCharacterManager != 0, "CharacterManager has not been created yet.\n" ); + return spCharacterManager; +} +/* +============================================================================== +CharacterManager::DestroyInstance +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void CharacterManager::DestroyInstance( void ) +{ + rAssertMsg( spCharacterManager != 0, "CharacterManager has not been created.\n" ); + delete ( GMA_PERSISTENT, spCharacterManager ); +} + +/* +============================================================================== +CharacterManager::CharacterManager +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterManager + +============================================================================= +*/ +CharacterManager::CharacterManager( void ) +{ + int i; + for ( i = 0; i < MAX_CHARACTERS; i++ ) + { + mpCharacter[ i ] = 0; + mGarbage[i] = false; + } + + GetEventManager()->AddListener( this, (EventEnum)( EVENT_LOCATOR + LocatorEvent::CAR_DOOR ) ); + GetEventManager()->AddListener( this, (EventEnum)( EVENT_LOCATOR + LocatorEvent::BOUNCEPAD ) ); + GetEventManager()->AddListener( this, (EventEnum)( EVENT_LOCATOR + LocatorEvent::GENERIC_BUTTON_HANDLER_EVENT ) ); + GetEventManager()->AddListener( this, EVENT_DEATH_VOLUME_SCREEN_BLANK ); + GetEventManager()->AddListener( this, EVENT_STAGE_COMPLETE ); + GetEventManager()->AddListener( this, EVENT_MISSION_SUCCESS ); + GetEventManager()->AddListener( this, EVENT_CARD_COLLECTED ); + GetEventManager()->AddListener( this, EVENT_MISSION_CHARACTER_RESET ); + GetEventManager()->AddListener( this, EVENT_GETINTOVEHICLE_START ); + GetEventManager()->AddListener( this, EVENT_GETOUTOFVEHICLE_END ); + GetEventManager()->AddListener( this, EVENT_TOGGLE_FIRSTPERSON ); + + CharacterTune::sfLocoRotateRate = 10.0f; + CharacterTune::sfLocoAcceleration = 20.0f; + CharacterTune::sfLocoDecceleration = -10.0f; + + CharacterTune::bLocoTest = false; + + CharacterTune::sfAirRotateRate = 4.0f; + CharacterTune::sfAirAccelScale = 0.078f; //was .1 + CharacterTune::sfAirGravity = -25.0f; //was -30 + CharacterTune::sfStompGravityScale = 3.22f; + CharacterTune::sfJumpHeight = 1.9f; //was 1.65 + CharacterTune::sfDoubleJumpHeight = 1.0f; + CharacterTune::sfDoubleJumpAllowUp = 2.0f; + CharacterTune::sfDoubleJumpAllowDown = 12.0f; //was 6 + CharacterTune::sfHighJumpHeight = 1.9f; + + CharacterTune::sfDashBurstMax = 4.0f; // 7.0 + CharacterTune::sfDashAcceleration = 200.0f; + CharacterTune::sfDashDeceleration = 200.0f; + CharacterTune::sfMaxSpeed = 4.0f; + CharacterTune::sGetInPosition.Set( 1.05f, 0.0f, -0.86f ); + CharacterTune::sGetInHeightThreshold = 0.1f; + CharacterTune::sGetInOpenDelay = 0.0f; + CharacterTune::sGetInOpenSpeed = 0.2f; + CharacterTune::sGetInCloseDelay = 0.0f; + CharacterTune::sGetInCloseSpeed = 0.2f; + CharacterTune::sGetOutOpenDelay = 0.0f; + CharacterTune::sGetOutOpenSpeed = 0.2f; + CharacterTune::sGetOutCloseDelay = 0.0f; + CharacterTune::sGetOutCloseSpeed = 0.2f; + + CharacterTune::sfTurboRotateRate = 2.0f; // 1.2 + + CharacterTune::sfGetInOutOfCarAnimSpeed = 30.0f; + CharacterTune::sfKickingForce = 400.0f; + CharacterTune::sfSlamForce = 800.0f; + + CharacterManager::sbFixedSimRate = false; + + Console* pConsole = GetConsole( ); + rAssert( pConsole ); + if ( pConsole ) + { + pConsole->AddFunction( "SetCharacterPosition", CharacterManager::SetCharacterPosition, "Sets the character position", 3, 3 ); + pConsole->AddFunction( "ResetCharacter", CharacterManager::ResetCharacter, "Sets the character to the named locator", 2, 2 ); + pConsole->AddFunction( "AddTeleportDest", AddTeleportDest, "Set a valid location for a teleport", 3, 5 ); + + pConsole->AddFunction( "SetInitialWalk", SetInitialWalk, "Set locator to walk to on startup", 1, 1 ); + } +#ifdef DEBUGWATCH + const int MAX_LEN = 64; + char debugName[ MAX_LEN ]; + int len = sprintf( debugName, "Character" ); + rAssert( len < MAX_LEN ); + + radDbgWatchAddBoolean(&CharacterTune::bLocoTest, "Steer Facing", debugName, NULL, 0 ); + radDbgWatchAddFloat(&CharacterTune::sfLocoRotateRate, "Loco Turning Rate", debugName, NULL, 0, 0.1f, 50.0f); + radDbgWatchAddFloat(&CharacterTune::sfLocoAcceleration, "Loco Acceleration", debugName, NULL, 0, 0.1f, 50.0f); + radDbgWatchAddFloat(&CharacterTune::sfLocoDecceleration, "Loco Deceleration", debugName, NULL, 0, -50.0f, -0.1f); + + radDbgWatchAddFloat(&CharacterTune::sfAirRotateRate, "Air Turning Rate", debugName, NULL, 0, 0.0f, 20.0f); + radDbgWatchAddFloat(&CharacterTune::sfAirAccelScale, "Air Acceleration", debugName, NULL, 0, 0.0f, 1.0f ); + radDbgWatchAddFloat(&CharacterTune::sfAirGravity, "Air Gravity", debugName, NULL, 0, -100.0f, 0.1f ); + radDbgWatchAddFloat(&CharacterTune::sfStompGravityScale, "Stomp Gravity Scale", debugName, NULL, 0, 0.1f, 4.0f); + radDbgWatchAddFloat(&CharacterTune::sfJumpHeight, "Jump Height (Tap)", debugName, NULL, 0, 0.0f, 100.0f ); + radDbgWatchAddFloat(&CharacterTune::sfDoubleJumpHeight, "Jump Height (Double Jump)", debugName, NULL, 0, 0.0f, 100.0f ); + radDbgWatchAddFloat(&CharacterTune::sfDoubleJumpAllowUp, "Double Jump Sweet Spot (Up)", debugName, NULL, 0, 0.0f, 30.0f ); + radDbgWatchAddFloat(&CharacterTune::sfDoubleJumpAllowDown, "Double Jump Sweet Spot (Down)", debugName, NULL, 0, 0.0f, 30.0f ); + radDbgWatchAddFloat(&CharacterTune::sfHighJumpHeight, "Jump Height (Full Press)", debugName, NULL, 0, 0.0f, 100.0f ); + + radDbgWatchAddFloat(&CharacterTune::sfDashAcceleration, "Dash Accel", debugName, NULL, 0, 0.0f, 200.0f ); + radDbgWatchAddFloat(&CharacterTune::sfDashDeceleration, "Dash Decel", debugName, NULL, 0, 0.0f, 200.0f ); + + radDbgWatchAddFloat(&CharacterTune::sfMaxSpeed, "Max Speed", debugName, NULL, 0, 0.0f, 8.0f ); + + radDbgWatchAddFloat((float*)&CharacterTune::sGetInPosition.x, "Get In Offset X", "Get In/Get Out", NULL, 0, 0.0f, 3.0f ); + radDbgWatchAddFloat((float*)&CharacterTune::sGetInPosition.z, "Get In Offset Z", "Get In/Get Out", NULL, 0, -1.5f, 1.5f ); + radDbgWatchAddFloat(&CharacterTune::sGetInHeightThreshold, "Height Threshold", "Get In/Get Out", NULL, 0, -2.0f, 2.0f ); + radDbgWatchAddFloat(&CharacterTune::sfGetInOutOfCarAnimSpeed, "Speed", "Get In/Get Out", NULL, 0, 15.0f, 240.0f ); + radDbgWatchAddFloat(&CharacterTune::sGetInOpenDelay, "Get In Open Delay", "Get In/Get Out", NULL, 0, 0.0f, 2.0f ); + radDbgWatchAddFloat(&CharacterTune::sGetInOpenSpeed, "Get In Open Speed", "Get In/Get Out", NULL, 0, 0.0f, 2.0f ); + radDbgWatchAddFloat(&CharacterTune::sGetInCloseDelay, "Get In Close Delay", "Get In/Get Out", NULL, 0, 0.0f, 2.0f ); + radDbgWatchAddFloat(&CharacterTune::sGetInCloseSpeed, "Get In Close Speed", "Get In/Get Out", NULL, 0, 0.0f, 2.0f ); + radDbgWatchAddFloat(&CharacterTune::sGetOutOpenDelay, "Get Out Open Delay", "Get In/Get Out", NULL, 0, 0.0f, 2.0f ); + radDbgWatchAddFloat(&CharacterTune::sGetOutOpenSpeed, "Get Out Open Speed", "Get In/Get Out", NULL, 0, 0.0f, 2.0f ); + radDbgWatchAddFloat(&CharacterTune::sGetOutCloseDelay, "Get Out Close Delay", "Get In/Get Out", NULL, 0, 0.0f, 2.0f ); + radDbgWatchAddFloat(&CharacterTune::sGetOutCloseSpeed, "Get Out Close Speed", "Get In/Get Out", NULL, 0, 0.0f, 2.0f ); + + radDbgWatchAddBoolean( &CharacterManager::sbFixedSimRate, "Fixed sim rate", debugName, NULL, 0 ); + + radDbgWatchAddFloat(&CharacterTune::sfDashBurstMax, "Turbo Speed", debugName, NULL, 0, 0.0f, 100.0f ); + radDbgWatchAddFloat(&CharacterTune::sfTurboRotateRate, "Turbo Rotate Rate", debugName, NULL, 0, 0.0f, 10.0f ); + + radDbgWatchAddString(sCharacterToSpawn, 64, "NPC to Spawn", "CharacterManager"); + radDbgWatchAddFunction( "Spawn NPC", &Spawn, NULL, "CharacterManager"); + + radDbgWatchAddFunction( "Next Skin", &NextSkin, NULL, "CharacterManager"); + radDbgWatchAddFloat(&CharacterTune::sfKickingForce, "Kicking Force", debugName, NULL, 0, 0.0f, 2000.0f ); + radDbgWatchAddFloat(&CharacterTune::sfSlamForce, "Stomping Force", debugName, NULL, 0, 0.0f, 4000.0f ); + radDbgWatchAddFloat(&CharacterTune::sfShockTime, "Time(sec) to be shocked",debugName , NULL, NULL ,0 , 10.0f); + +#ifdef RAD_XBOX + extern float g_XBoxMipmapBias; + radDbgWatchAddFloat(&g_XBoxMipmapBias, "Mipmap Bias", "XBox", NULL, NULL , -10.0f , 10.0f); +#endif + +#endif //DEBUGWATCH + + strcpy(mDummyLoadData.modelName, "npd"); + strcpy(mDummyLoadData.animName, "npd"); + FillLoadData( mDummyLoadData, "npd", "npd"); + + mGarbageCollect = false; + + mUniversalPose = new tPose(64); + mUniversalPose->AddRef(); + + mNumCharactersAdded = 0; +} + +CharacterManager::~CharacterManager( void ) +{ + Destroy(true); + tRefCounted::Release(mUniversalPose); + GetEventManager()->RemoveAll( this ); +} + +void CharacterManager::PreLoad( void ) +{ + // Load the global textures + char fname[128]; + sprintf(fname, "art\\chars\\global.p3d"); + char section[128]; + sprintf(section, "Eakkachaichanvet"); + + p3d::inventory->AddSection(section); + GetLoadingManager()->AddRequest( FILEHANDLER_PURE3D, fname, GMA_LEVEL_OTHER, section ); + + // Load in a dummy, lightweight model + LoadModel("nps"); + LoadModel("ndr"); + LoadModel("npd"); + + // these animation banks are loaded all the time + LoadAnimation("nps"); + LoadAnimation("ndr"); + LoadAnimation("npd"); +} + + +void CharacterManager::Destroy(bool permenant) +{ + mUniversalPose->SetSkeleton(NULL); + + ClearTeleportDests(); + + if(permenant) + { + p3d::inventory->RemoveSectionElements( "Eakkachaichanvet" ); + p3d::inventory->DeleteSection( "Eakkachaichanvet" ); + } + + int i; + for ( i = 0; i < MAX_CHARACTERS; i++ ) + { + g_Blinkers[ i ].SetCharacter( NULL ); + if ( mpCharacter[ i ] != 0 ) + { + GetWorldPhysicsManager()->RemoveFromAnyOtherCurrentDynamicsListAndCollisionArea(mpCharacter[i]); + mpCharacter[ i ]->RemoveFromWorldScene( ); + mpCharacter[ i ]->ClearAllActionButtonHandlers(); + mpCharacter[ i ]->SetManaged(false); + mpCharacter[ i ]->Release(); + mpCharacter[ i ] = 0; + } + + // Clean out the load data names, to make sure we clean up all memory in use + // + mCharacterLoadData[ i ].modelSection.SetText (0); + mCharacterLoadData[ i ].animSection.SetText (0); + mCharacterLoadData[ i ].animModelSection.SetText (0); + mCharacterLoadData[ i ].mModelHigh.SetText (0); + mCharacterLoadData[ i ].mModelMedium.SetText (0); + mCharacterLoadData[ i ].mModelLow.SetText (0); + mCharacterLoadData[ i ].mChoreoName.SetText (0); + } + + for ( i = permenant ? 0 : 3; i < MAX_LOADS; i++ ) + { + rAssert(mModelData[i].state != LOADING); + rAssert(mAnimData[i].state != LOADING); + + if(mModelData[i].state == LOADED || mModelData[i].state == GARBAGE ) + { + p3d::inventory->RemoveSectionElements(mModelData[i].section); + p3d::inventory->DeleteSection(mModelData[i].section); + } + + if(mAnimData[i].state == LOADED || mAnimData[i].state == GARBAGE ) + { + p3d::inventory->RemoveSectionElements(mAnimData[i].section); + p3d::inventory->DeleteSection(mAnimData[i].section); + } + + mModelData[i].state = NOT_LOADED; + mAnimData[i].state = NOT_LOADED; + } + + mNumCharactersAdded = 0; +} + +void CharacterManager::RemoveCharacter( Character* c) +{ + rAssert(c); + if(c) + { + for ( int i = 0; i < MAX_CHARACTERS; i++ ) + { + if ( mpCharacter[ i ] == c ) + { + if(mpCharacter[ i ]->GetRole() == Character::ROLE_COMPLETED_BONUS) + { + GetVehicleCentral()->RemoveSuppressedDriver(this->mRealModelNames[i]); + } + + // if you get this assert, please leave it running and go get Nigel or Dusit + rAssert(mpCharacter[ i ]->GetRole() != Character::ROLE_PEDESTRIAN); + + g_Blinkers[ i ].SetCharacter( NULL ); + GetWorldPhysicsManager()->RemoveFromAnyOtherCurrentDynamicsListAndCollisionArea(mpCharacter[i]); + mpCharacter[ i ]->RemoveFromWorldScene( ); + GetRenderManager()->mEntityDeletionList.Add((tRefCounted*&)mpCharacter[ i ]); + mpCharacter[ i ]->SetManaged(false); + mpCharacter[ i ] = 0; + + GarbageCollectModel(mCharacterModelData[i]); + GarbageCollectAnim(mCharacterAnimData[i]); + return; + } + } + } +} + + +void CharacterManager::SetGarbage(Character* c, bool garbage) +{ + for( int i = 0; i < MAX_CHARACTERS; i++ ) + { + if ( mpCharacter[ i ] == c) + { + mGarbage[i] = garbage; + } + } +} + +void CharacterManager::GarbageCollectModel(unsigned modelIndex) +{ + bool modelUsed = (modelIndex < 3); // first three animation are npd, ndr, nps, don't garbage colelct + + for ( int j = 0; (j < MAX_CHARACTERS) && !modelUsed; j++ ) + { + if(mpCharacter[j]) + { + modelUsed |= modelIndex == mCharacterModelData[j]; + } + } + + if(!modelUsed) + { + if(mModelData[modelIndex].state == LOADING) + { + mModelData[modelIndex].state = LOADING_GARBAGE; + mModelData[modelIndex].gracePeriod = 3.0f; + } + else + { + mModelData[modelIndex].state = GARBAGE; + mModelData[modelIndex].gracePeriod = 3.0f; + } + } +} + +void CharacterManager::GarbageCollectAnim(unsigned animIndex) +{ + bool animUsed = (animIndex < 3); // first three animation are npd, ndr, nps, don't garbage colelct + + for ( int j = 0; (j < MAX_CHARACTERS) && !animUsed; j++ ) + { + if(mpCharacter[j]) + { + animUsed |= animIndex == mCharacterAnimData[j]; + } + } + + if(!animUsed) + { + if(mAnimData[animIndex].state == LOADING) + { + mAnimData[animIndex].state = LOADING_GARBAGE; + } + else + { + mAnimData[animIndex].state = GARBAGE; + } + + } +} + +bool CharacterManager::IsModelLoaded(const char* name) +{ + unsigned index = FindLoad(mModelData, tEntity::MakeUID(name)); + if( index != INVALID_LOAD && mModelData[index].state == LOADED ) + { + return true; + } + return false; + + //return FindLoad(mModelData, tEntity::MakeUID(name)) != INVALID_LOAD; +} + +bool CharacterManager::IsAnimLoaded(const char* name) +{ + unsigned index = FindLoad(mAnimData, tEntity::MakeUID(name)); + if( index != INVALID_LOAD && mAnimData[index].state == LOADED ) + { + return true; + } + return false; + + //return FindLoad(mAnimData, tEntity::MakeUID(name)) != INVALID_LOAD; +} + +unsigned CharacterManager::LoadModel(const char* model, LoadingManager::ProcessRequestsCallback* callback, void* userData) +{ + tUID uid = tEntity::MakeUID(model); + unsigned loadIndex = AllocLoad(mModelData, uid); + + + if(mModelData[loadIndex].state == GARBAGE) + { + mModelData[loadIndex].state = LOADED; + } + + if(mModelData[loadIndex].state == LOADING_GARBAGE) + { + mModelData[loadIndex].state = LOADING; + } + + if((mModelData[loadIndex].state == LOADED) && callback) + { + callback->OnProcessRequestsComplete(userData); + } + else + { + mModelData[loadIndex].callback = callback; + mModelData[loadIndex].userData = userData; + } + + if(mModelData[loadIndex].state == NOT_LOADED) + { + char truncModel[ 32 ]; + + // filenames are truncated at 6 characters + strcpy(truncModel, model); + truncModel[6] = 0; + + char modelName[128]; + sprintf(modelName, "art\\chars\\%s_m.p3d", truncModel); + char modelSection[128]; + sprintf(modelSection, "%s_m", model); + + mModelData[loadIndex].name = uid; + mModelData[loadIndex].section = tEntity::MakeUID(modelSection); + mModelData[loadIndex].state = LOADING; + + HeapMgr()->PushHeap( GMA_LEVEL_OTHER ); + p3d::inventory->AddSection(modelSection); + HeapMgr()->PopHeap( GMA_LEVEL_OTHER ); + + GetLoadingManager()->AddRequest( FILEHANDLER_PURE3D, modelName, GMA_CHARS_AND_GAGS, modelSection); + GetLoadingManager()->AddCallback(this, (void*)loadIndex); + } + + return loadIndex; +} + +unsigned CharacterManager::LoadAnimation(const char* anim) +{ + tUID uid = tEntity::MakeUID(anim); + unsigned loadIndex = AllocLoad(mAnimData, uid); + + if(mAnimData[loadIndex].state == GARBAGE) + { + mAnimData[loadIndex].state = LOADED; + } + + if(mAnimData[loadIndex].state == LOADING_GARBAGE) + { + mAnimData[loadIndex].state = LOADING; + } + + if(mAnimData[loadIndex].state == NOT_LOADED) + { + char truncAnim[ 32 ]; + + // filenames are truncated at 6 characters + strcpy(truncAnim, anim); + truncAnim[6] = 0; + + char animName[128]; + char choreoName[128]; + sprintf(animName, "art\\chars\\%s_a.p3d", truncAnim); + sprintf(choreoName, "art\\chars\\%s.cho", anim); + char animSection[128]; + sprintf(animSection, "%s_a", anim); + + mAnimData[loadIndex].name = uid; + mAnimData[loadIndex].section = tEntity::MakeUID(animSection); + mAnimData[loadIndex].state = LOADING; + + HeapMgr()->PushHeap( GMA_TEMP ); + p3d::inventory->AddSection(animSection); + HeapMgr()->PopHeap( GMA_TEMP ); + GetLoadingManager()->AddRequest( FILEHANDLER_PURE3D, animName, GMA_LEVEL_OTHER, animSection); + GetLoadingManager()->AddRequest( FILEHANDLER_CHOREO, choreoName, GMA_LEVEL_OTHER, animSection); + GetLoadingManager()->AddCallback(this, (void*)(0x10000000 | loadIndex)); + } + + return loadIndex; +} + +unsigned CharacterManager::FindLoad(Load* loads, tUID name) +{ + for(int i = 0; i < MAX_LOADS; i++) + { + if(loads[i].name == name) + { + return i; + } + } + + return INVALID_LOAD; +} + +unsigned CharacterManager::AllocLoad(Load* loads, tUID name) +{ + unsigned index = FindLoad(loads, name); + + if(index == INVALID_LOAD) + { + for(int i = 0; i < MAX_LOADS; i++) + { + if(loads[i].state == NOT_LOADED) + { + loads[i].name = name; + return i; + } + } + } + else + { + return index; + } + + rAssertMsg(0, "Can't load any more character data"); + return INVALID_LOAD; +} + +CharacterManager::LoadState CharacterManager::GetState(Load* loads, tUID name) +{ + unsigned index = FindLoad(loads, name); + if(index != INVALID_LOAD) + { + return loads[index].state; + } + return NOT_LOADED; +} + +void CharacterManager::FillLoadData( CharacterLoadData& data, const char* useModel, const char* useAnim) +{ + const int MAX_LEN = 32; + char modelName[ MAX_LEN ]; + + int len = sprintf( modelName, "%s_h", useModel); + rAssert( len < MAX_LEN ); + data.mModelHigh.SetText( modelName ); + + len = sprintf( modelName, "%s_m", useModel ); + rAssert( len < MAX_LEN ); + data.mModelMedium.SetText( modelName ); + + len = sprintf( modelName, "%s_l", useModel ); + rAssert( len < MAX_LEN ); + data.mModelLow.SetText( modelName ); + + data.mChoreoName.SetText( useAnim ); + + char sec[256]; + sprintf(sec, "%s_m", useModel); + data.modelSection.SetText(sec); + sprintf(sec, "%s_a", useAnim); + data.animSection.SetText(sec); + sprintf(sec, "%s_m", useAnim); + data.animModelSection.SetText(sec); +} + +Character* CharacterManager::AddCharacter( CharacterType type, const char* characterName, const char* modelName, const char* choreoPuppet, const char* location) +{ + unsigned int addCharTime = radTimeGetMicroseconds(); + + + unsigned int MakeUIDTime = radTimeGetMicroseconds(); + tUID modelUID = tEntity::MakeUID(modelName); + tUID animUID = tEntity::MakeUID(choreoPuppet); + MakeUIDTime = radTimeGetMicroseconds() - MakeUIDTime; + + Character* character; + + +// #ifdef RAD_GAMECUBE +// HeapMgr()->PushHeap( GMA_GC_VMM ); +// #else + HeapMgr()->PushHeap( GMA_LEVEL_OTHER ); +// #endif + + unsigned int NewTime = radTimeGetMicroseconds(); + if(type == PC) + { + if( (int)mNumCharactersAdded >= GetGameplayManager()->GetNumPlayers() ) + { + rReleasePrintf("Tried to add too many PCs, not supported right now. Check level scrips for multiple AddCharacter calls.\n"); +// #ifdef RAD_GAMECUBE +// HeapMgr()->PopHeap( GMA_GC_VMM ); +// #else + HeapMgr()->PopHeap( GMA_LEVEL_OTHER ); +// #endif + return NULL; + } + MEMTRACK_PUSH_GROUP( "CharacterManager - Add PC" ); + character = new Character; + character->mbAllowUnload = false; + //character->SetSimpleShadow( false ); + } + else + { + rAssert( mNumCharactersAdded == (unsigned int)GetGameplayManager()->GetNumPlayers() ); + + MEMTRACK_PUSH_GROUP( "CharacterManager - Add NPC" ); + character = new NPCharacter; + } + + if((strcmp(modelName, "lisa") == 0) || (strncmp(modelName, "l_", 2) == 0)) + { + character->SetIsLisa(true); + } + + if((strcmp(modelName, "marge") == 0) || (strncmp(modelName, "m_", 2) == 0)) + { + character->SetIsMarge(true); + } + + rAssert( character ); + NewTime = radTimeGetMicroseconds() - NewTime; + +// #ifdef RAD_GAMECUBE +// HeapMgr()->PopHeap( GMA_GC_VMM ); +// #else + HeapMgr()->PopHeap( GMA_LEVEL_OTHER ); +// #endif + + unsigned int SetNameTime = radTimeGetMicroseconds(); + character->SetName( characterName ); + SetNameTime = radTimeGetMicroseconds() - SetNameTime; + + + unsigned int AddCharacterTime = radTimeGetMicroseconds(); + + int characterIndex = AddCharacter( character, type ); + strcpy(mRealModelNames[ characterIndex ], modelName); + strcpy(mRealAnimNames[ characterIndex ], choreoPuppet); + mLoaded[characterIndex] = true; + mGarbage[characterIndex] = false; + + if ( type == PC ) + { + rAssert( characterIndex == (int)mNumCharactersAdded ); + ++mNumCharactersAdded; + } + + AddCharacterTime = radTimeGetMicroseconds() - AddCharacterTime; + + unsigned int FillLoadTime = radTimeGetMicroseconds(); + strcpy(mCharacterLoadData[characterIndex].modelName, modelName); + strcpy(mCharacterLoadData[characterIndex].animName, choreoPuppet); + + FillLoadData( mCharacterLoadData[characterIndex], modelName, choreoPuppet); + FillLoadTime = radTimeGetMicroseconds() - FillLoadTime; + + unsigned int LoadModelTime = 0; + unsigned int SetupCharacterTime; + + LoadModelTime = radTimeGetMicroseconds(); + mCharacterModelData[characterIndex] = LoadModel(modelName); + mCharacterAnimData[characterIndex] = LoadAnimation(choreoPuppet); + LoadModelTime = radTimeGetMicroseconds() - LoadModelTime; + + if( (GetState(mAnimData, animUID) == LOADED) && (GetState(mModelData, modelUID) == LOADED) ) + { + SetupCharacterTime = radTimeGetMicroseconds(); + SetupCharacter( mCharacterLoadData[characterIndex], character ); + SetupCharacterTime = radTimeGetMicroseconds() - SetupCharacterTime; + } + else + { + SetupCharacterTime = radTimeGetMicroseconds(); + SetupCharacter( mDummyLoadData, character ); + SetupCharacterTime = radTimeGetMicroseconds() - SetupCharacterTime; + } + + unsigned int InitTime = radTimeGetMicroseconds(); + character->Init(); + InitTime = radTimeGetMicroseconds() - InitTime; + + if(type == NPC) + { + // Dusit [Oct 22, 2002]: + // Don't solve collisions for NPCs.. They just sit there... + // We'll enable solve collisions as needed... + character->SetSolveCollisions( false ); + } + else + { + // PCs get immediatly added to scene, NPCs might not be visible (i.e pedestrians) + character->AddToWorldScene(); + } + + if(location) + { + //Set the initial character position. + Locator* loc = p3d::find<Locator>( location ); + CarStartLocator* cloc = dynamic_cast<CarStartLocator*>( loc ); + + rmt::Vector position; + if ( cloc ) + { + cloc->GetLocation( &position ); + character->RelocateAndReset( position, cloc->GetRotation() ); + } + else if ( loc ) + { + rmt::Vector position; + loc->GetLocation( &position ); + character->RelocateAndReset( position, 0 ); + } + else + { + rDebugPrintf( "Couldn't find locator \"%s\" for %s\n", location, characterName ); + } + + // if NPC, add the locator as the first waypoint of this character + if( loc && type == NPC ) + { + // the controller should have been created in NPC constructor + NPCController* npcController = (NPCController*) character->GetController(); + rAssert( npcController ); + + bool res = npcController->AddNPCWaypoint( position ); + rAssert( res ); + } + } + addCharTime = radTimeGetMicroseconds() - addCharTime; + +#ifdef OUTPUT_TIMES + rReleasePrintf("AddCharacter %s Time %d\n",characterName, addCharTime); + rReleasePrintf(" AddCharacter MakeUIDTime %s Time %d\n",characterName, MakeUIDTime); + rReleasePrintf(" AddCharacter NewTime %s Time %d\n",characterName, NewTime); + rReleasePrintf(" AddCharacter SetNameTime %s Time %d\n",characterName, SetNameTime); + rReleasePrintf(" AddCharacter AddCharacterTime %s Time %d\n",characterName, AddCharacterTime); + rReleasePrintf(" AddCharacter FillLoadTime %s Time %d\n",characterName, FillLoadTime); + rReleasePrintf(" AddCharacter LoadModelTime %s Time %d\n",characterName, LoadModelTime); + rReleasePrintf(" AddCharacter SetupCharacterTime %s Time %d\n",characterName, SetupCharacterTime); + rReleasePrintf(" AddCharacter InitTime %s Time %d\n",characterName, InitTime); +#endif + if(type == PC) + { + MEMTRACK_POP_GROUP( "CharacterManager - Add PC" ); + } + else + { + MEMTRACK_POP_GROUP( "CharacterManager - Add NPC" ); + } + + if(type == PC && GetGameplayManager()->IsSuperSprint() == false && GetGameplayManager()->mIsDemo == false) + { + //check to see if there is a valid skin that is not called NULL + if(strcmp(GetCharacterSheetManager()->QueryCurrentSkin(GetGameplayManager()->GetCurrentLevelIndex()),"NULL") != 0) + { + SwapData(character, GetCharacterSheetManager()->QueryCurrentSkin(GetGameplayManager()->GetCurrentLevelIndex()), GetAnimName(character)); + } + } + + return character; +} + +Character* CharacterManager::AddCharacterDeferedLoad( CharacterType type, const char* characterName, const char* modelName, const char* choreoPuppet, const char* location) +{ + Character* c = AddCharacter(type, characterName, "npd", "npd", location); + + unsigned characterIndex = INVALID_LOAD; + + for ( int i = 0; i < MAX_CHARACTERS; i++ ) + { + if ( mpCharacter[ i ] == c ) + { + characterIndex = i; + break; + } + } + + rAssert(characterIndex != INVALID_LOAD); + + strcpy(mRealModelNames[ characterIndex ], modelName); + strcpy(mRealAnimNames[ characterIndex ], choreoPuppet); + mLoaded[characterIndex] = false; + + return c; +} + +void CharacterManager::SwapData(Character* character, const char* modelName, const char* choreoPuppet) +{ + unsigned index = InternalSwapData(character, modelName, choreoPuppet); + rAssert(index != INVALID_LOAD); + strcpy(mRealModelNames[ index ], modelName); + strcpy(mRealAnimNames[ index ], choreoPuppet); +} + + +unsigned CharacterManager::InternalSwapData(Character* character, const char* modelName, const char* choreoPuppet) +{ + unsigned int SwapDataTime = radTimeGetMicroseconds(); + + + unsigned characterIndex = INVALID_LOAD; + + for ( int i = 0; i < MAX_CHARACTERS; i++ ) + { + if ( mpCharacter[ i ] == character ) + { + characterIndex = i; + break; + } + } + + rAssert(characterIndex != INVALID_LOAD); + + tUID modelUID; + tUID animUID; + + unsigned modelIndex = mCharacterModelData[characterIndex]; + unsigned animIndex = mCharacterAnimData[characterIndex]; + + if(modelName) + { + modelUID = tEntity::MakeUID(modelName); + strcpy(mCharacterLoadData[characterIndex].modelName, modelName); + } + else + { + modelUID = tEntity::MakeUID(mCharacterLoadData[characterIndex].modelName); + } + + if(choreoPuppet) + { + animUID = tEntity::MakeUID(choreoPuppet); + strcpy(mCharacterLoadData[characterIndex].animName, choreoPuppet); + } + else + { + animUID = tEntity::MakeUID(mCharacterLoadData[characterIndex].animName); + } + + + unsigned int FillLoadTime = radTimeGetMicroseconds(); + FillLoadData( mCharacterLoadData[characterIndex], mCharacterLoadData[characterIndex].modelName, mCharacterLoadData[characterIndex].animName); + FillLoadTime = radTimeGetMicroseconds() - FillLoadTime; + + unsigned int LoadModelTime = 0; + unsigned int SetupCharacterTime = 0; + unsigned int CreatePuppetTime = 0; + + LoadModelTime = radTimeGetMicroseconds(); + mCharacterModelData[characterIndex] = LoadModel(mCharacterLoadData[characterIndex].modelName); + mCharacterAnimData[characterIndex] = LoadAnimation(mCharacterLoadData[characterIndex].animName); + LoadModelTime = radTimeGetMicroseconds() - LoadModelTime; + + if((GetState(mAnimData, animUID) == LOADED) && (GetState(mModelData, modelUID) == LOADED)) + { + SetupCharacterTime = radTimeGetMicroseconds(); + CreatePuppetTime = SetupCharacter( mCharacterLoadData[characterIndex], character ); + SetupCharacterTime = radTimeGetMicroseconds() - SetupCharacterTime; + } + else + { + SetupCharacterTime = radTimeGetMicroseconds(); + CreatePuppetTime = SetupCharacter( mDummyLoadData, character ); + SetupCharacterTime = radTimeGetMicroseconds() - SetupCharacterTime; + } + + unsigned int GarbageCollectTime = radTimeGetMicroseconds(); + GarbageCollectModel(modelIndex); + GarbageCollectAnim(animIndex); + GarbageCollectTime = radTimeGetMicroseconds() - GarbageCollectTime; + + SwapDataTime = radTimeGetMicroseconds() - SwapDataTime; + +#ifdef OUTPUT_TIMES + rReleasePrintf("SwapDataTime %s Time %d\n",modelName, SwapDataTime); + rReleasePrintf(" FillLoadTime %s Time %d\n",modelName, FillLoadTime); + if( LoadModelTime != 0 ) + rReleasePrintf(" LoadModelTime %s Time %d\n",modelName, LoadModelTime); + rReleasePrintf(" GarbageCollectTime %s Time %d\n",modelName, GarbageCollectTime ); + rReleasePrintf(" SetupCharacterTime %s Time %d\n",modelName, SetupCharacterTime ); + rReleasePrintf(" CreatePuppetTime %s Time %d\n\n",modelName, CreatePuppetTime ); +#endif + + return characterIndex; +} + +//============================================================================= +// CharacterManager::SetupCharacter +//============================================================================= +// Description: Comment +// +// Parameters: ( CharacterLoadData& data, Character* pCharacter, bool isNPC ) +// +// Return: void +// +//============================================================================= +unsigned int CharacterManager::SetupCharacter( CharacterLoadData& data, Character* pCharacter) +{ + p3d::inventory->PushSection(); + + bool currOnly = p3d::inventory->GetCurrentSectionOnly(); + tName curr = p3d::inventory->GetCurrentSection()->GetName(); + p3d::inventory->SetCurrentSectionOnly(true); + +// #ifdef RAD_GAEMCUBE +// HeapMgr()->PushHeap( GMA_GC_VMM ); +// #else + HeapMgr()->PushHeap( GMA_LEVEL_OTHER ); +// #endif + +MEMTRACK_PUSH_GROUP( "CharacterManager - CharacterRenderable" ); + p3d::inventory->SelectSection(data.modelSection.GetUID()); + + CharacterRenderable* pCharacterRenderable; + if(data.modelSection.GetUID() != tEntity::MakeUID("npd_m")) + { + pCharacterRenderable = new CharacterRenderable( + p3d::find<tDrawablePose>( data.mModelHigh.GetUID() ), + p3d::find<tDrawablePose>( data.mModelMedium.GetUID() ), + p3d::find<tDrawablePose>( data.mModelLow.GetUID() )); + pCharacterRenderable->SetSwatchShader( p3d::find<tShader>( "char_swatches_lit_m" ) ); + } + else + { + pCharacterRenderable = new CharacterRenderable(NULL, NULL, NULL); + pCharacterRenderable->SetSwatchShader( NULL ); + } + + pCharacterRenderable->SetShadowColour( pCharacter->GetShadowColour() ); + + // Lets find the electrocution effect + p3d::inventory->SelectSection( P3D_DEFAULT_INV_SECTION ); + p3d::inventory->SetCurrentSectionOnly( false ); + pCharacterRenderable->SetShockEffect( p3d::find< tDrawable >( "electrocuted" ) ); + p3d::inventory->SetCurrentSectionOnly( true ); + + + p3d::inventory->SelectSection( "Eakkachaichanvet" ); + pCharacterRenderable->SetSwatchTexture( 0, p3d::find<tTexture>( "char_swatches_lit.bmp" ) ); + pCharacterRenderable->SetSwatchTexture( 1, p3d::find<tTexture>( "char_swatches1.bmp" ) ); + pCharacterRenderable->SetSwatchTexture( 2, p3d::find<tTexture>( "char_swatches2.bmp" ) ); + pCharacterRenderable->SetSwatchTexture( 3, p3d::find<tTexture>( "char_swatches3.bmp" ) ); + pCharacterRenderable->SetSwatchTexture( 4, p3d::find<tTexture>( "char_swatches4.bmp" ) ); + pCharacterRenderable->SetSwatch( 0 ); + p3d::inventory->SelectSection( data.modelSection.GetUID() ); + + pCharacter->SetDrawable( pCharacterRenderable ); +MEMTRACK_POP_GROUP("CharacterManager - CharacterRenderable"); + + tDrawablePose* pDrawablePose = pCharacterRenderable->GetDrawable(); + tSkeleton* pSkeleton = NULL; + + if(pDrawablePose) + { + pSkeleton = pDrawablePose->GetSkeleton(); + } + else + { + p3d::inventory->SelectSection(data.animModelSection.GetUID()); + pSkeleton = p3d::find<tSkeleton>( data.mChoreoName.GetUID() ); + } + + if(!pSkeleton) + { + tUID uid = tEntity::MakeUID("npd"); + p3d::inventory->SelectSection(uid); + pSkeleton = p3d::find<tSkeleton>(uid); + } + + rAssert( pSkeleton); + + p3d::inventory->SelectSection(data.animSection.GetUID()); + choreo::Bank* bank = p3d::find<choreo::Bank>( data.mChoreoName.GetUID() ); + rAssert( bank != 0 ); + +MEMTRACK_PUSH_GROUP( "CharacterManager - Puppet" ); + unsigned int PuppetTime = radTimeGetMicroseconds(); + // Make sure the skeleton to be remapped are equivalent. + // + choreo::Rig* rig = bank->GetRig(); + rAssert( rig != 0 ); + tSkeleton* rigSkeleton = rig->GetSkeleton( ); + int i = 0; + if ( rigSkeleton->GetNumJoint() != pSkeleton->GetNumJoint() ) + { + rDebugPrintf( "%s skeleton is not the same as choreo::Rig skeleton %s. Animations will be hooped.\n", + rigSkeleton->GetName( ), pSkeleton->GetName( ) ); + } + + mUniversalPose->SetSkeleton(pSkeleton); + mUniversalPose->ResetToRestPose(); + + pCharacter->SetInCar(false); + pCharacter->SetPuppet( new choreo::Puppet( mUniversalPose, bank, false, 0x20, 5 ) ); + + choreo::Puppet* pPuppet = pCharacter->GetPuppet(); + + PuppetTime = radTimeGetMicroseconds() - PuppetTime; + +// #ifdef RAD_GAEMCUBE +// HeapMgr()->PopHeap( GMA_GC_VMM ); +// #else + HeapMgr()->PopHeap( GMA_LEVEL_OTHER ); +// #endif + +MEMTRACK_POP_GROUP( "CharacterManager - Puppet" ); + + + // + // Determine & store y offset needed for this character + // + + float yAdjustDrawable = 0.0f, yAdjustPuppet = 0.0f; + if( pCharacterRenderable->GetDrawable() && + pCharacterRenderable->GetDrawable()->GetSkeleton() ) + { + yAdjustDrawable = pCharacterRenderable->GetDrawable()->GetSkeleton()-> + FindJoint( tEntity::MakeUID("Balance_Root") )->worldMatrix.Row(3).y; + } + if( pPuppet && pPuppet->GetSkeleton() ) + { + yAdjustPuppet = pPuppet->GetSkeleton()-> + FindJoint( tEntity::MakeUID("Balance_Root") )->worldMatrix.Row(3).y; + } + + pCharacter->SetYAdjust((yAdjustDrawable - yAdjustPuppet) * 0.30f); + + // + // Setup the blinker for this character + // + unsigned int characterIndex = GetCharacterIndex( pCharacter ); + g_Blinkers[ characterIndex ].SetCharacter( pCharacter ); + + p3d::inventory->SetCurrentSectionOnly(currOnly); + p3d::inventory->SelectSection( curr ); + + p3d::inventory->PopSection(); + + return PuppetTime; + +} + +void CharacterManager::GarbageCollect(bool ignoreDist) +{ + int i; + + const float GARBAGE_COLLECT_DISTANCE = 100.0f; + + // garbage collect + if(mGarbageCollect) + { + for(i = MAX_PLAYERS; i < MAX_CHARACTERS; i++) + { + if(mpCharacter[i]) + { + rmt::Vector charPos; + rmt::Vector camPos; + rmt::Vector distance; + + mpCharacter[i]->GetPosition(charPos); +// camPos = GetSuperCamManager()->GetSCC(0)->GetCamera()->GetPosition(); + GetCharacter(0)->GetPosition(camPos); + distance.Sub(camPos, charPos); + + float fDist = rmt::Abs(distance.Magnitude()); + + if(fDist > 10000) + { + continue; + } + + bool suppressLoad = false; + + if(mpCharacter[i]->GetRole() == Character::ROLE_ACTIVE_BONUS) + { + if(!GetGameplayManager()->GetCurrentMission()->IsBonusMission() && + !GetGameplayManager()->GetCurrentMission()->IsRaceMission() && + !GetGameplayManager()->GetCurrentMission()->IsWagerMission() && + !GetGameplayManager()->GetCurrentMission()->IsSundayDrive()) + { + suppressLoad = true; + } + } + + if(mGarbage[i]) + { + if(fDist > GARBAGE_COLLECT_DISTANCE || GetGameplayManager()->IsIrisClosed() || ignoreDist ) + { + if(mpCharacter[i]->GetRole() != Character::ROLE_ACTIVE_BONUS) + { + if(mpCharacter[i]->IsAmbient()) + { + mGarbage[i] = false; + mpCharacter[i]->EnableAmbientDialogue(true); + mpCharacter[i]->ResetAmbientPosition(); + } + else + { + RemoveCharacter(mpCharacter[i]); + } + } + else + { + mGarbage[i] = false; + } + } + } + else if (mLoaded[i] && (fDist > 150) && mpCharacter[i]->mbAllowUnload) + { + InternalSwapData(mpCharacter[i], "npd", "npd"); + mLoaded[i] = false; + mpCharacter[i]->RemoveFromPhysics(); + } + else if (!mLoaded[i] && (fDist < 150) && !suppressLoad) + { + if( !CommandLineOptions::Get( CLO_NO_PEDS ) ) + InternalSwapData(mpCharacter[i], mRealModelNames[i], mRealAnimNames[i]); + mLoaded[i] = true; + if(!mpCharacter[i]->IsInCar()) + { + mpCharacter[i]->AddToPhysics(); + } + } + } + + } + + for(i = 1; i < MAX_LOADS; i++) + { + if(mModelData[i].state == GARBAGE) + { + if(mModelData[i].gracePeriod < 0.0f) + { + p3d::inventory->RemoveSectionElements(mModelData[i].section); + p3d::inventory->DeleteSection(mModelData[i].section); + mModelData[i].state = NOT_LOADED; + } + } + + if(mAnimData[i].state == GARBAGE) + { + if(mAnimData[i].gracePeriod < 0.0f) + { + p3d::inventory->RemoveSectionElements(mAnimData[i].section); + p3d::inventory->DeleteSection(mAnimData[i].section); + mAnimData[i].state = NOT_LOADED; + } + } + } + } +} + +/* +============================================================================== +CharacterManager::PreSimUpdate +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void CharacterManager::PreSimUpdate( float timeins ) +{ + int i; + + for ( i = 1; i < MAX_CHARACTERS; i++ ) + { + if ( mpCharacter[ i ] && (mpCharacter[ i ]->GetRenderLayer() == GetRenderManager()->rCurWorldRenderLayer()) ) + { +BEGIN_PROFILE("Character::PreSimUpdate") + mpCharacter[ i ]->PreSimUpdate( timeins ); +END_PROFILE("Character::PreSimUpdate") + } + } + + for(i = 1; i < MAX_LOADS; i++) + { + if(mModelData[i].state == GARBAGE) + { + mModelData[i].gracePeriod -= timeins; + } + + if(mAnimData[i].state == GARBAGE) + { + mAnimData[i].gracePeriod -= timeins; + } + } + +} + +/* +============================================================================== +CharacterManager::PostSimUpdate +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void CharacterManager::PostSimUpdate( float timeins ) +{ + int i; + for ( i = 1; i < MAX_CHARACTERS; i++ ) + { + if ( mpCharacter[ i ] && (mpCharacter[ i ]->GetRenderLayer() == GetRenderManager()->rCurWorldRenderLayer()) ) + { +BEGIN_PROFILE("Character::PostSimUpdate") + mpCharacter[ i ]->PostSimUpdate( timeins ); +END_PROFILE("Character::PostSimUpdate") + } + } +} + + +/* +============================================================================== +CharacterManager::Update +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void CharacterManager::Update( float timeins ) +{ + int i; + + for( i = 0; i < MAX_CHARACTERS; i++ ) + { + Character* character = mpCharacter[i]; + if( character && (character->GetRenderLayer() == GetRenderManager()->rCurWorldRenderLayer()) ) + { + g_Blinkers[ i ].Update( static_cast< int >( timeins * 1000 ) ); + + int collisionAreaIndex = character->GetCollisionAreaIndex(); + if( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ) + { + BEGIN_PROFILE("Character::UpdatePhObjects"); + character->UpdatePhysicsObjects( timeins, collisionAreaIndex ); + END_PROFILE("Character::UpdatePhObjects"); + } + + character->UpdateRoot( timeins ); + + sim::SimState* simState = character->GetSimState(); + if( simState != NULL && simState->GetControl() == sim::simSimulationCtrl ) + { + simState->GetSimulatedObject()->Update( timeins ); + character->UpdateSimState( timeins ); + + sim::CollisionObject* collObj = simState->GetCollisionObject(); + collObj->Update(); + + } + } + } + +} + +/* +============================================================================== +CharacterManager::PreSubstepUpdate +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void CharacterManager::PreSubstepUpdate( float timeins ) +{ + for( int i=0; i<MAX_CHARACTERS; i++ ) + { + if( mpCharacter[ i ] && mpCharacter[ i ]->IsInSubstep() ) + { + BEGIN_PROFILE("Character::PreSimUpdate") + mpCharacter[ i ]->PreSimUpdate( timeins ); + END_PROFILE("Character::PreSimUpdate") + } + } +} + +/* +============================================================================== +CharacterManager::PostSubstepUpdate +============================================================================== +Description: Comment + +Parameters: ( float timeins ) + +Return: void + +============================================================================= +*/ +void CharacterManager::PostSubstepUpdate( float timeins ) +{ + if ( mpCharacter[ 0 ] ) + { +BEGIN_PROFILE("Character::PostSimUpdate") + mpCharacter[ 0 ]->PostSimUpdate( timeins ); +END_PROFILE("Character::PostSimUpdate") + } +} + + +void CharacterManager::ClearTargetVehicle(Vehicle* v) +{ + for (int i = 0; i < MAX_CHARACTERS; i++ ) + { + if ( NULL != mpCharacter[ i ] ) + { + if(mpCharacter[ i ]->GetTargetVehicle() == v) + { + mpCharacter[ i ]->SetTargetVehicle(NULL); + } + } + } +} + +void CharacterManager::ResetBonusCharacters(void) +{ + for (int i = 0; i < MAX_CHARACTERS; i++ ) + { + if ( NULL != mpCharacter[ i ] ) + { + if(mpCharacter[ i ]->GetRole() == Character::ROLE_ACTIVE_BONUS) + { + ((NPCController*)(mpCharacter[ i ]->GetController()))->TeleportToPath(); + } + } + } +} + +/* +============================================================================== +CharacterManager::AddCharacter +============================================================================== +Description: Add a character to the array. Returns true if succesfully added. + +Parameters: ( Character* pCharacter, int& number, bool isNPC ) + +Return: bool + +============================================================================= +*/ +int CharacterManager::AddCharacter( Character* pCharacter, CharacterType type ) +{ + int i = 0; + + // if it's an NPC, skip the first couple of entries (reserved for players) + if ( type == NPC ) + { + i = MAX_PLAYERS; + } + + // find an empty slot + for (; i < MAX_CHARACTERS; i++ ) + { + if ( 0 == mpCharacter[ i ] ) + { + break; + } + } + + rAssertMsg(( i < MAX_CHARACTERS ), "Couldn't add character, no space in character array"); + + tRefCounted::Assign( mpCharacter[ i ], pCharacter ); + mpCharacter[ i ]->SetManaged(true); + mGarbage[i] = false; + return i; +} + +void CharacterManager::OnProcessRequestsComplete( void* pUserData ) +{ + unsigned index = (unsigned)pUserData; + + + if(index & 0x10000000) + { + index &= 0x0fffffff; + + if(mAnimData[index].state == LOADING) + { + mAnimData[index].state = LOADED; + + for(int i = 0; i < MAX_CHARACTERS; i++) + { + if(mpCharacter[i] && (mCharacterAnimData[i] == index)) + { + if(mModelData[mCharacterModelData[i]].state == LOADED) + { + unsigned int PuppetTime = SetupCharacter(mCharacterLoadData[i], mpCharacter[i] ); + #ifdef OUTPUT_TIMES + rReleasePrintf( "A) Calling SetupCharacter from OnProcessRequestsComplete for %s took %d\n", + mCharacterLoadData[i].modelName, PuppetTime ); + #endif + } + } + } + } else if(mAnimData[index].state == LOADING_GARBAGE) + { + mAnimData[index].state = GARBAGE; + } + else + { + rAssert(0); + } + } + else + { + if(mModelData[index].state == LOADING) + { + mModelData[index].state = LOADED; + + for(int i = 0; i < MAX_CHARACTERS; i++) + { + if(mpCharacter[i] && (mCharacterModelData[i] == index)) + { + if(mAnimData[mCharacterAnimData[i]].state == LOADED) + { + unsigned int PuppetTime = SetupCharacter(mCharacterLoadData[i], mpCharacter[i] ); + #ifdef OUTPUT_TIMES + rReleasePrintf( "B) Calling SetupCharacter from OnProcessRequestsComplete for %s took %d\n", + mCharacterLoadData[i].modelName, PuppetTime ); + #endif + } + } + } + + if(mModelData[index].callback) + { + mModelData[index].callback->OnProcessRequestsComplete(mModelData[index].userData); + mModelData[index].callback = NULL; + } + } else if(mModelData[index].state == LOADING_GARBAGE) + { + mModelData[index].state = GARBAGE; + } + else + { + rAssert(0); + } + + } +} + +//============================================================================= +Character* CharacterManager::GetCharacterByName( const char* name ) const +{ + return GetCharacterByName( tEntity::MakeUID( name ) ); +} + +/* +============================================================================== +CharacterManager::GetCharacterByName +============================================================================== +Description: Comment + +Parameters: ( const tUID uid ) + +Return: Character + +============================================================================= +*/ +Character* CharacterManager::GetCharacterByName( const tUID uid ) const +{ + int i; + for( i = 0; i < MAX_CHARACTERS; i++ ) + { + if ( mpCharacter[ i ] != 0 && mpCharacter[ i ]->GetUID() == uid ) + { + return mpCharacter[ i ]; + } + } + return NULL; + +} + +//============================================================================= +Character* CharacterManager::GetMissionCharacter( const char* name ) const +{ + Character* c = NULL; + + // pick an appropriate name based on if we are a bonus mission or not + char n[64]; + if(GetGameplayManager()->GetCurrentMission()->IsBonusMission()) + { + sprintf(n, "b_%s", name); + c = GetCharacterManager()->GetCharacterByName( n ); + } + + if(!c) + { + sprintf(n, "reward_%s", name); + c = GetCharacterManager()->GetCharacterByName( n ); + } + + if(!c) + { + c = GetCharacterManager()->GetCharacterByName( name ); + } + + return c; +} + +/* +============================================================================== +CharacterManager::GetCharacter +============================================================================== +Description: Return a pointer to the character. + +Parameters: ( int i ) + +Return: Character + +============================================================================= +*/ +Character* CharacterManager::GetCharacter( int i ) const +{ + if ( i < MAX_CHARACTERS ) + { + return mpCharacter[ i ]; + } + else + { + return NULL; + } +} + +/* +============================================================================== +CharacterManager::HandleEvent +============================================================================== +Description: Comment + +Parameters: ( EventEnum id, void* pEventData ) + +Return: void + +============================================================================= +*/ +void CharacterManager::HandleEvent( EventEnum id, void* pEventData ) +{ + switch ( id ) + { + case EVENT_GETINTOVEHICLE_START : + { + Character* c = (Character*)pEventData; + if(c->GetTargetVehicle()) + { + if(c->GetTargetVehicle()->mpDriver && (c->GetTargetVehicle()->mpDriver != c)) + { + c->GetTargetVehicle()->mpDriver->GetController()->SetIntention(CharacterController::WaveHello); + } + } + } + break; + case EVENT_GETOUTOFVEHICLE_END : + { + Character* c = (Character*)pEventData; + if(c->GetTargetVehicle()) + { + if(c->GetTargetVehicle()->mpDriver && (c->GetTargetVehicle()->mpDriver != c)) + { + c->GetTargetVehicle()->mpDriver->GetController()->SetIntention(CharacterController::WaveGoodbye); + } + } + } + break; + case EVENT_TOGGLE_FIRSTPERSON : + { + CGuiScreenHud* currentHud = GetCurrentHud(); + if(pEventData == 0) + { + if(GetCharacter(0)->GetActionButtonHandler()) + { + if ( GetCharacter(0)->GetActionButtonHandler()->IsInstanceEnabled() ) + { + if( currentHud != NULL ) + { + currentHud->HandleMessage( GUI_MSG_SHOW_HUD_OVERLAY, HUD_ACTION_BUTTON ); + } + } + } + } + else + { + if( currentHud != NULL ) + { + currentHud->HandleMessage( GUI_MSG_HIDE_HUD_OVERLAY, HUD_ACTION_BUTTON ); + } + } + } + break; + + case EVENT_STAGE_COMPLETE : + { + if(pEventData) + { + Character* ch = GetCharacter(0); + ch->GetController()->SetIntention(CharacterController::CelebrateSmall); + } + } + break; + + case EVENT_CARD_COLLECTED : + case EVENT_MISSION_SUCCESS : + { + Character* ch = GetCharacter(0); + ch->GetController()->SetIntention(CharacterController::CelebrateBig); + } + break; + + case EVENT_MISSION_CHARACTER_RESET : + { + if(pEventData && (sInitialWalkLocator[0] != 0)) + { + rmt::Vector dest; + Locator* l = p3d::find<Locator>(sInitialWalkLocator); + + if(l) + { + l->GetLocation(&dest); + + Character* c = GetCharacterManager()->GetCharacter(0); + + if(c->GetWalkerLocomotionAction()->GetStatus() != RUNNING) + { + c->GetWalkerLocomotionAction()->SetStatus(SLEEPING); + } + + Sequencer* pSeq = c->GetActionController()->GetNextSequencer(); + pSeq->BeginSequence(); + pSeq->AddAction( new Arrive( c, dest) ); + pSeq->AddAction( c->GetWalkerLocomotionAction() ); + pSeq->EndSequence( ); + + } + } + sInitialWalkLocator[0] = 0; + } + break; + + case EVENT_LOCATOR + LocatorEvent::CAR_DOOR: + { + EventLocator* pLocator = static_cast<EventLocator*>( pEventData ); + rAssert( pLocator ); + unsigned int playerId = pLocator->GetPlayerID(); + Character* pCharacter = GetCharacter( playerId ); + + unsigned int actionId = pLocator->GetData( ); + rAssert( actionId >= 0 ); + + ActionButton::GetInCar* pGetInCarAction = static_cast<ActionButton::GetInCar* >( GetActionButtonManager()->GetActionByIndex( actionId ) ); + rAssert( dynamic_cast<ActionButton::GetInCar*>( pGetInCarAction ) ); + rAssert( pGetInCarAction ); + + if ( pLocator->GetPlayerEntered() ) + { + + // Entered a door volume. + // + +#ifdef RAD_DEBUG + int vehicleId = pGetInCarAction->GetVehicleId(); + Vehicle* pVehicle = GetVehicleCentral()->GetVehicle( vehicleId ); + rAssert( pVehicle ); +#endif + + pCharacter->AddActionButtonHandler( pGetInCarAction ); + pGetInCarAction->Enter( pCharacter ); + + // TODO: Think about what will happen when the character is in two vols. + // at the same time. + // + + } + else if ( !pLocator->GetPlayerEntered() ) + { + // Exited a door volume. + // + pGetInCarAction->Exit( pCharacter ); + pCharacter->RemoveActionButtonHandler( pGetInCarAction ); + } + break; + } + case EVENT_LOCATOR + LocatorEvent::BOUNCEPAD: + { + + EventLocator* pLocator = static_cast<EventLocator*>( pEventData ); + if ( pLocator->GetPlayerEntered() ) + { + rAssert( pLocator ); + unsigned int playerId = pLocator->GetPlayerID(); + Character* pCharacter = GetCharacter( playerId ); + + ActionButton::Bounce::OnEnter( pCharacter, pLocator ); + } + break; + } + case EVENT_LOCATOR + LocatorEvent::GENERIC_BUTTON_HANDLER_EVENT: + { + EventLocator* pLocator = static_cast<EventLocator*>( pEventData ); + rAssert( pLocator ); + + if ( pLocator->GetPlayerEntered() ) + { + unsigned int playerId = pLocator->GetPlayerID(); + Character* pCharacter = GetCharacter( playerId ); + + unsigned int actionId = pLocator->GetData( ); + rAssert( actionId >= 0 ); + + ActionButton::GenericEventButtonHandler* pTED = static_cast<ActionButton::GenericEventButtonHandler* >( GetActionButtonManager()->GetActionByIndex( actionId ) ); + rAssert( dynamic_cast< ActionButton::GenericEventButtonHandler* > (pTED) ); + rAssert( pTED ); + + if ( pCharacter != static_cast<Character*>(pTED->GetEventData()) ) + { + pCharacter->AddActionButtonHandler( pTED ); + pTED->Enter( pCharacter ); + } + } + else + { + unsigned int playerId = pLocator->GetPlayerID(); + Character* pCharacter = GetCharacter( playerId ); + + unsigned int actionId = pLocator->GetData( ); + rAssert( actionId >= 0 ); + + ActionButton::GenericEventButtonHandler* pTED = static_cast<ActionButton::GenericEventButtonHandler* >( GetActionButtonManager()->GetActionByIndex( actionId ) ); + + if ( pTED && (pCharacter != static_cast<Character*>(pTED->GetEventData())) ) + { + rAssert( dynamic_cast<ActionButton::GenericEventButtonHandler* >( pTED ) != NULL ); + pTED->Exit( pCharacter ); + pCharacter->RemoveActionButtonHandler( pTED ); + } + } + + break; + } + case EVENT_DEATH_VOLUME_SCREEN_BLANK: + { + //TODO: Make the blends go away. + //Also, cut the camera properly... + EventLocator* pLocator = static_cast<EventLocator*>( pEventData ); + if( pLocator == NULL ) + { + break; + } + + if ( pLocator->GetPlayerID() >= static_cast<unsigned int>(MAX_PLAYERS) ) + { + //Ignore this! + break; + } + + //Ignore when player leaves. + if ( pLocator->GetPlayerEntered() ) + { + unsigned int playerId = pLocator->GetPlayerID(); + Character* pCharacter = GetCharacter( playerId ); + + //Only do this if he's on foot. + if ( pCharacter->GetStateManager()->GetState() == CharacterAi::GET_IN) + { + GetAvatarManager()->PutCharacterInCar(pCharacter, pCharacter->GetTargetVehicle()); + GetEventManager()->TriggerEvent(EVENT_GETINTOVEHICLE_END, pCharacter); + } + else + { + if ( pCharacter->GetStateManager()->GetState() == CharacterAi::GET_OUT ) + { + GetAvatarManager()->PutCharacterOnGround( pCharacter, pCharacter->GetTargetVehicle() ); + } + else if ( pCharacter->IsInCar() ) + { + return; + } + + rmt::Matrix mat = pLocator->GetMatrix(); + rmt::Vector facing = mat.Row(2); + rmt::Vector pos; + pLocator->GetLocation( &pos ); + + float fDesiredDir = choreo::GetWorldAngle( facing.x, facing.z ); + pCharacter->RelocateAndReset( pos, fDesiredDir, true, false ); + + GetSuperCamManager()->GetSCC( 0 )->DoCameraCut(); + } + } + + break; + } + default: + { + break; + } + } +} + +const char* CharacterManager::GetModelName(Character* c) +{ + for(int i = 0; i < MAX_CHARACTERS; i++) + { + if(c == mpCharacter[i]) + { + return mRealModelNames[i]; + } + } + return NULL; +} + +const char* CharacterManager::GetAnimName(Character* c) +{ + for(int i = 0; i < MAX_CHARACTERS; i++) + { + if(c == mpCharacter[i]) + { + return mRealAnimNames[i]; + } + } + return NULL; +} + +/* +============================================================================== +CharacterManager::SubmitStatics +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void CharacterManager::SubmitStatics( void ) +{ + int i; + for ( i = 0; i < MAX_CHARACTERS; i++ ) + { + Character* character = mpCharacter[i]; + if( character != NULL ) + { + character->SubmitStatics(); + } + } +} + +/* +============================================================================== +CharacterManager::SubmitAnimCollisions +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void CharacterManager::SubmitAnimCollisions( void ) +{ + int i; + for ( i = 0; i < MAX_CHARACTERS; i++ ) + { + if ( mpCharacter[ i ] ) + { + mpCharacter[ i ]->SubmitAnimCollisions(); + } + } +} + + /* +============================================================================== +CharacterManager::SubmitDynamics +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void CharacterManager::SubmitDynamics( void ) +{ + int i; + for ( i = 0; i < MAX_CHARACTERS; i++ ) + { + Character* character = mpCharacter[i]; + if( character != NULL ) + { + character->SubmitDynamics(); + } + } +} + + +/* +============================================================================== +CharacterManager::SetCharacterPosition +============================================================================== +Description: Comment + +Parameters: ( int argc, char** argv ) + +Return: void + +============================================================================= +*/ +void CharacterManager::SetCharacterPosition( int argc, char** argv ) +{ + int index = ::atoi( argv[ 1 ] ); + Character* pCharacter = GetCharacterManager()->GetCharacter( index ); + if ( pCharacter ) + { + rmt::Vector pos; + pos.x = static_cast<float>( atoi( argv[ 2 ] ) ); + pos.y = 0.0f; + pos.z = static_cast<float>( atoi( argv[ 3 ] ) ); + pCharacter->SetPosition( pos ); + } +} +/* +============================================================================== +CharacterManager::ResetCharacter +============================================================================== +Description: Comment + +Parameters: ( int argc, char** argv ) + +Return: void + +============================================================================= +*/ +void CharacterManager::ResetCharacter( int argc, char** argv ) +{ + int index = ::atoi( argv[ 1 ] ); + Character* pCharacter = GetCharacterManager()->GetCharacter( index ); + Locator* loc = p3d::find<Locator>( argv[2] ); + if ( pCharacter && loc ) + { + rmt::Vector pos; + loc->GetLocation( &pos ); + pCharacter->RelocateAndReset(pos, pCharacter->GetDesiredDir()); + } +} + + +char CharacterManager::sInitialWalkLocator[64] = ""; + +void CharacterManager::SetInitialWalk(int argc, char** argv) +{ + strcpy(sInitialWalkLocator, argv[1]); +} + +char CharacterManager::sCharacterToSpawn[64] = "homer"; +Character* CharacterManager::sSpawnedCharacter = NULL; + +void CharacterManager::Spawn(void*) +{ + if(sSpawnedCharacter) + { + GetCharacterManager()->RemoveCharacter(sSpawnedCharacter); + sSpawnedCharacter = NULL; + } + + sSpawnedCharacter = GetCharacterManager()->AddCharacter(NPC, "dummy", sCharacterToSpawn, "npd", "next_to_pc"); + + rmt::Vector position; + rmt::Vector facing; + GetCharacterManager()->GetCharacter(0)->GetPosition( &position ); + GetCharacterManager()->GetCharacter(0)->GetFacing( facing ); + position += facing; + position += facing; + + sSpawnedCharacter->RelocateAndReset( position, 0); + + if(sSpawnedCharacter) + sSpawnedCharacter->AddToWorldScene(); +} + + +static const int sNumBartSkins = 7; +static int sCurBartSkin = 1; +static char sBartSkins [sNumBartSkins][16] = +{ + "bart", + "b_man", + "b_military", + "b_ninja", + "b_football", + "b_tall", + "b_hugo" +}; + +static const int sNumHomerSkins = 7; +static int sCurHomerSkin = 1; +static char sHomerSkins [sNumHomerSkins][16] = +{ + "homer", + "h_fat", + "h_donut", + "h_stcrobe", + "h_undrwr", + "h_scuzzy", + "h_evil" +}; + +static const int sNumLisaSkins = 4; +static int sCurLisaSkin = 1; +static char sLisaSkins [sNumLisaSkins][16] = +{ + "lisa", + "l_cool", + "l_florida", + "l_jersey" +}; + +static const int sNumMargeSkins = 4; +static int sCurMargeSkin = 1; +static char sMargeSkins [sNumMargeSkins][16] = +{ + "marge", + "m_police", + "m_pink", + "m_prison" +}; + + +static const int sNumApuSkins = 4; +static int sCurApuSkin = 1; +static char sApuSkins [sNumApuSkins][16] = +{ + "apu", + "a_american", + "a_besharp", + "a_army" +}; + +void CharacterManager::NextSkin( void* ) +{ + tUID bart = tEntity::MakeUID("bart"); + tUID homer = tEntity::MakeUID("homer"); + tUID lisa = tEntity::MakeUID("lisa"); + tUID marge = tEntity::MakeUID("marge"); + tUID apu = tEntity::MakeUID("apu"); + + Character* pc = GetCharacterManager()->GetCharacter(0); + + if(pc->GetUID() == bart) + { + GetCharacterManager()->SwapData(pc, sBartSkins[sCurBartSkin], "bart"); + sCurBartSkin = (sCurBartSkin + 1) % sNumBartSkins; + } + + if(pc->GetUID() == homer) + { + GetCharacterManager()->SwapData(pc, sHomerSkins[sCurHomerSkin], "homer"); + sCurHomerSkin = (sCurHomerSkin + 1) % sNumHomerSkins; + } + + if(pc->GetUID() == lisa) + { + GetCharacterManager()->SwapData(pc, sLisaSkins[sCurLisaSkin], "lisa"); + sCurLisaSkin = (sCurLisaSkin + 1) % sNumLisaSkins; + } + + if(pc->GetUID() == marge) + { + GetCharacterManager()->SwapData(pc, sMargeSkins[sCurMargeSkin], "marge"); + sCurMargeSkin = (sCurMargeSkin + 1) % sNumMargeSkins; + } + + if(pc->GetUID() == apu) + { + GetCharacterManager()->SwapData(pc, sApuSkins[sCurApuSkin], "apu"); + sCurApuSkin = (sCurApuSkin + 1) % sNumApuSkins; + } + +} + +static const int s_numCheatModels = 107; + +static char s_CheatModels[s_numCheatModels][107] = +{ + "apu", + "askinner", + "a_american", + "a_army", + "a_besharp", + "barney", + "bart", + "beeman", + "brn_unf", + "burns", + "b_football", + "b_hugo", + "b_man", + "b_military", + "b_ninja", + "b_tall", + "captain", + "carl", + "cbg", + "cletus", + "dolph", + "eddie", + "frink", + "gil", + "grandpa", + "hibbert", + "homer", + "h_donut", + "h_evil", + "h_fat", + "h_scuzzy", + "h_stcrobe", + "h_undrwr", + "jasper", + "jimbo", + "kearney", + "krusty", + "lenny", + "lisa", + "lou", + "louie", + "l_cool", + "l_florida", + "l_jersey", + "marge", + "milhouse", + "moe", + "moleman", + "m_pink", + "m_police", + "m_prison", + "ned", + "nelson", + "nriviera", + "otto", + "patty", + "ralph", + "selma", + "skinner", + "smithers", + "snake", + "teen", + "wiggum", + "willie", + "boy1", + "boy2", + "boy3", + "bum", + "busm1", + "busm2", + "busw1", + "const1", + "const2", + "farmr1", + "fem1", + "fem2", + "fem3", + "fem4", + "girl1", + "girl2", + "hooker", + "joger1", + "joger2", + "male1", + "male2", + "male3", + "male4", + "male5", + "male6", + "mobstr", + "nuclear", + "olady1", + "olady2", + "olady3", + "rednk1", + "rednk2", + "sail1", + "sail2", + "sail3", + "sail4", + "zfem1", + "zfem5", + "zmale1", + "zmale3", + "zmale4", + "frankenstein", + "witch" +}; + +static int s_curCheatModel = 0; + +void CharacterManager::NextCheatModel(void) +{ + if( CommandLineOptions::Get( CLO_NO_PEDS ) ) + return; + + Character* pc = GetCharacterManager()->GetCharacter(0); + GetCharacterManager()->SwapData(pc, s_CheatModels[s_curCheatModel], GetCharacterManager()->GetAnimName(pc)); + s_curCheatModel = (s_curCheatModel + 1) % s_numCheatModels; +} + +#include <mission/gameplaymanager.h> +#include <meta/zoneeventlocator.h> + +static const unsigned MAX_TELEPORT_DESTS = 64; +static ZoneEventLocator* s_dynaloadLoc = NULL; +static unsigned s_numTeleportDests = 0; +struct TeleportDest +{ + char name[32]; + bool useLoc; + char loc[32]; + rmt::Vector pos; + char zone[64]; +} s_teleportDests[MAX_TELEPORT_DESTS]; + +unsigned CharacterManager::GetNumTeleportDests(void) +{ + return s_numTeleportDests; +} + +const char* CharacterManager::GetTeleportDest(unsigned i) +{ + static char dummy[] = "Invalid Teleport Destination"; + + if(i < s_numTeleportDests) + { + return s_teleportDests[i].name; + } + + return dummy; +} + +void CharacterManager::ClearTeleportDests(void) +{ + for(unsigned i = 0; i < s_numTeleportDests; i++) + { + radDbgWatchDelete( &DoTeleport ); + } + tRefCounted::Release(s_dynaloadLoc); + s_numTeleportDests = 0; +} + +void CharacterManager::AddTeleportDest(int argc, char** argv) +{ + rAssert(argc != 5); + strcpy(s_teleportDests[s_numTeleportDests].name, argv[1]); + + if(argc == 4) + { + strcpy(s_teleportDests[s_numTeleportDests].loc, argv[2]); + strcpy(s_teleportDests[s_numTeleportDests].zone, argv[3]); + s_teleportDests[s_numTeleportDests].useLoc = true; + } + else + { + s_teleportDests[s_numTeleportDests].pos.Set((float)atof(argv[2]), (float)atof(argv[3]), (float)atof(argv[4])); + strcpy(s_teleportDests[s_numTeleportDests].zone, argv[5]); + s_teleportDests[s_numTeleportDests].useLoc = false; + } + radDbgWatchAddFunction( argv[1], &DoTeleport, (void*)s_numTeleportDests, "Teleport"); + s_numTeleportDests++; +} + +void CharacterManager::DoTeleport(void* data) +{ + int which = (int)data; + GetRenderManager()->mpLayer( RenderEnums::LevelSlot )->DumpAllDynaLoads(1, GetRenderManager()->mEntityDeletionList ); + + tRefCounted::Assign(s_dynaloadLoc,new(GetGameplayManager()->GetCurrentMissionHeap()) ZoneEventLocator); + s_dynaloadLoc->SetZoneSize( strlen(s_teleportDests[which].zone) + 1 ); + s_dynaloadLoc->SetZone( s_teleportDests[which].zone ); + s_dynaloadLoc->SetPlayerEntered(); + GetEventManager()->TriggerEvent( EVENT_FIRST_DYNAMIC_ZONE_START, s_dynaloadLoc ); + + rmt::Vector pos = s_teleportDests[which].pos; + if(s_teleportDests[which].useLoc) + { + Locator* l = p3d::find<Locator>(s_teleportDests[which].loc); + l->GetLocation(&pos); + } + + if(GetCharacterManager()->GetCharacter(0)->IsInCar()) + { + GetCharacterManager()->GetCharacter(0)->GetTargetVehicle()->SetPosition(&pos); + } + else + { + GetCharacterManager()->GetCharacter(0)->RelocateAndReset(pos, 0); + } +} + + +//============================================================================= +// CharacterManager::ResetCharacter +//============================================================================= +// Description: Comment +// +// Parameters: ( int argc, char** argv ) +// +// Return: void +// +//============================================================================= +unsigned int CharacterManager::GetCharacterIndex( const Character* character ) const +{ + unsigned int i; + unsigned int size = MAX_CHARACTERS; + for( i = 0; i < size; ++i ) + { + if( mpCharacter[ i ] == character ) + { + return i; + } + } + rAssertMsg( false, "Searched for a chraracter that does not exist" ); + return 0; +}
\ No newline at end of file diff --git a/game/code/worldsim/character/charactermanager.h b/game/code/worldsim/character/charactermanager.h new file mode 100644 index 0000000..8f5f87b --- /dev/null +++ b/game/code/worldsim/character/charactermanager.h @@ -0,0 +1,240 @@ +#ifndef CHARACTERMANAGER_H_ +#define CHARACTERMANAGER_H_ + +#include <events/eventlistener.h> +#include <loading/loadingmanager.h> +#include <worldsim/ped/pedestrianmanager.h> +// Forward declarations. +// +class Character; +class CharacterRenderable; +class ActionButtonHandler; +class tSkeleton; +class tPose; +class Vehicle; + +namespace choreo +{ + class Puppet; +}; + +class CharacterManager +: +public EventListener, +public LoadingManager::ProcessRequestsCallback +{ +public: + // Are we a PC or NPC + // + enum CharacterType + { + PC, + NPC + }; + + // Singleton stuff + // + static CharacterManager* GetInstance( ); + static CharacterManager* CreateInstance( ); + static void DestroyInstance( ); + + // Called before loading begins to preload a little neccesary data + // + void PreLoad( void ); + + // Called when exiting gameplay, cleans up everything + // + void Destroy( bool permenant = false); + + // Create a new character and add it to the manager + // + Character* AddCharacter( CharacterType, const char* characterName, const char* modelName, const char* choreoPuppet, const char* location = NULL); + Character* AddCharacterDeferedLoad( CharacterType, const char* characterName, const char* modelName, const char* choreoPuppet, const char* location = NULL); + + // preload data for a character + // + void PreloadCharacter(const char* name, const char* anim, LoadingManager::ProcessRequestsCallback* callback = NULL, void* userData = NULL) { LoadAnimation(anim); LoadModel(name, callback, userData); } + bool IsModelLoaded(const char* name); + bool IsAnimLoaded(const char* name); + + // legacy add functions (just map to AddCharacter now) + // + Character* AddPCCharacter( const char* characterName, const char* choreoPuppet ) { return AddCharacter(PC, characterName, characterName, choreoPuppet); } + + // Get character by name + // + Character* GetCharacterByName( const char* name ) const; + Character* GetCharacterByName( const tUID uid ) const; + Character* GetMissionCharacter( const char* name ) const; + + // swap character data + void SwapData(Character*, const char* mesh, const char* anim); + + // Return a pointer to the character. + // + Character* GetCharacter( int i ) const; + + // remove a character + // + void RemoveCharacter( Character* ) ; + + // Character garbage collection + void AllowGarbageCollection(bool allow) { mGarbageCollect = allow;} + void SetGarbage(Character*, bool garbage); + + // event handling (mainly when characters touch various locators) + // + virtual void HandleEvent( EventEnum id, void* pEventData ); + + void GarbageCollect(bool ignoreDist = false); + + const char* GetModelName(Character*); + const char* GetAnimName(Character*); + + void ClearTargetVehicle(Vehicle*); + void ResetBonusCharacters(void); + + // Various simulation stuff + // + void PreSimUpdate(float timeins); + void PostSimUpdate(float timeins); + void PreSubstepUpdate(float timeins); + void PostSubstepUpdate(float timeins); + void Update( float timeins ); + void SubmitStatics( void ); + void SubmitAnimCollisions( void ); + void SubmitDynamics( void ); + + // Scripter hooks + // + static void SetCharacterPosition( int argc, char** argv ); + static void ResetCharacter( int argc, char** argv ); + + // ??? + // + static bool sbFixedSimRate; + + static float sfKickingForce; + static float sfSlamForce; + + enum LoadState + { + NOT_LOADED, + GARBAGE, + LOADING, + LOADING_GARBAGE, + LOADED + }; + + static void NextCheatModel(void); + + static int GetMaxCharacters(); + + static void Teleport(unsigned i) { DoTeleport((void*)i); } + static unsigned GetNumTeleportDests(void); + static const char* GetTeleportDest(unsigned i); + + void ClearInitialWalk(void) {sInitialWalkLocator[0] = 0;} + +protected: + unsigned int GetCharacterIndex( const Character* character ) const; +private: + static const int MAX_NPCS = 8; + static const int MAX_PEDESTRIANS = PedestrianManager::MAX_PEDESTRIANS; + static const int MAX_CHARACTERS = 64; //MAX_PLAYERS + MAX_PEDESTRIANS + MAX_NPCS; + static const int MAX_LOADS = 40; + + // Data we need for a character load. + // + struct CharacterLoadData + { + char modelName[64]; + char animName[64]; + + tName modelSection; + tName animSection; + tName animModelSection; + + tName mModelHigh; + tName mModelMedium; + tName mModelLow; + tName mChoreoName; + }; + + struct Load + { + Load() { state = NOT_LOADED; callback = NULL; } + tUID name; + tUID section; + LoadState state; + float gracePeriod; + LoadingManager::ProcessRequestsCallback* callback; + void* userData; + }; + + CharacterManager( void ); + ~CharacterManager( void ); + + int AddCharacter( Character* pCharacter, CharacterType ); + + // [Dusit: Nov 26,2002] + // Hacked setupcharacter to return the radTime for OUTPUT_TIMES debugging + // Uncomment #define OUTPUT_TIMES in .cpp to have rReleasePrintfs show time taken + // in dynaload calls for peds & other characters + unsigned int SetupCharacter( CharacterLoadData& data, Character* pCharacter); + + void FillLoadData( CharacterLoadData& data, const char* model, const char* anim); + + unsigned LoadModel(const char* model, LoadingManager::ProcessRequestsCallback* callback = NULL, void* userData = NULL); + unsigned LoadAnimation(const char* anim); + unsigned FindLoad(Load* loads, tUID name); + unsigned AllocLoad(Load* loads, tUID name); + LoadState GetState(Load* loads, tUID name); + + unsigned InternalSwapData(Character*, const char* mesh, const char* anim); + + void GarbageCollectModel(unsigned index); + void GarbageCollectAnim(unsigned index); + + void OnProcessRequestsComplete( void* pUserData ); + + static CharacterManager* spCharacterManager; + + CharacterLoadData mDummyLoadData; + + Character* mpCharacter[ MAX_CHARACTERS ]; + char mRealModelNames[ MAX_CHARACTERS ][64]; + char mRealAnimNames[ MAX_CHARACTERS ][64]; + bool mLoaded[MAX_CHARACTERS]; + CharacterLoadData mCharacterLoadData[ MAX_CHARACTERS ]; + unsigned mCharacterModelData[MAX_CHARACTERS]; + unsigned mCharacterAnimData[MAX_CHARACTERS]; + Load mModelData[MAX_LOADS]; + Load mAnimData[MAX_LOADS]; + + bool mGarbageCollect; + bool mGarbage[MAX_CHARACTERS]; + + tPose* mUniversalPose; + + static char sInitialWalkLocator[64]; + static void SetInitialWalk(int argc, char** argv); + + static char sCharacterToSpawn[64]; + static Character* sSpawnedCharacter; + static void Spawn(void*); + static void NextSkin( void* ); + + static void DoTeleport(void*); + static void ClearTeleportDests(void); + static void AddTeleportDest(int argc, char** argv); + + unsigned int mNumCharactersAdded; +}; + +// A little syntactic sugar for getting at this singleton. +inline CharacterManager* GetCharacterManager() { return( CharacterManager::GetInstance() ); } + +inline int CharacterManager::GetMaxCharacters() { return CharacterManager::MAX_CHARACTERS; } + +#endif //CHARACTERMANAGER_H_ diff --git a/game/code/worldsim/character/charactermappable.cpp b/game/code/worldsim/character/charactermappable.cpp new file mode 100644 index 0000000..b798761 --- /dev/null +++ b/game/code/worldsim/character/charactermappable.cpp @@ -0,0 +1,281 @@ +#include <worldsim/character/charactermappable.h> +#include <worldsim/character/character.h> +#include <worldsim/character/charactercontroller.h> +#include <p3d/camera.hpp> + +#include <input/inputmanager.h> +#include <input/usercontrollerwin32.h> + +// +// Temp +#include <worldsim/avatarmanager.h> + +#include <camera/supercammanager.h> +#include <camera/supercamcentral.h> +#include <camera/supercam.h> + + +// Constructor. +// +/* +============================================================================== +CharacterMappable::CharacterMappable +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterMappable + +============================================================================= +*/ +CharacterMappable::CharacterMappable( void ) +: +Mappable(Input::ACTIVE_GAMEPLAY), +mpCharacterController( 0 ) +{ +} + +// Destructor +// +/* +============================================================================== +CharacterMappable::~CharacterMappable +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterMappable + +============================================================================= +*/ +CharacterMappable::~CharacterMappable( void ) +{ + if ( mpCharacterController ) + { + mpCharacterController->Release( ); + mpCharacterController = 0; + } +} +/* +============================================================================== +CharacterMappable::GetCharacterController +============================================================================== +Description: Comment + +Parameters: () + +Return: CharacterController + +============================================================================= +*/ +CharacterController* CharacterMappable::GetCharacterController() const +{ + return mpCharacterController; +} + +void CharacterMappable::SetCharacterController( CharacterController* pCharacterController ) +{ + tRefCounted::Assign( mpCharacterController, pCharacterController ); +} +// This method is called when ever a button state changes. +//void +void CharacterMappable::OnButton( int controllerId, int id, const IButton* pButton ) +{ +} + +// This method is called when a button changes state from "Pressed" to "Released". +// +void CharacterMappable::OnButtonUp( int controllerId, int buttonId, const IButton* pButton ) +{ +} + +// This method is called when a button changes state from "Released" to "Pressed". +// +void CharacterMappable::OnButtonDown( int controllerId, int buttonId, const IButton* pButton ) +{ +} + +// This is how we create our controller device mappings to logical game mappings. +// The mappings set up in this method are platform specific. +// +// The basic format of the calls is to "Map" a input, to a enumerated output id. +// The output of the specified input will be contained in the Button[] array. +// This id will also be sent as a the second parameter in the OnButton... messages. +// +void CharacterMappable::LoadControllerMappings( unsigned int controllerId ) +{ +#ifdef RAD_XBOX + ClearMap( 0 ); + Map( "LeftStickX", CharacterController::LeftStickX, 0, controllerId ); + Map( "LeftStickY", CharacterController::LeftStickY, 0, controllerId ); + Map( "DPadUp", CharacterController::DPadUp, 0, controllerId ); + Map( "DPadDown", CharacterController::DPadDown, 0, controllerId ); + Map( "DPadLeft", CharacterController::DPadLeft, 0, controllerId ); + Map( "DPadRight", CharacterController::DPadRight, 0, controllerId ); + Map( "Y", CharacterController::DoAction, 0, controllerId ); + Map( "A", CharacterController::Jump, 0, controllerId ); + Map( "B", CharacterController::Dash, 0, controllerId ); + Map( "X", CharacterController::Attack, 0, controllerId ); +#endif +#ifdef RAD_PS2 + ClearMap( 0 ); + Map( "LeftStickX", CharacterController::LeftStickX, 0, controllerId ); + Map( "LeftStickY", CharacterController::LeftStickY, 0, controllerId ); + Map( "DPadUp", CharacterController::DPadUp, 0, controllerId ); + Map( "DPadDown", CharacterController::DPadDown, 0, controllerId ); + Map( "DPadLeft", CharacterController::DPadLeft, 0, controllerId ); + Map( "DPadRight", CharacterController::DPadRight, 0, controllerId ); + Map( "Triangle", CharacterController::DoAction, 0, controllerId ); + Map( "X", CharacterController::Jump, 0, controllerId ); + Map( "Circle", CharacterController::Dash, 0, controllerId ); + Map( "Square", CharacterController::Attack, 0, controllerId ); +#endif +#ifdef RAD_GAMECUBE + ClearMap( 0 ); + Map( "LeftStickX", CharacterController::LeftStickX, 0, controllerId ); + Map( "LeftStickY", CharacterController::LeftStickY, 0, controllerId ); + Map( "DPadUp", CharacterController::DPadUp, 0, controllerId ); + Map( "DPadDown", CharacterController::DPadDown, 0, controllerId ); + Map( "DPadLeft", CharacterController::DPadLeft, 0, controllerId ); + Map( "DPadRight", CharacterController::DPadRight, 0, controllerId ); + Map( "Y", CharacterController::DoAction, 0, controllerId ); + Map( "A", CharacterController::Jump, 0, controllerId ); + Map( "X", CharacterController::Dash, 0, controllerId ); + Map( "B", CharacterController::Attack, 0, controllerId ); +#endif +#ifdef RAD_WIN32 + ClearMap( 0 ); + Map( "MoveUp", CharacterController::DPadUp, 0, controllerId ); + Map( "MoveDown", CharacterController::DPadDown, 0, controllerId ); + Map( "MoveLeft", CharacterController::DPadLeft, 0, controllerId ); + Map( "MoveRight", CharacterController::DPadRight, 0, controllerId ); + Map( "DoAction", CharacterController::DoAction, 0, controllerId ); + Map( "GetOutCar", CharacterController::GetOutCar, 0, controllerId ); + Map( "Jump", CharacterController::Jump, 0, controllerId ); + Map( "Sprint", CharacterController::Dash, 0, controllerId ); + Map( "Attack", CharacterController::Attack, 0, controllerId ); + Map( "feMouseRight", CharacterController::MouseLookRight, 0, controllerId ); + Map( "feMouseLeft", CharacterController::MouseLookLeft, 0, controllerId ); +#endif +} + +void CharacterMappable::GetDirection( rmt::Vector& outDirection ) const +{ +} + +BipedCharacterMappable::BipedCharacterMappable( void ) +: +CharacterMappable( ) +{ +} + +BipedCharacterMappable::~BipedCharacterMappable( ) +{ +} +void BipedCharacterMappable::OnButtonDown( int controllerId, int buttonId, const IButton* pButton ) +{ + CharacterMappable::OnButtonDown( controllerId, buttonId, pButton ); + + switch ( buttonId ) + { + case CharacterController::DoAction: + { + GetCharacterController()->SetIntention( CharacterController::DoAction ); + break; + } + case CharacterController::Jump: + { + GetCharacterController()->SetIntention( CharacterController::Jump ); + break; + } + case CharacterController::Dash: + { + GetCharacterController()->SetIntention( CharacterController::Dash ); + break; + } + case CharacterController::Attack: + { + GetCharacterController()->SetIntention( CharacterController::Attack ); + break; + } + default: + { + break; + } + } +} +void BipedCharacterMappable::GetDirection( rmt::Vector& outDirection ) const +{ +#ifdef RAD_WIN32 + if ( GetSuperCamManager()->GetSCC( 0 )->GetActiveSuperCam()->GetType() == SuperCam::PC_CAM ) //Mouse look enabled + { + float right = GetValue( CharacterController::MouseLookRight ); + float left = GetValue( CharacterController::MouseLookLeft ); + outDirection.x = ( right > left ) ? right : -left; + + float dirPad = GetValue( CharacterController::DPadUp ) - GetValue( CharacterController::DPadDown ); + float dirAnalog = GetValue( CharacterController::LeftStickY ); + + outDirection.z = rmt::Fabs( dirPad ) > rmt::Fabs( dirAnalog ) ? dirPad : dirAnalog; + } + else + { +#endif + rmt::Vector tempDir; + tempDir.x = GetValue( CharacterController::LeftStickX ); + tempDir.y = 0.0f; + tempDir.z = GetValue( CharacterController::LeftStickY ); + + rmt::Vector tempDir2; + tempDir2.x = GetValue( CharacterController::DPadRight ) - GetValue( CharacterController::DPadLeft ); + tempDir2.y = 0.0f; + tempDir2.z = GetValue( CharacterController::DPadUp ) - GetValue( CharacterController::DPadDown ); + + //The DPad overrides the analog stick. + outDirection = tempDir2.MagnitudeSqr() != 0.0f ? tempDir2 : tempDir; +#ifdef RAD_WIN32 + } +#endif +} + +InCarCharacterMappable::InCarCharacterMappable( void ) +: +CharacterMappable( ) +{ +} + +InCarCharacterMappable::~InCarCharacterMappable( ) +{ +} + +void InCarCharacterMappable::OnButtonDown( int controllerId, int buttonId, const IButton* pButton ) +{ + CharacterMappable::OnButtonDown( controllerId, buttonId, pButton ); + + switch ( buttonId ) + { + case CharacterController::DoAction: + { + GetCharacterController()->SetIntention( CharacterController::DoAction ); + break; + } +#ifdef RAD_WIN32 + case CharacterController::GetOutCar: + { + GetCharacterController()->SetIntention( CharacterController::GetOutCar ); + break; + } +#endif + default: + { + break; + } + } +} + +void InCarCharacterMappable::GetDirection( rmt::Vector& outDirection ) const +{ + outDirection.Set( 0.0f, 0.0f, 0.0f ); +} diff --git a/game/code/worldsim/character/charactermappable.h b/game/code/worldsim/character/charactermappable.h new file mode 100644 index 0000000..6c67f5f --- /dev/null +++ b/game/code/worldsim/character/charactermappable.h @@ -0,0 +1,126 @@ +#ifndef BIPEDCHARACTERMAPPABLE_H_ +#define BIPEDCHARACTERMAPPABLE_H_ + +#include <input/mappable.h> +#include <radmath/radmath.hpp> + + +class Character; +class CharacterController; +class tCamera; + +class CharacterMappable +: +public Mappable +{ +public: + + CharacterMappable( void ); + virtual ~CharacterMappable( void ); + + // This method is called when ever a button state changes. + // + virtual void OnButton( int controllerId, int id, const IButton* pButton ); + + // This method is called when a button changes state from "Pressed" to "Released". + // + virtual void OnButtonUp( int controllerId, int buttonId, const IButton* pButton ); + + // This method is called when a button changes state from "Released" to "Pressed". + // + virtual void OnButtonDown( int controllerId, int buttonId, const IButton* pButton ); + + // This is how we create our controller device mappings to logical game mappings. + // The mappings set up in this method are platform specific. + // + // The basic format of the calls is to "Map" a input, to a enumerated output id. + // The output of the specified input will be contained in the Button[] array. + // This id will also be sent as a the second parameter in the OnButton... messages. + // + virtual void LoadControllerMappings( unsigned int controllerId ); + + // Return the controller input direction transformed from camera to world space. + // + virtual void GetDirection( rmt::Vector& outDirection ) const; + + CharacterController* GetCharacterController( void ) const; + void SetCharacterController( CharacterController* pCharacterController ); +private: + CharacterController* mpCharacterController; +}; + +class BipedCharacterMappable +: +public CharacterMappable +{ +public: + + BipedCharacterMappable( void ); + virtual ~BipedCharacterMappable( void ); + + // This method is called when ever a button state changes. + // + //virtual void OnButton( int controllerId, int id, const IButton* pButton ); + + // This method is called when a button changes state from "Pressed" to "Released". + // + //virtual void OnButtonUp( int controllerId, int buttonId, const IButton* pButton ); + + // This method is called when a button changes state from "Released" to "Pressed". + // + virtual void OnButtonDown( int controllerId, int buttonId, const IButton* pButton ); + + // This is how we create our controller device mappings to logical game mappings. + // The mappings set up in this method are platform specific. + // + // The basic format of the calls is to "Map" a input, to a enumerated output id. + // The output of the specified input will be contained in the Button[] array. + // This id will also be sent as a the second parameter in the OnButton... messages. + // + //virtual void LoadControllerMappings( unsigned int controllerId ); + + // Return the controller input direction transformed from camera to world space. + // + virtual void GetDirection( rmt::Vector& outDirection ) const; + +private: +}; + + +class InCarCharacterMappable +: +public CharacterMappable +{ +public: + + InCarCharacterMappable( void ); + virtual ~InCarCharacterMappable( void ); + + // This method is called when ever a button state changes. + // + //virtual void OnButton( int controllerId, int id, const IButton* pButton ); + + // This method is called when a button changes state from "Pressed" to "Released". + // + //void OnButtonUp( int controllerId, int buttonId, const IButton* pButton ); + + // This method is called when a button changes state from "Released" to "Pressed". + // + virtual void OnButtonDown( int controllerId, int buttonId, const IButton* pButton ); + + // This is how we create our controller device mappings to logical game mappings. + // The mappings set up in this method are platform specific. + // + // The basic format of the calls is to "Map" a input, to a enumerated output id. + // The output of the specified input will be contained in the Button[] array. + // This id will also be sent as a the second parameter in the OnButton... messages. + // + //virtual void LoadControllerMappings( unsigned int controllerId ); + + // Return the controller input direction transformed from camera to world space. + // + virtual void GetDirection( rmt::Vector& outDirection ) const; + +private: +}; +#endif // BIPEDCHARACTERMAPPABLE_H_
\ No newline at end of file diff --git a/game/code/worldsim/character/characterrenderable.cpp b/game/code/worldsim/character/characterrenderable.cpp new file mode 100644 index 0000000..eb94e36 --- /dev/null +++ b/game/code/worldsim/character/characterrenderable.cpp @@ -0,0 +1,581 @@ +#include <worldsim/character/characterrenderable.h> +#include <mission/gameplaymanager.h> +#include <p3d/texture.hpp> +#include <p3d/shader.hpp> +#include <p3d/anim/drawablepose.hpp> +#include <p3d/anim/pose.hpp> +#include <p3d/matrixstack.hpp> +#include <p3d/shadow.hpp> +#include <p3d/view.hpp> +#include <pddi/pddi.hpp> +#include <camera/supercammanager.h> +#include <contexts/bootupcontext.h> + +// Hack. +// [6/27/2002] +#include <worldsim/character/charactermanager.h> +#include <worldsim/character/character.h> + +/* +============================================================================== +CharacterRenderable::CharacterRenderable +============================================================================== +Description: Comment + +Parameters: ( + tDrawablePose* pDrawablePoseHigh, + tDrawablePose* pDrawablePoseMedium, + tDrawablePose* pDrawablePoseLow, + tPose* pPose ) + +Return: CharacterRenderable + +============================================================================= +*/ +CharacterRenderable::CharacterRenderable( + tDrawablePose* pDrawablePoseHigh, + tDrawablePose* pDrawablePoseMedium, + tDrawablePose* pDrawablePoseLow) +: +mCurrentLOD( High ), +mbInAnyonesFrustrum( false ), +mpSwatchTexture( NULL ), +mpSwatchShader( NULL ), +mHaveShadowJoints( false ), +mFadeAlpha( 255 ), +mpShockedDrawable( NULL ), +mIsShocked( false ), +mDisplayingSkeletonShock( false ) +{ + int i; + for ( i = 0; i < MAX_LOD; i++ ) + { + mpDrawableList[ i ] = 0; + } + tRefCounted::Assign( mpDrawableList[ High ], pDrawablePoseHigh ); + tRefCounted::Assign( mpDrawableList[ Medium ], pDrawablePoseMedium ); + tRefCounted::Assign( mpDrawableList[ Low ], pDrawablePoseLow ); + + if(pDrawablePoseHigh) + { + pDrawablePoseHigh->SetPose(NULL); + } + + if(pDrawablePoseMedium) + { + pDrawablePoseMedium->SetPose(NULL); + } + + if(pDrawablePoseLow) + { + pDrawablePoseLow->SetPose(NULL); + } + + // null these out for now... CharacterManager::SetupCharacter will be + // setting these swatch textures... + for( i=0; i<CharacterRenderable::NUM_CHARACTER_SWATCHES; i++ ) + { + mpSwatchTextures[i] = NULL; + } + + mShadowColour.Set( 0, 0, 0 ); +} + +/* +============================================================================== +CharacterRenderable::~CharacterRenderable +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterRenderable + +============================================================================= +*/ +CharacterRenderable::~CharacterRenderable( void ) +{ + int i; + for ( i = 0; i < MAX_LOD; i++ ) + { + if ( mpDrawableList[ i ] ) + { + mpDrawableList[ i ]->Release(); + mpDrawableList[ i ] = 0; + } + } + + if( mpSwatchTexture ) + { + mpSwatchTexture->Release(); + mpSwatchTexture = NULL; + } + + for( i=0; i<CharacterRenderable::NUM_CHARACTER_SWATCHES; i++ ) + { + if( mpSwatchTextures[i] != NULL ) + { + mpSwatchTextures[i]->Release(); + mpSwatchTextures[i] = NULL; + } + } + if( mpSwatchShader ) + { + mpSwatchShader->Release(); + mpSwatchShader = NULL; + } + + if ( mpShockedDrawable != NULL ) + { + mpShockedDrawable->Release(); + mpShockedDrawable = NULL; + } +} +/* +============================================================================== +CharacterRenderable::Display +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: void + +============================================================================= +*/ +void CharacterRenderable::Display( rmt::Vector iPosn, tPose* pose ) +{ +BEGIN_PROFILE("CharRender Cull") + tPointCamera* pCam = (tPointCamera*)GetSuperCamManager()->GetSCC(0)->GetCamera(); + rmt::Vector camPosn; + pCam->GetPosition( &camPosn ); + iPosn.Sub( iPosn, camPosn ); + float sqrDistFromCam = iPosn.MagnitudeSqr(); +END_PROFILE("CharRender Cull") + + float dist = (pCam->GetNearPlane() + 1.5f); + dist *= dist; + + if ( sqrDistFromCam < dist ) + { + return; + } + + if( mbInAnyonesFrustrum ) + { + + if(sqrDistFromCam > 900.0f ) + { + if(sqrDistFromCam > 6400.0f ) //>80m + { + SetLOD(Low); + } + else // <= 80m + { + SetLOD(Medium); + } + } + else // <= 30m + { + SetLOD(High); + } + + BEGIN_PROFILE("CharRender Display") + if ( mIsShocked == false ) + { + DisplayModel( pose ); + } + else + { + DisplayShocked( pose ); + } + + END_PROFILE("CharRender Display") + } +} +////////////////////////////////////////////////////////////////////////// +int CharacterRenderable::CastsShadow() +{ + return 1; +} + +/* +============================================================================== +CharacterRenderable::DisplayShadow +============================================================================== +Description: Comment + +Parameters: () + +Return: void + +============================================================================= +*/ +void CharacterRenderable::DisplayShadow( tPose* pose, const BlobShadowParams* BlobParams ) +{ + if( BlobParams ) + { + BEGIN_PROFILE("Char Blobby Shadow"); + if( !mHaveShadowJoints ) + { + mHaveShadowJoints = true; + mShadowJoints[ 0 ] = pose->FindJoint( "Elbow_L" ); + mShadowJoints[ 1 ] = pose->FindJoint( "Ankle_L" ); + mShadowJoints[ 2 ] = pose->FindJoint( "Elbow_R" ); + mShadowJoints[ 3 ] = pose->FindJoint( "Ankle_R" ); + } + + tColour OutsideColour; + tColour insideColour; + + // Hack for brightly colored shadows for special game mode + if( ::GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + OutsideColour.Set( 0, 0, 0, 0 ); // black outline + //OutsideColour = mShadowColour; + insideColour = mShadowColour; + } + else + { + OutsideColour.Set( 255, 255, 255, 255 ); + const int Inside = 128; + float shadowAlpha = BlobParams->ShadowAlpha; + if( mFadeAlpha != 255 ) + { + shadowAlpha = mFadeAlpha / 255.0f; + } + int c = rmt::Clamp( int( Inside + ( 255 - Inside ) * ( 1.0f - shadowAlpha ) ), 0, 255 ); + insideColour.Set( c, c, c, c ); + } + + + const float ArticulatedBlobbyShadowLODThreshold = 15.0f; + const float MinShadowSize = 0.15f; + const float NonArticulatedScale = 0.2f; + const int NumShadowJoints = 4; + + + const float BlobRadius = 1.0f; + const float BlobFadeOff = 0.2f; + const int NumBlobSlices = 16; // Keep the number even or you're in trouble. + const int HalfCircle = NumBlobSlices >> 1; // We only keep half the circle and then mirror it. Hence the reason to keep the number of points even. + static float BlobPoints[ HalfCircle ][ 2 ]; + static float FadePoints[ HalfCircle ][ 2 ]; + float pointScales[ NumBlobSlices ]; // How much to move the points to envolope the character. + bool articulated = false; + + static float DoOnce = true; + if( DoOnce ) + { + DoOnce = false; + const float angleStep = rmt::PI_2 / ((float) NumBlobSlices ); + const float angleHalfStep = angleStep * 0.5f; + float angle = rmt::PI_2; + for( int j = 0; j < HalfCircle; ++j ) + { + float sa, ca; + rmt::SinCos( angle, &sa, &ca); + BlobPoints[ j ][ 0 ] = sa * BlobRadius; + BlobPoints[ j ][ 1 ] = ca * BlobRadius; + rmt::SinCos( angle - angleHalfStep, &sa, &ca ); + FadePoints[ j ][ 0 ] = sa * BlobFadeOff; + FadePoints[ j ][ 1 ] = ca * BlobFadeOff; + angle -= angleStep; + } + } + + // Translate the shadow towards the camera slightly, instead of moving it off the + //ground in the direction of the the ground normal. Hopefully this will cause less distortion of the shadow. + rmt::Vector camPos; + tCamera* camera = p3d::context->GetView()->GetCamera(); + rAssert( camera ); + camera->GetWorldPosition( &camPos ); + camPos.Sub( BlobParams->GroundPos ); + float camDirDot = camPos.DotProduct( camera->GetCameraToWorldMatrix().Row( 2 ) ); + // Note that the camPos vector is towards the camera so the camDirDot will be negative when the camera is facing + //the object. So if it's positive then we can assume it's behind the camera. + if( camDirDot > 0.0f ) + { + // Please excuse the early return. + END_PROFILE( "Char Blobby Shadow" ); + return; + } + float cameraToShadow = camPos.Magnitude(); + camPos.Normalize(); + camPos.Scale( 0.25f ); + rmt::Matrix transform; + transform.Identity(); + transform.FillTranslate( BlobParams->GroundPos ); + transform.FillHeading( BlobParams->GroundNormal, BlobParams->ShadowFacing ); + transform.Row( 3 ).Add( camPos ); + p3d::stack->PushMultiply( transform ); + + if( ( mHaveShadowJoints ) && ( cameraToShadow < ArticulatedBlobbyShadowLODThreshold ) ) + { + articulated = true; + // Find the position of the joints to deform the shadow by the character's movement. + float jointPos[ NumShadowJoints ][ 2 ]; +/* + Uncomment this if you want to weight the joint position by how close + they line up to the blob point position. + float jointNor[ NumShadowJoints ][ 2 ]; +*/ + rmt::Vector tempPos; + rmt::Matrix m = pose->GetJoint( 0 )->worldMatrix; + m.Invert(); + for( int i = 0; i < NumShadowJoints; ++i ) + { + m.Transform( mShadowJoints[ i ]->worldMatrix.Row( 3 ), &tempPos ); + jointPos[ i ][ 0 ] = tempPos.x; + jointPos[ i ][ 1 ] = -tempPos.z; +/* + Uncomment this if you want to weight the joint positions by how close + they line up to the blob point position. + float mag; + mag = rmt::Sqrt( ( tempPos.x * tempPos.x ) + ( tempPos.z * tempPos.z ) ); + mag = ( mag != 0.0f ) ? 1 / mag : 0.0f; + jointNor[ i ][ 0 ] = jointPos[ i ][ 0 ] * mag; + jointNor[ i ][ 1 ] = jointPos[ i ][ 1 ] * mag; +*/ + } + + for( int i = 0; i < HalfCircle; ++i ) + { + pointScales[ i ] = MinShadowSize; + pointScales[ i + HalfCircle ] = -MinShadowSize; + + for( int j = 0; j < NumShadowJoints; ++j ) + { + float dot; + // Project joint position onto blob point position. + dot = ( BlobPoints[ i ][ 0 ] * jointPos[ j ][ 0 ] ) + ( BlobPoints[ i ][ 1 ] * jointPos[ j ][ 1 ] ); +/* + Uncomment this if you want to weight the joint position by how close + they line up to the blob point position. + dot *= rmt::Abs( ( BlobPoints[ i ][ 0 ] * jointNor[ j ][ 0 ] ) + ( BlobPoints[ i ][ 1 ] * jointNor[ j ][ 1 ] ) ); +*/ + pointScales[ i ] = rmt::Max( pointScales[ i ], dot ); + pointScales[ i + HalfCircle ] = rmt::Min( pointScales[ i + HalfCircle ], dot ); + } + } + // Since we'll be mirroring the circle, we'll conviently keep the second half of the scales array + //as negative numbers so that it will automatically mirror our points for us. + } + + pddiPrimStream* blob = 0; + + pddiShader* blobShader = BootupContext::GetInstance()->GetSharedShader(); + rAssert( blobShader != NULL ); + + if( ::GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + blobShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_NONE ); + } + else + { + blobShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_MODULATE ); + } + blobShader->SetInt( PDDI_SP_ISLIT, 0 ); + blobShader->SetInt( PDDI_SP_ALPHATEST, 0 ); + blobShader->SetInt( PDDI_SP_SHADEMODE, PDDI_SHADE_GOURAUD ); + + blob = p3d::pddi->BeginPrims( blobShader, PDDI_PRIM_TRIANGLES, PDDI_V_C, 9 * NumBlobSlices ); + for(int i=0; i < NumBlobSlices; i++) + { + float x0, y0, x1, y1, x2, y2, x3, y3; + int index = i % HalfCircle; + int nextIndex = ( i + 1 ) % HalfCircle; + int nextScaleIndex = ( i + 1 ) % NumBlobSlices; + float fadeScale = ( i < HalfCircle ) ? BlobParams->ShadowScale : -BlobParams->ShadowScale; + float nextFadeScale = ( nextScaleIndex < HalfCircle ) ? BlobParams->ShadowScale : -BlobParams->ShadowScale; + + // Draw inside. + blob->Colour( insideColour ); + blob->Coord( 0.0f, 0.0f, 0.0f ); + + blob->Colour( insideColour ); + x0 = BlobPoints[ index ][ 0 ]; + x0 *= BlobParams->ShadowScale; + x0 *= articulated ? pointScales[ i ] : ( index == i ) ? NonArticulatedScale : -NonArticulatedScale; + y0 = BlobPoints[ index ][ 1 ]; + y0 *= BlobParams->ShadowScale; + y0 *= articulated ? pointScales[ i ] : ( index == i ) ? NonArticulatedScale : -NonArticulatedScale; + blob->Coord( x0, y0, 0.0f ); + + blob->Colour( insideColour ); + x1 = BlobPoints[ nextIndex ][ 0 ]; + x1 *= BlobParams->ShadowScale; + x1 *= articulated ? pointScales[ nextScaleIndex ] : ( nextIndex == nextScaleIndex ) ? NonArticulatedScale : -NonArticulatedScale; + y1 = BlobPoints[ nextIndex ][ 1 ]; + y1 *= BlobParams->ShadowScale; + y1 *= articulated ? pointScales[ nextScaleIndex ] : ( nextIndex == nextScaleIndex ) ? NonArticulatedScale : -NonArticulatedScale; + blob->Coord( x1, y1, 0.0f ); + + // Draw first part of outside. + blob->Colour( insideColour ); + blob->Coord( x0, y0, 0.0f ); + + blob->Colour( OutsideColour ); + x2 = x1 + ( FadePoints[ nextIndex ][ 0 ] * nextFadeScale ); + y2 = y1 + ( FadePoints[ nextIndex ][ 1 ] * nextFadeScale ); + blob->Coord( x2, y2, 0.0f ); + + blob->Colour( insideColour ); + blob->Coord( x1, y1, 0.0f ); + + // Draw second part of outside. + blob->Colour( insideColour ); + blob->Coord( x0, y0, 0.0f ); + + blob->Colour( OutsideColour ); + x3 = x0 + ( FadePoints[ index ][ 0 ] * fadeScale ); + y3 = y0 + ( FadePoints[ index ][ 1 ] * fadeScale ); + blob->Coord( x3, y3, 0.0f ); + + blob->Colour( OutsideColour ); + blob->Coord( x2, y2, 0.0f ); + } + p3d::pddi->EndPrims( blob ); + + p3d::stack->Pop(); + END_PROFILE( "Char Blobby Shadow" ); + } +} +/* +============================================================================== +CharacterRenderable::GetDrawable +============================================================================== +Description: Comment + +Parameters: ( ) + +Return: tDrawablePose + +============================================================================= +*/ +tDrawablePose* CharacterRenderable::GetDrawable( ) const +{ + for(int lod = (int)mCurrentLOD; lod >= (int)Low; lod--) + { + if(mpDrawableList[ lod ] ) + { + return mpDrawableList[ lod ]; + } + } + return NULL; +} + +/* +============================================================================== +CharacterRenderable::GetLOD +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: CharacterRenderable + +============================================================================= +*/ +int CharacterRenderable::GetLOD( void ) const +{ + return mCurrentLOD; +} +/* +============================================================================== +CharacterRenderable::SetLOD +============================================================================== +Description: Comment + +Parameters: ( CharacterLOD LOD ) + +Return: void + +============================================================================= +*/ +void CharacterRenderable::SetLOD( int LOD ) +{ + mCurrentLOD = (LOD > High) ? LOD : High; +} + +void CharacterRenderable::SetSwatch( int index ) +{ + rAssert( 0 <= index && index < NUM_CHARACTER_SWATCHES ); + if( mpSwatchTextures[index] != NULL && mpSwatchTexture != mpSwatchTextures[index] ) + { + // TODO: + // Assign here? or just set mpSwatchTexture = mpSwatchTextures[index]? + tRefCounted::Assign(mpSwatchTexture, mpSwatchTextures[index]); + } +} + +void CharacterRenderable::SetSwatchTexture( int index, tTexture* pTexture ) +{ + rAssert( 0 <= index && index < NUM_CHARACTER_SWATCHES ); + tRefCounted::Assign(mpSwatchTextures[index], pTexture); +} + +void CharacterRenderable::SetShockEffect( tDrawable* pDrawable ) +{ + tRefCounted::Assign(mpShockedDrawable, pDrawable); +} + + +void CharacterRenderable::SetSwatchShader( tShader* pShader ) +{ + tRefCounted::Assign(mpSwatchShader, pShader); +} + +void CharacterRenderable::SetFadeAlpha( int fadeAlpha ) +{ + mFadeAlpha = rmt::Clamp( fadeAlpha, 0, 255 ); +} + +void CharacterRenderable::SetShadowColour( tColour colour ) +{ + mShadowColour = colour; +} + +void CharacterRenderable::DisplayModel( tPose* pose ) +{ + tDrawablePose* draw = GetDrawable(); + if(draw) + { + if( mpSwatchShader != NULL ) + { + if( mpSwatchTexture != NULL ) + { + mpSwatchShader->SetTexture( PDDI_SP_BASETEX, mpSwatchTexture ); + } + } + tShaderIntBroadcast blendAlpha( PDDI_SP_BLENDMODE, PDDI_BLEND_ALPHA ); + draw->ProcessShaders( blendAlpha ); + tShaderIntBroadcast emissiveFade( PDDI_SP_EMISSIVEALPHA, mFadeAlpha ); + draw->ProcessShaders( emissiveFade ); + draw->Display( pose ); + } +} + + +void CharacterRenderable::DisplayShocked( tPose* pose ) +{ + // Alternate displaying the skeleton and displaying the model + if ( mDisplayingSkeletonShock ) + { + if ( mpShockedDrawable != NULL ) + { + p3d::stack->PushMultiply( pose->GetJoint(0)->worldMatrix ); + mpShockedDrawable->Display(); + p3d::stack->Pop(); + } + mDisplayingSkeletonShock = false; + } + else + { + + //pose->GetSkeleton(); + //DisplayModel( pose ); + mDisplayingSkeletonShock = true; + } +} + diff --git a/game/code/worldsim/character/characterrenderable.h b/game/code/worldsim/character/characterrenderable.h new file mode 100644 index 0000000..be5a1ab --- /dev/null +++ b/game/code/worldsim/character/characterrenderable.h @@ -0,0 +1,100 @@ +#ifndef CHARACTERRENDERABLE_H_ +#define CHARACTERRENDERABLE_H_ + +#include <radmath/vector.hpp> +#include <p3d/anim/pose.hpp> + +class tDrawablePose; +class tShadowSkin; +class tShader; +class tTexture; +class tDrawable; + +struct BlobShadowParams +{ + BlobShadowParams( const rmt::Vector& Pos, const rmt::Vector& Normal, const rmt::Vector& Facing ) : + GroundPos( Pos ), GroundNormal( Normal ), ShadowFacing( Facing ), ShadowAlpha( 1.0f ), ShadowScale( 1.0f ) {} + const rmt::Vector& GroundPos; + const rmt::Vector& GroundNormal; + const rmt::Vector& ShadowFacing; + float ShadowAlpha; + float ShadowScale; +}; + +class CharacterRenderable +{ +public: + static const int NUM_CHARACTER_SWATCHES = 5; + +public: + // Constructor + CharacterRenderable( tDrawablePose* pDrawablePoseHigh, + tDrawablePose* pDrawablePoseMedium, + tDrawablePose* pDrawablePoseLow); + + ~CharacterRenderable( void ); + + void Display( rmt::Vector iPosn, tPose* pose ); + + enum CharacterLOD + { + Low, + Medium, + High, + MAX_LOD + }; + + int GetLOD( void ) const; + void SetLOD( int LOD ); + + tDrawablePose* GetDrawable( ) const; + + int CastsShadow(); + + // If GroundPos/GroundNormal are null then it's a volumetric shadow, otherwise it's a simple shadow. + void DisplayShadow( tPose* pose, const BlobShadowParams* BlobParams ); + + void SetInAnyonesFrustrum( bool inFrustrum ) {mbInAnyonesFrustrum = inFrustrum;} + + void SetSwatch( int index ); + void SetSwatchShader( tShader* pShader ); + void SetSwatchTexture( int index, tTexture* pTexture ); + void SetShockEffect( tDrawable* pDrawable ); + + void SetFadeAlpha( int fadeAlpha ); + + void SetShadowColour( tColour colour ); + + void SetShocked( bool isShocked ){ mIsShocked = isShocked; } + bool GetIsShocked()const{ return mIsShocked; } + +private: + // No default construction. + CharacterRenderable( void ); + CharacterRenderable( const CharacterRenderable& characterRenderable ); + + void DisplayModel( tPose* pose ); + void DisplayShocked( tPose* pose ); + + tDrawablePose* mpDrawableList[ MAX_LOD ]; + int mCurrentLOD; + + bool mbInAnyonesFrustrum; + + tTexture* mpSwatchTexture; + tTexture* mpSwatchTextures[NUM_CHARACTER_SWATCHES]; + tShader* mpSwatchShader; + + tPose::Joint* mShadowJoints[ 4 ]; + bool mHaveShadowJoints; + + int mFadeAlpha; + + tColour mShadowColour; + tDrawable* mpShockedDrawable; + bool mIsShocked; + bool mDisplayingSkeletonShock; +}; + + +#endif // CHARACTERRENDERABLE_H_
\ No newline at end of file diff --git a/game/code/worldsim/character/charactertarget.cpp b/game/code/worldsim/character/charactertarget.cpp new file mode 100644 index 0000000..39c7f45 --- /dev/null +++ b/game/code/worldsim/character/charactertarget.cpp @@ -0,0 +1,312 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: +// +// Description: Implementation of class CharacterTarget +// +// History: 5/13/2002 + Created -- NAME +// +//============================================================================= + +//======================================== +// System Includes +//======================================== +// Foundation Tech +#include <raddebug.hpp> + +//======================================== +// Project Includes +//======================================== +#include <worldsim/character/charactertarget.h> +#include <worldsim/character/character.h> + + + +//****************************************************************************** +// +// Global Data, Local Data, Local Classes +// +//****************************************************************************** + +//****************************************************************************** +// +// Public Member Functions +// +//****************************************************************************** + +/* +============================================================================== +CharacterTarget::CharacterTarget +============================================================================== +Description: Comment + +Parameters: ( Character* pCharacter ) + +Return: CharacterTarget + +============================================================================= +*/ +CharacterTarget::CharacterTarget( Character* pCharacter ) : + mWalkerID( CharacterEnum::INVALID ) +{ + mpCharacter = pCharacter; +} +/* +============================================================================== +CharacterTarget::~CharacterTarget +============================================================================== +Description: Comment + +Parameters: () + +Return: CharacterTarget + +============================================================================= +*/ +CharacterTarget::~CharacterTarget() +{ +} + +/* +============================================================================== +CharacterTarget::GetPosition +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector* position ) + +Return: void + +============================================================================= +*/ +void CharacterTarget::GetPosition( rmt::Vector* position ) +{ + mpCharacter->GetPosition( *position ); +} +/* +============================================================================== +CharacterTarget::GetHeading +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector* heading ) + +Return: void + +============================================================================= +*/ +void CharacterTarget::GetHeading( rmt::Vector* heading ) +{ + mpCharacter->GetFacing( *heading ); +} +/* +============================================================================== +CharacterTarget::GetVUP +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector* vup ) + +Return: void + +============================================================================= +*/ +void CharacterTarget::GetVUP( rmt::Vector* vup ) +{ + vup->Set( 0.0f, 1.0f, 0.0f ); +} +/* +============================================================================== +CharacterTarget::GetVelocity +============================================================================== +Description: Comment + +Parameters: ( rmt::Vector* velocity ) + +Return: void + +============================================================================= +*/ +void CharacterTarget::GetVelocity( rmt::Vector* velocity ) +{ + mpCharacter->GetVelocity( *velocity ); +} + +//============================================================================= +// CharacterTarget::GetID +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: unsigned +// +//============================================================================= +unsigned int CharacterTarget::GetID() +{ + return mWalkerID; +} + +/* +============================================================================== +CharacterTarget::IsCar +============================================================================== +Description: Comment + +Parameters: () + +Return: bool + +============================================================================= +*/ +bool CharacterTarget::IsCar() const +{ + return false; +} +/* +============================================================================== +CharacterTarget::IsAirborn +============================================================================== +Description: Comment + +Parameters: () + +Return: bool + +============================================================================= +*/ +bool CharacterTarget::IsAirborn() +{ + return !mpCharacter->IsStanding(); +} +/* +============================================================================== +CharacterTarget::IsUnstable +============================================================================== +Description: Comment + +Parameters: () + +Return: bool + +============================================================================= +*/ +bool CharacterTarget::IsUnstable() +{ + return false; + /* + return( !mpCharacter->IsNPC() && + mpCharacter->GetSimState()->GetControl() == sim::simSimulationCtrl ); + */ +} +/* +============================================================================== + CharacterTarget::IsQuickTurn + ============================================================================== + Description: Comment + +Parameters: () + +Return: bool + +============================================================================= +*/ +bool CharacterTarget::IsQuickTurn() +{ + return false; +} + +//============================================================================= +// CharacterTarget::GetFirstPersonPosition +//============================================================================= +// Description: Comment +// +// Parameters: ( rmt::Vector* position ) +// +// Return: void +// +//============================================================================= +void CharacterTarget::GetFirstPersonPosition( rmt::Vector* position ) +{ + poser::Pose* pose = mpCharacter->GetPuppet()->GetPose(); + + rmt::Vector pos; + pos = pose->GetJoint(17)->GetWorldMatrix().Row(3); // 17 is the head (should be looking it up by name, but what the hell) + pos -= pose->GetJoint(0)->GetWorldMatrix().Row(3); // 0 is the motion root + pos.y += 0.05f; // bump it up a little, the head joint actually a little too low + + //Find the eye spot on the character and return that position. + position->Set( 0.0f, pos.y, 0.0f ); +} + + +/* +============================================================================== +CharacterTarget::GetName +============================================================================== +Description: This is only for debugging, so in the implementation go ahead and + make this return NULL in release. + +Parameters: () + +Return: const + +============================================================================= +*/ +const char* const CharacterTarget::GetName() +{ + return 0; +} + +//============================================================================= +// CharacterTarget::SetID +//============================================================================= +// Description: Comment +// +// Parameters: ( CharacterEnum::WalkerID id ) +// +// Return: void +// +//============================================================================= +void CharacterTarget::SetID( CharacterEnum::WalkerID id ) +{ + mWalkerID = id; +} + +//============================================================================= +// CharacterTarget::IsInReverse +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: bool +// +//============================================================================= +bool CharacterTarget::IsInReverse() +{ + return false; +} + +//============================================================================= +// CharacterTarget::GetTerrainIntersect +//============================================================================= +// Description: Comment +// +// Parameters: ( rmt::Vector& pos, rmt::Vector& normal ) +// +// Return: void +// +//============================================================================= +void CharacterTarget::GetTerrainIntersect( rmt::Vector& pos, rmt::Vector& normal ) const +{ + mpCharacter->GetTerrainIntersect( pos, normal ); +} + + +//****************************************************************************** +// +// Private Member Functions +// +//****************************************************************************** diff --git a/game/code/worldsim/character/charactertarget.h b/game/code/worldsim/character/charactertarget.h new file mode 100644 index 0000000..0ad5595 --- /dev/null +++ b/game/code/worldsim/character/charactertarget.h @@ -0,0 +1,72 @@ +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: charactertarget.h +// +// Description: Blahblahblah +// +// History: 5/13/2002 + Created -- NAME +// +//============================================================================= + +#ifndef CHARACTERTARGET_H +#define CHARACTERTARGET_H + +//======================================== +// Nested Includes +//======================================== +#include <radmath/radmath.hpp> +#include <camera/isupercamtarget.h> +#include <constants/characterenum.h> +#include <presentation/gui/utility/hudmap.h> + +//======================================== +// Forward References +//======================================== +class Character; + +//============================================================================= +// +// Synopsis: Blahblahblah +// +//============================================================================= + +class CharacterTarget +: +public ISuperCamTarget, public IHudMapIconLocator +{ +public: + CharacterTarget( Character* pCharacter ); + ~CharacterTarget(); + + virtual void GetPosition( rmt::Vector* position ); + virtual void GetHeading( rmt::Vector* heading ); + virtual void GetVUP( rmt::Vector* vup ); + virtual void GetVelocity( rmt::Vector* velocity ); + virtual unsigned int GetID(); + virtual bool IsCar() const; + virtual bool IsAirborn(); + virtual bool IsUnstable(); + virtual bool IsQuickTurn(); + virtual bool IsInReverse(); + virtual void GetFirstPersonPosition( rmt::Vector* position ); + virtual void GetTerrainIntersect( rmt::Vector& pos, rmt::Vector& normal ) const; + + + + //This is only for debugging, so in the implementation go ahead and + //make this return NULL in release. + virtual const char* const GetName(); + + void SetID( CharacterEnum::WalkerID id ); +private: + + //Prevent wasteful constructor creation. + CharacterTarget( void ); + CharacterTarget( const CharacterTarget& charactertarget ); + CharacterTarget& operator=( const CharacterTarget& charactertarget ); + + Character* mpCharacter; + CharacterEnum::WalkerID mWalkerID; +}; + +#endif //CHARACTERTARGET_H
\ No newline at end of file diff --git a/game/code/worldsim/character/controllereventhandler.h b/game/code/worldsim/character/controllereventhandler.h new file mode 100644 index 0000000..7d3c024 --- /dev/null +++ b/game/code/worldsim/character/controllereventhandler.h @@ -0,0 +1,54 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: controllereventhandler.h +// +// Description: Blahblahblah +// +// History: 09/08/2002 + Created -- NAME +// +//============================================================================= + +#ifndef CONTROLLEREVENTHANDLER_H_ +#define CONTROLLEREVENTHANDLER_H_ + +//======================================== +// Nested Includes +//======================================== + +//======================================== +// Forward References +//======================================== + +//============================================================================= +// +// Synopsis: Blahblahblah +// +//============================================================================= +#include <events/eventlistener.h> + +class CameraRelativeCharacterController; + +class CameraRelativeCharacterControllerEventHandler +: +public EventListener +{ +public: + CameraRelativeCharacterControllerEventHandler( CameraRelativeCharacterController* pParent ) + : + mpParent( pParent ) + { + } + // Derived classes must implement this method to receive + // event notification. + virtual void HandleEvent( EventEnum id, void* pEventData ) + { + mpParent->HandleEvent( id, pEventData ); + } +protected: +private: + CameraRelativeCharacterControllerEventHandler( void ); + CameraRelativeCharacterController* mpParent; +}; + +#endif //CONTROLLEREVENTHANDLER_H_
\ No newline at end of file diff --git a/game/code/worldsim/character/footprint/allfootprint.cpp b/game/code/worldsim/character/footprint/allfootprint.cpp new file mode 100644 index 0000000..88e1a6f --- /dev/null +++ b/game/code/worldsim/character/footprint/allfootprint.cpp @@ -0,0 +1 @@ +#include <worldsim\character\footprint\footprintmanager.cpp>
\ No newline at end of file diff --git a/game/code/worldsim/character/footprint/footprint.cpp b/game/code/worldsim/character/footprint/footprint.cpp new file mode 100644 index 0000000..7b019ab --- /dev/null +++ b/game/code/worldsim/character/footprint/footprint.cpp @@ -0,0 +1,49 @@ +//=========================================================================== +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// Component: footprintmanager +// +// Description: Holds a list of all footprints, and fades them out over time +// +// Authors: Michael Riegger +// +//=========================================================================== + + +//--------------------------------------------------------------------------- +// Includes +//=========================================================================== + +#include <worldsim/character/footprint/footprint.h> + + +//=========================================================================== +// Local Constants, Typedefs, and Macros +//=========================================================================== + + +//=========================================================================== +// Global Data, Local Data, Local Classes +//=========================================================================== + + +//=========================================================================== +// Member Functions +//=========================================================================== + +Footprint::Footprint() +{ + +} + +Footprint::~Footprint() +{ + +} + +void +Footprint::Display() +{ + +} + diff --git a/game/code/worldsim/character/footprint/footprint.h b/game/code/worldsim/character/footprint/footprint.h new file mode 100644 index 0000000..3e299a0 --- /dev/null +++ b/game/code/worldsim/character/footprint/footprint.h @@ -0,0 +1,73 @@ +//=========================================================================== +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// Component: footprint +// +// Description: Simple class inherited from tDrawable. Draws a foot print +// +// Authors: Michael Riegger +// +//=========================================================================== + +// Recompilation protection flag. +#ifndef FOOTPRINT_H +#define FOOTPRINT_H + + +//=========================================================================== +// Nested Includes +//=========================================================================== + +#include <p3d\drawable.hpp> + +//=========================================================================== +// Forward References +//=========================================================================== + +//=========================================================================== +// Constants, Typedefs, and Macro Definitions (needed by external clients) +//=========================================================================== + +//=========================================================================== +// Interface Definitions +//=========================================================================== + +//=========================================================================== +// +// Description: +// Generally, describe what behaviour this class possesses that +// clients can rely on, and the actions that this service +// guarantees to clients. +// +// Constraints: +// Describe here what you require of the client which uses or +// has this class - essentially, the converse of the description +// above. A constraint is an expression of some semantic +// condition that must be preserved, an invariant of a class or +// relationship that must be preserved while the system is in a +// steady state. +// +//=========================================================================== +class Footprint : public tDrawable +{ + public: + Footprint(); + virtual ~Footprint(); + + virtual void Display(); + + + protected: + + private: + // These methods defined as private and not implemented ensure that + // clients will not be able to use them. For example, we will + // disallow Footprint from being copied and assigned. + Footprint( const Footprint& ); + Footprint& operator=( const Footprint& ); + +}; + + + +#endif
\ No newline at end of file diff --git a/game/code/worldsim/character/footprint/footprintmanager.cpp b/game/code/worldsim/character/footprint/footprintmanager.cpp new file mode 100644 index 0000000..7a9a313 --- /dev/null +++ b/game/code/worldsim/character/footprint/footprintmanager.cpp @@ -0,0 +1,252 @@ +//=========================================================================== +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// Component: footprintmanager +// +// Description: Holds a list of all footprints, and fades them out over time +// +// Authors: Michael Riegger +// +//=========================================================================== + + +//--------------------------------------------------------------------------- +// Includes +//=========================================================================== + +#include <worldsim/character/footprint/footprintmanager.h> +#include <memory/srrmemory.h> +#include <p3d/utility.hpp> +#include <contexts/bootupcontext.h> +#include <p3d/texture.hpp> +#include <p3d/matrixstack.hpp> +#include <p3d/shader.hpp> +//=========================================================================== +// Local Constants, Typedefs, and Macros +//=========================================================================== + +const float FOOTPRINT_LIVE_TIME = 0.5f; +const float FOOTPRINT_FADE_PER_MS = 1 / 2000.0f; + + +//=========================================================================== +// Global Data, Local Data, Local Classes +//=========================================================================== + +FootprintManager* FootprintManager::spFootprintManager = 0; + +//=========================================================================== +// Member Functions +//=========================================================================== + +FootprintManager::Footprint::Footprint(): +alpha( 0 ) +{ + const float FOOTPRINT_WIDTH = 1.0f; + const float FOOTPRINT_HEIGHT = 1.0f; + const float HALF_FOOTPRINT_WIDTH = FOOTPRINT_WIDTH / 2.0f; + const float HALF_FOOTPRINT_HEIGHT = FOOTPRINT_HEIGHT / 2.0f; + + rmt::Vector topLeft( -HALF_FOOTPRINT_WIDTH, 0, HALF_FOOTPRINT_HEIGHT ); + rmt::Vector topRight( HALF_FOOTPRINT_WIDTH, 0 , HALF_FOOTPRINT_HEIGHT ); + rmt::Vector bottomLeft( -HALF_FOOTPRINT_WIDTH,0 ,-HALF_FOOTPRINT_HEIGHT ); + rmt::Vector bottomRight( HALF_FOOTPRINT_WIDTH,0, -HALF_FOOTPRINT_HEIGHT ); + + points[0] = bottomLeft; + points[1] = bottomRight; + points[2] = topLeft; + points[3] = topRight; +} + +FootprintManager::FootprintManager() +{ + for ( int i = 0 ; i < eNumFootprintTypes ; i++ ) + { + m_ActiveFootprints[ i ].Allocate( MAX_NUM_FOOTPRINTS ); + mpTextures[ i ] = NULL; + } +} + +FootprintManager::~FootprintManager() +{ + FreeTextures(); +} + +FootprintManager* FootprintManager::CreateInstance( void ) +{ + rAssertMsg( spFootprintManager == 0, "FootprintManager already created.\n" ); + HeapManager::GetInstance()->PushHeap( GMA_PERSISTENT ); + spFootprintManager = new FootprintManager; + rAssert( spFootprintManager ); + HeapManager::GetInstance()->PopHeap(GMA_PERSISTENT); + return FootprintManager::GetInstance(); +} + +FootprintManager* FootprintManager::GetInstance( void ) +{ + rAssertMsg( spFootprintManager != 0, "FootprintManager has not been created yet.\n" ); + return spFootprintManager; +} + +void FootprintManager::DestroyInstance( void ) +{ + rAssertMsg( spFootprintManager != 0, "FootprintManager has not been created.\n" ); + delete spFootprintManager; + spFootprintManager = 0; +} + +bool FootprintManager::CreateFootprint( const rmt::Matrix& transform, TYPE type ) +{ + + bool footprintCreated; + + if ( mpTextures[ type ] == NULL ) + return false; + + Footprint footprint; + footprint.alpha = 1.0f; + for ( int i = 0 ; i < 4 ; i++ ) + { + transform.Transform( footprint.points[i], &footprint.points[i] ); + } + + if ( m_ActiveFootprints[ type ].mSize > m_ActiveFootprints[ type ].mUseSize ) + { + m_ActiveFootprints[ type ].Add( footprint ); + footprintCreated = true; + } + else + { + footprintCreated = false; + } + return footprintCreated; +} + +void FootprintManager::Render() +{ + return; + int i,j; + + + SetTexture( FootprintManager::eSquishies, "footprint.bmp" ); + + bool oldZWrite = p3d::pddi->GetZWrite(); + pddiCompareMode oldZComp = p3d::pddi->GetZCompare(); + if( oldZWrite ) + { + p3d::pddi->SetZWrite( false ); + } + if( oldZComp != PDDI_COMPARE_LESS ) + { + p3d::pddi->SetZCompare( PDDI_COMPARE_LESS ); + } + pddiCullMode cm = p3d::pddi->GetCullMode(); + p3d::pddi->SetCullMode(PDDI_CULL_NONE); + + for ( i = 0 ; i < eNumFootprintTypes ;i++) + { + if ( m_ActiveFootprints[i].mUseSize == 0 ) + continue; + + if ( mpTextures[i] == NULL ) + continue; + + pddiShader* pShader = GetBootupContext()->GetSharedShader(); + pShader->SetInt( PDDI_SP_ISLIT, 0 ); + pShader->SetInt( PDDI_SP_ALPHATEST, 0 ); + pShader->SetInt( PDDI_SP_SHADEMODE, PDDI_SHADE_FLAT ); + pShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ALPHA ); + pShader->SetTexture( PDDI_SP_BASETEX, mpTextures[ i ]->GetTexture() ); + + pddiPrimStream* stream = p3d::pddi->BeginPrims( pShader, PDDI_PRIM_TRIANGLES, PDDI_V_CT, m_ActiveFootprints[i].mUseSize * 6 ); + + for ( j = 0 ; j < m_ActiveFootprints[i].mUseSize ; j++ ) + { + Footprint& footprint = m_ActiveFootprints[i][j]; + tColour colour; + colour.Set( 255,255,255, static_cast< int >( footprint.alpha * 255.0f )); + + // 2 triangles for every foot step + // Now draw the triangle. + // bottom left tri. + stream->Colour( colour ); + stream->UV( 0.0f, 0.0f ); + stream->Coord( footprint.points[0].x, footprint.points[0].y, footprint.points[0].z ); + + stream->Colour( colour ); + stream->UV( 1.0f, 0.0f ); + stream->Coord( footprint.points[1].x, footprint.points[1].y, footprint.points[1].z ); + + stream->Colour( colour ); + stream->UV( 0.0f, 1.0f ); + stream->Coord( footprint.points[2].x, footprint.points[2].y, footprint.points[2].z ); + + // Top right tri. + stream->Colour( colour ); + stream->UV( 1.0f, 1.0f ); + stream->Coord( footprint.points[3].x, footprint.points[3].y, footprint.points[3].z ); + + stream->Colour( colour ); + stream->UV( 0.0f, 1.0f ); + stream->Coord( footprint.points[2].x, footprint.points[2].y, footprint.points[2].z ); + + stream->Colour( colour ); + stream->UV( 1.0f, 0.0f ); + stream->Coord( footprint.points[1].x, footprint.points[1].y, footprint.points[1].z ); + + } + p3d::pddi->EndPrims( stream ); + pShader->SetTexture( PDDI_SP_BASETEX, 0 ); + + } + + if( oldZWrite ) + { + p3d::pddi->SetZWrite( true ); + } + if( oldZComp != PDDI_COMPARE_ALWAYS ) + { + p3d::pddi->SetZCompare( oldZComp ); + } + p3d::pddi->SetCullMode(cm); + + +} + +void FootprintManager::Update( unsigned int timeInMS ) +{ + for ( int i = 0 ; i < eNumFootprintTypes ; i++ ) + { + for ( int j = 0 ; j < m_ActiveFootprints[ i ].mUseSize ; j++ ) + { + m_ActiveFootprints[i][j].alpha -= static_cast< float >( timeInMS )* FOOTPRINT_FADE_PER_MS; + if ( m_ActiveFootprints[i][j].alpha < 0 ) + { + m_ActiveFootprints[i].Remove( j ); + } + } + } +} + +void FootprintManager::SetTexture( TYPE type, const char* textureName ) +{ + tTexture* pTexture = p3d::find< tTexture >( textureName ); + if ( pTexture != NULL ) + { + tRefCounted::Assign( mpTextures[type], pTexture ); + } +} + +void FootprintManager::FreeTextures() +{ + for ( int i = 0 ; i < eNumFootprintTypes ; i++) + { + if ( mpTextures[i] != NULL ) + { + mpTextures[ i ]->Release(); + mpTextures[ i ] = NULL; + } + } +} + + diff --git a/game/code/worldsim/character/footprint/footprintmanager.h b/game/code/worldsim/character/footprint/footprintmanager.h new file mode 100644 index 0000000..12f83d2 --- /dev/null +++ b/game/code/worldsim/character/footprint/footprintmanager.h @@ -0,0 +1,105 @@ +//=========================================================================== +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// Component: footprintmanager +// +// Description: Holds a list of all footprints, and fades them out over time +// +// Authors: Michael Riegger +// +//=========================================================================== + +// Recompilation protection flag. +#ifndef FOOTPRINTMANAGER_H +#define FOOTPRINTMANAGER_H + + +//=========================================================================== +// Nested Includes +//=========================================================================== + +#include <worldsim\character\footprint\footprintmanager.h> +#include <render\culling\swaparray.h> +#include <p3d\utility.hpp> + +//=========================================================================== +// Forward References +//=========================================================================== + +class tShader; +class tTexture; + +//=========================================================================== +// Constants, Typedefs, and Macro Definitions (needed by external clients) +//=========================================================================== + + + +//=========================================================================== +// Interface Definitions +//=========================================================================== + +//=========================================================================== +// +// Description: +// Footprint manager - creates and renders footprints +// +// Constraints: +// +// +//=========================================================================== +class FootprintManager +{ + public: + FootprintManager(); + ~FootprintManager(); + + static FootprintManager* GetInstance( void ); + static FootprintManager* CreateInstance( void ); + static void DestroyInstance( void ); + + enum { MAX_NUM_FOOTPRINTS = 1 }; + enum TYPE { eSquishies, eNumFootprintTypes }; + + bool CreateFootprint( const rmt::Matrix& transform, TYPE type ); + void Update( unsigned int timeInMS ); + void FreeTextures(); + + void SetTexture( TYPE, const char* texture ); + void ClearAllFootPrints(); + void Render(); + + protected: + + private: + + static FootprintManager* spFootprintManager; + + struct Footprint + { + Footprint(); + + rmt::Vector points[4]; + float alpha; + }; + + typedef SwapArray< Footprint > ListOfFootprints; + + ListOfFootprints m_ActiveFootprints[eNumFootprintTypes]; + + tTexture* mpTextures[ eNumFootprintTypes ]; + + // These methods defined as private and not implemented ensure that + // clients will not be able to use them. For example, we will + // disallow FootprintManager from being copied and assigned. + FootprintManager( const FootprintManager& ); + FootprintManager& operator=( const FootprintManager& ); + + // A little syntactic sugar for getting at this singleton. +}; + +inline FootprintManager* GetFootprintManager() { return( FootprintManager::GetInstance() ); } + + + +#endif
\ No newline at end of file diff --git a/game/code/worldsim/coins/allcoins.cpp b/game/code/worldsim/coins/allcoins.cpp new file mode 100644 index 0000000..b8ea98e --- /dev/null +++ b/game/code/worldsim/coins/allcoins.cpp @@ -0,0 +1,2 @@ +#include <worldsim/coins/coinmanager.cpp> +#include <worldsim/coins/sparkle.cpp>
\ No newline at end of file diff --git a/game/code/worldsim/coins/coinmanager.cpp b/game/code/worldsim/coins/coinmanager.cpp new file mode 100644 index 0000000..0f7d50d --- /dev/null +++ b/game/code/worldsim/coins/coinmanager.cpp @@ -0,0 +1,1132 @@ +//============================================================================= +// Copyright (C) 2003 Radical Entertainment Ltd. All rights reserved. +// +// File: CoinManager.cpp +// +// Description: Implementation of class CoinManager +// +// History: 29/1/2003 + Created -- James Harrison +// +//============================================================================= + +//======================================== +// System Includes +//======================================== + +#include <radmath/matrix.hpp> +#include <radmath/random.hpp> +#include <p3d/entity.hpp> +#include <p3d/view.hpp> +#include <p3d/camera.hpp> +#include <p3d/matrixstack.hpp> +#include <pddi/pddi.hpp> + +//#include <math.h> //need this if we want extra accurate coin paths. + +//======================================== +// Project Includes +//======================================== +#include <gameflow/gameflow.h> +#include <worldsim/coins/coinmanager.h> +#include <worldsim/coins/sparkle.h> +#include <worldsim/avatarmanager.h> +#include <worldsim/redbrick/vehicle.h> +#include <memory/srrmemory.h> +#include <events/eventmanager.h> +#include <render/intersectmanager/intersectmanager.h> +#include <render/rendermanager/rendermanager.h> +#include <render/rendermanager/worldrenderlayer.h> +#include <data/persistentworldmanager.h> +#include <mission/gameplaymanager.h> +#include <mission/charactersheet/charactersheetmanager.h> + +/* Watcher stuff */ +#ifndef RAD_RELEASE +#include <raddebug.hpp> +#include <raddebugwatch.hpp> + +static float COIN_HOVER = 0.5f; // How far off the ground will the coin hover. +#if !defined( RAD_PS2 ) && defined( RAD_RELEASE ) +static const float FRAME_RATIO = 1.0f / 60.0f; +//static float COIN_DRAG = 0.9848f; // .4 to the power of 1/60 of a second. +#elif defined( RAD_RELEASE ) +static const float FRAME_RATIO = 1.0f / 30.0f; +//static float COIN_DRAG = 0.9700f; // .4 to the power of 1/30 of a second. +#else +static const float FRAME_RATIO = 1.0f / 15.0f; +//static float COIN_DRAG = 0.9407f; // .4 to the power of 1/15 of a second. +#endif +static float COIN_SPAWN_VELOCITY = 9.5f;// * FRAME_RATIO; +static float COIN_EXTRA_VERTICAL = 1.1f;// * FRAME_RATIO; +// Use these values if you want accurate coins. +static float GRAVITY = 21.0f; // * FRAME_RATIO; +static float COIN_DRAG = 0.1f; + +// Use these if you want faster (but less accurate coins. +//static float GRAVIYT = 0.9f * FRAME_RATIO; + +static float LIFE_TIME = 5.0f; // How many seconds the coin lives for. +static float DECAY_TIME = 5.0f; // How long does it take to disappear. +#define I_DECAY_TIME ( 1.0f / DECAY_TIME ) // Instead of doing a divide. +static float DECAY_EXTRA_SPIN = rmt::PI * 8.0f; // How much extra spin per frame do we want. +static float SPIN_MULTIPLIER = 3.0f; +static float RANGE = 2.25f; // Coin collection radius. +#define INNER_RANGE ( RANGE * ( 4.0f / 3.0f ) ) +static float COLLECT_TIME = 1.0f; // How long does it take a coin to suck into the player. +#define I_COLLECT_TIME ( 1.0f / COLLECT_TIME ) +static float POP_SCALE = 0.4f; // How much pop up does the coin have when it is collected. +static float BOUNCE_DAMPENING = 0.6f; // How much energy does the coin retain when it hits the ground. + +static float FLYING_TIME = 0.25f; // How long does it take to fly up to the HUD. +#define I_FLYING_TIME ( 1.0f / FLYING_TIME ) +static float MAX_VISIBLITY = 60.0f; // How far away is the coin. +static float COUNTER_X = 0.45f; // Where is the destination we are flying the coin up to. +static float COUNTER_Y = 0.45f; // Where is the destination we are flying the coin up to. +#ifdef RAD_XBOX +static float SPARKLE_FLOAT = 0.025f; // How much vertical velocity for the sparkles per frame. +#else +static float SPARKLE_FLOAT = 0.05f; +#endif +static float IN_CAR_RANGE_MULTIPLIER = 2.25f; +static int dbg_CoinsDrawn = 0; +static int dbg_MaxCoins = 0; + +#else /* Same as the variables available to the watcher, but const so the compiler can optimize them. */ +const static float COIN_HOVER = 0.5f; // How far off the ground will the coin hover. +// Use this for more accurate coins. +//static const float COIN_SPAWN_VELOCITY = 4.0f; +//static const float COIN_EXTRA_VERTICAL = 2.0f +#if !defined( RAD_PS2 ) && defined ( RAD_RELEASE ) +static const float FRAME_RATIO = 1.0f / 60.0f; +//static const float COIN_DRAG = 0.9848f; // .4 to the power of 1/60 of a second. +#elif defined( RAD_RELEASE ) +static const float FRAME_RATIO = 1.0f / 30.0f; +//static const float COIN_DRAG = 0.9700f; // .4 to the power of 1/30 of a second. +#else +static const float FRAME_RATIO = 1.0f / 15.0f; +//static const float COIN_DRAG = 0.9407f; // .4 to the power of 1/15 of a second. +#endif +static const float COIN_SPAWN_VELOCITY = 9.5f;// * FRAME_RATIO; +static const float COIN_EXTRA_VERTICAL = 1.1f;// * FRAME_RATIO; +static const float GRAVITY = 21.0f; +static const float COIN_DRAG = 0.1f; +//static const float GRAVITY = 0.9f * FRAME_RATIO; +static const float LIFE_TIME = 11.0f; // How many seconds the coin lives for. +static const float DECAY_TIME = 5.0f; // How long does it take to disappear. +static const float I_DECAY_TIME = 1.0f / DECAY_TIME; // Instead of doing a divide. +static const float DECAY_EXTRA_SPIN = rmt::PI * 8.0f; // How much extra spin per frame do we want. +static const float SPIN_MULTIPLIER = 3.0f; +static const float RANGE = 2.25f; // Coin collection radius. +static const float INNER_RANGE = RANGE * ( 4.0f / 3.0f ); +static const float COLLECT_TIME = 1.0f; // How long does it take a coin to suck into the player. +static const float I_COLLECT_TIME = 1.0f / COLLECT_TIME; +static const float POP_SCALE = 0.4f; // How much pop up does the coin have when it is collected. +// Use these values if you want accurate coins. +/* +static const float COIN_DRAG = 0.5f; +static const float GRAVITY = 0.9f; +*/ +static const float BOUNCE_DAMPENING = 0.6f; // How much energy does the coin retain when it hits the ground. +static const float FLYING_TIME = 0.25f; // How long does it take to fly up to the HUD. +static const float I_FLYING_TIME = 1.0f / FLYING_TIME; +static const float MAX_VISIBLITY = 60.0f; // How far away is the coin. +static const float COUNTER_X = 0.25f; // Where is the destination we are flying the coin up to. +static const float COUNTER_Y = 0.4f; // Where is the destination we are flying the coin up to. +#ifdef RAD_XBOX +static const float SPARKLE_FLOAT = 0.025f; +#else +static const float SPARKLE_FLOAT = 0.05f; +#endif +static const float IN_CAR_RANGE_MULTIPLIER = 2.25f; +#endif + +//****************************************************************************** +// +// Global Data, Local Data, Local Classes +// +//****************************************************************************** +CoinManager* CoinManager::spCoinManager = 0; + +static const int NUM_COINS = 200; +static const int NUM_SPARKLES = 120; // Although we seldom use all these sparkles we do when the vehicle explodes. +#ifdef RAD_XBOX +static int Sparkle_Rate = 6; +static const int Glint_Rate = 4; +static const int SPARKLE_RATE_TOGGLE = Sparkle_Rate ^ ( Sparkle_Rate + 2 ); +static const int HUD_SPARKLE_RATE = 60; +#else +static int Sparkle_Rate = 3; +static const int Glint_Rate = 2; +static const int SPARKLE_RATE_TOGGLE = Sparkle_Rate ^ ( Sparkle_Rate + 1 ); +static const int HUD_SPARKLE_RATE = 30; +#endif +static int DoSparkle = 0; +static int DoGlint = 0; +static int GlintDelay = 0; + +//****************************************************************************** +// +// Public Member Functions +// +//****************************************************************************** + +CoinManager* CoinManager::CreateInstance( void ) +{ + rAssertMsg( spCoinManager == 0, "CoinManager already created.\n" ); + HeapManager::GetInstance()->PushHeap( GMA_PERSISTENT ); + spCoinManager = new CoinManager; + rAssert( spCoinManager ); + HeapManager::GetInstance()->PopHeap( GMA_PERSISTENT ); + return CoinManager::GetInstance(); +} + +CoinManager* CoinManager::GetInstance( void ) +{ + rAssertMsg( spCoinManager != 0, "CoinManager has not been created yet.\n" ); + return spCoinManager; +} + +void CoinManager::DestroyInstance( void ) +{ + rAssertMsg( spCoinManager != 0, "CoinManager has not been created.\n" ); + delete spCoinManager; + spCoinManager = 0; +} + +//============================================================================== +// Description: Constructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +CoinManager::CoinManager() : + m_pCoinDrawable( 0 ), + mActiveCoins( NULL), + mNumActiveCoins( 0 ), + mNextInactiveCoin( 0 ), + mNumHUDFlying( 0 ), + mHUDSparkle( 0 ), + mHUDCoinX( -10.0f ), + mHUDCoinY( -10.0f ), + mHUDCoinAngle( 0.0f ), + mDrawAfterGui(false) +{ +#ifndef RAD_RELEASE + radDbgWatchAddFloat( &COIN_SPAWN_VELOCITY, "Spawn - Initial velocity", "Coins", 0, 0, 0.01f, 5.0f ); + radDbgWatchAddFloat( &COIN_EXTRA_VERTICAL, "Spawn - Initial extra vertical", "Coins", 0, 0, 0.0f, 2.5f ); + radDbgWatchAddFloat( &COIN_DRAG, "Spawn - Movement drag", "Coins", 0, 0, 0.1f, 1.0f ); + radDbgWatchAddFloat( &GRAVITY, "Spawn - Gravity", "Coins", 0, 0, 0.1f, 2.0f ); + radDbgWatchAddFloat( &BOUNCE_DAMPENING, "Spawn - Bounce energy dampening", "Coins", 0, 0, 0.0f, 2.0f ); + radDbgWatchAddFloat( &COIN_HOVER, "Spawn - Ground hover", "Coins", 0, 0, 0.0f, 2.0f ); + radDbgWatchAddFloat( &LIFE_TIME, "Duration - Life", "Coins", 0, 0, 1.0f, 30.0f ); + radDbgWatchAddFloat( &DECAY_TIME, "Duration - Decay", "Coins", 0, 0, 1.0f, 10.0f ); + radDbgWatchAddFloat( &RANGE, "Collection - Radius", "Coins", 0, 0, 0.5f, 10.0f ); + radDbgWatchAddFloat( &IN_CAR_RANGE_MULTIPLIER, "Collection - In car multiplier", "Coins", 0, 0, 0.0f, 5.0f ); + radDbgWatchAddFloat( &DECAY_EXTRA_SPIN, "Animation - Extra Decay spin", "Coins", 0, 0, 0.0f, 50.0f ); + radDbgWatchAddFloat( &SPIN_MULTIPLIER, "Animation - Spin multiplier", "Coins", 0, 0, 0.01f, 5.0f ); + radDbgWatchAddFloat( &COLLECT_TIME, "Animation - Collection duration", "Coins", 0, 0, 0.1f, 5.0f ); + radDbgWatchAddFloat( &POP_SCALE, "Animation - Collection pop up", "Coins", 0, 0, 0.1f, 1.0f ); + radDbgWatchAddFloat( &FLYING_TIME, "Animation - Fly to HUD duration", "Coins", 0, 0, 0.1f, 1.0f ); + radDbgWatchAddFloat( &COUNTER_X, "Animation - HUD X position", "Coins", 0, 0, -0.7f, 0.7f ); + radDbgWatchAddFloat( &COUNTER_Y, "Animation - HUD Y position", "Coins", 0, 0, -0.7f, 0.7f ); + radDbgWatchAddFloat( &SPARKLE_FLOAT, "Animation - Sparkle float", "Coins", 0, 0, 0.0f, 0.5f ); + radDbgWatchAddFloat( &MAX_VISIBLITY, "Visibility culling distance", "Coins", 0, 0, 10.0f, 100.0f ); + radDbgWatchAddShort( &mNumActiveCoins, "Active coin count", "Coins", 0, 0, 0, NUM_COINS, true ); + radDbgWatchAddInt( &dbg_CoinsDrawn, "Drawn coin count", "Coins", 0, 0, 0, NUM_COINS, true ); + radDbgWatchAddInt( &dbg_MaxCoins, "Max active coins", "Coins", 0, 0, 0, NUM_COINS, true ); +#endif +} + +//============================================================================== +// Description: Destructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +CoinManager::~CoinManager() +{ +#ifndef RAD_RELEASE + radDbgWatchDelete( &COIN_SPAWN_VELOCITY ); + radDbgWatchDelete( &COIN_EXTRA_VERTICAL ); + radDbgWatchDelete( &COIN_DRAG ); + radDbgWatchDelete( &GRAVITY ); + radDbgWatchDelete( &BOUNCE_DAMPENING ); + radDbgWatchDelete( &COIN_HOVER ); + radDbgWatchDelete( &LIFE_TIME ); + radDbgWatchDelete( &DECAY_TIME ); + radDbgWatchDelete( &RANGE ); + radDbgWatchDelete( &DECAY_EXTRA_SPIN ); + radDbgWatchDelete( &SPIN_MULTIPLIER ); + radDbgWatchDelete( &COLLECT_TIME ); + radDbgWatchDelete( &POP_SCALE ); + radDbgWatchDelete( &FLYING_TIME ); + radDbgWatchDelete( &COUNTER_X ); + radDbgWatchDelete( &COUNTER_Y ); + radDbgWatchDelete( &SPARKLE_FLOAT ); + radDbgWatchDelete( &MAX_VISIBLITY ); + radDbgWatchDelete( &mNumActiveCoins ); + radDbgWatchDelete( &dbg_CoinsDrawn ); + radDbgWatchDelete( &dbg_MaxCoins ); +#endif + tRefCounted::Release( m_pCoinDrawable ); + Destroy( ); +} + +//============================================================================== +// Description: Destruction method for all transient data. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +void CoinManager::Destroy( void ) +{ + SetCoinDrawable( NULL ); + + if( mActiveCoins != NULL ) + { + GetCheatInputSystem()->UnregisterCallback( this ); + + GetEventManager()->RemoveAll( this ); + + delete [] mActiveCoins; + mActiveCoins = NULL; + } + mNumHUDFlying = 0; + mNumActiveCoins = 0; +} + +/*============================================================================== +Description: This will be called set up as the game session begins. + +Parameters: ( void ) + +Return: void + +=============================================================================*/ +void CoinManager::Init( void ) +{ + #ifdef RAD_GAMECUBE + HeapManager::GetInstance()->PushHeap( GMA_GC_VMM ); + #else + HeapManager::GetInstance()->PushHeap( GMA_LEVEL_ZONE ); + #endif + mActiveCoins = new ActiveCoin[ NUM_COINS ]; + rAssert( mActiveCoins ); + #ifdef RAD_GAMECUBE + HeapManager::GetInstance()->PopHeap( GMA_GC_VMM ); + #else + HeapManager::GetInstance()->PopHeap( GMA_LEVEL_ZONE ); + #endif + mNextInactiveCoin = 0; + mNumActiveCoins = 0; + mNumHUDFlying = 0; + DoSparkle = 0; + DoGlint = 0; +#ifndef RAD_RELEASE + dbg_MaxCoins = 0; +#endif + + GetEventManager()->AddListener( this, EVENT_VEHICLE_DESTROYED ); + GetEventManager()->AddListener( this, EVENT_DUMP_DYNA_SECTION ); + + GetCheatInputSystem()->RegisterCallback( this ); +} + +void CoinManager::HandleEvent( EventEnum id, void* pEventData ) +{ + switch ( id ) + { + case EVENT_VEHICLE_DESTROYED: + { + Vehicle* vehicle = reinterpret_cast<Vehicle*>( pEventData ); + OnVehicleDestroyed( vehicle ); + } + break; + case EVENT_DUMP_DYNA_SECTION: + { + RemoveWorldCoins( reinterpret_cast<tName*>( pEventData )->GetUID() ); + } + break; + default: + { + break; + } + } +} + +/*============================================================================= +Go nosing through he active coin array looking for an inactive coin. Update +the next inactive coin index while we are at it to speed up the search. +=============================================================================*/ +CoinManager::ActiveCoin* CoinManager::GetInactiveCoin( void ) +{ + if( mNumActiveCoins >= NUM_COINS ) + { + return 0; + } + for( int i = 0; i < NUM_COINS; ++i ) + { + ActiveCoin* c = &(mActiveCoins[ mNextInactiveCoin ]); + mNextInactiveCoin = ( mNextInactiveCoin + 1 ) % NUM_COINS; + if( c->State == CS_Inactive ) + { + c->HeadingCos = 0.0f; + c->HeadingSin = 1.0f; + return c; + } + } + return 0; +} + +/*============================================================================= +Spawn count number of coins at position. You can optionally override the +groundY position (handy for exploding wasps), or it takes the Y value form +the position. +=============================================================================*/ +void CoinManager::SpawnCoins( int Count, const rmt::Vector& Position, const rmt::Vector* Direction, bool Force ) +{ + SpawnCoins( Count, Position, Position.y, Direction, Force ); +} + +void CoinManager::SpawnCoins( int Count, const rmt::Vector& Position, float GroundY, const rmt::Vector* Direction, bool Force ) +{ + rAssert( Count >= 0 ); + if( Count <= 0 ) + { + return; + } + + + // Check for a valid avatar pointer + rTuneAssert( GetAvatarManager() != NULL ); + rTuneAssert( GetAvatarManager()->GetAvatarForPlayer(0) != NULL ); + bool instaCollect = !Force && GetAvatarManager()->GetAvatarForPlayer( 0 )->IsInCar(); + + for( int i = 0; i < Count; ++i ) + { + ActiveCoin* c = GetInactiveCoin(); + if( !c ) + { + CollectCoins( Count - i ); + break; + } + SpawnCoin( *c, Position, GroundY + COIN_HOVER, Direction, instaCollect ); + } + #ifdef RAD_GAMECUBE + HeapMgr()->PushHeap( GMA_GC_VMM ); + #else + HeapMgr()->PushHeap( GMA_LEVEL_ZONE ); + #endif + GetEventManager()->TriggerEvent( EVENT_SPAWNED_COINS, reinterpret_cast<void*>( Count ) ); + #ifdef RAD_GAMECUBE + HeapMgr()->PopHeap( GMA_GC_VMM ); + #else + HeapMgr()->PopHeap( GMA_LEVEL_ZONE ); + #endif + +} + +/*============================================================================= +This spawns a coin which is immediately collected by the player. Used for giving +the player a coin reward and you want to make sure they don't miss it. +=============================================================================*/ +void CoinManager::SpawnInstantCoins(int Count, const rmt::Vector& Position) +{ + rAssert( Count >= 0 ); + if( Count <= 0 ) + { + return; + } + + // Check for a valid avatar pointer + rTuneAssert( GetAvatarManager() != NULL ); + rTuneAssert( GetAvatarManager()->GetAvatarForPlayer(0) != NULL ); + + for( int i = 0; i < Count; ++i ) + { + ActiveCoin* c = GetInactiveCoin(); + if( !c ) + { + CollectCoins( Count - i ); + break; + } + SpawnCoin(*c, Position, Position.y + COIN_HOVER, 0, true); + } + #ifdef RAD_GAMECUBE + HeapMgr()->PushHeap( GMA_GC_VMM ); + #else + HeapMgr()->PushHeap( GMA_LEVEL_ZONE ); + #endif + GetEventManager()->TriggerEvent( EVENT_SPAWNED_COINS, reinterpret_cast<void*>( Count ) ); + #ifdef RAD_GAMECUBE + HeapMgr()->PopHeap( GMA_GC_VMM ); + #else + HeapMgr()->PopHeap( GMA_LEVEL_ZONE ); + #endif +} + +/*============================================================================= +Spawn a single coin. +=============================================================================*/ +void CoinManager::SpawnCoin( ActiveCoin& Coin, const rmt::Vector& Start, float Ground, const rmt::Vector* Direction, bool InstaCollect ) +{ + Coin.Position = Start; + Coin.Position.y = Ground + COIN_HOVER; + Coin.State = InstaCollect ? CS_SpawnToCollect : CS_InitialSpawning; + Coin.Age = Sparkle::sRandom.Float() * rmt::PI; + Coin.Ground = Ground; + Coin.Velocity.x = Sparkle::sRandom.FloatSigned(); + Coin.Velocity.y = Sparkle::sRandom.Float(); + Coin.Velocity.z = Sparkle::sRandom.FloatSigned(); + Coin.Velocity.Normalize(); + Coin.Velocity.y += COIN_EXTRA_VERTICAL; + if( Direction ) + { + if( Direction->Dot( Coin.Velocity ) < 0 ) + { + Coin.Velocity.x *= -1.0f; + Coin.Velocity.z *= -1.0f; + } + } + Coin.Velocity.Scale(COIN_SPAWN_VELOCITY); + ++mNumActiveCoins; +#ifndef RAD_RELEASE + dbg_MaxCoins = rmt::Max( (int)mNumActiveCoins, dbg_MaxCoins ); +#endif +} + +/*============================================================================= +Add a HUD coin at the coin counter position and have it fly down to the middle +of the screen. Used when the player's coin amount decrements so that they can +see that it's going down a little easier. +=============================================================================*/ +void CoinManager::AddFlyDownCoin(void) +{ + return; + // They didn't like it. Method is now a nop...until they decide they want it again. +/* ActiveCoin* c = GetInactiveCoin(); + if( !c ) + { + return; + } + c->Position.x = 0.0f; + c->Position.y = 0.0f; + c->Position.z = 0.0f; + c->State = CS_FlyingFromHUD; + c->Age = 0.0f; + c->Velocity.Set(-0.5f, -0.5f, 0.0f); + c->Velocity.Scale(FLYING_TIME * 2.0f); + ++mNumActiveCoins; + ++mNumHUDFlying; +#ifndef RAD_RELEASE + dbg_MaxCoins = rmt::Max( (int)mNumActiveCoins, dbg_MaxCoins ); +#endif +*/ +} + +/*============================================================================= +Update the position/animation of all the coins. +=============================================================================*/ +void CoinManager::Update( int ElapsedMS ) +{ + float deltaSeconds = (float)ElapsedMS * 0.001f; + mHUDCoinAngle += deltaSeconds; +#ifndef RAD_RELEASE + dbg_CoinsDrawn = 0; +#endif + if(GetGameFlow()->GetCurrentContext() == CONTEXT_PAUSE) + { + return; + } + rmt::Vector avatarPos; + GetAvatarManager()->GetAvatarForPlayer( 0 )->GetPosition( avatarPos ); + bool isInCar = GetAvatarManager()->GetAvatarForPlayer( 0 )->IsInCar(); + for( int i = 0; i < NUM_COINS; ++i ) + { + ActiveCoin& c = mActiveCoins[ i ]; + if( c.State != CS_Inactive ) + { + float decaySpin = 0.0f; + if( c.State != CS_InitialSpawning && c.State != CS_SpawnToCollect && c.State != CS_Collecting && c.State != CS_Collected ) + { + CheckCollection( c, avatarPos, isInCar ? IN_CAR_RANGE_MULTIPLIER : 1.0f ); + } + if( c.State == CS_Collecting ) + { + UpdateCollecting( c, avatarPos ); + } + else if((c.State == CS_FlyingToHUD) || (c.State == CS_FlyingFromHUD)) + { + UpdateHUDFlying( c ); + } + else if( c.State != CS_RestingIndefinitely && c.State != CS_Collecting && c.State != CS_Collected ) + { + if( c.Age > LIFE_TIME + DECAY_TIME ) + { + // Coin has expired. + c.State = CS_Inactive; + --mNumActiveCoins; + GetSparkleManager()->AddSparkle( rmt::Vector( c.Position.x, c.Position.y + mCoinBounding.radius, c.Position.z ), 0.25f, 0.5f, rmt::Vector( 0.0f, 0.01f, 0.0f ), Sparkle::SE_Vanish ); + } + else if( c.Age > LIFE_TIME ) + { + c.State = CS_Decaying; + decaySpin = ( c.Age - LIFE_TIME ) * I_DECAY_TIME; + decaySpin *= decaySpin; + } + if( c.State == CS_Spawning || c.State == CS_InitialSpawning || c.State == CS_SpawnToCollect ) + { + UpdateSpawning(c, avatarPos, deltaSeconds); + } + } + c.Age += deltaSeconds; + // Rotate the coin based on age. + float coinSin, coinCos; + rmt::SinCos( ( c.Age * SPIN_MULTIPLIER ) + ( decaySpin * DECAY_EXTRA_SPIN ), &coinSin, &coinCos ); + c.HeadingCos = coinCos; + c.HeadingSin = coinSin; + } + } +} + +/*============================================================================= +Damaging out the player's vehicle causes their bank to go down. +=============================================================================*/ +void CoinManager::OnVehicleDestroyed( Vehicle* DestroyedVehicle ) +{ + if( DestroyedVehicle->mVehicleType == VT_USER ) + { + int lose = rmt::Min( 20, GetBankValue() ); + LoseCoins( lose, &( DestroyedVehicle->GetPosition() ) ); + } +} + +/*============================================================================= +Whatever drawable is set here will be used for all the coin drawables. Note +that there isn't any support for shadows or animation. It's just geometry. +=============================================================================*/ +void CoinManager::SetCoinDrawable( tDrawable* Drawable ) +{ + tRefCounted::Assign( m_pCoinDrawable, Drawable ); + if( m_pCoinDrawable ) + { + m_pCoinDrawable->GetBoundingSphere( &mCoinBounding ); + } + else + { + mCoinBounding.centre.Set( 0.0f, 0.0f, 0.0f ); + mCoinBounding.radius = 0.0f; + } +} + +/*============================================================================= +Check if a coin is within range to do the collection animation to the player. +If it is we set the state to Collecting. +=============================================================================*/ +void CoinManager::CheckCollection( ActiveCoin& Coin, const rmt::Vector& AvatarPos, float RangeMultiplier ) +{ + float x = rmt::Abs( Coin.Position.x - AvatarPos.x ); + float z = rmt::Abs( Coin.Position.z - AvatarPos.z ); + float y = rmt::Abs( Coin.Position.y - AvatarPos.y ); + // Note, no multipler for Y. + if( ( x < ( RANGE * RangeMultiplier ) ) && ( z < ( RANGE * RangeMultiplier ) ) && ( y < RANGE ) && ( x + z < ( INNER_RANGE * RangeMultiplier ) ) ) + { + if( Coin.State == CS_RestingIndefinitely ) + { + GetPersistentWorldManager()->OnObjectBreak( Coin.PersistentObjectID ); + } + Coin.State = CS_Collecting; + Coin.Age = 0.0f; + } +} + +/*============================================================================= +Player has picked up a coin. This will increase the player's bank value, fire +a coin collected event so that we can play a sound +=============================================================================*/ +void CoinManager::CollectCoins( int Count ) +{ + AdjustBankValue( Count ); + #ifdef RAD_GAMECUBE + HeapMgr()->PushHeap( GMA_GC_VMM ); + #else + HeapMgr()->PushHeap( GMA_LEVEL_ZONE ); + #endif + GetEventManager()->TriggerEvent( EVENT_COLLECTED_COINS, reinterpret_cast<void*>( Count ) ); + #ifdef RAD_GAMECUBE + HeapMgr()->PopHeap( GMA_GC_VMM ); + #else + HeapMgr()->PopHeap( GMA_LEVEL_ZONE ); + #endif +} + +/*============================================================================= +Player loses coins for whatever reason. We play a sound and HUD animation. +If Position isn't null then we respawn the coins at the specified location. +=============================================================================*/ +void CoinManager::LoseCoins( int Count, const rmt::Vector* Position ) +{ + int count = rmt::Min( Count, GetBankValue() ); + rAssert( count >= 0 ); + AdjustBankValue( -count ); + if( Position ) + { + // Because we are losing the coins when we are shot (or car explodes) the position tends to be lower + //then for a crate. So we raise the position a tiny bit so that the coin's travel is the same as + //the designers tune for when kicking the crates around. + SpawnCoins( count, rmt::Vector(Position->x, Position->y + 0.25f, Position->z), Position->y, 0, true ); + } + #ifdef RAD_GAMECUBE + HeapMgr()->PushHeap( GMA_GC_VMM ); + #else + HeapMgr()->PushHeap( GMA_LEVEL_ZONE ); + #endif + GetEventManager()->TriggerEvent( EVENT_LOST_COINS, reinterpret_cast<void*>( count ) ); + #ifdef RAD_GAMECUBE + HeapMgr()->PopHeap( GMA_GC_VMM ); + #else + HeapMgr()->PopHeap( GMA_LEVEL_ZONE ); + #endif +} + +int +CoinManager::GetBankValue() const +{ + RenderEnums::LevelEnum currentLevel = GetGameplayManager()->GetCurrentLevelIndex(); + int bankValue = GetCharacterSheetManager()->GetNumberOfTokens( currentLevel ); + + return bankValue; +} + +/*============================================================================= +Modify the player's bank value without any special effects. +=============================================================================*/ +void CoinManager::AdjustBankValue( int DeltaCoins ) +{ +/* + mBankValue += DeltaCoins; + mBankValue = rmt::Max( mBankValue, 0 ); +*/ + // tell the character sheet manager + // + GetCharacterSheetManager()->AddTokens( GetGameplayManager()->GetCurrentLevelIndex(), + DeltaCoins ); + + if( DeltaCoins > 0 ) + { + GetEventManager()->TriggerEvent( EVENT_COLLECTED_COINS ); + } + else if( DeltaCoins < 0 ) + { + GetEventManager()->TriggerEvent( EVENT_LOST_COINS ); + } +} + +/* +/*============================================================================= +Force bank to a specific value. No special effects. +=============================================================================/ +void CoinManager::SetBankValue( int Coins ) +{ + rAssert( Coins >= 0 ); + mBankValue = Coins; + + // update character sheet + // + GetCharacterSheetManager()->SetNumberOfTokens( GetGameplayManager()->GetCurrentLevelIndex(), + mBankValue ); +} +*/ + +/*============================================================================= +World coins just sit around until their zone unloads. They take up space in the +active coin array so don't use them lightly. +=============================================================================*/ +void CoinManager::AddWorldCoin( const rmt::Vector& Position, tUID Sector ) +{ + if( !mActiveCoins ) + { + return; + } + + short persistentID = GetPersistentWorldManager()->GetPersistentObjectID( Sector ); + if( persistentID < -1 ) + { + return; + } + + ActiveCoin* c = GetInactiveCoin(); + if( !c ) + { + return; + } + + c->Position = Position; + c->State = CS_RestingIndefinitely; + c->Age = Sparkle::sRandom.Float() * rmt::PI; + c->Sector = Sector; + c->PersistentObjectID = persistentID; + ++mNumActiveCoins; +#ifndef RAD_RELEASE + dbg_MaxCoins = rmt::Max( (int)mNumActiveCoins, dbg_MaxCoins ); +#endif +} + +/*============================================================================= +Remove any coins resting in the world which are in the sector which was just +unloaded. Only remove the coins which are in CS_RestingIndefinite state since +others could be spawning or collecting, etc (that's kind of unlikely however). +=============================================================================*/ +void CoinManager::RemoveWorldCoins( tUID Sector ) +{ + for( int i = 0; i < NUM_COINS; ++i ) + { + ActiveCoin& c = mActiveCoins[ i ]; + if( ( c.State == CS_RestingIndefinitely ) && ( c.Sector == Sector ) ) + { + c.State = CS_Inactive; + --mNumActiveCoins; + } + } +} + +/*============================================================================= +Update the animation for the coins when they are collected. +=============================================================================*/ +void CoinManager::UpdateCollecting( ActiveCoin& Coin, const rmt::Vector& AvatarPos ) +{ + if( Coin.Age > ( COLLECT_TIME * 0.4f ) ) + { + Coin.State = CS_Collected; + Coin.Age = 0.0f; + Coin.HeadingCos = 1; + CollectCoins( 1 ); + } + else + { + rmt::Vector diff; + diff.Sub( AvatarPos, Coin.Position ); + diff.Scale( Coin.Age * I_COLLECT_TIME ); + float pop = ( COLLECT_TIME - Coin.Age ) * I_COLLECT_TIME; + diff.y += pop * POP_SCALE; + Coin.Position.Add( diff ); + --DoSparkle; + if( DoSparkle < 0 ) + { + DoSparkle = Sparkle_Rate; + Sparkle_Rate ^= SPARKLE_RATE_TOGGLE; + rmt::Vector vel; + vel.Scale(FRAME_RATIO * 0.1f, Coin.Velocity); + vel.y += SPARKLE_FLOAT; + GetSparkleManager()->AddSparkle( Coin.Position, 0.5f, 1.0f, vel, Sparkle::SE_Trail ); + } + } +} + +/*============================================================================= +Update the spawning animation for the coins. +=============================================================================*/ +void CoinManager::UpdateSpawning(ActiveCoin& Coin, const rmt::Vector& AvatarPos, float DeltaSeconds) +{ + rmt::Vector vel; + vel.Scale(DeltaSeconds, Coin.Velocity); + Coin.Velocity.Scale( (float)pow(COIN_DRAG, DeltaSeconds) ); + Coin.Velocity.y -= GRAVITY * DeltaSeconds; + Coin.Position.Add( vel ); + // Use this if we want faster (but less accurate) coins. + /* + Coin.Velocity.Scale( COIN_DRAG ); + Coin.Velocity.y -= GRAVITY; + Coin.Position.Add( Coin.Velocity ); + */ + if( Coin.State == CS_InitialSpawning ) + { + --DoSparkle; + if( DoSparkle < 0 ) + { + DoSparkle = Sparkle_Rate; + Sparkle_Rate ^= SPARKLE_RATE_TOGGLE; + GetSparkleManager()->AddSparkle( Coin.Position, 0.5f, 1.0f, rmt::Vector( Coin.Velocity.x * 0.1f, ( Coin.Velocity.y * 0.1f ) + SPARKLE_FLOAT, Coin.Velocity.z * 0.1f ), Sparkle::SE_Trail ); + } + } + if( Coin.Position.y < Coin.Ground ) + { + // Bounce. + Coin.Velocity.y = rmt::Abs( Coin.Velocity.y * BOUNCE_DAMPENING ); + if( Coin.State == CS_SpawnToCollect ) + { + Coin.State = CS_Collecting; + Coin.Age = 0.0f; + } + else + { + Coin.State = CS_Spawning; + if( Coin.Velocity.y < 0.01f ) + { + Coin.State = CS_Resting; + } + } + Coin.Position.y = Coin.Ground; + } +} + +void CoinManager::AddGlint( ActiveCoin& Coin, const rmt::Vector& ToCamera, const rmt::Vector& CoinAxis, const rmt::Vector& CameraRight ) +{ + rmt::Vector pos; + pos.Add( Coin.Position, mCoinBounding.centre ); + float dot = ToCamera.DotProduct( CoinAxis ); + float radius = ( dot < 0.0f ) ? -mCoinBounding.radius : mCoinBounding.radius; + pos.ScaleAdd( radius, CoinAxis ); + rmt::Vector left; + left.Scale( -0.0025f, CameraRight ); + GetSparkleManager()->AddSparkle( pos, 1.0f, 0.25f, left, Sparkle::SE_Glint ); +} + +/*============================================================================= +Animation of the coin zipping up to the HUD counter. +=============================================================================*/ +void CoinManager::UpdateHUDFlying( ActiveCoin& Coin ) +{ + // Do fly to HUD animation. + if(((Coin.State == CS_FlyingToHUD) && (Coin.Age > FLYING_TIME )) || + ((Coin.State == CS_FlyingFromHUD) && (Coin.Age > 0.5f))) + { + Coin.State = CS_Inactive; + --mNumActiveCoins; + --mNumHUDFlying; + } +} + +void CoinManager::ClearHUDCoins(void) +{ + if(mNumHUDFlying == 0) + { + return; + } + for(int i = 0; i < NUM_COINS; ++i) + { + ActiveCoin& c = mActiveCoins[i]; + if((c.State == CS_FlyingToHUD) || (c.State == CS_FlyingFromHUD)) + { + c.State = CS_Inactive; + --mNumActiveCoins; + --mNumHUDFlying; + } + } +} + +/*============================================================================= +Render pass called form within the opaque rendering stage of world render. It +does a simple distance and camera culling check to determine which coins to +draw. We don't add the coins to the DSG since the overhead of moving/adding/ +removing them is too much. Also, the DSG nodes are hard coded to only allow a +certain number of extra entities over the amount preallocated in the pipeline. +It's very possible that we'll end up with more then then that value. +=============================================================================*/ +void CoinManager::Render( void ) +{ + if( ( mNumActiveCoins - mNumHUDFlying ) > 0 ) + { + tCamera* camera = p3d::context->GetView()->GetCamera(); + rAssert( camera ); + const rmt::Matrix& camTrans = camera->GetCameraToWorldMatrix(); + + for( int i = 0; i < NUM_COINS; ++i ) + { + ActiveCoin& c = mActiveCoins[ i ]; + if( c.State != CS_Inactive ) + { + if( c.State == CS_Collected ) + { + camera->WorldToView( c.Position, &c.Position ); + c.Velocity.Sub( rmt::Vector( COUNTER_X, COUNTER_Y, 0.0f ), c.Position ); + c.State = CS_FlyingToHUD; + ++mNumHUDFlying; + continue; + } + if( !m_pCoinDrawable ) + { + continue; + } + // Visiblity test. + rmt::Vector diff; + diff.Sub( camTrans.Row( 3 ), c.Position ); + float camDirDot = diff.DotProduct( camTrans.Row( 2 ) ); + // Note that the diff vector is towards the camera so the camDirDot will be negative when the camera is facing + //the object. So if it's positive then we can assume it's behind the camera. + if( camDirDot > 0.0f ) + { + continue; + } + // Distance check. + float distance = diff.Magnitude(); + if( distance > MAX_VISIBLITY ) + { + continue; + } +#ifndef RAD_RELEASE + ++dbg_CoinsDrawn; +#endif + if( ( GlintDelay <= 0 ) && ( DoGlint == i ) ) + { + if( c.State != CS_InitialSpawning && c.State != CS_Collecting && c.State != CS_Collected ) + { + if(GetGameFlow()->GetCurrentContext() != CONTEXT_PAUSE) + { + AddGlint( c, diff, rmt::Vector( c.HeadingCos, 0.0f, -c.HeadingSin ), camTrans.Row( 0 ) ); + } + } + } + // Down the columns, across the rows. + rmt::Matrix transform( c.HeadingCos, 0.0f, -c.HeadingSin, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + c.HeadingSin, 0.0f, c.HeadingCos, 0.0f, + c.Position.x, c.Position.y, c.Position.z, 1.0f ); + + p3d::pddi->PushMultMatrix( PDDI_MATRIX_MODELVIEW, &transform ); + m_pCoinDrawable->Display(); + p3d::pddi->PopMatrix( PDDI_MATRIX_MODELVIEW ); + } + } + } + --GlintDelay; + if( GlintDelay < 0 ) + { + GlintDelay = Glint_Rate; + DoGlint = Sparkle::sRandom.IntRanged( 0, NUM_COINS ); + } +} + +void CoinManager::SetHUDCoin( int X, int Y, bool IsShowing ) +{ + const static float ScreenWidthRatio = 1.0f / 640.0f; + const static float ScreenHeightRatio = 1.0f / 480.0f; + const static float ScreenAspect = 4.0f / 3.0f; + if( IsShowing ) + { + mHUDCoinX = ( ( ( (float)X * ScreenWidthRatio ) ) - 0.5f ) * ScreenAspect * ScreenAspect; + mHUDCoinY = ( ( ( (float)Y * ScreenHeightRatio ) ) - 0.5f ) * ScreenAspect; + } + else + { + mHUDCoinX = -10.f; + } +} + +void CoinManager::OnCheatEntered( eCheatID cheatID, bool isEnabled ) +{ + if( cheatID == CHEAT_ID_EXTRA_COINS ) + { + this->AdjustBankValue( +100 ); // add 100 coins to bank value + } +} + +/*============================================================================= +Rendering pass for the HUD. Used for when the coin flies up to the HUD counter. +We set the state once and do all the rendering. +=============================================================================*/ +void CoinManager::HUDRender( void ) +{ + if( !m_pCoinDrawable ) + { + return; + } + bool renderHUDCoin = false; + if( mHUDCoinX > -1.0f && mHUDCoinX < 1.0f && mHUDCoinY > -1.0f && mHUDCoinY < 1.0f ) + { + renderHUDCoin = true; + } + if( ( mNumHUDFlying > 0 ) || renderHUDCoin ) + { + p3d::stack->Push(); + bool oldZWrite = p3d::pddi->GetZWrite(); + pddiCompareMode oldZComp = p3d::pddi->GetZCompare(); + if( oldZWrite ) + { + p3d::pddi->SetZWrite( false ); + } + if( oldZComp != PDDI_COMPARE_ALWAYS ) + { + p3d::pddi->SetZCompare( PDDI_COMPARE_ALWAYS ); + } + p3d::pddi->SetProjectionMode( PDDI_PROJECTION_ORTHOGRAPHIC ); + pddiColour oldAmbient = p3d::pddi->GetAmbientLight(); + p3d::pddi->SetAmbientLight( pddiColour( 255, 255, 255 ) ); + + rmt::Vector pos; + if( renderHUDCoin ) + { + p3d::stack->LoadIdentity(); + p3d::stack->Scale( 0.1f, 0.1f, 1.0f ); + float coinSin, coinCos; + rmt::SinCos( ( mHUDCoinAngle * SPIN_MULTIPLIER ), &coinSin, &coinCos ); + rmt::Matrix transform( coinCos, 0.0f, -coinSin, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + coinSin, 0.0f, coinCos, 0.0f, + mHUDCoinX * 5.0f, mHUDCoinY * 5.0f, 5.0f, 1.0f ); +#ifndef RAD_RELEASE + ++dbg_CoinsDrawn; +#endif + if( mHUDSparkle <= 0 ) + { + rmt::Vector pos; + pos.ScaleAdd( rmt::Vector( mHUDCoinX, mHUDCoinY, 5.0f ), 0.2f, mCoinBounding.centre ); + float radius = 0.2f * ( ( coinCos < 0.0f ) ? mCoinBounding.radius : -mCoinBounding.radius ); + pos.Add( rmt::Vector( coinCos * radius, 0.0f, coinSin * radius ) ); + GetSparkleManager()->AddSparkle( pos, 1.0f, 0.25f, rmt::Vector( 0.0f, 0.0f, 0.0f ), Sparkle::SE_HUDGlint ); + mHUDSparkle = Sparkle::sRandom.IntRanged( HUD_SPARKLE_RATE, HUD_SPARKLE_RATE * 3 ); + } + --mHUDSparkle; + p3d::pddi->PushMultMatrix( PDDI_MATRIX_MODELVIEW, &transform ); + m_pCoinDrawable->Display(); + p3d::pddi->PopMatrix( PDDI_MATRIX_MODELVIEW ); + } + p3d::stack->LoadIdentity(); + p3d::stack->Scale( 0.05f, 0.05f, 1.0f); + + for( int i = 0; i < NUM_COINS; ++i ) + { + ActiveCoin& c = mActiveCoins[ i ]; + if((c.State == CS_FlyingToHUD) || (c.State == CS_FlyingFromHUD)) + { + if(c.State == CS_FlyingToHUD) + { + float a = c.Age * I_FLYING_TIME; + pos.ScaleAdd( c.Position, a * a, c.Velocity ); + } + else + { + pos.ScaleAdd(c.Position, c.Age * I_FLYING_TIME, c.Velocity); + } + // Down the columns, across the rows. + rmt::Matrix transform( 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + pos.x * 10.0f, pos.y * 10.0f, 5.0f, 1.0f ); + #ifndef RAD_RELEASE + ++dbg_CoinsDrawn; + #endif + p3d::pddi->PushMultMatrix( PDDI_MATRIX_MODELVIEW, &transform ); + m_pCoinDrawable->Display(); + p3d::pddi->PopMatrix( PDDI_MATRIX_MODELVIEW ); + } + } + p3d::pddi->SetProjectionMode(PDDI_PROJECTION_PERSPECTIVE); + p3d::pddi->SetAmbientLight( oldAmbient ); + if( oldZWrite ) + { + p3d::pddi->SetZWrite( true ); + } + if( oldZComp != PDDI_COMPARE_ALWAYS ) + { + p3d::pddi->SetZCompare( oldZComp ); + } + p3d::stack->Pop(); + } +}
\ No newline at end of file diff --git a/game/code/worldsim/coins/coinmanager.h b/game/code/worldsim/coins/coinmanager.h new file mode 100644 index 0000000..28faee2 --- /dev/null +++ b/game/code/worldsim/coins/coinmanager.h @@ -0,0 +1,168 @@ +//============================================================================= +// Copyright (C) 2003 Radical Entertainment Ltd. All rights reserved. +// +// File: coinmanager.h +// +// Description: Coin manager looks after everything to do with the collectable coins +// +// History: 29/01/2003 + Created -- James Harrison +// +//============================================================================= + +#ifndef COINMANAGER_H +#define COINMANAGER_H + +//======================================== +// Nested Includes +//======================================== +#include <radmath/vector.hpp> +#include <radmath/geometry.hpp> +#include <events/eventlistener.h> +#include <constants/breakablesenum.h> +#include <cheats/cheatinputsystem.h> + +//======================================== +// Forward References +//======================================== +class Vehicle; +class tDrawable; +class tTexture; + +//============================================================================= +// +// Synopsis: Creating and placing coins in the world, picking up coins, +// losing coins, +// +//============================================================================= +class CoinManager : public EventListener, + public ICheatEnteredCallback +{ +public: + CoinManager(); + ~CoinManager(); + + static CoinManager* GetInstance( void ); + static CoinManager* CreateInstance( void ); + static void DestroyInstance( void ); + + void Init( void ); + void Destroy( void ); + void SetCoinDrawable( tDrawable* Drawable ); + virtual void HandleEvent( EventEnum id, void* pEventData ); + + void Update( int ElapsedMS ); // Elapsed milliseconds. + void Render( void ); // Draw coins. Do simple frustum and distance check on them. + void HUDRender( void ); // Draw any HUD animations. + + int GetBankValue( void ) const; // How much is in the bank. + void AdjustBankValue( int DeltaCoins ); // Adjust the bank value without any visual SFX. + void CollectCoins( int Count ); // Collect coins with sound SFX. + void LoseCoins( int Count, const rmt::Vector* Position = 0 ); // decrease bank value with visual SFX. + // spawn coins from this position. If in car, the coin actually just go to the player, unless the Force + //parameter is true. + void SpawnCoins( int Count, const rmt::Vector& Position, const rmt::Vector* Direction = 0, bool Force = false ); + void SpawnCoins( int Count, const rmt::Vector& Position, float GroundY, const rmt::Vector* Direction = 0, bool Force = false ); // spawn coins and force specific ground value. + void SpawnInstantCoins(int Count, const rmt::Vector& Position); + + void AddWorldCoin( const rmt::Vector& Position, tUID Sector ); // Place a coin to sit happily in a zone until the zone unloads. + + void SetHUDCoin( int X, int Y, bool IsShowing = true ); + void AddFlyDownCoin(void); // Create an animation of a single coin zipping down from the HUD counter. + void ClearHUDCoins(void); // Remove all HUD coins. + + bool DrawAfterGui() const { return mDrawAfterGui;} + void SetDrawAfterGui(bool d) { mDrawAfterGui = d;} + + enum eCoinState + { + CS_Inactive, + CS_InitialSpawning, // Coins aren't collectable during this time. + CS_SpawnToCollect, // Coins spawn and then are collected on first bounce. + CS_Spawning, // Still moving, but can be collected. + CS_Resting, // Sitting happily. + CS_RestingIndefinitely, // Sitting happily in the world. I doesn't decay. + CS_Decaying, // Spinning away into nothing. + CS_Collecting, // Reverse spawning. Attracted to the player. + CS_Collected, // Special state held for one frame before the coin heads up into the HUD. + CS_FlyingToHUD, // Coin is doing the little fly up animation. + CS_FlyingFromHUD, // Coin is doing the little fly down animation. + CS_NUM_STATES + }; + + tDrawable* GetCoinDrawable( void ) const { return m_pCoinDrawable; } + const rmt::Sphere& GetCoinBounding( void ) const { return mCoinBounding; } + + virtual void OnCheatEntered( eCheatID cheatID, bool isEnabled ); + +protected: + //Prevent wasteful constructor creation. + CoinManager( const CoinManager& That ); + CoinManager& operator=( const CoinManager& That ); + + void OnVehicleDestroyed( Vehicle* DestroyedVehicle ); + struct ActiveCoin; + void SpawnCoin( ActiveCoin& Coin, const rmt::Vector& Start, float Ground, const rmt::Vector* Direction = 0, bool InstaCollect = false ); + void CheckCollection( ActiveCoin& Coin, const rmt::Vector& AvatarPos, float RangeMultiplier = 1.0f ); + void RemoveWorldCoins( tUID Sector ); + void AddGlint( ActiveCoin& Coin, const rmt::Vector& ToCamera, const rmt::Vector& CoinAxis, const rmt::Vector& CameraRight ); + + void UpdateCollecting( ActiveCoin& Coin, const rmt::Vector& AvatarPos ); + void UpdateSpawning(ActiveCoin& Coin, const rmt::Vector& AvatarPos, float DeltaSeconds); + void UpdateHUDFlying( ActiveCoin& Coin ); + + static CoinManager* spCoinManager; + + tDrawable* m_pCoinDrawable; + rmt::Sphere mCoinBounding; + + struct ActiveCoin + { + ActiveCoin() : State( CS_Inactive ) {}; + union + { +#ifdef RAD_GAMECUBE + rmt::Vector Velocity; + tUID Sector; +#else + struct + { + rmt::Vector Velocity; + }; + + struct + { + tUID Sector; + short PersistentObjectID; + }; +#endif + }; +#ifdef RAD_GAMECUBE + short PersistentObjectID; +#endif + rmt::Vector Position; + float HeadingCos; + float HeadingSin; + float Age; + float Ground; + eCoinState State; + }; + + ActiveCoin* GetInactiveCoin( void ); + + ActiveCoin* mActiveCoins; + short mNumActiveCoins; + short mNextInactiveCoin; + short mNumHUDFlying; // So we can early out of the HUD render. + short mHUDSparkle; + + float mHUDCoinX; + float mHUDCoinY; + float mHUDCoinAngle; + + bool mDrawAfterGui; +}; + +// A little syntactic sugar for getting at this singleton. +inline CoinManager* GetCoinManager() { return( CoinManager::GetInstance() ); } + +#endif //COINMANAGER_H diff --git a/game/code/worldsim/coins/sparkle.cpp b/game/code/worldsim/coins/sparkle.cpp new file mode 100644 index 0000000..77004b8 --- /dev/null +++ b/game/code/worldsim/coins/sparkle.cpp @@ -0,0 +1,1222 @@ +//============================================================================= +// Copyright (C) 2003 Radical Entertainment Ltd. All rights reserved. +// +// File: Sparkle.cpp +// +// Description: Implementation of class Sparkle +// +// History: 18/2/2003 + Created -- James Harrison +// +//============================================================================= + +//======================================== +// System Includes +//======================================== + +#include <radmath/matrix.hpp> +#include <p3d/entity.hpp> +#include <p3d/view.hpp> +#include <p3d/camera.hpp> +#include <p3d/matrixstack.hpp> +#include <p3d/texture.hpp> +#include <pddi/pddi.hpp> +#ifndef RAD_RELEASE +#include <raddebug.hpp> +#include <raddebugwatch.hpp> +#endif + +//======================================== +// Project Includes +//======================================== +#include <contexts/bootupcontext.h> +#include <worldsim/coins/sparkle.h> +#include <memory/srrmemory.h> +#include <main/game.h> +#include <camera/supercam.h> +#include <camera/supercamcentral.h> +#include <camera/supercammanager.h> +#include <gameflow/gameflow.h> +#include <mission/gameplaymanager.h> + +//****************************************************************************** +// +// Global Data, Local Data, Local Classes +// +//****************************************************************************** +#if defined( RAD_XBOX ) || defined( RAD_WIN32 ) + #ifdef RAD_RELEASE + static const float FRAME_RATE = 60.0f; + #elif RAD_TUNE + static const float FRAME_RATE = 30.0f; + #else + static const float FRAME_RATE = 15.0f; + #endif + static const float FRAME_RATIO = 1.0f / FRAME_RATE; +#endif + +static const float DRIFT_DRAG = 0.99f; // How much horizontal inertia is lost per frame. +static const int NUM_SPARK_PARTICLES = 10; +static const float SPARK_RADIUS = 1.8f; +static const float SPARK_SIZE = 0.25f; +static const float SPARK_VERTICAL_SCALE = 0.25f; +static const float SPARK_DURATION = 0.3f; +static const float SPARK_DURATION_RATIO = 1.0f / SPARK_DURATION; +static const float SPARK_VELOCITY_TO_SIZE_RATIO = 1.0f / ( SPARK_RADIUS * SPARK_DURATION_RATIO * FRAME_RATIO ); +static const float DASH_SIZE = 0.2f; +static const float DASH_SIZE_INCREASE = 0.2f; +static const float DASH_DURATION_RATIO = 2.0f; +static const float LANDING_SIZE = 0.5f; +static const float LANDING_NUM = 16; +static const float LANDING_DURATION_RATIO = 1.5f; +static const int NUM_PAINT_CHIPS = 10; +static const float PAINT_CHIP_SIZE = 0.05f; +static const float SHOCK_RING_RADIUS = 3.0f; +static const float SHOCK_RING_SPEED = 1.75f; +static const int NUM_STAR_PARTICLES = 5; +static const float STAR_DURATION_RATIO = 1.0f / 0.5f; +static const float STAR_RADIUS = 10.0f; +static const float STAR_SIZE = 0.25f; +static const float STAR_DUST_RADIUS = 2.0f; +static const float STAR_DUST_SIZE = 1.0f; +static const float STAR_DUST_DURATION_RATIO = 1.0f; +static const int NUM_BOTTOMOUT_SPARKS = 30; +static const float BOTTOMOUT_RADIUS = 2.5f; +static const int NUM_SMOKE_PARTICLES = 2; // Maximum number of particles per call for the smoke. +static const float SMOKE_SIZE = 0.3f; +static const int NUM_GAG_SPARKLES = 1; +static const float GAG_SPARKLE_ELEVATION = 1.0f; +static const float GAG_SPARKLE_EMIT_RADIUS = 0.75f; +static const float GAG_SPARKLE_DISTANCE = 0.15f; +static const float GAG_SPARKLE_DURATION_RATIO = 1.0f / 1.6f; +static const float GAG_SPARKLE_SIZE = 0.45f; +static const tColour GAG_SPARKLE_COLOUR(32, 128, 192); +static const tColour STAR_COLOUR( 255, 240, 100, 255 ); +static const tColour STAR_DUST_COLOUR( 150, 140, 110, 128 ); +static const tColour TRAIL_COLOUR( 0xFF, 0xFF, 0x7F ); +static const tColour SPARK_COLOUR( 0xFF, 0xC6, 0x00 ); +static const tColour DASH_COLOUR( 220, 210, 180, 32 ); +static const tColour LANDING_COLOUR( 220, 210, 180, 128 ); +static const tColour RING_COLOUR( 0xFF, 0xFF, 0xFF ); +static const tColour LEAF_COLOUR( 120, 200, 100 ); +static const tColour LIGHT_SMOKE_COLOUR( 0xC0, 0xD0, 0xE0, 0x10 ); +static const tColour DARK_SMOKE_COLOUR( 0x40, 0x40, 0x40, 0xA0 ); +#ifndef RAD_RELEASE +static int dbg_MaxSparkles = 0; +#endif + +rmt::Randomizer Sparkle::sRandom( 0 ); // Set in the constructor. +bool Sparkle::sRandomSeeded = false; + +Sparkle* Sparkle::spInstance = 0; + +Sparkle* Sparkle::CreateInstance( unsigned char NumTextures, unsigned short NumSparkles ) +{ + rAssertMsg( spInstance == 0, "SparkleManager already created.\n" ); + HeapManager::GetInstance()->PushHeap( GMA_PERSISTENT ); + spInstance = new Sparkle( NumTextures, NumSparkles ); + rAssert( spInstance ); + HeapManager::GetInstance()->PopHeap( GMA_PERSISTENT ); + return Sparkle::GetInstance(); +} + +Sparkle* Sparkle::GetInstance( void ) +{ + rAssertMsg( spInstance != 0, "SparkleManager has not been created yet.\n" ); + return spInstance; +} + +void Sparkle::DestroyInstance( void ) +{ + rAssertMsg( spInstance != 0, "SparkleManager has not been created.\n" ); + delete spInstance; + spInstance = 0; +} + + +//============================================================================== +// Description: Constructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +Sparkle::Sparkle( unsigned char NumTextures, unsigned short NumSparkles ) : + mActiveSparkles( 0 ), + mpTextures( 0 ), + mNumSparkles( NumSparkles ), + mNextInactiveSparkle( 0 ), + mNumActiveSparkles( 0 ), + mNumHUDSparkles( 0 ), + mNumTextures( NumTextures ) +{ + rAssert( mNumSparkles <= 65535 ); // We're using an unsigned short for our counters! + rAssert( mNumTextures <= ST_NUM_TEXTURES ); + + if (!sRandomSeeded) + { + sRandom.Seed (Game::GetRandomSeed ()); + sRandomSeeded = true; + } +} + +//============================================================================== +// Description: Destructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +Sparkle::~Sparkle() +{ + Destroy(); +#ifndef RAD_RELEASE + radDbgWatchDelete( &dbg_MaxSparkles ); +#endif +} + +void Sparkle::Init( void ) +{ + #ifdef RAD_GAMECUBE + HeapManager::GetInstance()->PushHeap( GMA_GC_VMM ); + #else + HeapManager::GetInstance()->PushHeap( GMA_LEVEL_ZONE ); + #endif + mActiveSparkles = new ActiveSparkle[ mNumSparkles ]; + rAssert( mActiveSparkles ); + mpTextures = new tTexture*[ mNumTextures ]; + rAssert( mpTextures ); + mNumActiveSparkles = new unsigned short[ mNumTextures << 1 ]; + rAssert( mNumActiveSparkles ); + mNumHUDSparkles = new unsigned short[ mNumTextures ]; + rAssert( mNumHUDSparkles ); + #ifdef RAD_GAMECUBE + HeapManager::GetInstance()->PopHeap( GMA_GC_VMM ); + #else + HeapManager::GetInstance()->PopHeap( GMA_LEVEL_ZONE ); + #endif + + for( int i = 0; i < mNumTextures; ++i ) + { + mpTextures[ i ] = 0; + mNumActiveSparkles[ i ] = 0; + mNumActiveSparkles[ i + ST_NUM_TEXTURES ] = 0; + mNumHUDSparkles[ i ] = 0; + } +} + +void Sparkle::Destroy( void ) +{ + if( mpTextures ) + { + for( int i = 0; i < mNumTextures; ++i ) + { + tRefCounted::Release( mpTextures[ i ] ); + } + delete [] mpTextures; + mpTextures = 0; + } + delete [] mNumActiveSparkles; + mNumActiveSparkles = 0; + delete [] mNumHUDSparkles; + mNumHUDSparkles = 0; + delete [] mActiveSparkles; + mActiveSparkles = 0; +} + +/*============================================================================= +Set the texture the sparkles will be drawn with. Perhaps in the future we'll +have some method of doing multiple textures. +=============================================================================*/ +void Sparkle::SetTexture( unsigned char Index, tTexture* Texture ) +{ + rAssert( Index < mNumTextures ); + tRefCounted::Assign( mpTextures[ Index ], Texture ); +} + +/*============================================================================= +Find an unused sparkle in our pre-allocated array of sparkles. +=============================================================================*/ +Sparkle::ActiveSparkle* Sparkle::GetInactiveSparkle( void ) +{ + for( int i = 0; i < mNumSparkles; ++i ) + { + ActiveSparkle* s = &(mActiveSparkles[ mNextInactiveSparkle ]); + mNextInactiveSparkle = ( mNextInactiveSparkle + 1 ) % mNumSparkles; + if( !s->Active ) + { + s->Active = 1; + s->HUD = 0; + return s; + } + } +#ifndef RAD_RELEASE + // rTunePrintf( "WARNING: Ran out of sparkles!" ); +#endif + return 0; +} + +/*============================================================================= +Add a sparkle to the world. This is used for collection/spawn trails. +=============================================================================*/ +void Sparkle::AddSparkle( const rmt::Vector& Position, float Size, float Duration, const rmt::Vector& Velocity, eSparkleEffect Effect ) +{ + rAssert( Duration > 0.0f ); // No infinite druation sparkles. + if( Duration <= 0.0f ) + { + return; + } + ActiveSparkle* s = GetInactiveSparkle(); + if( !s ) + { + return; + } + s->Texture = ST_Sparkle; + if( Effect == SE_Trail ) + { + s->Align = SA_None; + s->Scale = SS_LinearDown; + s->ColourAnim = SC_LinearRedDownAtHalf; + s->CornerFade = 0; + s->Motion = SM_Linear; + s->Colour = TRAIL_COLOUR; + } + else if( Effect == SE_Glint ) + { + s->Align = SA_None; + s->Scale = SS_LinearDown; + s->ColourAnim = SC_LinearDown; + s->CornerFade = 0; + s->Motion = SM_Linear; + s->Colour = TRAIL_COLOUR; + } + else if( Effect == SE_Vanish ) + { + s->Align = SA_None; + s->Scale = SS_BulgeDown; + s->ColourAnim = SC_LinearRedDownAtHalf; + s->CornerFade = 0; + s->Motion = SM_Linear; + s->Colour = TRAIL_COLOUR; + } + else + { + s->HUD = 1; + s->Align = SA_None; + s->Scale = SS_LinearDown; + s->ColourAnim = SC_LinearDown; + s->CornerFade = 0; + s->Motion = SM_Linear; + s->Colour = TRAIL_COLOUR; + ++( mNumHUDSparkles[ s->Texture ] ); + } + s->Size = Size; + s->DurationRatio = 1.0f / Duration; + s->Life = 1.0f; + s->Position = Position; + s->Velocity = Velocity; + ++( mNumActiveSparkles[ s->Texture ] ); +#ifndef RAD_RELEASE +// dbg_MaxSparkles = rmt::Max( mNumActiveSparkles, dbg_MaxSparkles ); +#endif +} + +/*============================================================================= +Add the blue gag sparkle to the world. Size is used to scale the particles down +for indoors. A 1 is full sized, .5 is half, etc. The Strength value is used to +fade the sparkle out for distance culling. +Okay, so the problem is we don't want sparkles shooting out every frame for all +those gags. It's too many particles and it looks bad. We'd either have to store +a state with each gag, some sort of counter, or store a state here to only +activate every few calls. The problem with storing a state with each gag is an +extra byte for anything using this. +Currently only gags uses the sparkles, but it could be others (doorbells, NPCs +you talk to). The problem with only activating every few calls, is if there is +exactly the number of gags as the number we wait, then only one gag gets a +sparkle. I perfer to hold the state here anyway, since it's less memory. So we +have two solutions, randomize the amount we skip before we spit out another +sparkle, or hold the state of the objects here. If we held the state here we +could create a hash table and then group the calling objects by their hash +value (we'd have to pass in a this pointer so we know who called us). So if we +took bits 4 to 7 (i.e. the high order bits of the first byte of the address +(remember since the address is aligned we don't want to take the first bits of +the first bytes) we'd end up with a number between 0 and 15 which we could use +as an index into some bits we are using to hold a counter controling when to +spit out a sparkle. I think I'll try something along those lines. +=============================================================================*/ +void Sparkle::AddGagSparkle(const rmt::Vector& Position, float Size, float Strength, unsigned int Caller) +{ + static unsigned int actionFlag = 0; + if(GetGameFlow()->GetCurrentContext() == CONTEXT_PAUSE) + { + return; + } + + if((Size <= 0.0f) || (Strength <= 0.0f) || (int(255 * Strength) <= 0)) + { + return; + } + int bitMask = 1 << ((Caller >> 4) & 31); + actionFlag ^= bitMask; + if(actionFlag & bitMask) + { + return; + } + ActiveSparkle* s = GetInactiveSparkle(); + if(!s) + { + return; + } + s->Align = SA_None; + s->Scale = SS_Constant; + s->ColourAnim = SC_LinearDown; + s->CornerFade = 0; + s->Motion = SM_Linear; + s->Texture = ST_Sparkle; + s->Position = Position; + s->Position.y += GAG_SPARKLE_ELEVATION * Size; + s->Velocity.x = sRandom.FloatSigned(); + s->Velocity.y = sRandom.FloatSigned(); + s->Velocity.z = sRandom.FloatSigned(); + s->Position.ScaleAdd(GAG_SPARKLE_EMIT_RADIUS * Size, s->Velocity); + s->Velocity.Normalize(); + s->Velocity.Scale( GAG_SPARKLE_DISTANCE * GAG_SPARKLE_DURATION_RATIO * FRAME_RATIO * Size); + s->Size = GAG_SPARKLE_SIZE * Size; + s->Life = 1.0f + ( sRandom.FloatSigned() * 0.1f ); + s->DurationRatio = GAG_SPARKLE_DURATION_RATIO; + int red = rmt::Clamp(int(GAG_SPARKLE_COLOUR.Red() * Strength), 0, 255); + int green = rmt::Clamp(int(GAG_SPARKLE_COLOUR.Green() * Strength), 0, 255); + int blue = rmt::Clamp(int(GAG_SPARKLE_COLOUR.Blue() * Strength), 0, 255); + s->Colour.Set(red, green, blue); + ++(mNumActiveSparkles[ s->Texture ]); +} + +/*============================================================================= +Add spark to the world. A spark is like a spherical explosion. +=============================================================================*/ +void Sparkle::AddSparks( const rmt::Vector& Position, const rmt::Vector& Velocity, float Strength ) +{ + int numParticles = int( sRandom.IntRanged( NUM_SPARK_PARTICLES, NUM_SPARK_PARTICLES * 2 ) * Strength ) + 1; + for( int i = 0; i < numParticles; ++i ) + { + ActiveSparkle* s = GetInactiveSparkle(); + if( !s ) + { + return; + } + s->Align = SA_MotionStreak; + s->Scale = SS_Constant; + s->ColourAnim = SC_LinearDownAtHalf; + s->CornerFade = 0; + s->Motion = SM_Decelerate; + s->Texture = ST_Spark; + s->Position = Position; + s->Velocity.x = sRandom.FloatSigned(); + s->Velocity.y = sRandom.FloatSigned(); + s->Velocity.z = sRandom.FloatSigned(); + s->Velocity.Normalize(); + s->Velocity.Scale( SPARK_RADIUS * SPARK_DURATION_RATIO ); + s->Velocity.ScaleAdd(SPARK_DURATION_RATIO, Velocity ); + s->Velocity.Scale( FRAME_RATIO ); + s->Size = s->Velocity.Magnitude() * SPARK_VELOCITY_TO_SIZE_RATIO * SPARK_SIZE; + s->Life = 1.0f + ( sRandom.FloatSigned() * 0.1f ); + s->DurationRatio = SPARK_DURATION_RATIO; + s->Colour = SPARK_COLOUR; + ++( mNumActiveSparkles[ s->Texture ] ); + } +} + +/*============================================================================= +This is basically the same as the AddSparks, except it's radial sparks at the +position and more pronounced. +=============================================================================*/ +void Sparkle::AddBottomOut( const rmt::Vector& Position ) +{ + for( int i = 0; i < NUM_BOTTOMOUT_SPARKS; ++i ) + { + ActiveSparkle* s = GetInactiveSparkle(); + if( !s ) + { + return; + } + s->Align = SA_MotionStreak; + s->Scale = SS_Constant; + s->ColourAnim = SC_LinearDownAtHalf; + s->CornerFade = 0; + s->Motion = SM_Decelerate; + s->Texture = ST_Spark; + s->Position = Position; + s->Velocity.x = sRandom.FloatSigned(); + s->Velocity.y = sRandom.Float() * 0.1f; + s->Velocity.z = sRandom.FloatSigned(); + s->Velocity.Normalize(); + s->Velocity.Scale( BOTTOMOUT_RADIUS * SPARK_DURATION_RATIO ); + s->Velocity.Scale( FRAME_RATIO ); + s->Size = SPARK_SIZE; + s->Life = 1.0f + ( sRandom.FloatSigned() * 0.1f ); + s->DurationRatio = SPARK_DURATION_RATIO; + s->Colour = SPARK_COLOUR; + ++( mNumActiveSparkles[ s->Texture ] ); + } +} + +/*============================================================================= +Add the dash cloud. Note that the velocity is the travel of the character, +not the direction you want the dash cloud to travel. +=============================================================================*/ +void Sparkle::AddDash( const rmt::Vector& Position, const rmt::Vector& Velocity, float Strength ) +{ + int alpha = int(DASH_COLOUR.Alpha() * (Strength*Strength+Strength)*0.5f); + if(alpha <= 0) + { + return; + } + int numParticles = sRandom.IntRanged( NUM_SPARK_PARTICLES >> 2, NUM_SPARK_PARTICLES ) + 1; + for( int i = 0; i < numParticles; ++i ) + { + ActiveSparkle* s = GetInactiveSparkle(); + if( !s ) + { + return; + } + s->Align = SA_Rotation; + s->Scale = SS_LinearGrow; + s->ColourAnim = SC_LinearAlphaDown; + s->CornerFade = 0; + s->Motion = SM_Linear; + s->Texture = ST_Puff; + s->Position = Position; + s->Position.y += 0.05f; + s->Position.ScaleAdd(-0.05f, Velocity); + s->Velocity.x = -Velocity.x * sRandom.FloatSigned() * 2.0f; + s->Velocity.y = ( sRandom.Float() * 0.5f ) + 0.5f; + s->Velocity.z = -Velocity.z * sRandom.FloatSigned() * 2.0f; + s->Velocity.Normalize(); + s->Velocity.Scale( 0.2f + ( 0.2f * sRandom.Float() ) ); + s->Velocity.Scale( FRAME_RATIO ); + s->Size = DASH_SIZE; + s->Life = 1.0f + ( sRandom.FloatSigned() * 0.5f ); + s->DurationRatio = DASH_DURATION_RATIO; + s->Colour = DASH_COLOUR; + s->Colour.SetAlpha(int(DASH_COLOUR.Alpha() * (Strength*Strength+Strength)*0.5f)); + ++( mNumActiveSparkles[ s->Texture ] ); + } +} + +/*============================================================================= +Add the landing puff. It's a radial explosion of puffs used in the dash. +The strength is used to control the look over a jump landing (~0.5), a jump- +jump landing (~0.7), and a stomp (1.0). +=============================================================================*/ +void Sparkle::AddLanding( const rmt::Vector& Position, float Strength ) +{ + if( Strength <= 0.0f ) + { + return; + } + int numParticles = int( LANDING_NUM + ( LANDING_NUM * Strength ) ); + for( int i = 0; i < numParticles; ++i ) + { + ActiveSparkle* s = GetInactiveSparkle(); + if( !s ) + { + return; + } + s->Align = SA_Rotation; + s->Scale = SS_LinearUp; + s->ColourAnim = SC_LinearAlphaDown; + s->CornerFade = 0; + s->Motion = SM_Linear; + s->Texture = ST_Puff; + s->Position = Position; + s->Position.y += 0.25f * Strength; + + s->Velocity.x = sRandom.FloatSigned(); + s->Velocity.y = sRandom.Float() * Strength * 0.25f; + s->Velocity.z = sRandom.FloatSigned(); + s->Velocity.Normalize(); + s->Velocity.Scale( 1.0f + Strength ); + s->Velocity.Scale( FRAME_RATIO ); + + s->Size = LANDING_SIZE * Strength; + s->Life = ( sRandom.Float() * 0.2f ) + Strength; + s->DurationRatio = LANDING_DURATION_RATIO; + s->Colour = DASH_COLOUR; + ++( mNumActiveSparkles[ s->Texture ] ); + } +} +/*============================================================================= +Add a shockwave ring, which is basically a billboard with a texture which +expands from the center and then disappears. Really easy. Handy for things like +the jump-jump-stomp. +=============================================================================*/ +void Sparkle::AddShockRing( const rmt::Vector& Position, float Strength ) +{ + ActiveSparkle* s = GetInactiveSparkle(); + if( !s ) + { + return; + } + s->Align = SA_Flat; + s->Scale = SS_LinearUp; + s->ColourAnim = SC_LinearDown; + s->CornerFade = 0; + s->Motion = SM_Linear; + s->Texture = ST_Ring; + s->Position = Position; + s->Position.y += 0.25f; + s->Velocity.Set( 0.0f, 0.0f, 0.0f ); + s->Size = SHOCK_RING_RADIUS * Strength; + s->Life = 1.0f; + s->DurationRatio = SHOCK_RING_SPEED; + s->Colour = RING_COLOUR; + ++( mNumActiveSparkles[ s->Texture ] ); +} + +/*============================================================================= +Add the leaves spray when the car drives over a hedge. The animation is like a +coins spawning. +=============================================================================*/ +void Sparkle::AddLeaves( const rmt::Vector& Position, const rmt::Vector& Velocity, float Strength ) +{ + int numParticles = int( sRandom.IntRanged( NUM_SPARK_PARTICLES, NUM_SPARK_PARTICLES * 2 ) * Strength ) + 1; + for( int i = 0; i < numParticles; ++i ) + { + ActiveSparkle* s = GetInactiveSparkle(); + if( !s ) + { + return; + } + s->Align = SA_Rotation; + s->Scale = SS_Constant; + s->ColourAnim = SC_LinearAlphaDown; + s->CornerFade = 0; + s->Motion = SM_Gravity; + s->Texture = ST_Leaf; + s->Position = Position; + s->Velocity.x = sRandom.FloatSigned(); + s->Velocity.y = sRandom.FloatSigned(); + s->Velocity.z = sRandom.FloatSigned(); + s->Velocity.Normalize(); + s->Velocity.Scale( SPARK_RADIUS * SPARK_DURATION_RATIO ); + s->Velocity.ScaleAdd(SPARK_DURATION_RATIO, Velocity ); + s->Velocity.Scale( FRAME_RATIO ); + s->Size = s->Velocity.Magnitude() * SPARK_VELOCITY_TO_SIZE_RATIO; + s->Life = 1.0f + ( sRandom.FloatSigned() * 0.1f ); + s->DurationRatio = SPARK_DURATION_RATIO; + s->Colour = LEAF_COLOUR; + ++( mNumActiveSparkles[ s->Texture ] ); + } +} + +/*============================================================================= +Add paint chips when upon vehicle-vehicle collision. The colour depends +upon the colour of the vehicle getting hit. +=============================================================================*/ +void Sparkle::AddPaintChips( const rmt::Vector& Position, const rmt::Vector& Velocity, pddiColour Colour, float Strength ) +{ + return; + + int numParticles = int( sRandom.IntRanged( NUM_PAINT_CHIPS, NUM_PAINT_CHIPS * 2 ) * Strength ) + 1; + for( int i = 0; i < numParticles; ++i ) + { + ActiveSparkle* s = GetInactiveSparkle(); + if( !s ) + { + return; + } + + const float VELOCITY_DAMPENING = 0.2f; + + s->Align = SA_Rotation; + s->Scale = SS_Constant; + s->ColourAnim = SC_LinearAlphaDown; + s->CornerFade = 0; + s->Motion = SM_Gravity; + s->Texture = ST_Paint; + s->Position = Position; + s->Velocity.x = sRandom.FloatSigned(); + s->Velocity.y = sRandom.FloatSigned(); + s->Velocity.z = sRandom.FloatSigned(); + s->Velocity.Normalize(); + s->Velocity.Scale( VELOCITY_DAMPENING ); + + s->Size = PAINT_CHIP_SIZE; + s->Life = 1.0f + ( sRandom.FloatSigned() * 0.1f ); + s->DurationRatio = SPARK_DURATION_RATIO; + const float COLOUR_DAMPENING = 0.5f; + int red = static_cast< int >( static_cast< float >(Colour.Red()) * COLOUR_DAMPENING ); + int green = static_cast< int >( static_cast< float >(Colour.Green()) * COLOUR_DAMPENING ); + int blue = static_cast< int >( static_cast< float >(Colour.Blue()) * COLOUR_DAMPENING ); + s->Colour.Set( red, green, blue ); + ++( mNumActiveSparkles[ s->Texture ] ); + } +} +/*============================================================================= +Explosion of stars and smoke. Useful when the car hits a static. +=============================================================================*/ +void Sparkle::AddStars( const rmt::Vector& Position, float Strength ) +{ + int numParticles = NUM_STAR_PARTICLES + int( NUM_STAR_PARTICLES * Strength ); + for( int i = 0; i < numParticles; ++i ) + { + ActiveSparkle* s = GetInactiveSparkle(); + if( !s ) + { + return; + } + s->Align = SA_MotionStreak; + s->Scale = SS_LinearDown; + s->ColourAnim = SC_LinearAlphaDown; + s->CornerFade = 0; + s->Motion = SM_Linear; + s->Position = Position; + s->Velocity.x = sRandom.FloatSigned(); + s->Velocity.y = sRandom.FloatSigned(); + s->Velocity.z = sRandom.FloatSigned(); + s->Velocity.Normalize(); + s->Velocity.Scale( FRAME_RATIO * STAR_RADIUS ); + s->DurationRatio = STAR_DURATION_RATIO; + s->Size = s->Velocity.Magnitude() * SPARK_VELOCITY_TO_SIZE_RATIO * SPARK_SIZE; + s->Life = 1.0f; + s->Texture = ST_Spark; + s->Colour = STAR_COLOUR; + ++( mNumActiveSparkles[ s->Texture ] ); + s = GetInactiveSparkle(); + if( !s ) + { + return; + } + s->Align = SA_Rotation; + s->Scale = SS_LinearGrow; + s->ColourAnim = SC_LinearAlphaDown; + s->CornerFade = 1; + s->Motion = SM_Linear; + s->Position = Position; + s->Velocity.x = sRandom.FloatSigned(); + s->Velocity.y = sRandom.FloatSigned(); + s->Velocity.z = sRandom.FloatSigned(); + s->Velocity.Normalize(); + s->Velocity.Scale( FRAME_RATIO * STAR_DUST_RADIUS ); + s->DurationRatio = STAR_DUST_DURATION_RATIO; + s->Size = STAR_DUST_SIZE; + s->Life = 1.0f; + s->Texture = ST_Puff; + s->Colour = STAR_DUST_COLOUR; + ++( mNumActiveSparkles[ s->Texture ] ); + } +} + +/*============================================================================= +Upward spray of smoke. Used for car damage smoke. +Transform is the position of the vehicle and the offset is the position of the +emission of the smoke _relative_ to the vehicle. The velocity is the vehicle's +movement per frame. +=============================================================================*/ +void Sparkle::AddSmoke( const rmt::Matrix& Transform, const rmt::Vector& Offset, const rmt::Vector& Velocity, float Strength ) +{ + if( Strength <= 0.0f ) + { + return; + } + rmt::Vector pos; + Transform.Transform( Offset, &pos ); + // Do some distance type LODing. The further from the camera, the fewer particles. + rmt::Vector camPos; + GetSuperCamManager()->GetSCC(0)->GetActiveSuperCam()->GetPosition( &camPos ); + camPos.Sub( pos ); + int d = 0; + if( GetGameplayManager()->GetGameType() != GameplayManager::GT_SUPERSPRINT ) + { + d = int( rmt::Max( rmt::Abs( camPos.x ), rmt::Abs( camPos.z ) ) * 0.02f ); + } + int numParticles = int( NUM_SMOKE_PARTICLES * Strength ) + 1 - d; + if( numParticles <= 0 ) + { + return; + } + for( int i = 0; i < numParticles; ++i ) + { + ActiveSparkle* s = GetInactiveSparkle(); + if( !s ) + { + return; + } + s->Align = SA_None; + s->Scale = SS_LinearTriple; + s->ColourAnim = SC_LinearAlphaDown; + s->CornerFade = 0; + s->Motion = SM_Drift; + s->Position = pos; + s->Position.x += sRandom.FloatSigned() * 0.15f; + s->Position.z += sRandom.FloatSigned() * 0.15f; + s->Position.y += sRandom.FloatSigned() * 0.15f; + s->Velocity.x = sRandom.FloatSigned() * 2.0f; + s->Velocity.z = sRandom.FloatSigned() * 2.0f; + s->Velocity.y = 2.0f + ( sRandom.Float() * 2.0f ); + s->Velocity.Normalize(); + s->Velocity.Scale( FRAME_RATIO * ( 2.0f + ( 1.0f * Strength ) ) ); + //s->Velocity.ScaleAdd( s->Velocity, FRAME_RATIO, Velocity ); + s->Velocity.Add(Velocity); + s->DurationRatio = 0.2f / 1.0f + Strength + sRandom.Float(); + s->Size = SMOKE_SIZE; // + ( ( Strength + sRandom.Float() ) * 0.1f ); + s->Life = 1.0f + ( Strength * 0.5f ); + int red = rmt::Clamp( int( ( LIGHT_SMOKE_COLOUR.Red() * ( 1.0f - Strength ) ) + ( DARK_SMOKE_COLOUR.Red() * Strength ) ), 0, 255 ); + int green = rmt::Clamp( int( ( LIGHT_SMOKE_COLOUR.Green() * ( 1.0f - Strength ) ) + ( DARK_SMOKE_COLOUR.Green() * Strength ) ), 0, 255 ); + int blue = rmt::Clamp( int( ( LIGHT_SMOKE_COLOUR.Blue() * ( 1.0f - Strength ) ) + ( DARK_SMOKE_COLOUR.Blue() * Strength ) ), 0, 255 ); + int alpha = rmt::Clamp( int( ( LIGHT_SMOKE_COLOUR.Alpha() * ( 1.0f - Strength ) ) + ( DARK_SMOKE_COLOUR.Alpha() * Strength ) ), 0, 255 ); + s->Colour.Set( red, green, blue, alpha ); + s->Texture = ST_SortedPuff; + ++( mNumActiveSparkles[ s->Texture ] ); + } +} + +/*============================================================================= +Update the position/animation of all the coins. +=============================================================================*/ +void Sparkle::Update( unsigned int Elapsedms ) +{ + float deltaSeconds = (float)Elapsedms * 0.001f; + bool HUDOnly = GetGameFlow()->GetCurrentContext() == CONTEXT_PAUSE; + + for( int i = 0; i < mNumSparkles; ++i ) + { + ActiveSparkle& s = mActiveSparkles[ i ]; + if(s.Active) + { + if(HUDOnly && s.HUD != 1) + { + continue; + } + if( s.Life < 0.0f ) + { + if(s.HUD) + { + s.HUD = 0; + --( mNumHUDSparkles[ s.Texture ] ); + } + s.Active = 0; + --(mNumActiveSparkles[ s.Texture ]); + } + if( s.Motion == SM_Decelerate ) + { + s.Position.ScaleAdd( 2.0f - s.Life, s.Velocity ); + } + else if ( s.Motion == SM_Gravity ) + { + s.Position.Add( s.Velocity ); + const float GRAVITY = 0.02f; + s.Velocity.y -= static_cast<float>(Elapsedms) * GRAVITY; + } + else if( s.Motion == SM_Drift ) + { + s.Position.Add( s.Velocity ); + s.Velocity.x *= DRIFT_DRAG; + s.Velocity.z *= DRIFT_DRAG; + } + else + { + s.Position.Add( s.Velocity ); + } + s.Life -= deltaSeconds * s.DurationRatio; + } + } +} + +Sparkle::ActiveSparkle::ActiveSparkle() : Active( 0 ), HUD( 0 ) +{} + + +/*============================================================================= +Render pass for the world sparkles. They are aligned to face the camera. +=============================================================================*/ +void Sparkle::Render( eSparkleRenderMode Mode ) +{ + unsigned short numWorldSparkles[ ST_NUM_TEXTURES ]; + int totalSparkles = 0; + int ti; + for( ti = 0; ti < mNumTextures; ++ti ) + { + if( mpTextures[ ti ] ) + { + if( Mode == SRM_IncludeSorted ) + { + numWorldSparkles[ ti ] = mNumActiveSparkles[ ti ] + mNumActiveSparkles[ ti + ST_NUM_TEXTURES ] - mNumHUDSparkles[ ti ]; + } + else if( Mode == SRM_ExcludeSorted ) + { + numWorldSparkles[ ti ] = mNumActiveSparkles[ ti ] - mNumHUDSparkles[ ti ]; + } + else + { + numWorldSparkles[ ti ] = mNumActiveSparkles[ ti + ST_NUM_TEXTURES ]; + } + } + else + { + numWorldSparkles[ ti ] = 0; + } + totalSparkles += numWorldSparkles[ ti ]; + } + if( totalSparkles <= 0 ) + { + return; + } + + tCamera* camera = p3d::context->GetView()->GetCamera(); + rAssert( camera ); + const rmt::Matrix& camTransform = camera->GetCameraToWorldMatrix(); + rmt::Vector cameraDiagR = camTransform.Row( 1 ); + rmt::Vector cameraDiagL = cameraDiagR; + cameraDiagR.Add( camTransform.Row( 0 ) ); + cameraDiagL.ScaleAdd( -1.0f, camTransform.Row( 0 ) ); + rmt::Vector offset; + offset.Scale( -0.1f, camTransform.Row( 2 ) ); + + bool oldZWrite = p3d::pddi->GetZWrite(); + pddiCompareMode oldZComp = p3d::pddi->GetZCompare(); + if( oldZWrite ) + { + p3d::pddi->SetZWrite( false ); + } + + pddiPrimStream* sprite = 0; + + pddiShader* spriteShader = BootupContext::GetInstance()->GetSharedShader(); + rAssert( spriteShader ); + + spriteShader->SetInt( PDDI_SP_ISLIT, 0 ); + spriteShader->SetInt( PDDI_SP_ALPHATEST, 1 ); + spriteShader->SetInt( PDDI_SP_SHADEMODE, PDDI_SHADE_FLAT ); + spriteShader->SetInt( PDDI_SP_FILTER, PDDI_FILTER_BILINEAR ); + for( ti = 0; ti < mNumTextures; ++ti ) + { + if( numWorldSparkles[ ti ] <= 0 ) + { + continue; + } + spriteShader->SetTexture( PDDI_SP_BASETEX, mpTextures[ ti ]->GetTexture() ); + if((ti == ST_Puff) || (ti == ST_Leaf)) + { + spriteShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ALPHA ); + spriteShader->SetInt( PDDI_SP_ALPHATEST, 0 ); + } + else + { + spriteShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ADD ); + } + + + sprite = p3d::pddi->BeginPrims( spriteShader, PDDI_PRIM_TRIANGLES, PDDI_V_CT , numWorldSparkles[ ti ] * 6 ); + for( int i = 0; i < mNumSparkles; ++i ) + { + ActiveSparkle& s = mActiveSparkles[ i ]; + if( ( s.Active == 0 ) || ( s.HUD == 1 ) ) + { + continue; + } + unsigned short matchTexture = s.Texture; + if( Mode == SRM_IncludeSorted ) + { + matchTexture %= ST_NUM_TEXTURES; + } + else if( Mode == SRM_SortedOnly ) + { + matchTexture -= ST_NUM_TEXTURES; + } + if( ( matchTexture != ti ) ) + { + continue; + } + float life = rmt::Max( s.Life, 0.0f ); + float scale; + // Triangle points: + // 2 - 3 + // | \ | + // 0 - 1 + tColour colour[ 4 ]; + rmt::Vector corners[ 4 ]; + + // Calculate the scale first. + if( s.Scale == SS_LinearDown ) + { + scale = s.Size * life; + } + else if( s.Scale == SS_BulgeDown ) + { + scale = s.Size * ( ( 1.0f - ( rmt::Abs( ( life * life ) - 0.5f ) * 2.0f ) ) + life ); + } + else if( s.Scale == SS_LinearGrow ) + { + scale = s.Size + ( s.Size * ( 1.0f - life ) * DASH_SIZE_INCREASE ); + } + else if( s.Scale == SS_LinearTriple ) + { + scale = s.Size + ( s.Size * 3.0f * ( 1.0f - rmt::Min( life, 1.0f ) ) ); + } + else if( s.Scale == SS_LinearUp ) + { + scale = s.Size * ( 1.0f - life ); + } + else + { + scale = s.Size; + } + // Now do colour. + if( s.ColourAnim == SC_LinearRedDownAtHalf ) + { + float l = rmt::Clamp( life * 2.0f, 0.0f, 1.0f ); + float l_half = rmt::Clamp( life - 0.5f, 0.0f, 1.0f ); + colour[ 0 ].Set( rmt::FtoL( s.Colour.Red() * l ), + rmt::FtoL( s.Colour.Green() * life ), + rmt::FtoL( s.Colour.Blue() * l_half ) ); + } + else if( s.ColourAnim == SC_LinearDownAtHalf ) + { + float l = rmt::Clamp( life * 2.0f, 0.0f, 1.0f ); + colour[ 0 ].Set( rmt::FtoL( s.Colour.Red() * l ), + rmt::FtoL( s.Colour.Green() * l), + rmt::FtoL( s.Colour.Blue() * l ) ); + } + else if( s.ColourAnim == SC_LinearAlphaDown ) + { + float l = rmt::Clamp( life, 0.0f, 1.0f ); + colour[ 0 ].Set( s.Colour.Red(), + s.Colour.Green(), + s.Colour.Blue(), + rmt::FtoL( s.Colour.Alpha() * l ) ); + } + else + { + float l = rmt::Clamp( life, 0.0f, 1.0f ); + colour[ 0 ].Set( rmt::FtoL( s.Colour.Red() * l ), + rmt::FtoL( s.Colour.Green() * l ), + rmt::FtoL( s.Colour.Blue() * l ) ); + } + if( s.CornerFade == 1 ) + { + colour[ 1 ] = colour[ 0 ]; + colour[ 1 ].SetAlpha( int( colour[ 0 ].Alpha() * 0.75f ) ); + colour[ 2 ] = colour[ 1 ]; + colour[ 3 ] = colour[ 0 ]; + colour[ 3 ].SetAlpha( colour[ 0 ].Alpha() >> 1 ); + } + else + { + for( int i = 1; i < 4; ++i ) + { + colour[ i ] = colour[ 0 ]; + } + } + + // Now do corners two corners. + if( s.Align == SA_MotionStreak ) + { + // Align to motion. + // Project travel onto plane of the camera. + rmt::Vector travel = s.Velocity; + travel.Normalize(); + float dot = travel.DotProduct( camTransform.Row( 2 ) ); + travel.ScaleAdd( -dot, camTransform.Row( 2 ) ); + dot = 1.0f - rmt::Abs( dot ); + corners[ 2 ].Set( -scale * dot, scale * SPARK_VERTICAL_SCALE, 0.0f ); + corners[ 3 ].Set( scale * dot, scale * SPARK_VERTICAL_SCALE, 0.0f ); + + travel.Normalize(); + // Because textures like to be wider then they are taller + //on the PS2, the spark textures is aligned down the X axis instead of Y. + //so travel becomes the right in the matrix, the camera direction becomes facing, + //and the up is a cross of those two. + rmt::Vector cross; + cross.CrossProduct( camTransform.Row( 2 ), travel ); + rmt::Matrix r( travel.x, travel.y, travel.z, 0.0f, + cross.x, cross.y, cross.z, 0.0f, + camTransform.Row( 2 ).x, camTransform.Row( 2 ).y, camTransform.Row( 2 ).z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f ); + r.Transform( 2, &( corners[ 2 ] ), &( corners[ 2 ] ) ); + } + else if( s.Align == SA_Rotation ) + { + rmt::Vector diagR; + diagR.Scale( scale, cameraDiagR ); + rmt::Vector diagL; + diagL.Scale( scale, cameraDiagL ); + corners[ 2 ] = diagL; + corners[ 3 ] = diagR; + rmt::Matrix r; + r.Identity(); + r.FillRotation( camTransform.Row( 2 ), rmt::PI_BY2 * life ); + r.Transform( 2, &( corners[ 2 ] ), &( corners[ 2 ] ) ); + } + else if( s.Align == SA_Flat ) + { + corners[ 2 ].Set( scale, 0.0f, -scale ); + corners[ 3 ].Set( scale, 0.0f, scale ); + } + else + { + rmt::Vector diagR; + diagR.Scale( scale, cameraDiagR ); + rmt::Vector diagL; + diagL.Scale( scale, cameraDiagL ); + corners[ 2 ] = diagL; + corners[ 3 ] = diagR; + } + + // Now mirror those two corners. + corners[ 0 ].Scale( -1.0f, corners[ 3 ] ); + corners[ 1 ].Scale( -1.0f, corners[ 2 ] ); + + // Now move corners into world position. + rmt::Vector pos; + pos.Add( s.Position, offset ); + for( int i = 0; i < 4; ++i ) + { + corners[ i ].Add( pos ); + } + + // Now draw the triangle. + // bottom left tri. + sprite->Colour( colour[ 0 ] ); + sprite->UV( 0.0f, 0.0f ); + sprite->Coord( corners[ 0 ].x, corners[ 0 ].y, corners[ 0 ].z ); + sprite->Colour( colour[ 1 ] ); + sprite->UV( 1.0f, 0.0f ); + sprite->Coord( corners[ 1 ].x, corners[ 1 ].y, corners[ 1 ].z ); + sprite->Colour( colour[ 2 ] ); + sprite->UV( 0.0f, 1.0f ); + sprite->Coord( corners[ 2 ].x, corners[ 2 ].y, corners[ 2 ].z ); + + // Top right tri. + sprite->Colour( colour[ 3 ] ); + sprite->UV( 1.0f, 1.0f ); + sprite->Coord( corners[ 3 ].x, corners[ 3 ].y, corners[ 3 ].z ); + sprite->Colour( colour[ 2 ] ); + sprite->UV( 0.0f, 1.0f ); + sprite->Coord( corners[ 2 ].x, corners[ 2 ].y, corners[ 2 ].z ); + sprite->Colour( colour[ 1 ] ); + sprite->UV( 1.0f, 0.0f ); + sprite->Coord( corners[ 1 ].x, corners[ 1 ].y, corners[ 1 ].z ); + } + p3d::pddi->EndPrims( sprite ); + } + spriteShader->SetTexture( PDDI_SP_BASETEX, 0 ); + if( oldZWrite ) + { + p3d::pddi->SetZWrite( true ); + } +} + +/*============================================================================= +Rendering pass for the HUD. Used for when the coin flies up to the HUD counter. +We set the state once and do all the rendering. +=============================================================================*/ +void Sparkle::HUDRender( void ) +{ + if( !mpTextures ) + { + return; + } + unsigned char totalSparkles = 0; + unsigned int ti; + for( ti = 0; ti < mNumTextures; ++ti ) + { + totalSparkles += mNumHUDSparkles[ ti ]; + } + if( totalSparkles <= 0 ) + { + return; + } + p3d::stack->Push(); + bool oldZWrite = p3d::pddi->GetZWrite(); + pddiCompareMode oldZComp = p3d::pddi->GetZCompare(); + if( oldZWrite ) + { + p3d::pddi->SetZWrite( false ); + } + if( oldZComp != PDDI_COMPARE_ALWAYS ) + { + p3d::pddi->SetZCompare( PDDI_COMPARE_ALWAYS ); + } + p3d::stack->LoadIdentity(); + p3d::stack->Scale( 0.1f, 0.1f, 1.0f); + p3d::pddi->SetProjectionMode( PDDI_PROJECTION_ORTHOGRAPHIC ); + pddiColour oldAmbient = p3d::pddi->GetAmbientLight(); + p3d::pddi->SetAmbientLight( pddiColour( 255, 255, 255 ) ); + + pddiPrimStream* sprite = 0; + + pddiShader* spriteShader = BootupContext::GetInstance()->GetSharedShader(); + rAssert( spriteShader ); + + spriteShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ADD ); + spriteShader->SetInt( PDDI_SP_ISLIT, 0 ); + spriteShader->SetInt( PDDI_SP_ALPHATEST, 1 ); + spriteShader->SetInt( PDDI_SP_SHADEMODE, PDDI_SHADE_FLAT ); + + for( ti = 0; ti < mNumTextures; ++ti ) + { + if( mNumHUDSparkles[ ti ] <= 0 ) + { + continue; + } + spriteShader->SetTexture( PDDI_SP_BASETEX, mpTextures[ ti ]->GetTexture() ); + + sprite = p3d::pddi->BeginPrims( spriteShader, PDDI_PRIM_TRIANGLES, PDDI_V_CT, mNumHUDSparkles[ ti ] * 6 ); + + for( int i = 0; i < mNumSparkles; ++i ) + { + ActiveSparkle& s = mActiveSparkles[ i ]; + if( s.Active == 0 || s.HUD == 0 || ( s.Texture != ti ) ) + { + continue; + } + float life = s.Life; + tColour colour; + colour.Set( rmt::Clamp( int( 255 * life ), 0, 255 ), + rmt::Clamp( int( 255 * life ), 0, 255 ), + rmt::Clamp( int( ( 255 * life ) - 128 ), 0, 255 ) ); + float scale; + scale = s.Size * life; + rmt::Vector pos = s.Position; + pos.Scale( 5.0f ); + + // bottom left tri. + sprite->Colour( colour ); + sprite->UV( 0.0f, 0.0f ); + sprite->Coord( pos.x - scale, pos.y - scale, pos.z ); + sprite->Colour( colour ); + sprite->UV( 1.0f, 0.0f ); + sprite->Coord( pos.x + scale, pos.y - scale, pos.z ); + sprite->Colour( colour ); + sprite->UV( 0.0f, 1.0f ); + sprite->Coord( pos.x - scale, pos.y + scale, pos.z ); + + // Top right tri. + sprite->Colour( colour ); + sprite->UV( 1.0f, 1.0f ); + sprite->Coord( pos.x + scale, pos.y + scale, pos.z ); + sprite->Colour( colour ); + sprite->UV( 0.0f, 1.0f ); + sprite->Coord( pos.x - scale, pos.y + scale, pos.z ); + sprite->Colour( colour ); + sprite->UV( 1.0f, 0.0f ); + sprite->Coord( pos.x + scale, pos.y - scale, pos.z ); + } + p3d::pddi->EndPrims( sprite ); + } + spriteShader->SetTexture( PDDI_SP_BASETEX, 0 ); + + p3d::pddi->SetProjectionMode(PDDI_PROJECTION_PERSPECTIVE); + p3d::pddi->SetAmbientLight( oldAmbient ); + if( oldZWrite ) + { + p3d::pddi->SetZWrite( true ); + } + if( oldZComp != PDDI_COMPARE_ALWAYS ) + { + p3d::pddi->SetZCompare( oldZComp ); + } + p3d::stack->Pop(); +}
\ No newline at end of file diff --git a/game/code/worldsim/coins/sparkle.h b/game/code/worldsim/coins/sparkle.h new file mode 100644 index 0000000..aa977f7 --- /dev/null +++ b/game/code/worldsim/coins/sparkle.h @@ -0,0 +1,174 @@ +//============================================================================= +// Copyright (C) 2003 Radical Entertainment Ltd. All rights reserved. +// +// File: coinmanager.h +// +// Description: Sparkle effect looks after drawing sparkle effects. It bundles +// them together under one set up call to PDDI. +// +// History: 29/01/2003 + Created -- James Harrison +// +//============================================================================= + +#ifndef SPARKLE_H +#define SPARKLE_H + +//======================================== +// Nested Includes +//======================================== +#include <radmath/vector.hpp> +#include <radmath/matrix.hpp> +#include <radmath/random.hpp> +#include <render/enums/renderenums.h> + +//======================================== +// Forward References +//======================================== +class tDrawable; +class tTexture; + +//============================================================================= +// +// Synopsis: Creating, place, animate sparkles in the world. Handy for coin +// collection trails, etc. +// +//============================================================================= +static const int DEFAULT_NUM_SPARKLES = 150; + +class Sparkle +{ +public: + enum eSparkleTextures + { + ST_Sparkle, // 0 + ST_Spark, // 1 + ST_Puff, // 2 + ST_Leaf, // 3 + ST_BlueSparkle, // 4 - used for gags. + ST_Paint, // 5 + ST_Ring, // 6 + ST_NUM_TEXTURES, + ST_SortedSparkle, + ST_SortedSpark, + ST_SortedPuff, + ST_SortedLeaf, + ST_SortedBlueSparkle, + ST_SortedPaint, + ST_SortedRing + }; + + Sparkle( unsigned char NumTextures = 1, unsigned short NumSparkles = DEFAULT_NUM_SPARKLES ); + ~Sparkle(); + + static Sparkle* GetInstance( void ); + static Sparkle* CreateInstance( unsigned char NumTextures = ST_NUM_TEXTURES, unsigned short NumSparkles = DEFAULT_NUM_SPARKLES ); + static void DestroyInstance( void ); + + void Init( void ); + void Destroy( void ); + + enum eSparkleEffect + { + SE_Trail, + SE_Glint, + SE_HUDGlint, + SE_Vanish + }; + + void AddSparkle( const rmt::Vector& Position, float Size, float Duration, const rmt::Vector& Velocity, eSparkleEffect Effect ); + void AddGagSparkle(const rmt::Vector& Position, float Size, float Strength, unsigned int Caller); // See notes for information on the Caller. + void AddSparks( const rmt::Vector& Position, const rmt::Vector& Velocity, float Strength ); + void AddDash( const rmt::Vector& Position, const rmt::Vector& Velocity, float Strength ); // NOTE: Velocity is direction of character travel. + void AddLanding( const rmt::Vector& Position, float Strength ); + void AddLeaves( const rmt::Vector& Position, const rmt::Vector& Velocity, float Strength ); + void AddPaintChips( const rmt::Vector& Position, const rmt::Vector& Velocity, pddiColour colour, float Strength ); + void AddShockRing( const rmt::Vector& Position, float Strength ); + void AddStars( const rmt::Vector& Position, float Strength ); + void AddBottomOut( const rmt::Vector& Position ); + void AddSmoke( const rmt::Matrix& Transform, const rmt::Vector& Offset, const rmt::Vector& Velocity, float Strength ); + + void Update( unsigned int Elapsedms ); // Elapsed milliseconds. + enum eSparkleRenderMode + { + SRM_IncludeSorted, + SRM_ExcludeSorted, + SRM_SortedOnly + }; + // Note that the RenderSorted Draws all the particles tagged as being sorted. This is a pretty crude implementation and designed just the fix the car smoke. + void Render( eSparkleRenderMode Mode = SRM_IncludeSorted ); + void HUDRender( void ); // Draw any HUD sparkles. + + void SetTexture( unsigned char Index, tTexture* Texture ); + + static rmt::Randomizer sRandom; + static bool sRandomSeeded; + +protected: + //Prevent wasteful constructor creation. + Sparkle( const Sparkle& That ); + Sparkle& operator=( const Sparkle& That ); + + struct ActiveSparkle + { + ActiveSparkle(); + rmt::Vector Position; + rmt::Vector Velocity; + float Size; + float DurationRatio; + float Life; + pddiColour Colour; + unsigned int Active : 1; + unsigned int HUD : 1; + unsigned int Texture : 4; + unsigned int Align : 2; + unsigned int Scale : 3; + unsigned int ColourAnim : 3; + unsigned int CornerFade : 1; + unsigned int Motion : 2; + }; + enum eSparkleMotion + { + SM_Linear, + SM_Decelerate, + SM_Gravity, + SM_Drift + }; + enum eSparkleColour + { + SC_LinearDown, + SC_LinearDownAtHalf, + SC_LinearAlphaDown, + SC_LinearRedDownAtHalf, + SC_BulgeDown + }; + enum eSparkleScale + { + SS_Constant, + SS_LinearDown, + SS_BulgeDown, + SS_LinearUp, + SS_LinearGrow, + SS_LinearTriple + }; + enum eSparkleAlign + { + SA_None, + SA_MotionStreak, + SA_Flat, + SA_Rotation + }; + ActiveSparkle* GetInactiveSparkle( void ); + static Sparkle* spInstance; + ActiveSparkle* mActiveSparkles; + tTexture** mpTextures; + unsigned short mNumSparkles; + unsigned short mNextInactiveSparkle; + unsigned short* mNumActiveSparkles; + unsigned short* mNumHUDSparkles; + unsigned char mNumTextures; +}; + +// A little syntactic sugar for getting at this singleton. +inline Sparkle* GetSparkleManager() { return( Sparkle::GetInstance() ); } + +#endif // #ifndef SPARKLE_H diff --git a/game/code/worldsim/groundplanepool.cpp b/game/code/worldsim/groundplanepool.cpp new file mode 100644 index 0000000..6a8bba8 --- /dev/null +++ b/game/code/worldsim/groundplanepool.cpp @@ -0,0 +1,326 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: groundplanepool.cpp +// +// Description: manage pool of ground planes for dynamic physics objects +// +// History: July 31, 2002 - created, gmayer +// +//============================================================================= + + +//======================================== +// System Includes +//======================================== + +#include <simcommon/simstate.hpp> + + +#include <simcommon/simenvironment.hpp> +#include <simcollision/collisionmanager.hpp> + +#include <simcollision/collisionvolume.hpp> +#include <simcollision/collisiondisplay.hpp> + +#include <simcommon/physicsproperties.hpp> + +#include <raddebug.hpp> + + +//======================================== +// Project Includes +//======================================== + +#include <worldsim/groundplanepool.h> +#include <worldsim/physicsairef.h> + +#include <memory/srrmemory.h> + + +//============================================================================= +// GroundPlanePool::GroundPlanePool +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: GroundPlanePool +// +//============================================================================= +GroundPlanePool::GroundPlanePool(int num) +{ +MEMTRACK_PUSH_GROUP( "GroundPlanePool" ); + mTotalNum = num; + +// #ifdef RAD_GAMECUBE +// HeapMgr()->PushHeap( GMA_GC_VMM ); +// #else + HeapMgr()->PushHeap( GMA_PERSISTENT ); +// #endif + + mPool = new sim::ManualSimState*[mTotalNum]; + //mPool = new sim::SimState*[mTotalNum]; + mInUse = new bool[mTotalNum]; + + mSimStateOwners = new sim::SimState*[mTotalNum]; + + mGroundPlanePhysicsProperties = new sim::PhysicsProperties; + + int i; + for(i = 0; i < mTotalNum; i++) + { + rmt::Vector p(0.0f, 0.0f, 0.0f); + rmt::Vector n(0.0f, 1.0f, 0.0f); + + sim::WallVolume* tempwall = new sim::WallVolume(p, n); + + // TODO - are the temp volumes getting deleted by the sim state? + mPool[i] = (sim::ManualSimState*)(sim::SimState::CreateManualSimState(tempwall)); + + //static SimState* CreateSimState(CollisionVolume* inCollisionVolume, char* inName = NULL, tEntityStore* inStore = NULL); + + //mPool[i] = sim::SimState::CreateSimState(tempwall); + + mPool[i]->AddRef(); + + mPool[i]->GetCollisionObject()->SetManualUpdate(true); + mPool[i]->GetCollisionObject()->SetAutoPair(false); + mPool[i]->GetCollisionObject()->SetIsStatic(true); + + //mPool[i]->GetCollisionObject()->SetPhysicsProperties(mGroundPlanePhysicsProperties); + mPool[i]->SetPhysicsProperties(this->mGroundPlanePhysicsProperties); + + // give a reasonable name for debugging purposes... + char buffy[128]; + sprintf(buffy, "groundplanepool_id%d", i); + mPool[i]->GetCollisionObject()->SetName(buffy); + + mInUse[i] = false; + + mPool[i]->GetCollisionObject()->SetCollisionEnabled(false); + + mPool[i]->mAIRefIndex = PhysicsAIRef::redBrickPhizMoveableGroundPlane; + mPool[i]->mAIRefPointer = 0; // only set if object is derived from CollisionEntityDSG + + + mSimStateOwners[i] = 0; + + + + } +// #ifdef RAD_GAMECUBE +// HeapMgr()->PopHeap( GMA_GC_VMM ); +// #else + HeapMgr()->PopHeap( GMA_PERSISTENT ); +// #endif +MEMTRACK_POP_GROUP( "GroundPlanePool" ); +} + +//============================================================================= +// GroundPlanePool::~GroundPlanePool +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: GroundPlanePool +// +//============================================================================= +GroundPlanePool::~GroundPlanePool() +{ + int i; + for(i = 0; i < mTotalNum; i++) + { + mPool[i]->Release(); + } + delete mPool; + delete mInUse; + + delete mSimStateOwners; + +} + + +//============================================================================= +// GroundPlanePool::GetNewGroundPlane +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: int +// +//============================================================================= +int GroundPlanePool::GetNewGroundPlane(sim::SimState* simStateOwner) +{ + // find first one that's not in use + int i; + for(i = 0; i < mTotalNum; i++) + { + if(mInUse[i] == false) + { + mInUse[i] = true; + + mSimStateOwners[i] = simStateOwner; + + return i; + } + } + + // failure + return -1; +} + +//============================================================================= +// GroundPlanePool::UpdateGroundPlane +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: void +// +//============================================================================= +void GroundPlanePool::UpdateGroundPlane(int index, rmt::Vector& position, rmt::Vector& normal) +{ + rAssert( 0 <= index && index < mTotalNum ); + + if(index > -1 && index < mTotalNum) // just in case + { + rAssert(mInUse[index]); + + // for convenience: + sim::CollisionObject* co = mPool[index]->GetCollisionObject(); + sim::WallVolume* wall = (sim::WallVolume*)(co->GetCollisionVolume()); + + wall->mPosition = position; + wall->mNormal = normal; + + co->PostManualUpdate(); + + //co->Relocated(); + //obbox->UpdateBBox(); + + // only need to do this once - TODO .. ? only enable when 'owner' object get's hit? - + //co->SetCollisionEnabled(true); + } +} + + +//============================================================================= +// GroundPlanePool::EnableCollision +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: void +// +//============================================================================= +void GroundPlanePool::EnableCollision(int index) +{ + rAssert( 0 <= index && index < mTotalNum ); + rAssert( mInUse[index] ); + + sim::CollisionObject* co = mPool[index]->GetCollisionObject(); + co->SetCollisionEnabled(true); +} + + +//============================================================================= +// GroundPlanePool::DisableCollision +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: void +// +//============================================================================= +void GroundPlanePool::DisableCollision(int index) +{ + rAssert( 0 <= index && index < mTotalNum ); + rAssert( mInUse[index] ); + + sim::CollisionObject* co = mPool[index]->GetCollisionObject(); + co->SetCollisionEnabled(false); +} + + +//============================================================================= +// GroundPlanePool::FreeGroundPlane +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: void +// +//============================================================================= +void GroundPlanePool::FreeGroundPlane(int index) +{ + // make this safe to call with any value. + // + // note: we are not responsible for taking it out of any collision or anything like that. + if(index < 0 || index >= mTotalNum) + { + return; + } + mInUse[index] = false; + mSimStateOwners[index] = 0; +} + + + +//============================================================================= +// GroundPlanePool::FreeAllGroundPlanes +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: bool +// +//============================================================================= +bool GroundPlanePool::FreeAllGroundPlanes() // returns false if there was a problem... +{ + // called when quitting game or a level + bool ok = true; + int i; + for(i = 0; i < mTotalNum; i++) + { + if(mInUse[i]) + { + ok = false; + rAssert(false); + } + mInUse[i] = false; + mSimStateOwners[i] = 0; + } + return ok; + +} + + + +//============================================================================= +// GroundPlanePool::GetSimState +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: sim +// +//============================================================================= +sim::ManualSimState* GroundPlanePool::GetSimState(int index) +//sim::SimState* GroundPlanePool::GetSimState(int index) +{ + rAssert( 0 <= index && index < mTotalNum ); + rAssert(mInUse[index]); + + return mPool[index]; +} + + diff --git a/game/code/worldsim/groundplanepool.h b/game/code/worldsim/groundplanepool.h new file mode 100644 index 0000000..ca93e33 --- /dev/null +++ b/game/code/worldsim/groundplanepool.h @@ -0,0 +1,71 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: groundplanepool.h +// +// Description: manage pool of ground planes for dynamic physics objects +// +// History: July 31, 2002 - created, gmayer +// +//============================================================================= + +#ifndef GROUNDPLANEPOOL_H +#define GROUNDPLANEPOOL_H + +//======================================== +// Nested Includes +//======================================== + +#include <simcommon/simstate.hpp> + +//======================================== +// Forward References +//======================================== + + +// note to self: maybe vehicles should use this? + +class GroundPlanePool +{ +public: + + GroundPlanePool(int num); // how big the pool should be + ~GroundPlanePool(); + + //sim::ManualSimState* GetNewGroundPlane(); + + //int GetNewGroundPlane(); // user refers to it with the returned index + + int GetNewGroundPlane(sim::SimState* simStateOwner); + + + void UpdateGroundPlane(int index, rmt::Vector& position, rmt::Vector& normal); + void FreeGroundPlane(int index); + bool FreeAllGroundPlanes(); // returns false if there was a problem... + + sim::ManualSimState* GetSimState(int index); + //sim::SimState* GetSimState(int index); + + void EnableCollision(int index); + void DisableCollision(int index); + + +private: + + int mTotalNum; + sim::ManualSimState** mPool; + //sim::SimState** mPool; + + bool* mInUse; + + + sim::SimState** mSimStateOwners; + + // to save memory, and potentially have more control over the values + sim::PhysicsProperties* mGroundPlanePhysicsProperties; + + +}; + + +#endif //GROUNDPLANEPOOL_H diff --git a/game/code/worldsim/harass/allharass.cpp b/game/code/worldsim/harass/allharass.cpp new file mode 100644 index 0000000..d587474 --- /dev/null +++ b/game/code/worldsim/harass/allharass.cpp @@ -0,0 +1 @@ +#include <worldsim/harass/chasemanager.cpp> diff --git a/game/code/worldsim/harass/chasemanager.cpp b/game/code/worldsim/harass/chasemanager.cpp new file mode 100644 index 0000000..1aa9d0e --- /dev/null +++ b/game/code/worldsim/harass/chasemanager.cpp @@ -0,0 +1,911 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: chasemanager.cpp +// +// Description: ChaseManager Class Implementation +// +// History: 11/5/2002 + Created -- Dusit Eakkachaichanvet +// +//============================================================================= + +#include <worldsim/harass/chasemanager.h> +#include <stdlib.h> + +#include <worldsim/vehiclecentral.h> +#include <worldsim/avatarmanager.h> +#include <worldsim/avatar.h> +/* +#include <camera/supercammanager.h> +#include <camera/supercam.h> +*/ +#include <ai/vehicle/chaseai.h> +#include <roads/roadsegment.h> +#include <roads/geometry.h> +#include <render/Culling/ReserveArray.h> +#include <render/IntersectManager/IntersectManager.h> +#include <events/EventManager.h> + +#include <mission/gameplaymanager.h> + +//////////////////////////////////// +// Initialize Statics +//ChaseManager* spChaseManager = NULL; +static const float HARASS_BEELINE_DIST = 60.0f; + +///////////////////////////////////// + +// Constructors/Destructors +ChaseManager::ChaseManager() +: +mNumRegisteredModels( 0 ), +mTotalSpawnFrequencies( 0 ), +mMaxVehicles( MAX_CHASE_VEHICLES ) +{ + GetEventManager()->AddListener( this, EVENT_VEHICLE_DESTROYED ); + //GetEventManager()->AddListener( this, EVENT_REPAIR_CAR ); // Chase cars won't be repairing. + + for( int i=0; i<MAX_MODELS; i++ ) + { + mModels[i].spawnFreq = 0; + } + + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + mVehicles[i].v = NULL; + mVehicles[i].husk = NULL; + mVehicles[i].vAI = NULL; + mVehicles[i].isActive = false; + //mVehicles[i].activeListIndex = -1; + mVehicles[i].isOutOfSight = true; + mVehicles[i].markedForDeletion = false; + mVehicles[i].secondsOutOfSight = 0.0f; + } + + // Gotta set stuff belonging to superclass to prevent a check in superclass from failing + mSpawnRadius = CHASE_SPAWN_RADIUS; + mRemoveRadius = CHASE_REMOVE_RADIUS; +} + +ChaseManager::~ChaseManager() +{ + GetEventManager()->RemoveAll( this ); + DeactivateAllVehicles(); +} + +bool ChaseManager::IsChaseVehicle( Vehicle* v ) +{ + ChaseVehicle* cv = FindChaseVehicle( v ); + return( cv != NULL ); +} + + + +ChaseManager::ChaseVehicle* ChaseManager::FindChaseVehicle( Vehicle* v ) +{ + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( v == mVehicles[i].v ) + { + return &mVehicles[i]; + } + } + + return NULL; +} + + +void ChaseManager::HandleEvent( EventEnum id, void* pEventData ) +{ + if( id == EVENT_VEHICLE_DESTROYED ) + { + Vehicle* v = (Vehicle*) pEventData; + rAssert( v ); + + ChaseVehicle* cv = FindChaseVehicle( v ); + if( cv == NULL ) + { + return; + } + + rAssert( cv->isActive ); + + DeactivateVehicle( cv ); + /*** + WELL now they don't want husks for specifically for chase/harass cars + and so we go round in circles.... Initially, they wanted ALL to have husks + "for consistency"... Now, not so much. + + // obtain info from the vehicle + rmt::Vector initPos, initDir; + v->GetPosition( &initPos ); + initDir = v->GetFacing(); + rAssert( rmt::Epsilon( initDir.MagnitudeSqr(), 1.0f, 0.001f ) ); + + // Remove original from VehicleCentral's ActiveList + VehicleCentral* vc = ::GetVehicleCentral(); + rAssert( vc != NULL ); + + vc->RemoveVehicleFromActiveList( cv->v ); + GetEventManager()->TriggerEvent( EVENT_CHASE_VEHICLE_DESTROYED, cv->v ); + + // Update chase AI + vc->SetVehicleController( cv->activeListIndex, NULL ); + cv->activeListIndex = -1; + cv->vAI->SetActive( false ); + cv->vAI->Finalize(); + + // + // Now we grab husk and put it in place of the original vehicle + // + Vehicle* husk = GetVehicleCentral()->mHuskPool.RequestHusk( VT_AI, v ); + if( husk == NULL ) + { + 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; + } + cv->activeListIndex = res; + + husk->AddRef(); + husk->SetInitialPosition( &initPos ); + float angle = GetRotationAboutY( initDir.x, initDir.z ); + husk->SetResetFacingInRadians( angle ); + husk->Reset(); + husk->SetLocomotion( VL_PHYSICS ); + + cv->husk = husk; + */ + } +} + + + +void ChaseManager::Init() +{ + SpawnManager::Init(); + + //////////////////////////////////////////////////////////////// + // CLEAN UP PREVIOUS RUN + // + DeactivateAllVehicles(); + + //////////////////////////////////////////////////////////////// + // INITIALIZE FOR NEW RUN + // + + int count = 0; + + // create a temp string array of exactly this many elems or "buckets" + char** names = new char* [mTotalSpawnFrequencies]; + for( int i=0; i<MAX_MODELS; i++ ) + { + for( int j=count; j<(count+mModels[i].spawnFreq); j++ ) + { + names[j] = new char[160]; + strcpy( names[j], mModels[i].name ); + } + count += mModels[i].spawnFreq; + } + rAssert( count == mTotalSpawnFrequencies ); + + // pick a bucket random and spawn a car by that name + VehicleCentral* vc = ::GetVehicleCentral(); + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + int coinflip = rand() % mTotalSpawnFrequencies; + + + + Vehicle* v = vc->InitVehicle( names[coinflip], false, mConfileName, VT_AI, + VehicleCentral::ALLOW_DRIVER, + false, // not a playercar + false); // start with in car bv representation immediately + rAssert( v != NULL ); + + mVehicles[i].v = v; + mVehicles[i].husk = NULL; + mVehicles[i].v->AddRef(); + mVehicles[i].v->SetLocomotion( VL_PHYSICS ); + mVehicles[i].vAI = new (GMA_LEVEL_OTHER) ChaseAI( v, HARASS_BEELINE_DIST ); + mVehicles[i].vAI->AddRef(); // Corresponding call to Release() will already call delete if refcount<=1 + mVehicles[i].isActive = false; + mVehicles[i].isOutOfSight = true; + mVehicles[i].markedForDeletion = false; + mVehicles[i].secondsOutOfSight = 0.0f; + } + + // clean up our temporary name buckets... + for( int i=0; i<mTotalSpawnFrequencies; i++ ) + { + delete[] names[i]; + } + delete[] names; +} + + + +void ChaseManager::ClearAllObjects() +{ + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].isActive ) + { + DeactivateVehicle( &mVehicles[i] ); + } + } +} + +int ChaseManager::GetNumActiveVehicles() +{ + int numActive = 0; + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].isActive ) + { + numActive++; + } + } + return numActive; +} + +void ChaseManager::ClearOutOfSightVehicles() +{ + static const float SECONDS_OUT_OF_SIGHT_BEFORE_REMOVAL = 5.0f; + + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].isActive && + mVehicles[i].isOutOfSight && + ( mVehicles[i].husk != NULL || mVehicles[i].markedForDeletion == true ) && + mVehicles[i].secondsOutOfSight > SECONDS_OUT_OF_SIGHT_BEFORE_REMOVAL ) + { + DeactivateVehicle( &mVehicles[i] ); + } + } +} + +void ChaseManager::ClearObjectsInsideRadius( rmt::Vector center, float radius ) +{ + int nObjectsRemoved = 0; + float minDistSqr = radius * radius; + + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].isActive ) + { + Vehicle* v = mVehicles[i].v; + rAssert( v != NULL ); + + rmt::Vector vPos; + v->GetPosition( &vPos ); + + rmt::Vector toSphere = center - vPos; + + if( toSphere.MagnitudeSqr() <= minDistSqr ) + { + DeactivateVehicle( &mVehicles[i] ); + } + } + } +} + + +void ChaseManager::ClearObjectsOutsideRadius( rmt::Vector center, float radius ) +{ + float minDistSqr = radius * radius; + + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].isActive ) + { + Vehicle* v = mVehicles[i].v; + rAssert( v != NULL ); + + rmt::Vector vPos; + v->GetPosition( &vPos ); + + rmt::Vector toSphere = center - vPos; + + if( toSphere.MagnitudeSqr() > minDistSqr ) + { + DeactivateVehicle( &mVehicles[i] ); + } + } + } +} + +bool ChaseManager::RegisterModel( const char* name, int spawnFreq ) +{ + rAssert( name != NULL ); + rAssert( spawnFreq > 0 ); + + // search existing SPARSE list for name + int freeIndex = -1; + for( int i=0; i<MAX_MODELS; i++ ) + { + // use spawnFreq = 0 to mark empty spots + if( mModels[i].spawnFreq == 0 ) + { + freeIndex = i; + continue; + } + + // found an existing model registered under same name, so overwrite w/ new values + if( strcmp(mModels[i].name, name)==0 ) + { + mTotalSpawnFrequencies += spawnFreq - mModels[i].spawnFreq; + mModels[i].spawnFreq = spawnFreq; + return true; + } + } + + if( 0 <= freeIndex && freeIndex < MAX_MODELS ) + { + int len = strlen( name ); + rAssert( len <= MAX_STRING_LEN ); + strncpy( mModels[freeIndex].name, name, MAX_STRING_LEN ); + mModels[freeIndex].name[MAX_STRING_LEN] = '\0'; + + mModels[freeIndex].spawnFreq = spawnFreq; + mTotalSpawnFrequencies += spawnFreq; + mNumRegisteredModels++; + return true; + } + return false; +} + +bool ChaseManager::IsModelRegistered( const char* name ) +{ + rAssert( name != NULL ); + + // search existing SPARSE list for name + for( int i=0; i<MAX_MODELS; i++ ) + { + // found an existing model registered under this name + if( strcmp(mModels[i].name, name)==0 ) + { + return true; + } + } + return false; +} + +bool ChaseManager::UnregisterModel( const char* name ) +{ + rAssert( name != NULL ); + + // search existing SPARSE list for name + for( int i=0; i<MAX_MODELS; i++ ) + { + if( mModels[i].spawnFreq == 0 ) + { + continue; + } + int nameLen = strlen( name ); + int modelNameLen = strlen( mModels[i].name ); + if( (nameLen == modelNameLen) && + (strncmp(mModels[i].name, name, nameLen)==0) ) + { + mTotalSpawnFrequencies -= mModels[i].spawnFreq; + mNumRegisteredModels--; + mModels[i].spawnFreq = 0; + return true; + } + } + return false; +} + +void ChaseManager::SetConfileName( const char* name ) +{ + int len = strlen( name ); + rAssert( len <= MAX_STRING_LEN ); + strncpy( mConfileName, name, MAX_STRING_LEN ); + mConfileName[MAX_STRING_LEN] = '\0'; +} + + +void ChaseManager::DisableAllActiveVehicleAIs() +{ + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].isActive ) + { + //mVehicles[i].vAI->SetActive( false ); + mVehicles[i].vAI->EnterLimbo(); + } + } +} + +void ChaseManager::EnableAllActiveVehicleAIs() +{ + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].isActive ) + { + //mVehicles[i].vAI->SetActive( true ); + mVehicles[i].vAI->ExitLimbo(); + } + } +} + +void ChaseManager::AddObjects( float seconds ) +{ + rAssert( seconds >= 0.0f ); + + // Get VehicleCentral. We'll need it lots. + VehicleCentral* vc = ::GetVehicleCentral(); + rAssert( vc != NULL ); + + // Get Player info. + rmt::Vector playerPos, playerVel; + + /* + SuperCam* superCam = ::GetSuperCamManager()->GetSCC(0)->GetActiveSuperCam(); + rAssert( superCam != NULL ); + superCam->GetPosition( &playerPos ); + superCam->GetVelocity( &playerVel ); + */ + Avatar* avatar = ::GetAvatarManager()->GetAvatarForPlayer(0); + rAssert( avatar != NULL ); + avatar->GetPosition( playerPos ); + avatar->GetVelocity(playerVel); + + + float spawnRadius = GetSpawnRadius(); + + + // Do FindRoadElems to get the road segments that intersect our sphere. + // For each road segment's lane, tally a list of all intersection points (max of 2) + // For each intersection point, + // if mNumObjects < mMaxObjects, + // ActivateVehicle() + // Initialize vehicle + + ReserveArray<RoadSegment*> orList; + ::GetIntersectManager()->FindRoadSegmentElems( playerPos, spawnRadius, orList ); + + // This is the vehicle from our list of inactive chase vehicles that we'll spawn + // Abort early if there's no more vehicle available to spawn... + ChaseVehicle* cv = GetInactiveVehicle(); + if( cv == NULL ) + { + return; + } + + for( int i=0; i<orList.mUseSize; i++ ) + { + RoadSegment* segment = orList.mpData[i]; + rAssert( segment != NULL ); + + // loop through all the lanes for this segment + for( unsigned int j=0; j<segment->GetNumLanes(); j++ ) + { + // Find places where lane intersects with spawn radius (max 2 locations) + rmt::Vector startPos, startDir, endPos, endDir; + segment->GetLaneLocation( 0.0f, j, startPos, startDir ); + segment->GetLaneLocation( 1.0f, j, endPos, endDir ); + + rmt::Vector intPts[2]; + rmt::Sphere playerSphere( playerPos, spawnRadius ); + int numIntersections = IntersectLineSphere( startPos, endPos, playerSphere, intPts ); + if(numIntersections <= 0) + { + continue; // doesn't intersect our spawn sphere; skip this lane + } + + // for each intersection point found, plant a vehicle + for( int k=0; k<numIntersections; k++ ) + { + if( mNumObjects < mMaxVehicles ) + { + rmt::Vector vPos = intPts[k]; + + if( cv == NULL ) + { + return; // no more inactive vehicles available for spawning + } + Vehicle* v = cv->v; + rAssert( v != NULL ); + + // + // Detect if we're placing this car on top of another car + // by looping through active vehicles list. If there is car beneath us, + // don't spawn here and try next point. + // + rmt::Sphere vSphere; + v->GetBoundingSphere( &vSphere ); + vSphere.centre = vPos; + if( SpawningOnTopOfAnotherVehicle(vSphere) ) + { + continue; // gonna spawn on a car; try next spawn point + } + + // + // Check if in the next n seconds, vehicle will still be in + // player's spawn zone (so we didn't add it in vain). + // + + float timeToLiveInSeconds = 1.0f; //GetSecondsBetwAdds(); + + // + // Predict player pos after timeToLiveInSeconds seconds + // + rmt::Vector playerPos2 = playerPos + playerVel * timeToLiveInSeconds; + + // + // Predict v pos after timeToLiveInSeconds seconds + // + rmt::Vector vDir = endPos - startPos; + vDir.Normalize(); // *** SQUARE ROOT! *** + + float vAccel = v->mDesignerParams.mDpGasScale * + v->mSlipGasModifier * + 1.0f; //v->mGas; + + float vDistWillTravel = 0.5f * vAccel * timeToLiveInSeconds * timeToLiveInSeconds; + + rmt::Vector vPos2 = vPos + vDir * vDistWillTravel; + + // + // Make sure vPos2 still inside playerPos2's radius before we spawn it + // + float minDistSqr = spawnRadius * spawnRadius; + float distSqr = (vPos2 - playerPos2).MagnitudeSqr(); + if( distSqr < minDistSqr ) + { + // want the vehicle to spawn facing the player + rmt::Vector initFacing = playerPos - vPos; + + // initialize vehicle + v->SetInitialPosition( &vPos ); + float angle = GetRotationAboutY( initFacing.x, initFacing.z ); + v->SetResetFacingInRadians( angle ); + v->Reset(); + + bool succeeded = ActivateVehicle( cv ); + if( !succeeded ) + { + // vehiclecentral's activelist is full.. + // no point trying to add anymore + return; + } + + // just used the inactive vehicle; grab a new inactive vehicle + cv = GetInactiveVehicle(); + + } // end-of-if vehicle's next pos lies in radius of player's next spawn sphere + } // end-of-if mNumObjects < mMaxObjects + } // end-of-loop through all intersection points on spawn radius for this lane + } // end-of-loop through all lanes belonging to one segment + }// end-of-loop through all segments returned by DSGFind +} + +void ChaseManager::RemoveObjects( float seconds ) +{ + // Get Player info + + rmt::Vector playerPos; + /* + SuperCam* superCam = ::GetSuperCamManager()->GetSCC(0)->GetActiveSuperCam(); + rAssert( superCam != NULL ); + superCam->GetPosition( &playerPos ); + */ + Avatar* avatar = ::GetAvatarManager()->GetAvatarForPlayer(0); + rAssert( avatar != NULL ); + avatar->GetPosition( playerPos ); + + float radius = GetRemoveRadius(); + + // Remove! + ClearObjectsOutsideRadius( playerPos, radius ); + + ClearOutOfSightVehicles(); +} + +void ChaseManager::UpdateObjects( float seconds ) +{ + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].isActive ) + { + if( mVehicles[i].v->mVehicleDestroyed && + mVehicles[i].vAI->GetState() != VehicleAI::STATE_LIMBO ) + { + mVehicles[i].vAI->EnterLimbo(); + // NOTE: + // Never set mVehicles[i].isActive to false here because + // it means something entirely different (it means that it's not + // within your radius). Just set the AI's active flag to false + //mVehicles[i].isActive = false; + } + + if( mVehicles[i].husk || mVehicles[i].markedForDeletion ) + { + Vehicle* vehicle = mVehicles[i].v; + if(mVehicles[i].husk) + { + vehicle = mVehicles[i].husk; + } + + // Test for out of sight of player 0... If so, increment timer, if not reset timer + rmt::Vector pos; + + vehicle->GetPosition( &pos ); + mVehicles[i].isOutOfSight = !GetGameplayManager()->TestPosInFrustrumOfPlayer( pos, 0 ); + + mVehicles[i].secondsOutOfSight += seconds; + if( !mVehicles[i].isOutOfSight ) + { + mVehicles[i].secondsOutOfSight = 0.0f; + } + } + } + } + + /* + // VehicleCentral automatically updates all vehicles & their AI + // controllers if they're under Physics Locomotion (which they are) + // so we need do nothing here. + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].isActive ) + { + mVehicles[i].v->Update( seconds ); + } + } + */ +} + +void ChaseManager::SuspendAllVehicles() +{ + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].v != NULL && mVehicles[i].isActive) + { + mVehicles[i].v->CarDisplay( false ); + GetVehicleCentral()->RemoveVehicleFromActiveList( mVehicles[i].v ); + } + } +} + +void ChaseManager::ResumeAllVehicles() +{ + VehicleCentral* vc = ::GetVehicleCentral(); + rAssert( vc != NULL ); + + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].v != NULL && mVehicles[i].isActive) + { + mVehicles[i].v->CarDisplay( true ); + // add to vehiclecentral's activelist + if( vc->ActiveVehicleListIsFull() ) + { + rAssert(false); + return; + } + int res = vc->AddVehicleToActiveList( mVehicles[i].v ); + if( res == -1 ) + { + // not supposed to happen since we already eliminated the + // safe failure condition (activelistisfull).. So something else + // went wrong... + rAssert( false ); + } + + // update chase AI + vc->SetVehicleController( res, mVehicles[i].vAI ); + mVehicles[i].vAI->Initialize(); + mVehicles[i].vAI->SetActive( true ); + } + } +} + +void ChaseManager::DeactivateVehicle( ChaseVehicle* cv ) +{ + rAssert( cv->v != NULL ); + rAssert( cv->vAI != NULL ); + rAssert( cv->isActive ); + + // Remove from VehicleCentral's ActiveList + VehicleCentral* vc = ::GetVehicleCentral(); + rAssert( vc != NULL ); + + if( cv->husk == NULL ) + { + vc->RemoveVehicleFromActiveList( cv->v ); + GetEventManager()->TriggerEvent( EVENT_CHASE_VEHICLE_DESTROYED, cv->v ); + } + else + { + bool succeeded = vc->RemoveVehicleFromActiveList( cv->husk ); + rAssert( succeeded ); + + ::GetVehicleCentral()->mHuskPool.FreeHusk( cv->husk ); + cv->husk->Release(); // don't verify destruction cuz huskpool has final ownership + cv->husk = NULL; + } + + //if( cv->activeListIndex != -1 ) + //{ + // vc->SetVehicleController( cv->activeListIndex, NULL ); + //} + cv->vAI->SetActive( false ); + cv->vAI->Finalize(); + //cv->activeListIndex = -1; + cv->isActive = false; + cv->isOutOfSight = true; + cv->markedForDeletion = false; + cv->secondsOutOfSight = 0.0f; + mNumObjects--; +} + +bool ChaseManager::ActivateVehicle( ChaseVehicle* cv ) +{ + rAssert( cv->v != NULL ); + rAssert( cv->vAI != NULL ); + //rAssert( cv->activeListIndex == -1 ); + rAssert( !cv->isActive ); + + VehicleCentral* vc = ::GetVehicleCentral(); + rAssert( vc != NULL ); + + // add to vehiclecentral's activelist + if( vc->ActiveVehicleListIsFull() ) + { + return false; + } + int res = vc->AddVehicleToActiveList( cv->v ); + if( res == -1 ) + { + // not supposed to happen since we already eliminated the + // safe failure condition (activelistisfull).. So something else + // went wrong... + rAssert( false ); + } + + // update chase AI + vc->SetVehicleController( res, cv->vAI ); + cv->vAI->Initialize(); + cv->vAI->SetActive( true ); + + // update our variables + //cv->activeListIndex = res; + cv->isActive = true; + rAssert( cv->husk == NULL ); + cv->isOutOfSight = true; + cv->markedForDeletion = false; + cv->secondsOutOfSight = 0.0f; + mNumObjects++; + + // tell sound + GetEventManager()->TriggerEvent( EVENT_CHASE_VEHICLE_SPAWNED, cv->v ); + + return true; +} + +void ChaseManager::DeactivateAllVehicles() +{ + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].isActive ) + { + DeactivateVehicle( &mVehicles[i] ); + } + if( mVehicles[i].v != NULL ) + { + mVehicles[i].v->Release(); + mVehicles[i].v = NULL; + } + if( mVehicles[i].vAI != NULL ) + { + mVehicles[i].vAI->Release(); + mVehicles[i].vAI = NULL; + } + } + rAssert( mNumObjects == 0 ); +} + +void ChaseManager::MarkAllVehiclesForDeletion() +{ + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].v != NULL ) + { + mVehicles[i].markedForDeletion = true; + } + } +} + +ChaseManager::ChaseVehicle* ChaseManager::GetInactiveVehicle() +{ + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( !mVehicles[i].isActive ) + { + return &mVehicles[i]; + } + } + return NULL; +} + +bool ChaseManager::SpawningOnTopOfAnotherVehicle( const rmt::Sphere& s ) +{ + VehicleCentral* vc = ::GetVehicleCentral(); + + int nActiveVehicles = 0; + Vehicle** activeVehicles = NULL; + vc->GetActiveVehicleList( activeVehicles, nActiveVehicles ); + + Vehicle* aCar; + rmt::Vector aPos; + rmt::Sphere aSphere; + + // + // Because VehicleCentral's ActiveList is a SPARSE array, we have to loop + // through the MAX array size. But since we know how many actual vehicles + // there are in there, we can quit after we've reached that number. So keep + // a counter. + // + int vCount = 0; + for( int i=0; i<vc->GetMaxActiveVehicles(); i++ ) + { + if( vCount >= nActiveVehicles ) + { + break; + } + + aCar = activeVehicles[i]; + if( aCar == NULL ) + { + continue; + } + vCount++; + aCar->GetPosition( &aPos ); + aCar->GetBoundingSphere( &aSphere ); + + float distSqr = (aPos - s.centre).MagnitudeSqr(); + float minDist = aSphere.radius + s.radius; + float minDistSqr = minDist * minDist; + + // if we're colliding with another car + if( distSqr < minDistSqr ) + { + return true; + } + } + return false; +} + +float ChaseManager::GetClosestCarPosition(rmt::Vector* toPos, rmt::Vector* carPos) +{ + float distSqr = 999999999.0f; + + for( int i=0; i<MAX_CHASE_VEHICLES; i++ ) + { + if( mVehicles[i].isActive ) + { + if( mVehicles[i].v != NULL ) + { + rmt::Vector currPos; + mVehicles[i].v->GetPosition(&currPos); + float currDistSqr = (*toPos - currPos).MagnitudeSqr(); + if(currDistSqr < distSqr) + { + distSqr = currDistSqr; + *carPos = currPos; + } + } + } + } + return distSqr; +} + diff --git a/game/code/worldsim/harass/chasemanager.h b/game/code/worldsim/harass/chasemanager.h new file mode 100644 index 0000000..50fdc31 --- /dev/null +++ b/game/code/worldsim/harass/chasemanager.h @@ -0,0 +1,188 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: chasemanager.h +// +// Description: ChaseManager Class declaration. +// +// History: 11/5/2002 + Created -- Dusit Eakkachaichanvet +// +//============================================================================= + +#ifndef CHASEMANAGER_H +#define CHASEMANAGER_H + +#include <worldsim/spawn/spawnmanager.h> +#include <events/eventlistener.h> + +class Vehicle; +class VehicleAI; + +static const float CHASE_SPAWN_RADIUS = 100.0f; +static const float CHASE_REMOVE_RADIUS = 110.0f; +static const float SECONDS_BETW_CHASE_ADDS = 0.7f; +static const float SECONDS_BETW_CHASE_REMOVES = 0.5f; +static const float SECONDS_BETW_CHASE_UPDATES = 0.0f; + +class ChaseManager +: public SpawnManager, EventListener +{ +public: + + static const int MAX_CHASE_VEHICLES = 5; + static const int MAX_MODELS = 1; + static const int MAX_STRING_LEN = 64; + + // Dusit [Nov 4,2002]: + // Don't make ChaseManager static. Make it possible for there + // to be 1 ChaseManager to spawn the 1 cSedan that needs to always + // be present, and another ChaseManager for the other set of chase cars. + /////////////////////////////////////////////////////////////// + // Statics + //static ChaseManager* GetInstance(); + //static ChaseManager* DestroyInstance(); + + ChaseManager(); + virtual ~ChaseManager(); + + /////////////////////////////////////////////////////////////// + // Implementing SpawnManager Stuff + void Init(); + void ClearAllObjects(); + void ClearObjectsInsideRadius( rmt::Vector center, float radius ); + void ClearObjectsOutsideRadius( rmt::Vector center, float radius ); + int GetAbsoluteMaxObjects() const; + int GetMaxObjects() const; + void SetMaxObjects( int maxObjects ); + int GetMaxModels() const; + int GetNumRegisteredModels() const; + bool RegisterModel( const char* name, int spawnFreq ); + bool IsModelRegistered( const char* name ); + bool UnregisterModel( const char* name ); + void SetConfileName( const char* name ); + /////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////// + // EventListener + void HandleEvent( EventEnum id, void* pEventData ); + /////////////////////////////////////////////////////////////// + + bool IsChaseVehicle( Vehicle* v ); + void DisableAllActiveVehicleAIs(); + void EnableAllActiveVehicleAIs(); + void DeactivateAllVehicles(); + int GetNumActiveVehicles(); + void MarkAllVehiclesForDeletion(); + + void SuspendAllVehicles(); + void ResumeAllVehicles(); + + float GetClosestCarPosition(rmt::Vector* toPos, rmt::Vector* carPos); + +protected: + /////////////////////////////////////////////////////////////// + // Implementing SpawnManager Stuff + void AddObjects( float seconds ); + void RemoveObjects( float seconds ); + void UpdateObjects( float seconds ); + float GetSecondsBetwAdds() const; + float GetSecondsBetwRemoves() const; + float GetSecondsBetwUpdates() const; + + +private: + + //static ChaseManager* spChaseManager; + + struct Model + { + char name[MAX_STRING_LEN+1]; + int spawnFreq; + }; + Model mModels[MAX_MODELS]; + int mNumRegisteredModels; + int mTotalSpawnFrequencies; + + struct ChaseVehicle + { + Vehicle* v; + Vehicle* husk; + VehicleAI* vAI; + //int activeListIndex; + bool isActive; + bool isOutOfSight; + bool markedForDeletion; + float secondsOutOfSight; + }; + + int mMaxVehicles; // betw 0 and AbsoluteMax + + ChaseVehicle mVehicles[MAX_CHASE_VEHICLES]; + + char mConfileName[MAX_STRING_LEN+1]; + + void ClearOutOfSightVehicles(); + + void DeactivateVehicle( ChaseVehicle* cv ); + bool ActivateVehicle( ChaseVehicle* cv ); + + ChaseVehicle* FindChaseVehicle( Vehicle* v ); + + ChaseVehicle* GetInactiveVehicle(); + bool SpawningOnTopOfAnotherVehicle( const rmt::Sphere& s ); +}; + +////////////////////////////// INLINES ///////////////////////////////// + +// etc, etc, etc... +inline int ChaseManager::GetAbsoluteMaxObjects() const +{ + rAssert( MAX_CHASE_VEHICLES >= 0 ); + return MAX_CHASE_VEHICLES; +} +inline int ChaseManager::GetMaxObjects() const +{ + return mMaxVehicles; +} +inline void ChaseManager::SetMaxObjects( int maxObjects ) +{ + int absoluteMax = GetAbsoluteMaxObjects(); + rAssert( absoluteMax >= 0 ); + + if( maxObjects < 0 ) + { + maxObjects = 0; + } + else if( maxObjects > absoluteMax ) + { + maxObjects = absoluteMax; + } + + mMaxVehicles = maxObjects; +} + +inline int ChaseManager::GetMaxModels() const +{ + return MAX_MODELS; +} +inline int ChaseManager::GetNumRegisteredModels() const +{ + return mNumRegisteredModels; +} +inline float ChaseManager::GetSecondsBetwAdds() const +{ + rAssert( SECONDS_BETW_CHASE_ADDS >= 0.0f ); + return SECONDS_BETW_CHASE_ADDS; +} +inline float ChaseManager::GetSecondsBetwRemoves() const +{ + rAssert( SECONDS_BETW_CHASE_REMOVES >= 0.0f ); + return SECONDS_BETW_CHASE_REMOVES; +} +inline float ChaseManager::GetSecondsBetwUpdates() const +{ + rAssert( SECONDS_BETW_CHASE_UPDATES >= 0.0f ); + return SECONDS_BETW_CHASE_UPDATES; +} + +#endif
\ No newline at end of file diff --git a/game/code/worldsim/hitnrunmanager.cpp b/game/code/worldsim/hitnrunmanager.cpp new file mode 100644 index 0000000..070178b --- /dev/null +++ b/game/code/worldsim/hitnrunmanager.cpp @@ -0,0 +1,1118 @@ +//============================================================================= +// Copyright (C) 2003 Radical Entertainment Ltd. All rights reserved. +// +// File: HitnRunManager.cpp +// +// Description: Implementation of class HitnRunManager +// +// History: 26/03/2003 + Created -- Jesse Cluff +// +//============================================================================= + +//======================================== +// System Includes +//======================================== + +#include <radmath/matrix.hpp> +#include <radmath/random.hpp> + +//#include <math.h> //need this if we want extra accurate coin paths. + +//======================================== +// Project Includes +//======================================== +#include <gameflow/gameflow.h> + +#include <worldsim/hitnrunmanager.h> +#include <worldsim/coins/coinmanager.h> +#include <worldsim/character/character.h> +#include <worldsim/avatarmanager.h> +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/coins/coinmanager.h> +#include <memory/srrmemory.h> +#include <events/eventmanager.h> +#include <render/intersectmanager/intersectmanager.h> +#include <render/rendermanager/rendermanager.h> +#include <render/rendermanager/worldrenderlayer.h> +#include <render/dsg/statepropdsg.h> +#include <data/persistentworldmanager.h> +#include <mission/gameplaymanager.h> +#include <mission/charactersheet/charactersheetmanager.h> +#include <interiors/interiormanager.h> +#include <constants/breakablesenum.h> +#include <constants/vehicleenum.h> +#include <worldsim/harass/chasemanager.h> +#include <presentation\gui\ingame\guimanageringame.h> +#include <presentation\gui\ingame\guiscreenhud.h> +#include <presentation\gui\guisystem.h> +#include <ai\statemanager.h> +#include <contexts/context.h> + +/* Watcher stuff */ +#ifndef RAD_RELEASE +#include <raddebug.hpp> +#include <raddebugwatch.hpp> +#endif +//****************************************************************************** +// +// Global Data, Local Data, Local Classes +// +//****************************************************************************** +static const float MAX_HITNRUN = 100.0f; +static const float MIN_HITNRUN = 0.0f; +static const float FADE_TIME = 750.0f; +static float STOP_HITNRUN = 5.0f; //was 78 +static const float ALLOW_THROB = 60.0f; + +HitnRunManager* HitnRunManager::smpHitnRunManager = NULL; + +static const char* OBJECTS_THAT_NEVER_GIVE_COINS[] = +{ + "l1_hedgeunit", + "l7_hedgeunit" +}; + +static const int NUM_OBJECTS_THAT_NEVER_GIVE_COINS = sizeof(OBJECTS_THAT_NEVER_GIVE_COINS)/sizeof(OBJECTS_THAT_NEVER_GIVE_COINS[0]); + +//****************************************************************************** +// +// Public Member Functions +// +//****************************************************************************** + +HitnRunManager* HitnRunManager::CreateInstance( void ) +{ + rAssertMsg( smpHitnRunManager == NULL, "HitnRunManager already created.\n" ); + HeapManager::GetInstance()->PushHeap( GMA_PERSISTENT ); + smpHitnRunManager = new HitnRunManager; + rAssert( smpHitnRunManager ); + HeapManager::GetInstance()->PopHeap( GMA_PERSISTENT ); + return HitnRunManager::GetInstance(); +} + +HitnRunManager* HitnRunManager::GetInstance( void ) +{ + rAssertMsg( smpHitnRunManager != NULL, "HitnRunManager has not been created yet.\n" ); + return smpHitnRunManager; +} + +void HitnRunManager::DestroyInstance( void ) +{ + rAssertMsg( smpHitnRunManager != NULL, "HitnRunManager has not been created.\n" ); + delete smpHitnRunManager; + smpHitnRunManager = NULL; +} + +//============================================================================== +// Description: Constructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +HitnRunManager::HitnRunManager() : + mCurrHitnRun(0.0f), + mDecayRatePerSecond(1.0f), + mDecayRateWhileSpawning(6.0f), + mDecayRateInsidePerSecond(10.0f), + mVehicleDestroyedDelta(25.0f), + mVehicleDestroyedCoins( 10 ), + mVehicleHitDelta(5.0f), + mHitBreakableDelta(7.5f), + mHitBreakableCoins( 1 ), + mHitMoveableDelta(7.5f), + mHitMoveableCoins( 1 ), + mHitKrustyGlassDelta(0.0f), + mHitKrustyGlassCoins( 5 ), + mColaPropDestroyedDelta(0.0f), + mColaPropDestroyedCoins( 10 ), + mKickNPCDelta(10.0f), + mKickNPCCoins( 0 ), //no coins on peds + mPlayerCarHitNPCDelta(13.0f), + mPlayerCarHitNPCCoins( 0 ), //no coins on peds + mBustedCoins( 50 ), + mSwitchSkinDelta(-100.0f), + mChangeVehicleDelta(-100.0f), + mPrevVehicleID(VehicleEnum::INVALID), +// mPrevVehicleHit(NULL), +// mPrevMoveableHit(NULL), + mLeastRecentlyHit(0), + mChaseOn(false), + mSpawnOn(false), + mDecayDelayMS(0.0f), + mDecayDelay(3000.0f), + mDecayDelayWhileSpawning(0.0f), // was 3000 + mNumChaseCars(1), + mHitnRunDisabled(false), + mDecayDisabled(false), + mCopTicketDistance(10.0f), + mCopTicketDistanceOnFoot(10.0f), + mCopTicketSpeedThreshold(30.0f), + mCopTicketTimeThreshold(0.75f), // was 0.1; trying something to get rid of "bump = busted" + mTicketTimer(0.0f), + mLastUpdateValue(0.0f), + mLastHitnRunValue(0.0f), + mVehicleReset(true), + mVehicleResetTimer(0.0f), + mUnresponsiveTime(1500.0f), + mFadeDone(true), + mFadeTimer(0.0f), + mImmunityVehicle(NULL), + mWarningThreshold(0.78f), + mAllowThrob(true) +{ + for(int i = 0; i < 16; i++) + { + mPrevHits[i] = NULL; + } + // We don't want to do string compares when determining if an object that we just hit + // is to not give a coin + // So build a list of tUIDs in the ctor + mObjectsThatNeverGiveCoins = new tUID[ NUM_OBJECTS_THAT_NEVER_GIVE_COINS ]; + for(int i = 0 ; i < NUM_OBJECTS_THAT_NEVER_GIVE_COINS ; i++ ) + { + mObjectsThatNeverGiveCoins[ i ] = tName::MakeUID( OBJECTS_THAT_NEVER_GIVE_COINS[i] ); + } + +#ifndef RAD_RELEASE + radDbgWatchAddFloat( &mCurrHitnRun, "Current HitnRun Value", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddFloat( &mDecayRatePerSecond, "Decay Rate", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddFloat( &mDecayRateWhileSpawning, "Decay Rate while spawning", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddFloat( &mDecayRateInsidePerSecond, "Interior Decay Rate", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddFloat( &mVehicleDestroyedDelta, "Vehicle Destroyed Delta", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddInt( &mVehicleDestroyedCoins, "Vehicle Destroyed Coins", "HitnRun", NULL, NULL, 0, 100, false ); + radDbgWatchAddFloat( &mHitBreakableDelta, "Hit Breakable Delta", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddFloat( &mVehicleHitDelta, "Vehicle Hit Delta", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddInt( &mHitBreakableCoins, "Hit Breakable Coins", "HitnRun", NULL, NULL, 0, 100, false ); + radDbgWatchAddFloat( &mHitMoveableDelta, "Hit Moveable Delta", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddInt( &mHitMoveableCoins, "Hit Moveable Coins", "HitnRun", NULL, NULL, 0, 100, false ); + radDbgWatchAddFloat( &mHitKrustyGlassDelta, "Hit Krusty Glass Delta", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddInt( &mHitKrustyGlassCoins, "Hit Krusty Glass Coins", "HitnRun", NULL, NULL, 0, 100, false ); + radDbgWatchAddFloat( &mColaPropDestroyedDelta, "Cola Prop Destroyed Delta", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddInt( &mColaPropDestroyedCoins, "Cola Prop Destroyed Coins", "HitnRun", NULL, NULL, 0, 100, false ); + radDbgWatchAddFloat( &mKickNPCDelta, "Kick NPC Delta", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddInt( &mKickNPCCoins, "Kick NPC Coins", "HitnRun", NULL, NULL, 0, 100, false ); + radDbgWatchAddFloat( &mPlayerCarHitNPCDelta, "Vehicle Hit NPC Delta", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddInt( &mPlayerCarHitNPCCoins, "Vehicle Hit NPC Coins", "HitnRun", NULL, NULL, 0, 100, false ); + radDbgWatchAddInt( &mBustedCoins, "Coins lost when busted", "HitnRun", 0, 0, 0, 100, false ); + radDbgWatchAddFloat( &mSwitchSkinDelta, "Switch Skin Delta", "HitnRun", NULL, NULL, 0.0f, -100.0f, false ); //false for read and write + radDbgWatchAddFloat( &mChangeVehicleDelta, "Change Vehicle Delta", "HitnRun", NULL, NULL, 0.0f, -100.0f, false ); //false for read and write + radDbgWatchAddInt( &mNumChaseCars, "Number of Chase Vehicles", "HitnRun", NULL, NULL, 0, 5, false ); + radDbgWatchAddFloat( &mDecayDelay, "Delay until meter decay", "HitnRun", NULL, NULL, 0.0f, 5000.0f, false ); //false for read and write + radDbgWatchAddFloat( &mDecayDelayWhileSpawning, "Decay delay while spawning", "HitnRun", NULL, NULL, 0.0f, 5000.0f, false ); //false for read and write + radDbgWatchAddFloat( &STOP_HITNRUN, "Spawning Stop Percentage", "HitnRun", NULL, NULL, 0.0f, 100.0f, false ); //false for read and write + radDbgWatchAddFloat( &mCopTicketDistance, "Cop Ticket Distance", "HitnRun", NULL, NULL, 0.0f, 20.0f, false ); //false for read and write + radDbgWatchAddFloat( &mCopTicketDistanceOnFoot, "Cop Ticket Distance On Foot", "HitnRun", NULL, NULL, 0.0f, 20.0f, false ); //false for read and write + radDbgWatchAddFloat( &mCopTicketSpeedThreshold, "Cop Ticket Speed Threshold", "HitnRun", NULL, NULL, 0.0f, 30.0f, false ); //false for read and write + radDbgWatchAddFloat( &mCopTicketTimeThreshold, "Cop Ticket Time Threshold", "HitnRun", NULL, NULL, 0.0f, 10.0f, false ); //false for read and write + radDbgWatchAddFloat( &mUnresponsiveTime, "Busted Unresponsive Time", "HitnRun", NULL, NULL, 0.0f, 10000.0f, false ); //false for read and write +#endif +} + +//============================================================================== +// Description: Destructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +HitnRunManager::~HitnRunManager() +{ +#ifndef RAD_RELEASE + radDbgWatchDelete( &mCurrHitnRun ); + radDbgWatchDelete( &mDecayRatePerSecond ); + radDbgWatchDelete( &mDecayRateWhileSpawning ); + radDbgWatchDelete( &mDecayRateInsidePerSecond ); + radDbgWatchDelete( &mVehicleDestroyedDelta ); + radDbgWatchDelete( &mVehicleDestroyedCoins ); + radDbgWatchDelete( &mHitBreakableDelta ); + radDbgWatchDelete( &mVehicleHitDelta ); + radDbgWatchDelete( &mHitBreakableCoins ); + radDbgWatchDelete( &mHitMoveableDelta ); + radDbgWatchDelete( &mHitMoveableCoins ); + radDbgWatchDelete( &mHitKrustyGlassDelta ); + radDbgWatchDelete( &mHitKrustyGlassCoins ); + radDbgWatchDelete( &mColaPropDestroyedDelta ); + radDbgWatchDelete( &mColaPropDestroyedCoins ); + radDbgWatchDelete( &mKickNPCDelta ); + radDbgWatchDelete( &mKickNPCCoins ); + radDbgWatchDelete( &mPlayerCarHitNPCDelta ); + radDbgWatchDelete( &mPlayerCarHitNPCCoins ); + radDbgWatchDelete( &mBustedCoins ); + radDbgWatchDelete( &mSwitchSkinDelta ); + radDbgWatchDelete( &mChangeVehicleDelta ); + radDbgWatchDelete( &mNumChaseCars ); + radDbgWatchDelete( &mDecayDelay ); + radDbgWatchDelete( &mDecayDelayWhileSpawning ); + radDbgWatchDelete( &STOP_HITNRUN ); + radDbgWatchDelete( &mCopTicketDistance ); + radDbgWatchDelete( &mCopTicketDistanceOnFoot ); + radDbgWatchDelete( &mCopTicketSpeedThreshold ); + radDbgWatchDelete( &mCopTicketTimeThreshold ); + radDbgWatchDelete( &mUnresponsiveTime ); +#endif + Destroy( ); + delete [] mObjectsThatNeverGiveCoins; + mObjectsThatNeverGiveCoins = NULL; +} + +//============================================================================== +// Description: Destruction method for all transient data. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +void HitnRunManager::Destroy( void ) +{ + GetEventManager()->RemoveAll( this ); +} + +/*============================================================================== +Description: This will be called set up as the game session begins. + +Parameters: ( void ) + +Return: void + +=============================================================================*/ +void HitnRunManager::Init( void ) +{ + #ifdef RAD_GAMECUBE + HeapManager::GetInstance()->PushHeap( GMA_GC_VMM ); + #else + HeapManager::GetInstance()->PushHeap( GMA_LEVEL_ZONE ); + #endif + + //do some allocations here + + #ifdef RAD_GAMECUBE + HeapManager::GetInstance()->PopHeap( GMA_GC_VMM ); + #else + HeapManager::GetInstance()->PopHeap( GMA_LEVEL_ZONE ); + #endif + + GetEventManager()->AddListener( this, EVENT_VEHICLE_DESTROYED_BY_USER ); + GetEventManager()->AddListener( this, EVENT_VEHICLE_VEHICLE_COLLISION ); + //GetEventManager()->AddListener( this, EVENT_HIT_BREAKABLE ); //currently get multiple events - don't use + GetEventManager()->AddListener( this, EVENT_HIT_MOVEABLE ); + GetEventManager()->AddListener( this, EVENT_OBJECT_KICKED ); + GetEventManager()->AddListener( this, EVENT_COLAPROP_DESTROYED ); //event waiting for mike r + GetEventManager()->AddListener( this, EVENT_KICK_NPC ); //add trigger - complicated + GetEventManager()->AddListener( this, EVENT_PLAYER_CAR_HIT_NPC ); + + GetEventManager()->AddListener( this, EVENT_SWITCH_SKIN ); + //GetEventManager()->AddListener( this, EVENT_GETINTOVEHICLE_END ); //doesn't work for traffic cars + GetEventManager()->AddListener( this, EVENT_ENTERING_PLAYER_CAR ); + GetEventManager()->AddListener( this, EVENT_ENTERING_TRAFFIC_CAR ); + GetEventManager()->AddListener( this, EVENT_LEVEL_START ); + + GetEventManager()->AddListener( this, EVENT_HIT_AND_RUN_CAUGHT ); + GetEventManager()->AddListener( this, EVENT_CHASE_VEHICLE_SPAWNED ); + + GetEventManager()->AddListener( this, EVENT_MISSION_START ); + GetEventManager()->AddListener( this, EVENT_MISSION_SUCCESS ); + GetEventManager()->AddListener( this, EVENT_MISSION_FAILURE ); + GetEventManager()->AddListener( this, EVENT_MISSION_RESET ); + + GetEventManager()->AddListener( this, EVENT_ENTER_INTERIOR_START ); + GetEventManager()->AddListener( this, EVENT_ENTER_INTERIOR_END ); + GetEventManager()->AddListener( this, EVENT_EXIT_INTERIOR_START ); + GetEventManager()->AddListener( this, EVENT_EXIT_INTERIOR_END ); + GetEventManager()->AddListener( this, EVENT_CONVERSATION_START ); + GetEventManager()->AddListener( this, EVENT_CONVERSATION_DONE ); + GetEventManager()->AddListener( this, EVENT_CONVERSATION_SKIP ); +} + +void HitnRunManager::ResetState( void ) +{ + mCurrHitnRun = MIN_HITNRUN; + mSpawnOn = false; + mChaseOn = false; + mDecayDisabled = false; + mHitnRunDisabled = false; + mAllowThrob = true; + mVehicleResetTimer = 0.0f; + mFadeTimer = 0.0f; + mFadeDone = true; + mVehicleReset = true; + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + //chaseManager->DisableAllActiveVehicleAIs(); + //chaseManager->MarkAllVehiclesForDeletion(); + chaseManager->ClearAllObjects(); + } + } +} + +void HitnRunManager::HandleEvent( EventEnum id, void* pEventData ) +{ + switch ( id ) + { + case EVENT_ENTER_INTERIOR_START: + case EVENT_EXIT_INTERIOR_START: + case EVENT_CONVERSATION_START: + { + mHitnRunDisabled = true; + + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + chaseManager->DisableAllActiveVehicleAIs(); + chaseManager->SuspendAllVehicles(); + } + } + } + break; + case EVENT_ENTER_INTERIOR_END: + case EVENT_EXIT_INTERIOR_END: + case EVENT_CONVERSATION_DONE: + case EVENT_CONVERSATION_SKIP: + { + mHitnRunDisabled = false; + + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + chaseManager->EnableAllActiveVehicleAIs(); + chaseManager->ResumeAllVehicles(); + } + } + } + break; + default: + break; + } + + if(!mHitnRunDisabled && !GetInteriorManager()->IsInside()) + { + float decayDelay = mDecayDelay; + if(mSpawnOn) + { + decayDelay = mDecayDelayWhileSpawning; + } + + switch ( id ) + { + case EVENT_VEHICLE_DESTROYED_BY_USER: + { + Vehicle* vehicle = static_cast<Vehicle*>( pEventData ); + bool isChaseVehicle = false; + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + if(vehicle->mName) + { + isChaseVehicle = chaseManager->IsModelRegistered(vehicle->mName); + } + } + } + if( !isChaseVehicle && ( vehicle->mVehicleType == VT_TRAFFIC || vehicle->mVehicleType == VT_AI ) && vehicle != mImmunityVehicle) + { + mCurrHitnRun += mVehicleDestroyedDelta; + mDecayDelayMS = decayDelay; + GetCoinManager()->SpawnCoins( mVehicleDestroyedCoins, vehicle->GetPosition(), vehicle->GetGroundY() ); + } + } + break; + case EVENT_VEHICLE_VEHICLE_COLLISION: + { + CarOnCarCollisionEventData* data = static_cast<CarOnCarCollisionEventData*>( pEventData ); + Vehicle* vehicle = data->vehicle; + bool isChaseVehicle = false; + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + if(vehicle->mName) + { + isChaseVehicle = chaseManager->IsModelRegistered(vehicle->mName); + } + } + } + if( !isChaseVehicle && ( vehicle->mVehicleType == VT_TRAFFIC || vehicle->mVehicleType == VT_AI ) && vehicle != mImmunityVehicle) + { + if(mDecayDelayMS <= decayDelay/2) + { + mCurrHitnRun += mVehicleHitDelta; + mDecayDelayMS = decayDelay; + } + } + } + break; + case EVENT_HIT_MOVEABLE: + //case EVENT_HIT_BREAKABLE: + { + //currently this assumes hitting a breakable breaks it ;) + sim::SimState* simState = static_cast<sim::SimState*>(pEventData); + //if(simState && simState->GetControl() == sim::simAICtrl) + if(simState) + { + sim::SimControlEnum con = simState->GetControl(); + if(con == sim::simAICtrl) + { + CollisionEntityDSG* cedsg = static_cast< CollisionEntityDSG*>(simState->mAIRefPointer); + CollisionAttributes* collAttribs = cedsg->GetCollisionAttributes(); + if(collAttribs) + { + if(HasntBeenHit((void*)cedsg)) + { + BreakablesEnum::BreakableID id = collAttribs->GetBreakable(); + if(id == BreakablesEnum::eKrustyGlassBreaking) + { + mCurrHitnRun += mHitKrustyGlassDelta; + GetCoinManager()->SpawnCoins( mHitKrustyGlassCoins, simState->GetPosition() ); + } + else if(id == BreakablesEnum::eNull) + { + // Lets check if this thing that we impacted is actually allowed to give + // coins. (e.g. hedges are stateprops that unlike most + // others, should NEVER give coins and are thus in this list + if ( DoesObjectGiveCoins( cedsg ) ) + { + mCurrHitnRun += mHitMoveableDelta; + GetCoinManager()->SpawnCoins( mHitMoveableCoins, simState->GetPosition() ); + } + } + else + { + mCurrHitnRun += mHitBreakableDelta; + GetCoinManager()->SpawnCoins( mHitBreakableCoins, simState->GetPosition() ); + } + RegisterHit((void*)cedsg); + mDecayDelayMS = decayDelay; + } + } + } + } + } + break; + case EVENT_OBJECT_KICKED: + { + CollisionEntityDSG* cedsg = static_cast< CollisionEntityDSG*>(pEventData); + CollisionAttributes* collAttribs = cedsg->GetCollisionAttributes(); + if(collAttribs) + { + if(HasntBeenHit((void*)cedsg)) + { + + StatePropDSG* spdsg = dynamic_cast<StatePropDSG*>(cedsg); + if(spdsg) + { + if(spdsg->GetStatePropUID() == tEntity::MakeUID("waspray")) + { + break; + } + } + + rmt::Vector pos; + cedsg->GetPosition(&pos); + + BreakablesEnum::BreakableID id = collAttribs->GetBreakable(); + if(id == BreakablesEnum::eKrustyGlassBreaking) + { + mCurrHitnRun += mHitKrustyGlassDelta; + GetCoinManager()->SpawnCoins( mHitKrustyGlassCoins, pos ); + } + else if(id == BreakablesEnum::eNull) + { + mCurrHitnRun += mHitMoveableDelta; + GetCoinManager()->SpawnCoins( mHitMoveableCoins, pos ); + } + else + { + mCurrHitnRun += mHitBreakableDelta; + GetCoinManager()->SpawnCoins( mHitBreakableCoins, pos ); + } + RegisterHit((void*)cedsg); + mDecayDelayMS = decayDelay; + } + } + } + break; + case EVENT_COLAPROP_DESTROYED: + { + //handles cola state props being destroyed (krustyglass is handled as a breakable) + mCurrHitnRun += mColaPropDestroyedDelta; + mDecayDelayMS = decayDelay; + StatePropDSG* prop = static_cast<StatePropDSG*>( pEventData ); + GetCoinManager()->SpawnCoins( mColaPropDestroyedCoins, prop->rPosition() ); + } + break; + case EVENT_KICK_NPC: + { + Character* ch = static_cast<Character*>(pEventData); + mCurrHitnRun += mKickNPCDelta; + mDecayDelayMS = decayDelay; + if( !ch->HasBeenHit() ) + { + rmt::Vector pos; + ch->GetPosition( pos ); + GetCoinManager()->SpawnCoins( mKickNPCCoins, pos ); + } + } + break; + case EVENT_PLAYER_CAR_HIT_NPC: + { + Character* ch = static_cast<Character*>(pEventData); + if(ch->IsNPC()) + { + // TODO: + // Make sure this is correct by verifying with Jesse. + // We don't use NPCController::FLAILING state anymore.. + // Need a diff mechanism + if( ch->GetStateManager()->GetState() != CharacterAi::INSIM ) + { + mCurrHitnRun += mPlayerCarHitNPCDelta; + mDecayDelayMS = decayDelay; + } + /* + NPCharacter* npc = static_cast<NPCharacter*>(ch); + NPCController* npcCont = static_cast<NPCController*>(npc->GetController()); + if(npcCont->GetState() != NPCController::FLAILING) + { + mCurrHitnRun += mPlayerCarHitNPCDelta; + mDecayDelayMS = decayDelay; + } + */ + } + if( !ch->HasBeenHit() ) + { + rmt::Vector pos; + ch->GetPosition( pos ); + GetCoinManager()->SpawnCoins( mPlayerCarHitNPCCoins, pos ); + } + } + break; + case EVENT_SWITCH_SKIN: + { + //switching to same skin not handled currently + mCurrHitnRun += mSwitchSkinDelta; + mDecayDelayMS = decayDelay; + } + break; + //case EVENT_GETINTOVEHICLE_END: + case EVENT_ENTERING_PLAYER_CAR: + case EVENT_ENTERING_TRAFFIC_CAR: + { + //have to track vehicle type to prevent getting in and out of same car + //Character* character = static_cast<Character*>(pEventData); + //Vehicle* vehicle = character->GetTargetVehicle(); + Vehicle* vehicle = static_cast<Vehicle*>(pEventData); + if(mPrevVehicleID != vehicle->mVehicleID) + { + mCurrHitnRun += mChangeVehicleDelta; + mDecayDelayMS = decayDelay; + } + mPrevVehicleID = vehicle->mVehicleID; + } + break; + case EVENT_HIT_AND_RUN_CAUGHT: //triggered via collision system + { + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL && mChaseOn ) + { + chaseManager->DisableAllActiveVehicleAIs(); + chaseManager->MarkAllVehiclesForDeletion(); + mCurrHitnRun = MIN_HITNRUN; + mSpawnOn = false; + mChaseOn = false; + GetCoinManager()->LoseCoins( mBustedCoins ); + + Avatar* player = GetAvatarManager()->GetAvatarForPlayer(0); + rAssert( player ); + Character* ch = player->GetCharacter(); + + mVehicleResetTimer = mUnresponsiveTime; + mVehicleReset = false; + + if(ch->GetStateManager()->GetState() == CharacterAi::INCAR) + { + Vehicle* playerVehicle = player->GetVehicle(); + //make car unresponsive + playerVehicle->SetDisableGasAndBrake(true); + } + } + } + } + break; + case EVENT_CHASE_VEHICLE_SPAWNED: + { + mChaseOn = true; + } + break; + default: + { + } + break; + } + } + + switch ( id ) //do a second switch because these events are handled whether h&r is disabled or not + { + //case EVENT_MISSION_START: + // { + // mLastHitnRunValue = mCurrHitnRun; + // } + // break; + //case EVENT_MISSION_RESET: //commented out because it happens all the time before EVENT_MISSION_START + // { + // if(mLastHitnRunValue < mCurrHitnRun) + // { + // mCurrHitnRun = mLastHitnRunValue; + // } + // mSpawnOn = false; + // mChaseOn = false; + // } + // break; + case EVENT_MISSION_START: + { + mCurrHitnRun = MIN_HITNRUN; + mSpawnOn = false; + mChaseOn = false; + mAllowThrob = true; + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + chaseManager->DisableAllActiveVehicleAIs(); + chaseManager->MarkAllVehiclesForDeletion(); + } + } + } + break; + case EVENT_MISSION_SUCCESS: + { + mCurrHitnRun = MIN_HITNRUN; + mSpawnOn = false; + mChaseOn = false; + mDecayDisabled = false; + mHitnRunDisabled = false; + mAllowThrob = true; + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + chaseManager->DisableAllActiveVehicleAIs(); + chaseManager->MarkAllVehiclesForDeletion(); + } + } + } + break; + //case EVENT_MISSION_FAILURE: + // { + // //if(mLastHitnRunValue < mCurrHitnRun) + // //{ + // // mCurrHitnRun = mLastHitnRunValue; + // //} + // mCurrHitnRun = MIN_HITNRUN; + // mDecayDisabled = false; + // mHitnRunDisabled = false; + // mSpawnOn = false; + // mChaseOn = false; + // } + // break; + case EVENT_GUI_FADE_OUT_DONE: + { + mFadeTimer = FADE_TIME; + mFadeDone = false; + GetEventManager()->RemoveListener( this, EVENT_GUI_FADE_OUT_DONE ); + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + chaseManager->ClearAllObjects(); + } + } + } + break; + default: + { + } + break; + + } + if(mCurrHitnRun < MIN_HITNRUN) + { + mCurrHitnRun = MIN_HITNRUN; + } + else if(mCurrHitnRun > MAX_HITNRUN) + { + mCurrHitnRun = MAX_HITNRUN; + } + +} + + + + + +/*============================================================================= +Update the position/animation of all the coins. +=============================================================================*/ +void HitnRunManager::Update( int elapsedMS ) +{ + if(mHitnRunDisabled) + return; + + if(mCurrHitnRun < MAX_HITNRUN * (STOP_HITNRUN/100.0f)) + { + //do chase stuff + mSpawnOn = false; + } + + if(mCurrHitnRun < ALLOW_THROB) + { + mAllowThrob = true; + } + else if(mAllowThrob && (mCurrHitnRun > (mWarningThreshold * 100.0f))) + { + mAllowThrob = false; + GetEventManager()->TriggerEvent( EVENT_HIT_AND_RUN_METER_THROB, NULL ); + } + + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + if(!mSpawnOn) + { + if(mCurrHitnRun >= MAX_HITNRUN) + { + //do chase stuff + mSpawnOn = true; + chaseManager->SetMaxObjects(mNumChaseCars); + chaseManager->SetActive(true); + chaseManager->EnableAdd( true ); + chaseManager->EnableRemove( true ); + chaseManager->EnableUpdate( true ); + GetEventManager()->TriggerEvent( EVENT_HIT_AND_RUN_START, NULL ); + } + else + { + chaseManager->SetMaxObjects(0); //!!!!!!!!!!!!!!!!!!!!!!!!!!!every frame! + //chaseManager->SetActive( true ); + chaseManager->EnableAdd( false ); + chaseManager->EnableRemove( true ); + chaseManager->EnableUpdate( true ); + } + } + + if(mChaseOn) + { + int cars = chaseManager->GetNumActiveVehicles(); + if(cars <= 0) + { + if(!mSpawnOn) + { + mChaseOn = false; + GetEventManager()->TriggerEvent( EVENT_HIT_AND_RUN_EVADED, NULL ); + } + } + else + { + //If the player's vehicle speed is < CopTicketSpeedThreshold FOR CopTicketTimeThreshold seconds + //WHILE the distance from the nearest cop is < CopTicketDistance, you get ticketed + + Avatar* player = GetAvatarManager()->GetAvatarForPlayer(0); + rAssert( player ); + + Vehicle* playerVehicle = player->GetVehicle(); + if( playerVehicle != NULL ) + { + float currSpeed = playerVehicle->mSpeedKmh; + float copTicketDistSqr = mCopTicketDistance * mCopTicketDistance; + + // figure rough distance to player + rmt::Vector copPos, playerPos; + playerVehicle->GetPosition( &playerPos ); + float distToPlayerSqr = chaseManager->GetClosestCarPosition( &playerPos, &copPos ); + + if( currSpeed < mCopTicketSpeedThreshold && distToPlayerSqr < copTicketDistSqr) + { + mTicketTimer += elapsedMS; + } + else + { + mTicketTimer = 0.0f; + } + //rTunePrintf("dist: %f, timer: %f\n", distToPlayerSqr, mTicketTimer); + + if( mTicketTimer >= (mCopTicketTimeThreshold*1000.0f)) //convert from seconds to MS + { + GetEventManager()->TriggerEvent( EVENT_HIT_AND_RUN_CAUGHT, NULL ); + mTicketTimer = 0.0f; + } + } + else + { + float copTicketDistSqr = (mCopTicketDistanceOnFoot) * (mCopTicketDistanceOnFoot); + + // figure rough distance to player + rmt::Vector copPos, playerPos; + player->GetPosition( playerPos ); + float distToPlayerSqr = chaseManager->GetClosestCarPosition( &playerPos, &copPos ); + + if( distToPlayerSqr < copTicketDistSqr) + { + mTicketTimer += elapsedMS; + } + else + { + mTicketTimer = 0.0f; + } + //rTunePrintf("dist: %f, timer: %f\n", distToPlayerSqr, mTicketTimer); + + if( mTicketTimer >= (mCopTicketTimeThreshold*1000.0f*2)) //convert from seconds to MS + { + GetEventManager()->TriggerEvent( EVENT_HIT_AND_RUN_CAUGHT, NULL ); + mTicketTimer = 0.0f; + } + + } + } + } + else //if(!mChaseOn) + { + mTicketTimer = 0.0f; + } + } + } + + if( fabsf(mLastUpdateValue - mCurrHitnRun) > 0.5f) + { + //update hud (before decreasing value) + CGuiScreenHud* hud = GetCurrentHud(); + if(hud) + { + hud->SetHitAndRunMeter( mCurrHitnRun / MAX_HITNRUN ); + mLastUpdateValue = mCurrHitnRun; + } + } + + if(mDecayDelayMS <= 0.0f) + { + if( !mDecayDisabled ) + { + if(mCurrHitnRun > MIN_HITNRUN) + { + float decayRatePerSecond = mDecayRatePerSecond; + + if( GetInteriorManager()->IsInside() ) + { + decayRatePerSecond = mDecayRateInsidePerSecond; + } + else if(mSpawnOn) + { + decayRatePerSecond = mDecayRateWhileSpawning; + } + + + float delta = decayRatePerSecond * elapsedMS * (-1.0f / 1000.0f); + mCurrHitnRun += delta; + if(mCurrHitnRun <= MIN_HITNRUN) + { + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + if(chaseManager->GetNumActiveVehicles() > 0) + { + chaseManager->MarkAllVehiclesForDeletion(); + } + } + } + mCurrHitnRun = MIN_HITNRUN; + } + } + } + } + else + { + mDecayDelayMS -= elapsedMS; + } + + if(!mVehicleReset) + { + if(mVehicleResetTimer <= 0.0f) + { + //reset vehicle + mVehicleReset = true; + + Avatar* player = GetAvatarManager()->GetAvatarForPlayer(0); + rAssert( player ); + + if( player->IsInCar() ) + { + Vehicle* playerVehicle = player->GetVehicle(); + playerVehicle->SetDisableGasAndBrake(false); + GetGuiSystem()->HandleMessage( GUI_MSG_MANUAL_RESET, reinterpret_cast< unsigned int >(playerVehicle) ); + GameplayManager* gameplayManager = GetGameplayManager(); + if ( gameplayManager != NULL ) + { + ChaseManager* chaseManager = gameplayManager->GetChaseManager(0); + if ( chaseManager != NULL ) + { + chaseManager->ClearAllObjects(); + } + } + } + else + { + GetEventManager()->AddListener( this, EVENT_GUI_FADE_OUT_DONE ); + GetGameFlow()->GetContext( CONTEXT_GAMEPLAY )->Suspend(); + GetGuiSystem()->HandleMessage( GUI_MSG_INGAME_FADE_OUT ); + } + + } + else + { + mVehicleResetTimer -= elapsedMS; + } + } + + if(!mFadeDone) + { + if(mFadeTimer <= 0.0f) + { + //reset vehicle + mFadeDone = true; + GetGuiSystem()->HandleMessage( GUI_MSG_INGAME_FADE_IN ); + GetGameFlow()->GetContext( CONTEXT_GAMEPLAY )->Resume(); + } + else + { + mFadeTimer -= elapsedMS; + } + } + +} + +float HitnRunManager::GetHitnRunValue() const +{ + return mCurrHitnRun; +} + +/*============================================================================= +Modify the player's Hit and Run meter +=============================================================================*/ +void HitnRunManager::AdjustHitnRunValue( float delta ) +{ + mCurrHitnRun += delta; + if(mSpawnOn) + { + mDecayDelayMS = mDecayDelayWhileSpawning; + } + else + { + mDecayDelayMS = mDecayDelay; + } + + if(mCurrHitnRun < MIN_HITNRUN) + { + mCurrHitnRun = MIN_HITNRUN; + } + else if(mCurrHitnRun > MAX_HITNRUN) + { + mCurrHitnRun = MAX_HITNRUN; + } + +} + + +/*============================================================================= +Force the player's Hit and Run meter to a specific value +=============================================================================*/ +void HitnRunManager::SetHitnRunValue( float value ) +{ + rAssert(value <= MAX_HITNRUN && value >= MIN_HITNRUN); + mCurrHitnRun = value; +} + +void HitnRunManager::MaxHitnRunValue() +{ + mCurrHitnRun = MAX_HITNRUN; +} + +bool HitnRunManager::HasntBeenHit( void* ptr ) +{ + for(int i = 0; i < 16; i++) + { + if( mPrevHits[i] == ptr ) + { + return false; + } + } + return true; +} + +void HitnRunManager::RegisterHit( void* ptr ) +{ + mPrevHits[mLeastRecentlyHit] = ptr; + mLeastRecentlyHit++; + if(mLeastRecentlyHit >= 16) + { + mLeastRecentlyHit = 0; + } +} + + +bool HitnRunManager::DoesObjectGiveCoins( CollisionEntityDSG* cedsg ) +{ + bool givesCoins = true; + + // We want to get the type name, not the instance name. Get the type name from a + // stateprop from its embedded CStateProp object + if ( cedsg->GetAIRef() == PhysicsAIRef::StateProp ) + { + const StatePropDSG* spdsg = static_cast< const StatePropDSG* >( cedsg ); + rAssert( dynamic_cast< StatePropDSG* >( cedsg ) != NULL ); + tUID typeName = spdsg->GetStatePropUID(); + for ( int i = 0 ; i < NUM_OBJECTS_THAT_NEVER_GIVE_COINS ; i++ ) + { + if ( typeName == mObjectsThatNeverGiveCoins[i] ) + { + // Its a match, this object does not give coins + givesCoins = false; + break; + } + } + } + return givesCoins; +} + +void HitnRunManager::RegisterVehicleImmunity( Vehicle* v ) +{ + mImmunityVehicle = v; +} diff --git a/game/code/worldsim/hitnrunmanager.h b/game/code/worldsim/hitnrunmanager.h new file mode 100644 index 0000000..3fa4b5c --- /dev/null +++ b/game/code/worldsim/hitnrunmanager.h @@ -0,0 +1,150 @@ +//============================================================================= +// Copyright (C) 2003 Radical Entertainment Ltd. All rights reserved. +// +// File: hitnrunmanager.h +// +// Description: Hit & Run manager looks after everything to do with the hit & run system +// +// History: 26/03/2003 + Created -- Jesse Cluff +// +//============================================================================= + +#ifndef HITNRUNMANAGER_H +#define HITNRUNMANAGER_H + +//======================================== +// Nested Includes +//======================================== +#include <events/eventlistener.h> +#include <constants/breakablesenum.h> +#include <constants/vehicleenum.h> + +//======================================== +// Forward References +//======================================== +class Vehicle; +class CollisionEntityDSG; + +//============================================================================= +// +// Synopsis: Handling events that cause the hit & run value to change and spawning +// chase vehicles when value exceeds a given threshold +// +//============================================================================= +class HitnRunManager : public EventListener +{ +public: + static HitnRunManager* GetInstance( void ); + static HitnRunManager* CreateInstance( void ); + static void DestroyInstance( void ); + + void Init( void ); + void Destroy( void ); + virtual void HandleEvent( EventEnum id, void* pEventData ); + + void Update( int elapsedMS ); // spawns chase vehicles if hit & run value is too high. + void HUDRender( void ); // Renders HUD data. + + float GetHitnRunValue( void ) const; // What is the current Hit & Run value. + void AdjustHitnRunValue( float delta ); // Adjust the Hit & Run value. + void SetHitnRunValue( float value ); + void MaxHitnRunValue(); //sets hitnrun meter to maximum value + + void DisableMeterDecay() { mDecayDisabled = true; } + void EnableMeterDecay() { mDecayDisabled = false ; } + + bool IsHitnRunActive(){ return mChaseOn; } + bool IsHitnRunDisabled(){ return mHitnRunDisabled; } + + void ResetState(); + + void EnableHitnRun() { ResetState(); mHitnRunDisabled = false; } + void DisableHitnRun() { ResetState(); mHitnRunDisabled = true; } + + void SetNumChaseCars( int num ) { mNumChaseCars = num; } + void SetDecayRate( float rate ) { mDecayRatePerSecond = rate; } + float GetDecayRate() {return mDecayRatePerSecond;} + void SetDecayRateInside( float rate ) { mDecayRateInsidePerSecond = rate; } + float GetDecayRateInside() {return mDecayRateInsidePerSecond;} + + bool IsWaitingForReset() { return !mVehicleReset; } + + bool HasntBeenHit( void* ptr ); + void RegisterHit( void* ptr ); + + void RegisterVehicleImmunity( Vehicle* v ); + + float GetWarningThreshold() { return mWarningThreshold; } + + bool BustingPlayer() { return ( mVehicleResetTimer > 0.0f || mFadeTimer > 0.0f ); } + +protected: + //Prevent wasteful constructor creation. + HitnRunManager( const HitnRunManager& That ); + HitnRunManager& operator=( const HitnRunManager& That ); + + static HitnRunManager* smpHitnRunManager; + +private: + HitnRunManager(); + ~HitnRunManager(); + + float mCurrHitnRun; + float mDecayRatePerSecond; + float mDecayRateWhileSpawning; + float mDecayRateInsidePerSecond; + float mVehicleDestroyedDelta; + int mVehicleDestroyedCoins; + float mVehicleHitDelta; + float mHitBreakableDelta; + int mHitBreakableCoins; + float mHitMoveableDelta; + int mHitMoveableCoins; + float mHitKrustyGlassDelta; + int mHitKrustyGlassCoins; + float mColaPropDestroyedDelta; + int mColaPropDestroyedCoins; + float mKickNPCDelta; + int mKickNPCCoins; + float mPlayerCarHitNPCDelta; + int mPlayerCarHitNPCCoins; + int mBustedCoins; + float mSwitchSkinDelta; + float mChangeVehicleDelta; + VehicleEnum::VehicleID mPrevVehicleID; + //Vehicle* mPrevVehicleHit; + //CollisionEntityDSG* mPrevMoveableHit; + void* mPrevHits[16]; + int mLeastRecentlyHit; + bool mChaseOn; + bool mSpawnOn; + float mDecayDelayMS; + float mDecayDelay; + float mDecayDelayWhileSpawning; + int mNumChaseCars; + bool mHitnRunDisabled; + bool mDecayDisabled; + float mCopTicketDistance; + float mCopTicketDistanceOnFoot; + float mCopTicketSpeedThreshold; + float mCopTicketTimeThreshold; + float mTicketTimer; + float mLastUpdateValue; + float mLastHitnRunValue; + bool mVehicleReset; + float mVehicleResetTimer; + float mUnresponsiveTime; + bool mFadeDone; + float mFadeTimer; + + tUID* mObjectsThatNeverGiveCoins; + bool DoesObjectGiveCoins( CollisionEntityDSG* ); //const + + Vehicle* mImmunityVehicle; + float mWarningThreshold; + bool mAllowThrob; +}; + +inline HitnRunManager* GetHitnRunManager() { return( HitnRunManager::GetInstance() ); } + +#endif //HITNRUNMANAGER_H diff --git a/game/code/worldsim/huskpool.cpp b/game/code/worldsim/huskpool.cpp new file mode 100644 index 0000000..4a7cdd1 --- /dev/null +++ b/game/code/worldsim/huskpool.cpp @@ -0,0 +1,309 @@ +//============================================================================= +// Copyright (C) 2003 Radical Entertainment Ltd. All rights reserved. +// +// File: huskpool.cpp +// +// Description: +// +// History: Mar 27, 2003 - created, gmayer +// +//============================================================================= + + +#include <worldsim/vehiclecentral.h> +#include <worldsim/redbrick/geometryvehicle.h> +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/huskpool.h> +#include <worldsim/redbrick/trafficlocomotion.h> + +#include <memory/srrmemory.h> + +#include <mission/gameplaymanager.h> + + +//============================================================================= +// HuskPool::HuskPool +//============================================================================= +// Description: Comment +// +// Parameters: (int num) +// +// Return: HuskPool +// +//============================================================================= +HuskPool::HuskPool() +{ + mTotalNum = 0; +} + + +//============================================================================= +// ::~HuskPool +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: HuskPool +// +//============================================================================= +HuskPool::~HuskPool() +{ + +} + + +//============================================================================= +// HuskPool::Init +//============================================================================= +// Description: Comment +// +// this has to be called from GameplayContext and DemoContext OnStart + +// +// Parameters: (int num) +// +// Return: void +// +//============================================================================= +void HuskPool::Init(int num) +{ + +MEMTRACK_PUSH_GROUP( "HuskPool Init" ); + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + HeapMgr()->PushHeap (gma); + + mTotalNum = num; + + mHuskArray = new HuskData[num]; + + for( int i=0; i<num; i++) + { + // initially make all husks VT_TRAFFIC + // set the husk type to whatever the vehicle that just got destroyed is... + + Vehicle* huskVehicle = GetVehicleCentral()->InitVehicle("huskA", false, 0, VT_TRAFFIC); + rAssert( huskVehicle ); + + huskVehicle->AddRef(); + huskVehicle->SetLocomotion( VL_PHYSICS ); +#ifdef DEBUGWATCH + // HACK: + // Automatically take it out of vehicleAIRender, so we don't + // clutter up the render slots + huskVehicle->mTrafficLocomotion->GetAI()->UnregisterAI(); +#endif + + mHuskArray[i].huskVehicle = huskVehicle; + mHuskArray[i].originalVehicle = NULL; + mHuskArray[i].inUse = false; + } + HeapMgr()->PopHeap (gma); + +MEMTRACK_PUSH_GROUP( "HuskPool Init" ); + +} + + +//============================================================================= +// HuskPool::Empty +//============================================================================= +// Description: Comment +// +// +// called from PauseContext and DemoContext OnStop +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void HuskPool::Empty() +{ + // called when you're leaving the level + int i; + for(i = 0; i < mTotalNum; i++) + { + // TODO: + // Should do this just in case? + //GetVehicleCentral()->RemoveVehicleFromActiveList( mHuskArray[i].huskVehicle ); + + mHuskArray[i].huskVehicle->ReleaseVerified(); + mHuskArray[i].huskVehicle = NULL; + + if( mHuskArray[i].originalVehicle ) + { + mHuskArray[i].originalVehicle->Release(); + mHuskArray[i].originalVehicle = NULL; + } + } + + mTotalNum = 0; + delete[] mHuskArray; +} + + +//============================================================================= +// HuskPool::RequestHusk +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: Vehicle +// +//============================================================================= +Vehicle* HuskPool::RequestHusk( VehicleType vt, Vehicle* originalVehicle ) +{ + rAssert( originalVehicle ); + if( originalVehicle == NULL ) + { + return NULL; + } + + if( !WillMakeConvincingHusk( originalVehicle ) ) + { + return NULL; + } + + for( int i = 0; i < mTotalNum; i++ ) + { + if(mHuskArray[i].inUse == false) + { + // give 'em this one + mHuskArray[i].inUse = true; + mHuskArray[i].huskVehicle->mVehicleType = vt; // I'm pretty sure this is safe to set on the fly + mHuskArray[i].huskVehicle->mGeometryVehicle->SetFadeAlpha( 255 ); // restore vehicle fade alpha + + // store away the original vehicle + rAssert( mHuskArray[i].originalVehicle == NULL ); + mHuskArray[i].originalVehicle = originalVehicle; + mHuskArray[i].originalVehicle->AddRef(); + + return mHuskArray[i].huskVehicle; + } + } + + //rReleaseAssertMsg(0, "Not enough husks to go around!"); + return NULL; +} + + +//============================================================================= +// HuskPool::FreeHusk +//============================================================================= +// Description: Comment +// +// Parameters: (Vehicle* husk) +// +// Return: void +// +//============================================================================= +void HuskPool::FreeHusk(Vehicle* husk) +{ + if( husk == NULL ) + { + return; + } + + for( int i = 0; i < mTotalNum; i++) + { + if( mHuskArray[i].huskVehicle == husk && + mHuskArray[i].inUse ) + { + mHuskArray[i].inUse = false; + + rAssert( mHuskArray[i].originalVehicle ); + mHuskArray[i].originalVehicle->Release(); + mHuskArray[i].originalVehicle = NULL; + + return; + } + } +} + +bool HuskPool::IsHuskType( VehicleEnum::VehicleID id ) +{ + // NOTE: + // If we start using other Husk models other than "huskA", + // we add the ID checks here... + if( id == VehicleEnum::HUSKA ) + { + return true; + } + return false; +} + + +Vehicle* HuskPool::FindOriginalVehicleGivenHusk( Vehicle* husk ) +{ + if( husk == NULL ) + { + return NULL; + } + + for( int i = 0; i < mTotalNum; i++) + { + if(mHuskArray[i].huskVehicle == husk) + { + rAssert( mHuskArray[i].inUse ); + rAssert( mHuskArray[i].originalVehicle ); + return mHuskArray[i].originalVehicle; + } + } + + return NULL; +} + +Vehicle* HuskPool::FindHuskGivenOriginalVehicle( Vehicle* v ) +{ + if( v == NULL ) + { + return NULL; + } + + for( int i = 0; i < mTotalNum; i++) + { + if(mHuskArray[i].originalVehicle == v) + { + rAssert( mHuskArray[i].inUse ); + rAssert( mHuskArray[i].huskVehicle ); + return mHuskArray[i].huskVehicle; + } + } + + return NULL; + +} + + +bool HuskPool::WillMakeConvincingHusk( Vehicle* origV ) +{ + // these cars don't look anything remotely like + // HuskA, so we blow them up and leave nothing behind + + if( origV->mVehicleID == VehicleEnum::HONOR_V || + origV->mVehicleID == VehicleEnum::SCHOOLBU || + origV->mVehicleID == VehicleEnum::WILLI_V || + origV->mVehicleID == VehicleEnum::HALLO || + origV->mVehicleID == VehicleEnum::OBLIT_V || + + origV->mVehicleID == VehicleEnum::MONO_V || + origV->mVehicleID == VehicleEnum::KNIGH_V || + origV->mVehicleID == VehicleEnum::CFIRE_V || + origV->mVehicleID == VehicleEnum::CCOLA || + origV->mVehicleID == VehicleEnum::HBIKE_V || + + origV->mVehicleID == VehicleEnum::ROCKE_V || + origV->mVehicleID == VehicleEnum::ATV_V || + origV->mVehicleID == VehicleEnum::DUNE_V || + origV->mVehicleID == VehicleEnum::COFFIN || + origV->mVehicleID == VehicleEnum::SHIP || + + origV->mVehicleID == VehicleEnum::WITCHCAR ) + { + return false; + } + return true; +}
\ No newline at end of file diff --git a/game/code/worldsim/huskpool.h b/game/code/worldsim/huskpool.h new file mode 100644 index 0000000..7c582ac --- /dev/null +++ b/game/code/worldsim/huskpool.h @@ -0,0 +1,56 @@ +//============================================================================= +// Copyright (C) 2003 Radical Entertainment Ltd. All rights reserved. +// +// File: huskpool.h +// +// Description: +// +// History: Mar 27, 2003 - created, gmayer +// +//============================================================================= + +#ifndef HUSKPOOL_H +#define HUSKPOOL_H + + + +#include <worldsim/redbrick/vehicle.h> + +// vehicle central can own this +class HuskPool +{ +public: + + HuskPool(); + ~HuskPool(); + + void Init(int num); // how big the pool should be - the max number of simultaneous husks we ever want to have + void Empty(); // empty lists - for the OnStop.. + + Vehicle* RequestHusk( VehicleType vt, Vehicle* originalVehicle ); + void FreeHusk( Vehicle* husk ); + + Vehicle* FindOriginalVehicleGivenHusk( Vehicle* husk ); + Vehicle* FindHuskGivenOriginalVehicle( Vehicle* v ); + + bool IsHuskType( VehicleEnum::VehicleID id ); + +private: + bool WillMakeConvincingHusk( Vehicle* origV ); + +private: + int mTotalNum; + + struct HuskData + { + Vehicle* originalVehicle; + Vehicle* huskVehicle; + bool inUse; + }; + + HuskData* mHuskArray; + +}; + + +#endif // HUSKPOOL_H diff --git a/game/code/worldsim/parkedcars/allparkedcars.cpp b/game/code/worldsim/parkedcars/allparkedcars.cpp new file mode 100644 index 0000000..1318e1e --- /dev/null +++ b/game/code/worldsim/parkedcars/allparkedcars.cpp @@ -0,0 +1 @@ +#include <worldsim/parkedcars/parkedcarmanager.cpp>
\ No newline at end of file diff --git a/game/code/worldsim/parkedcars/parkedcarmanager.cpp b/game/code/worldsim/parkedcars/parkedcarmanager.cpp new file mode 100644 index 0000000..6d959d0 --- /dev/null +++ b/game/code/worldsim/parkedcars/parkedcarmanager.cpp @@ -0,0 +1,943 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: parkedcarmanager.cpp +// +// Description: Implement ParkedCarManager +// +// History: 2/6/2003 + Created -- NAME +// +//============================================================================= + +//======================================== +// System Includes +//======================================== +// Foundation Tech +#include <radmath/radmath.hpp> +#include <raddebug.hpp> +#include <string.h> +#include <stdlib.h> +#include <p3d/utility.hpp> +#include <pddi/pdditype.hpp> + +//======================================== +// Project Includes +//======================================== +#include <worldsim/parkedcars/parkedcarmanager.h> +#include <worldsim/redbrick/geometryvehicle.h> +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/vehiclecentral.h> +#include <worldsim/traffic/trafficmanager.h> +#include <worldsim/character/character.h> +#include <worldsim/avatarmanager.h> +#include <worldsim/avatar.h> + +#include <memory/srrmemory.h> + +#include <events/eventmanager.h> + +#include <render/rendermanager/rendermanager.h> +#include <render/rendermanager/worldrenderlayer.h> + +#include <meta/carstartlocator.h> + +#include <main/commandlineoptions.h> + +#include <mission/gameplaymanager.h> + +#include <cheats/cheatinputsystem.h> + +#include <presentation/gui/ingame/guimanageringame.h> +#include <presentation/gui/ingame/guiscreenhud.h> +#include <presentation/gui/utility/hudmap.h> + +static const char FREE_CAR_SECTION[] = "FreeCar"; + +//TODO: SHOULD I USE VT_USER? + +//***************************************************************************** +// +// Global Data, Local Data, Local Classes +// +//***************************************************************************** + +ParkedCarManager* ParkedCarManager::spInstance = NULL; + +//***************************************************************************** +// +// Public Member Functions +// +//***************************************************************************** + +//============================================================================= +// ParkedCarManager::GetInstance +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: ParkedCarManager +// +//============================================================================= +ParkedCarManager& ParkedCarManager::GetInstance() +{ + if ( spInstance == NULL ) + { + HeapMgr()->PushHeap( GMA_LEVEL_OTHER ); + spInstance = new ParkedCarManager(); + HeapMgr()->PopHeap( GMA_LEVEL_OTHER ); + } + + return *spInstance; +} + +//============================================================================= +// ParkedCarManager::DestroyInstance +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void ParkedCarManager::DestroyInstance() +{ + if ( spInstance != NULL ) + { + delete spInstance; + spInstance = NULL; + } +} + +//============================================================================= +// ParkedCarManager::HandleEvent +//============================================================================= +// Description: Comment +// +// Parameters: ( EventEnum id, void* pEventData ) +// +// Return: void +// +//============================================================================= +void ParkedCarManager::HandleEvent( EventEnum id, void* pEventData ) +{ + switch ( id ) + { + case EVENT_DYNAMIC_ZONE_LOAD_ENDED: + { + //Now we've finished loading locators (maybe) + //Let's choose some for this zone and reset the data. + if ( mNumLocators > 0 ) + { + //Choose a locator or two. + unsigned int max = mNumCarTypes / MAX_ZONES; + bool isOdd = mNumCarTypes % 2 == 1; + if ( isOdd || mNumCarTypes < MAX_ZONES ) //This is an odd number + { + max += 1; + } + + //Allocate the max num of parked cars among these locators. + unsigned int i; + unsigned int allocated = 0; + for ( i = 0; i < mNumLocators && i < max && allocated < mNumCarTypes - mNumParkedCars; ++i ) + { + unsigned int which = rand() % mNumLocators; + if ( !(mLocators[ which ]->GetFlag( Locator::ACTIVE )) ) + { + //Find another one, this one is used. + unsigned int j; + for ( j = (which + 1) % mNumLocators; j != which; j = (j + 1) % mNumLocators ) + { + if ( mLocators[ j ]->GetFlag( Locator::ACTIVE ) ) + { + which = j; + break; + } + } + } + + if ( CommandLineOptions::Get( CLO_PARKED_CAR_TEST ) || rand() % mNumCarTypes <= 2 ) //Weight it to placing the car. + { + //Mark the locator as used. + + //Park this one! + Vehicle* car = NULL; + ParkedCarInfo* info = NULL; + + //Find an available car + unsigned int j; + for ( j = 0; j < mNumCarTypes; ++j ) + { + bool inUseByPlayer = false; + Avatar* avatar = GetAvatarManager()->GetAvatarForPlayer( 0 ); + inUseByPlayer = ( avatar && + avatar->GetCharacter()->IsInCar() && + avatar->GetVehicle() == mParkedCars[ j ].mCar ); + + if ( mParkedCars[ j ].mLoadedZoneUID == static_cast< tUID >( 0 ) && !inUseByPlayer ) + { + //We found one! + info = &mParkedCars[ j ]; + car = mParkedCars[ j ].mCar; + mParkedCars[ j ].mLoadedZoneUID = mLoadingZoneUID; + break; + } + } + + //rAssert( car ); //If this fails then mNumParkedCars is invalid or there's mem stompage + + if ( car ) + { + //Assign a new random colour + pddiColour colour; + TrafficManager::GetInstance()->GenerateRandomColour( colour ); + car->mGeometryVehicle->SetTrafficBodyColour( colour ); + + int added = GetVehicleCentral()->AddVehicleToActiveList( car ); + rAssert( added != -1 ); + //info->mActiveListIndex = added; + + //Set the position and facing and reset. + rmt::Vector location; + mLocators[ which ]->GetLocation( &location ); + car->SetInitialPositionGroundOffsetAutoAdjust( &location ); + + car->SetResetFacingInRadians( mLocators[ which ]->GetRotation() ); + + car->Reset(); + + car->GetSimState()->SetControl( sim::simSimulationCtrl ); + + //Turn off the headlights + car->mGeometryVehicle->EnableLights( false ); + + ++mNumParkedCars; + ++allocated; + } + } + } + + //Remove all the locators + for ( i = 0; i < mNumLocators; ++i ) + { + p3d::inventory->Remove( mLocators[ i ] ); + } + + //Reset the data. + mLoadingZoneUID = 0; + mNumLocators = 0; + } + break; + } + case EVENT_DUMP_DYNA_SECTION: + { + //Make all the parked cars in the dynazone available to the new zone. + tUID sectionNameUID = static_cast<tName*>(pEventData)->GetUID(); + unsigned int i; + for ( i = 0; i < mNumCarTypes; ++i ) + { + if ( mParkedCars[ i ].mLoadedZoneUID == sectionNameUID ) + { + //Make this puppy available. + mParkedCars[ i ].mLoadedZoneUID = 0; + mNumParkedCars--; + + if( mParkedCars[ i ].mHusk ) + { + // remove husk + bool succeeded = GetVehicleCentral()->RemoveVehicleFromActiveList( mParkedCars[ i ].mHusk ); + rAssert( succeeded ); + + ::GetVehicleCentral()->mHuskPool.FreeHusk( mParkedCars[ i ].mHusk ); + mParkedCars[ i ].mHusk->Release(); + mParkedCars[ i ].mHusk = NULL; + } + } + } + + //Now test to see if we should dump the free car too. + if ( + (sectionNameUID == mFreeCar.mLoadedZoneUID || mFreeCar.mLoadedZoneUID == static_cast< tUID > ( 0 ) ) + && + (mFreeCar.mCar != NULL) + ) + + { + //Can we dump this car? + bool dump = true; + Avatar* avatar = GetAvatarManager()->GetAvatarForPlayer( 0 ); + rAssert( avatar ); + + if ( ( GetGameplayManager()->GetCurrentVehicle() == mFreeCar.mCar ) || + ( GetVehicleCentral()->IsCarUnderConstruction(mFreeCar.mCar) == true ) ) + { + dump = false; + } + else + { + //Either the guy's not in the car or he's in another car. + rmt::Vector charToCar; + rmt::Vector carPos, charPos; + avatar->GetPosition( charPos ); + rTuneAssertMsg(mFreeCar.mCar != NULL,"Attempting to calculate distance from Null free car to Character, Crash Imminent\n"); + mFreeCar.mCar->GetPosition( &carPos ); + + charToCar.Sub( carPos, charPos ); + + const float minDist = 200.0f * 200.0f; + if ( charToCar.MagnitudeSqr() > minDist ) + { + dump = true; + } + } + + if ( dump ) + { + RemoveFreeCar(); + p3d::inventory->RemoveSectionElements( FREE_CAR_SECTION ); + } + + //Reset this as it is either a message that we can try to dump the car, + //or the car is already gone. + mFreeCar.mLoadedZoneUID = 0; + + if ( mFreeCarLocator ) + { + mFreeCarLocator->Release(); + mFreeCarLocator = NULL; + } + } + break; + } + + case EVENT_VEHICLE_DESTROYED: + { + Vehicle* v = (Vehicle*) pEventData; + rAssert( v ); + + // the destroyed vehicle could be a free "moment" car or + // a normal parked traffic car. We must search for both + + ParkedCarInfo* info = NULL; + bool isFreeCar = false; + FindParkedCarInfo( v, info, isFreeCar ); + + // if we never found it, this event doesn't concern us + if( info == NULL ) + { + break; + } + // otherwise, it sure does... + + // First, grab some information about the car + rmt::Vector initPos, initDir; + v->GetPosition( &initPos ); + initDir = v->GetFacing(); + rAssert( rmt::Epsilon( initDir.MagnitudeSqr(), 1.0f, 0.001f ) ); + + // Now remove the old car from the activelist.. + bool succeeded = GetVehicleCentral()->RemoveVehicleFromActiveList( v ); + rAssert( succeeded ); + //GetVehicleCentral()->SetVehicleController( info->mActiveListIndex, NULL ); + //info->mActiveListIndex = -1; + + tUID zoneUID = info->mLoadedZoneUID; + info->mLoadedZoneUID = 0; + if( !isFreeCar ) + { + mNumParkedCars--; + } + + // Now grab a husk (if available), add it to the activelist, + // and place it exactly where theother car is... + + Vehicle* husk = NULL; + + // since we are just now destroying this car, it shouldn't have + // a husk yet. + rAssert( info->mHusk == NULL ); + if( info->mHusk ) + { + // weird! we shouldn't be here.. but we'll handle it gracefully + // otherwise we'll leak. + husk = info->mHusk; + } + else + { + husk = GetVehicleCentral()->mHuskPool.RequestHusk( VT_TRAFFIC, v ); + if( husk ) + { + husk->AddRef(); + } + } + + if( husk ) + { + 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 ); + break; + } + //info->mActiveListIndex = res; + + husk->SetInitialPosition( &initPos ); + float angle = GetRotationAboutY( initDir.x, initDir.z ); + husk->SetResetFacingInRadians( angle ); + husk->Reset(); + husk->SetLocomotion( VL_PHYSICS ); + + info->mHusk = husk; + info->mLoadedZoneUID = zoneUID; + + // add back the numparkedcars + mNumParkedCars++; + } + } + break; + + case 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* v = GetGameplayManager()->GetCurrentVehicle(); + if( v == NULL ) + { + return; // no current vehicle to repair.. oh well... + } + + ParkedCarInfo* info = NULL; + bool isFreeCar = false; + FindParkedCarInfo( v, info, isFreeCar ); + + if( info == NULL ) + { + return; + } + + // if the vehicle is a husk.. gotta replace husk with original vehicle + if( v->mVehicleDestroyed ) + { + if( info->mHusk ) + { + // Destroyed, but has a husk + // Husk had to have existed to get to this point + Vehicle* husk = info->mHusk; + + // 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( info->mActiveListIndex, NULL ); + //info->mActiveListIndex = -1; + + ::GetVehicleCentral()->mHuskPool.FreeHusk( husk ); + husk->Release(); + husk = NULL; + info->mHusk = NULL; + + v->SetInitialPosition( &initPos ); + float angle = GetRotationAboutY( initDir.x, initDir.z ); + v->SetResetFacingInRadians( angle ); + v->Reset(); + + } + + // put in original vehicle + int res = GetVehicleCentral()->AddVehicleToActiveList( v ); + 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; + } + //info->mActiveListIndex = res; + + // 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 + // + Avatar* avatar = GetAvatarManager()->GetAvatarForPlayer( 0 ); + rAssert( avatar ); + if( avatar->IsInCar() ) + { + rAssert( avatar->GetVehicle() ); + rAssert( GetVehicleCentral()->mHuskPool.IsHuskType( avatar->GetVehicle()->mVehicleID ) ); + + avatar->SetVehicle( v ); + + Character* character = avatar->GetCharacter(); + GetAvatarManager()->PutCharacterInCar( character, v ); + } + + // fire off event so Esan can know when we switch the vehicle on him. + GetEventManager()->TriggerEvent( + EVENT_VEHICLE_DESTROYED_SYNC_SOUND, v ); + + } + // repair any damage to original vehicle + bool resetDamage = true; + v->ResetFlagsOnly( resetDamage ); + } + break; + + default: + { + rAssert( false ); //Why? + break; + } + } +} + +void ParkedCarManager::FindParkedCarInfo( Vehicle* v, ParkedCarInfo*& info, bool& isFreeCar ) +{ + info = NULL; + isFreeCar = false; + + if( mFreeCar.mCar == v ) + { + info = &mFreeCar; + isFreeCar = true; + } + if( info == NULL ) + { + for( unsigned int i=0; i<mNumCarTypes; i++ ) + { + if( mParkedCars[i].mCar == v ) + { + // found it + info = &(mParkedCars[i]); + break; + } + } + } +} + + + +//============================================================================= +// ParkedCarManager::OnProcessRequestsComplete +//============================================================================= +// Description: Comment +// +// Parameters: ( void* pUserData ) +// +// Return: void +// +//============================================================================= +void ParkedCarManager::OnProcessRequestsComplete( void* pUserData ) +{ + if ( mFreeCar.mLoadedZoneUID != static_cast< tUID >( 0 ) ) + { + //Create the car. + CreateFreeCar(); + } + else + { + //Throw this data away... + rAssert( false ); + + //Fragment. + p3d::inventory->RemoveSectionElements( FREE_CAR_SECTION ); + } +} + + +//============================================================================= +// ParkedCarManager::AddCarType +//============================================================================= +// Description: Comment +// +// Parameters: ( const char* name ) +// +// Return: void +// +//============================================================================= +void ParkedCarManager::AddCarType( const char* name ) +{ + //return; + +#ifdef RAD_DEBUG + if ( CommandLineOptions::Get( CLO_PARKED_CAR_TEST ) ) + { + rAssert( mNumCarTypes < NUM_TEST_PARKED_CARS ); + } + else + { + rAssert( mNumCarTypes < MAX_DIFFERENT_CARS ); + } +#endif + + rAssert( strlen( name ) < MAX_CAR_NAME_LEN ); + + HeapMgr()->PushHeap( GMA_LEVEL_OTHER ); + + unsigned int i; + unsigned int iterations = CommandLineOptions::Get( CLO_PARKED_CAR_TEST ) ? NUM_TEST_PARKED_CARS / MAX_DIFFERENT_CARS : 1; + for ( i = 0; i < iterations; ++i ) + { + //Set the name. + unsigned int nameLen = strlen( name ) > MAX_CAR_NAME_LEN ? MAX_CAR_NAME_LEN : strlen( name ); + strncpy( mParkedCars[ mNumCarTypes ].mName, name, nameLen ); + mParkedCars[ mNumCarTypes ].mName[ nameLen ] = '\0'; + + mParkedCars[ mNumCarTypes ].mLoadedZoneUID = 0; + + Vehicle* car = GetVehicleCentral()->InitVehicle( mParkedCars[ mNumCarTypes ].mName, false, NULL, VT_TRAFFIC ); //TODO: Should I change the NULL to a con file? + rAssert( car ); + car->mCreatedByParkedCarManager = true; + + car->AddRef(); + + if ( car ) + { + mParkedCars[ mNumCarTypes ].mCar = car; + ++mNumCarTypes; + } + } + + HeapMgr()->PopHeap( GMA_LEVEL_OTHER ); +} + +//============================================================================= +// ParkedCarManager::AddLocator +//============================================================================= +// Description: Comment +// +// Parameters: ( CarStartLocator* loc ) +// +// Return: void +// +//============================================================================= +void ParkedCarManager::AddLocator( CarStartLocator* loc ) +{ + if(!mParkedCarsEnabled) + { + return; + } + + //return; + rAssert( mNumLocators < MAX_LOCATORS_PER_ZONE ); + + //Adding locators from the same dyna zone. + if ( mNumLocators < MAX_LOCATORS_PER_ZONE ) + { + mLocators[ mNumLocators ] = loc; + ++mNumLocators; + } + + if ( mLoadingZoneUID == static_cast< tUID >( 0 ) ) + { + //Set the zone ID + mLoadingZoneUID = GetRenderManager()->pWorldRenderLayer()->GetCurSectionName().GetUID(); + } +} + +//============================================================================= +// ParkedCarManager::AddFreeCar +//============================================================================= +// Description: Comment +// +// Parameters: ( const char* name, CarStartLocator* loc ) +// +// Return: void +// +//============================================================================= +void ParkedCarManager::AddFreeCar( const char* name, CarStartLocator* loc ) +{ + if ( GetCheatInputSystem()->IsCheatEnabled( CHEAT_ID_REDBRICK ) ) + { + //This Bud's for you. + name = "redbrick"; + } + + if(!mParkedCarsEnabled) + { + return; + } + + if ( mFreeCar.mCar == NULL ) + { + unsigned int nameLen = strlen( name ) > MAX_CAR_NAME_LEN ? MAX_CAR_NAME_LEN : strlen( name ); + strncpy( mFreeCar.mName, name, nameLen ); + mFreeCar.mName[ nameLen ] = '\0'; + + mFreeCar.mLoadedZoneUID = GetRenderManager()->pWorldRenderLayer()->GetCurSectionName().GetUID(); + rAssert( mFreeCar.mLoadedZoneUID != static_cast< tUID >( 0 ) ); + + tRefCounted::Assign( mFreeCarLocator, loc ); + + //Try to load the car + char fileName[MAX_CAR_NAME_LEN + 5 + 32]; + sprintf( fileName, "art\\cars\\%s.p3d", name ); + GetLoadingManager()->AddRequest( FILEHANDLER_LEVEL, fileName, GMA_LEVEL_OTHER, FREE_CAR_SECTION, FREE_CAR_SECTION, this ); + } +} + +//============================================================================= +// ParkedCarManager::RemoveFreeCar +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void ParkedCarManager::RemoveFreeCar() +{ + //TODO: Do I need this? What if I make sure the car is not in the view frustrum? + //p3d::pddi->DrawSync(); + if ( mFreeCar.mCar != NULL ) + { + if( mFreeCar.mHusk == NULL ) + { + GetVehicleCentral()->RemoveVehicleFromActiveList( mFreeCar.mCar ); + } + else + { + GetVehicleCentral()->RemoveVehicleFromActiveList( mFreeCar.mHusk ); + + GetVehicleCentral()->mHuskPool.FreeHusk( mFreeCar.mHusk ); + mFreeCar.mHusk->Release(); + mFreeCar.mHusk = NULL; + } + /* + if( mFreeCar.mActiveListIndex != -1 ) + { + GetVehicleCentral()->SetVehicleController( mFreeCar.mActiveListIndex, NULL ); + } + mFreeCar.mActiveListIndex = -1; + */ + + CGuiScreenHud* currentHud = GetCurrentHud(); + if( currentHud != NULL ) + { + HudMapIcon* playerCarIcon = currentHud->GetHudMap( 0 )->FindIcon( HudMapIcon::ICON_PLAYER_CAR ); + if( playerCarIcon != NULL && playerCarIcon->m_dynamicLocator == mFreeCar.mCar ) + { + GetGameplayManager()->UnregisterVehicleHUDIcon(); + } + } + + mFreeCar.mCar->Release(); + mFreeCar.mCar = NULL; + mFreeCar.mLoadedZoneUID = 0; + } + + if ( mFreeCarLocator ) + { + mFreeCarLocator->Release(); + mFreeCarLocator = NULL; + } +} + +void ParkedCarManager::RemoveFreeCarIfClose( const rmt::Vector& position ) +{ + // where is the free car? + rmt::Vector freeCarPosition; + if ( mFreeCar.mCar != NULL ) + { + if(mFreeCar.mCar == GetAvatarManager()->GetAvatarForPlayer(0)->GetVehicle()) + { + return; + } + + mFreeCar.mCar->GetPosition( &freeCarPosition ); + } + if( ( position - freeCarPosition ).MagnitudeSqr() < 10.0 * 10.0 ) + { + RemoveFreeCar(); + } +} + +//***************************************************************************** +// +// Private Member Functions +// +//***************************************************************************** + +//============================================================================= +// ParkedCarManager::ParkedCarManager +//============================================================================= +// Description: Constructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================= +ParkedCarManager::ParkedCarManager() : + mNumCarTypes( 0 ), + mNumParkedCars( 0 ), + mNumLocators( 0 ), + mLoadingZoneUID( 0 ), + mFreeCarLocator( NULL ) +{ + GetEventManager()->AddListener( this, EVENT_DUMP_DYNA_SECTION ); + GetEventManager()->AddListener( this, EVENT_DYNAMIC_ZONE_LOAD_ENDED ); + GetEventManager()->AddListener( this, EVENT_VEHICLE_DESTROYED ); + GetEventManager()->AddListener( this, EVENT_REPAIR_CAR ); + + if ( CommandLineOptions::Get( CLO_PARKED_CAR_TEST ) ) + { + mParkedCars = new ParkedCarInfo[ NUM_TEST_PARKED_CARS ]; + } + else + { + mParkedCars = new ParkedCarInfo[ MAX_DIFFERENT_CARS ]; + } + + //We want an inventory section for the car + p3d::inventory->AddSection( FREE_CAR_SECTION ); + + mParkedCarsEnabled = true; + +} + +//============================================================================= +// ParkedCarManager::~ParkedCarManager +//============================================================================= +// Description: Destructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================= +ParkedCarManager::~ParkedCarManager() +{ + GetEventManager()->RemoveAll( this ); + + //Remove all the car types + MDKParkedCars(); + delete[] mParkedCars; + + //Clear out leftover locators. + for( unsigned int i = 0; i < mNumLocators; ++i ) + { + p3d::inventory->Remove( mLocators[ i ] ); + } + + RemoveFreeCar(); + + if ( mFreeCarLocator ) + { + p3d::inventory->Remove( mFreeCarLocator ); + + mFreeCarLocator->Release(); + mFreeCarLocator = NULL; + } + + p3d::inventory->RemoveSectionElements( FREE_CAR_SECTION ); + p3d::inventory->DeleteSection( FREE_CAR_SECTION ); + +} + +//============================================================================= +// ParkedCarManager::CreateFreeCar +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void ParkedCarManager::CreateFreeCar() +{ + rAssert( mFreeCarLocator ); + + Vehicle* car = GetVehicleCentral()->InitVehicle( mFreeCar.mName, true, NULL, VT_TRAFFIC ); //TODO: Should I change the NULL to a con file? + rAssert( car ); + car->mCreatedByParkedCarManager = true; + + car->AddRef(); + + + //Set the position and facing and reset. + rmt::Vector location; + mFreeCarLocator->GetLocation( &location ); + car->SetInitialPositionGroundOffsetAutoAdjust( &location ); + + car->SetResetFacingInRadians( mFreeCarLocator->GetRotation() ); + + car->Reset(); + + + int added = GetVehicleCentral()->AddVehicleToActiveList( car ); + rAssert( added != -1 ); + //mFreeCar.mActiveListIndex = added; + + + //Turn off the lights. + car->mGeometryVehicle->EnableLights( false ); + + mFreeCar.mCar = car; + + //Throw away the locator since we'll not need it again and we'll prevent the little memory block. + mFreeCarLocator->Release(); + mFreeCarLocator = NULL; +} + +//============================================================================= +// ParkedCarManager::MDKParkCar +//============================================================================= +// Description: Deletes the park car instances , so that the art can be removed from the inventory. +// +// Parameters: () +// +// Return: void +// +//============================================================================= + + +void ParkedCarManager::MDKParkedCars() +{ + //Remove all the car types + for( unsigned int i = 0; i < mNumCarTypes; ++i ) + { + rAssert( mParkedCars[ i ].mCar != NULL ); + + if( mParkedCars[ i ].mHusk == NULL ) + { + GetVehicleCentral()->RemoveVehicleFromActiveList( mParkedCars[ i ].mCar ); + } + else + { + GetVehicleCentral()->RemoveVehicleFromActiveList( mParkedCars[ i ].mHusk ); + + GetVehicleCentral()->mHuskPool.FreeHusk( mParkedCars[ i ].mHusk ); + mParkedCars[ i ].mHusk->Release(); + mParkedCars[ i ].mHusk = NULL; + + } + /* + if( mParkedCars[ i ].mActiveListIndex != -1 ) + { + GetVehicleCentral()->SetVehicleController( mParkedCars[ i ].mActiveListIndex, NULL ); + } + mParkedCars[ i ].mActiveListIndex = -1; + */ + mParkedCars[ i ].mCar->Release(); + mParkedCars[ i ].mCar = NULL; + mParkedCars[ i ].mLoadedZoneUID = 0; + } + + mNumParkedCars = 0; + +} diff --git a/game/code/worldsim/parkedcars/parkedcarmanager.h b/game/code/worldsim/parkedcars/parkedcarmanager.h new file mode 100644 index 0000000..35d0096 --- /dev/null +++ b/game/code/worldsim/parkedcars/parkedcarmanager.h @@ -0,0 +1,127 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: parkedcarmanager.h +// +// Description: Blahblahblah +// +// History: 2/6/2003 + Created -- NAME +// +//============================================================================= + +#ifndef PARKEDCARMANAGER_H +#define PARKEDCARMANAGER_H + +//======================================== +// Nested Includes +//======================================== +#include <p3d/p3dtypes.hpp> + +#include <events/eventlistener.h> + +#include <worldsim/vehiclecentral.h> + +#include <loading/loadingmanager.h> + +//======================================== +// Forward References +//======================================== +class Vehicle; +class CarStartLocator; + +//============================================================================= +// +// Synopsis: Blahblahblah +// +//============================================================================= + +class ParkedCarManager : public EventListener, public LoadingManager::ProcessRequestsCallback +{ +public: + enum + { + MAX_DIFFERENT_CARS = 5, + MAX_CAR_NAME_LEN = 32, + MAX_LOCATORS_PER_ZONE = 50, + MAX_ZONES = 3, + NUM_TEST_PARKED_CARS = VehicleCentral::MAX_ACTIVE_VEHICLES - 6 + }; + + static ParkedCarManager& GetInstance(); + static void DestroyInstance(); + + //From EventListener + virtual void HandleEvent( EventEnum id, void* pEventData ); + + //From LoadingManager + virtual void OnProcessRequestsComplete( void* pUserData ); + + //Local + void AddCarType( const char* name ); + void AddLocator( CarStartLocator* loc ); + + void AddFreeCar( const char* name, CarStartLocator* loc ); + void RemoveFreeCar(); + void RemoveFreeCarIfClose( const rmt::Vector& position ); + + //Chuck: release the parkcar instances so Gameplaymanager can free up the art + void MDKParkedCars(); + + void EnableParkedCars() {mParkedCarsEnabled = true;} + void DisableParkedCars() {mParkedCarsEnabled = false;} + +private: + static ParkedCarManager* spInstance; + + struct ParkedCarInfo + { + ParkedCarInfo() : + mCar( NULL ), + mHusk( NULL ), + mLoadedZoneUID( 0 ) + //mActiveListIndex( -1 ) + { mName[0] ='\0'; }; + + char mName[ MAX_CAR_NAME_LEN + 1]; + Vehicle* mCar; + Vehicle* mHusk; + tUID mLoadedZoneUID; + //int mActiveListIndex; + }; + + //Where all the parked cars are + ParkedCarInfo* mParkedCars; + unsigned int mNumCarTypes; + unsigned int mNumParkedCars; + + //For loading locators, we store all the locators loaded while a given UID is active. + CarStartLocator* mLocators[ MAX_LOCATORS_PER_ZONE ]; + unsigned int mNumLocators; + tUID mLoadingZoneUID; + + //Free "Moment" cars + ParkedCarInfo mFreeCar; + CarStartLocator* mFreeCarLocator; + + ParkedCarManager(); + virtual ~ParkedCarManager(); + + void CreateFreeCar(); + + void FindParkedCarInfo( Vehicle* v, ParkedCarInfo*& info, bool& isFreeCar ); + + bool mParkedCarsEnabled; // little bool to disable these during street races + + //Prevent wasteful constructor creation. + ParkedCarManager( const ParkedCarManager& parkedcarmanager ); + ParkedCarManager& operator=( const ParkedCarManager& parkedcarmanager ); +}; + +//***************************************************************************** +// +//Inline Public Member Functions +// +//***************************************************************************** +inline ParkedCarManager& GetPCM() { return ParkedCarManager::GetInstance(); }; + +#endif //PARKEDCARMANAGER_H diff --git a/game/code/worldsim/ped/allped.cpp b/game/code/worldsim/ped/allped.cpp new file mode 100644 index 0000000..62dbc20 --- /dev/null +++ b/game/code/worldsim/ped/allped.cpp @@ -0,0 +1,2 @@ +#include <worldsim/ped/pedestrianmanager.cpp> +#include <worldsim/ped/pedestrian.cpp> diff --git a/game/code/worldsim/ped/pedestrian.cpp b/game/code/worldsim/ped/pedestrian.cpp new file mode 100644 index 0000000..b6ac4a9 --- /dev/null +++ b/game/code/worldsim/ped/pedestrian.cpp @@ -0,0 +1,448 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: pedestrian.cpp +// +// Description: Implements Pedestrian class +// +// History: 09/18/2002 + Created -- Dusit Eakkachaichanvet +// +//============================================================================= +#include <string.h> +#include <raddebug.hpp> // for rAssert & other debug print outs +#include <radmath/radmath.hpp> // for rmt::Vector + +#include <worldsim/ped/pedestrian.h> +#include <worldsim/character/charactermanager.h> +#include <worldsim/character/character.h> +#include <worldsim/avatar.h> +#include <worldsim/avatarmanager.h> + +#include <camera/supercammanager.h> +#include <camera/supercam.h> + +#include <pedpaths/path.h> +#include <pedpaths/pathsegment.h> + +#include <render/RenderManager/RenderManager.h> +#include <render/Culling/WorldScene.h> +#include <render/IntersectManager/IntersectManager.h> + +#include <ai/sequencer/action.h> +#include <input/inputmanager.h> + +#include <mission/gameplaymanager.h> + +static const float SECONDS_BETW_ACTIVATE_POLLS = 0.0f;//0.5f; +static const float PEDESTRIAN_PATHFIND_BACK_SPEED_MPS = 1.0f; +static const float PEDESTRIAN_WALK_SPEED_MPS = 1.0f; +static const float PEDESTRIAN_BRISK_WALK_SPEED_MPS = 1.5f; +static const float PEDESTRIAN_RUN_SPEED_MPS = 3.0f; + +Pedestrian::Pedestrian() : + mMillisecondsOutOfSight( 0 ), + mIsActive( false ), + mPath( NULL ), + mFollowPathSpeedMps( 0.0f ), + mMarkedForActivation( false ), + mSecondsTillActivateSelf( 0.0f ) +{ +} + +Pedestrian::~Pedestrian() +{ +} + + +///////////////////////////////////////////////////////////////////////// +// CharacterController +///////////////////////////////////////////////////////////////////////// +void Pedestrian::Update( float seconds ) +{ + if( mMarkedForActivation ) + { + rAssert( !mIsActive ); + + + //mSecondsTillActivateSelf -= seconds; + //if( mSecondsTillActivateSelf < 0.0f ) + //{ + // mSecondsTillActivateSelf = SECONDS_BETW_ACTIVATE_POLLS; + if( ::GetCharacterManager()->IsModelLoaded( mModelName ) ) + { + ActivateSelf(); + } + //} + else + { + // don't update if marked for activation but not yet activated + return; + } + } + + // don't update if not active! + if( !mIsActive ) + { + return; + } + BEGIN_PROFILE( "Pedestrian::Update" ); + + if( mState == NPCController::FOLLOWING_PATH ) + { + + // Detect if another ped is near me + // if so, deal with "walk through one another" scenario if we're + // close enough to the player + + rmt::Vector myPos; + GetCharacter()->GetPosition( &myPos ); + + rmt::Vector playerPos; + GetAvatarManager()->GetAvatarForPlayer( 0 )->GetPosition( playerPos ); + + const float MIN_DIST_SQR_FROM_PLAYER = 1225.0f; + if( (myPos - playerPos).MagnitudeSqr() <= MIN_DIST_SQR_FROM_PLAYER || + GetInputManager()->GetGameState() == Input::ACTIVE_FIRST_PERSON ) + { + + int i = PedestrianManager::GetInstance()->mActivePeds.GetHead(); + while( 0 <= i && i < DListArray::MAX_ELEMS ) + { + PedestrianManager::PedestrianStruct* pedStr = (PedestrianManager::PedestrianStruct*) + PedestrianManager::GetInstance()->mActivePeds.GetDataAt(i); + rAssert( pedStr ); + + Pedestrian* ped = pedStr->ped; + rAssert( ped ); + + if( !ped->GetIsActive() ) + { + i = PedestrianManager::GetInstance()->mActivePeds.GetNextOf( i ); + continue; + } + + if( ped != this ) + { + rmt::Vector pedPos; + ped->GetCharacter()->GetPosition( &pedPos ); + + rmt::Vector toPed = pedPos - myPos; + + static const float MINDISTSQR_FROM_ANOTHER_PED_SMALL = 2.25f; + static const float MINDISTSQR_FROM_ANOTHER_PED_LARGE = 4.00f; + + float distToleranceFromAnotherPedSqr = (mFollowPathSpeedMps > 1.0f) ? + MINDISTSQR_FROM_ANOTHER_PED_SMALL : + MINDISTSQR_FROM_ANOTHER_PED_LARGE; + + if( toPed.MagnitudeSqr() < distToleranceFromAnotherPedSqr ) + { + // here, we're too close to another ped on same path... + // If he's just following path as well, engage him in conversation! + if( ped->GetState() == NPCController::FOLLOWING_PATH ) + { + // determine direction + toPed.Normalize(); // *** SQUARE ROOT! *** + + // tell myself to transit to talking + TransitToState( NPCController::TALKING ); + mTalkTarget = ped; + SetDirection( toPed ); + mpCharacter->SetDesiredDir( choreo::GetWorldAngle(toPed.x,toPed.z) ); + + // tell him to transit to talking + toPed.Scale( -1.0f ); + ped->TransitToState( NPCController::TALKING ); + ped->mTalkTarget = this; + ped->SetDirection( toPed ); + ped->GetCharacter()->SetDesiredDir( choreo::GetWorldAngle(toPed.x,toPed.z) ); + + StartTalking(); + + break; + } + else if( ped->GetState() == NPCController::PANICKING ) + { + // He's... um.. panicking... We'd better panick too + IncitePanic(); + break; + } + else + { + // he's not available for talking, but he's still nearby + // check if he's in front of us. If so, STAND and wait. + + rmt::Vector forward, right, up; + up.Set( 0.0f, 1.0f, 0.0f ); + + mpCharacter->GetFacing( forward ); + rAssert( rmt::Epsilon(forward.MagnitudeSqr(), 1.0f, 0.001f ) ); + + right.CrossProduct( up, forward ); + bool pedOnMyRight = false; + bool gonnaHit = WillCollide( myPos, + forward, // heading (forward) + right, // right vector + 1.0f, 3.0f, pedPos, pedOnMyRight ); + + if( gonnaHit ) + { + TransitToState( STANDING ); + break; + } + } + } + } + i = PedestrianManager::GetInstance()->mActivePeds.GetNextOf( i ); + } // end of WHILE + } + } + NPCController::Update( seconds ); + + END_PROFILE( "Pedestrian::Update" ); + +} + +float Pedestrian::GetValue( int buttonId ) const +{ + return 0.0f; +} + +bool Pedestrian::IsButtonDown( int buttonId ) const +{ + return false; +} +///////////////////////////////////////////////////////////////////////// + + + +void Pedestrian::Activate( + Path* path, + PathSegment* pathSegment, + rmt::Vector spawnPos, + const char* modelName + ) + +{ + rAssert( path ); + rAssert( pathSegment ); + rAssert( modelName ); + rAssert( path->IsClosed() ); + + mStartPanicking = false; + + // set where I am on the path + mPath = path; + + ///////////////////////////////////////////////////////// + // Add NPC Waypoints based on the path given... + int numSegs = path->GetNumPathSegments(); + int numPts = numSegs; // a closed path has same number of points as segments + + rAssert( 1 < numPts && numPts <= MAX_NPC_WAYPOINTS ); + + int currSegIndex = pathSegment->GetIndexToParentPath(); + + mNumNPCWaypoints = 0; + while( mNumNPCWaypoints < numPts ) + { + rmt::Vector segEndPos; + pathSegment->GetEndPos( segEndPos ); + + AddNPCWaypoint( segEndPos ); + + // next segment! + currSegIndex = ( currSegIndex + 1 ) % numSegs; + rAssert( 0 <= currSegIndex && currSegIndex < numSegs ); + pathSegment = path->GetPathSegmentByIndex( currSegIndex ); + } + mCurrNPCWaypoint = 0; + + + ////////////////////////////////////////////////////////// + rAssert( modelName != NULL ); + int modelNameLen = strlen(modelName); + rAssert( modelNameLen <= Pedestrian::MAX_STRING_LEN ); + strncpy( mModelName, modelName, Pedestrian::MAX_STRING_LEN ); + mModelName[ modelNameLen ] = '\0'; + + mMarkedForActivation = true; + mSecondsTillActivateSelf = SECONDS_BETW_ACTIVATE_POLLS; + + // set the Character's new spawn position (so whenever we do + // a Character::GetPosition, we get the correct position) + GetCharacter()->RelocateAndReset(spawnPos, 0.0f); + + // diff between spawnPos & actual character position at this point + // is the ground fix up... spawnpos is the actual ON SEGMENT + // location itself.. + mSpawnPos = spawnPos; +} + +void Pedestrian::ActivateSelf() +{ + mMillisecondsOutOfSight = 0; + mMarkedForActivation = false; + + // Test to see how close we are to the player... if too close + // just deactivate... + rmt::Vector myPos; + mpCharacter->GetPosition( myPos ); + + rmt::Vector playerPos, playerVel; + + Avatar* avatar = GetAvatarManager()->GetAvatarForPlayer( 0 ); + rAssert( avatar != NULL ); + avatar->GetPosition( playerPos ); + + /* + rmt::Vector camTarget; + SuperCam* pCam = GetSuperCamManager()->GetSCC(0)->GetActiveSuperCam(); + pCam->GetHeadingNormalized( &camTarget ); + rAssert( rmt::Epsilon(camTarget.MagnitudeSqr(), 1.0f, 0.0005f) ); + + rmt::Vector center = playerPos + camTarget * PedestrianManager::CENTER_OFFSET; + + float distSqr = (myPos - center).MagnitudeSqr(); + */ + float distSqr = (myPos - playerPos).MagnitudeSqr(); + float removeDistSqr = PedestrianManager::GetInstance()->FADE_RADIUS; + removeDistSqr *= removeDistSqr; + + if( GetGameplayManager()->TestPosInFrustrumOfPlayer( myPos, 0 ) && + distSqr < removeDistSqr ) + { + PedestrianManager::GetInstance()->RemovePed( mpCharacter ); + return; + } + + // make sure we don't activate too near another ped... + const float TOO_CLOSE_TO_ANOTHER_PED_SQR = 2.25f; + rmt::Vector pedPos; + int i = PedestrianManager::GetInstance()->mActivePeds.GetHead(); + while( 0 <= i && i < DListArray::MAX_ELEMS ) + { + PedestrianManager::PedestrianStruct* pedStr = (PedestrianManager::PedestrianStruct*) + PedestrianManager::GetInstance()->mActivePeds.GetDataAt(i); + rAssert( pedStr != NULL ); + + if( pedStr->ped == this ) + { + i = PedestrianManager::GetInstance()->mActivePeds.GetNextOf( i ); + continue; // I'm this ped! Ignore... + } + + if( !pedStr->ped->GetIsActive() ) + { + i = PedestrianManager::GetInstance()->mActivePeds.GetNextOf( i ); + continue; // ped is waiting to self-activate, ignore it + } + + pedStr->ped->GetCharacter()->GetPosition( &pedPos ); + pedPos.y = myPos.y; // ignore diff in y + + if( (pedPos-myPos).MagnitudeSqr() < TOO_CLOSE_TO_ANOTHER_PED_SQR ) + { + PedestrianManager::GetInstance()->RemovePed( mpCharacter ); + return; + } + i = PedestrianManager::GetInstance()->mActivePeds.GetNextOf( i ); + } + + + ///////////////////////////////// + // Otherwise, we're free to activate self + + mIsActive = true; + + // set my state + TransitToState(FOLLOWING_PATH); + mOffPath = false; + DetermineFollowPathSpeed(); + + mpCharacter->ResetCollisions(); + mpCharacter->AddToWorldScene( ); + mpCharacter->SetHasBeenHit(false); + + mpCharacter->AddToPhysics(); +} + +void Pedestrian::InitZero() +{ + mPath = NULL; + + mMillisecondsOutOfSight = 0; + mMarkedForActivation = false; + mIsActive = false; + + TransitToState(STOPPED); + mOffPath = false; +} + +void Pedestrian::Deactivate() +{ + InitZero(); + + // transit back to locomotion state + if( mpCharacter->GetStateManager()->GetState() != CharacterAi::LOCO ) + { + mpCharacter->GetStateManager()->SetState<CharacterAi::Loco>(); + } + + // transit back to AI control... + sim::SimState* simState = mpCharacter->GetSimState(); + if( simState != NULL ) + { + simState->SetControl( sim::simAICtrl ); + } + + //mpCharacter->SetSolveCollisions( false ); + mpCharacter->RemoveFromPhysics(); + mpCharacter->RemoveFromWorldScene( ); +} + +void Pedestrian::OnReachedWaypoint() +{ + // do nothing +} + + +float Pedestrian::GetFollowPathSpeedMps() const +{ + // when offpath, don't just RUN BACK on the path + // this can make us thrash back and forth (Oh we're off path! + // Oh now we're off path on the other side! Oh we're off path! So on...) + if( mOffPath ) + { + return PEDESTRIAN_PATHFIND_BACK_SPEED_MPS; + } + return mFollowPathSpeedMps; +} + +void Pedestrian::GetBoundingSphere( rmt::Sphere& s ) +{ + rmt::Sphere sphere; + mpCharacter->GetBoundingSphere( &sphere ); + + // make sure the bounding sphere has same position as character!!! + s = sphere; +} + + +void Pedestrian::DetermineFollowPathSpeed() +{ + int coinflip = rand() % 10; + if( 0 <= coinflip && coinflip < 4 ) + { + mFollowPathSpeedMps = PEDESTRIAN_WALK_SPEED_MPS; + } + else if( 4 <= coinflip && coinflip < 8 ) + { + mFollowPathSpeedMps = PEDESTRIAN_BRISK_WALK_SPEED_MPS; + } + else + { + mFollowPathSpeedMps = PEDESTRIAN_RUN_SPEED_MPS; + } +} + diff --git a/game/code/worldsim/ped/pedestrian.h b/game/code/worldsim/ped/pedestrian.h new file mode 100644 index 0000000..88e9d01 --- /dev/null +++ b/game/code/worldsim/ped/pedestrian.h @@ -0,0 +1,128 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: pedestrian.h +// +// Description: Defines Pedestrian class +// +// History: 09/18/2002 + Created -- Dusit Eakkachaichanvet +// +//============================================================================= + +#ifndef PEDESTRIAN_H // 80 character mark +#define PEDESTRIAN_H + +#include <worldsim/character/charactercontroller.h> +#include <roads/geometry.h> + +// because we're only storing pointers to these +// objects (all pointers are same size), we can +// use forward declarations here. +class Character; +class Path; +class PathSegment; + + +class Pedestrian +: public NPCController +{ +// METHODS +public: + Pedestrian(); + virtual ~Pedestrian(); + + void GetBoundingSphere( rmt::Sphere& s ); + + ///////////////////////////////////////////////////////////////////////// + // CharacterController + ///////////////////////////////////////////////////////////////////////// + virtual void Update( float seconds ); + virtual float GetValue( int buttonId ) const; + virtual bool IsButtonDown( int buttonId ) const; + ///////////////////////////////////////////////////////////////////////// + + // a magic all-in-one call to place ped in the world & make it active + // This only marks it for activation while waiting for CharaterManager + // to load our data... won't put in world till model is done loading... + void Activate( + Path* path, + PathSegment* pathSegment, + rmt::Vector spawnPos, + const char* modelName ); + + // This immediately removes model from the world and makes it inactive + void Deactivate(); + + // ACCESSORS + bool GetIsActive() const; + void SetIsActive( bool isActive ); + + Path* GetPath(); + void SetPath( Path* path ); + + void InitZero(); + +// MEMBERS +public: + unsigned int mMillisecondsOutOfSight; + +protected: + float GetFollowPathSpeedMps() const; + void OnReachedWaypoint(); + +// METHODS +private: + void DetermineFollowPathSpeed(); + void ActivateSelf(); + + //Prevent wasteful constructor creation. + Pedestrian( const Pedestrian& ped ); + Pedestrian& operator=( const Pedestrian& ped ); + +// MEMBERS +private: + + ///////////////////////////////////////////////////////////////////////// + // CharacterController + ///////////////////////////////////////////////////////////////////////// + rmt::Vector mNormalizedDir; + ///////////////////////////////////////////////////////////////////////// + + bool mIsActive; + + Path* mPath; // path I'm on + + float mFollowPathSpeedMps; + bool mMarkedForActivation; + + enum { + MAX_STRING_LEN = 64 + }; + + char mModelName [MAX_STRING_LEN+1]; + rmt::Vector mSpawnPos; + float mSecondsTillActivateSelf; + +}; + + +// **************************** INLINES ****************************** + +inline bool Pedestrian::GetIsActive() const +{ + return mIsActive; +} +inline void Pedestrian::SetIsActive( bool isActive ) +{ + mIsActive = isActive; +} +inline Path* Pedestrian::GetPath() +{ + return mPath; +} +inline void Pedestrian::SetPath( Path* path ) +{ + mPath = path; +} + +#endif // PEDESTRIAN_H
\ No newline at end of file diff --git a/game/code/worldsim/ped/pedestrianmanager.cpp b/game/code/worldsim/ped/pedestrianmanager.cpp new file mode 100644 index 0000000..f4e9011 --- /dev/null +++ b/game/code/worldsim/ped/pedestrianmanager.cpp @@ -0,0 +1,1593 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: pedestrianmanager.cpp +// +// Description: Implements Pedestrian Manager class +// +// History: 09/18/2002 + Created -- Dusit Eakkachaichanvet +// +//============================================================================= +#define USEAVATARPOS + +#include <raddebug.hpp> // for rAssert & other debug print outs +#include <stdlib.h> +#include <string.h> + +#include <debug/profiler.h> // for the Profiler +#include <radmath/radmath.hpp> // for rmt::Vector +#include <memory/srrmemory.h> + +#include <meta/locatorevents.h> +#include <meta/pedgrouplocator.h> + +#include <worldsim/ped/pedestrianmanager.h> + +#include <worldsim/avatar.h> +#include <worldsim/avatarmanager.h> + +/* +#include <camera/supercammanager.h> +#include <camera/supercam.h> +*/ + +#include <worldsim/character/character.h> +#include <worldsim/character/charactermanager.h> +#include <worldsim/character/characterrenderable.h> + +#include <render/Culling/ReserveArray.h> +#include <render/IntersectManager/IntersectManager.h> + +#include <roads/geometry.h> + +#include <camera/supercammanager.h> +#include <camera/supercamcentral.h> +#include <camera/supercam.h> + +#include <pedpaths/path.h> +#include <pedpaths/pathsegment.h> + +#include <mission/gameplaymanager.h> + +#include <worldsim/vehiclecentral.h> + +// in meters, how much to translate spawn & remove radii forward +// in the direction of the camera lookat + +//#define PED_TEST +#ifdef PED_TEST + const float PedestrianManager::FADE_RADIUS = 5.0f; // in meters + const float PedestrianManager::CENTER_OFFSET = 5.0f; // in meters + const float PedestrianManager::ADD_RADIUS = 10.0f; // in meters + const float PedestrianManager::REMOVE_RADIUS = 15.0f; // in meters + const float PedestrianManager::INITIAL_ADD_RADIUS = 10.0f; // in meters +#else + const float PedestrianManager::FADE_RADIUS = 60.0f; // in meters + const float PedestrianManager::CENTER_OFFSET = 30.0f; // in meters + const float PedestrianManager::ADD_RADIUS = 45.0f; // in meters + const float PedestrianManager::REMOVE_RADIUS = 50.0f; // in meters + const float PedestrianManager::INITIAL_ADD_RADIUS = 90.0f; // in meters +#endif +const unsigned int PedestrianManager::MILLISECONDS_PER_GROUND_INTERSECT = 10; +const unsigned int PedestrianManager::MILLISECONDS_BETW_ADDS = 1; +const unsigned int PedestrianManager::MILLISECONDS_BETW_REMOVES = 1000; +const unsigned int PedestrianManager::MILLISECONDS_POPULATE_WORLD = 3000; + +int PedestrianManager::mDefaultModelGroup = 0; + +// *********************** STATICS *********************** + +PedestrianManager* PedestrianManager::mInstance = NULL; + +PedestrianManager* PedestrianManager::GetInstance() +{ + if( mInstance == NULL ) + { + mInstance = new(GMA_LEVEL_OTHER) PedestrianManager(); + } + return mInstance; +} + +void PedestrianManager::DestroyInstance() +{ + if( mInstance != NULL ) + { + mInstance->mFreePeds.Clear(); + mInstance->mActivePeds.Clear(); + + // Clean up Pedestrians. + int i = 0; + for( i=0; i<PedestrianManager::MAX_PEDESTRIANS; i++ ) + { + PedestrianStruct* pedStr = &(mInstance->mPeds[i]); + rAssert( pedStr != NULL ); + + Pedestrian* ped = pedStr->ped; + rAssert( ped != NULL ); + + if( ped->GetIsActive() ) + { + ped->Deactivate(); + } + } + // Delete self + delete mInstance; + } + mInstance = NULL; +} + + +// ******************** PUBLICS ************************* + +// call this in loading context +void PedestrianManager::InitDefaultModelGroups() +{ + // initialize all model groups with a default group + // This is why we put PedManager::Init in LoadingContext, after + // CharacterManager::PreLoad and GamePlayManager::LoadBlahBlah, + // cuz GamePlayManager will want to parse the level script which + // will register the modelgroups... + for( int i=0; i<PedestrianManager::MAX_MODEL_GROUPS; i++ ) + { + mModelGroups[i].numModels = 4; + mModelGroups[i].models[0].Init( "male1", 5 ); + mModelGroups[i].models[1].Init( "fem1", 5 ); + mModelGroups[i].models[2].Init( "boy1", 5 ); + mModelGroups[i].models[3].Init( "girl1", 5 ); + for( int j=4; j<PedestrianManager::MAX_MODELS; j++ ) + { + mModelGroups[i].models[j].InitZero(); + } + } +} + +void PedestrianManager::SetDefaultModelGroup( int toGroupID ) +{ + mDefaultModelGroup = toGroupID; +} + +void PedestrianManager::Init() +{ + mAllowAdd = true; + mMillisecondsTillRemove = PedestrianManager::MILLISECONDS_BETW_REMOVES; + mMillisecondsTillAdd = PedestrianManager::MILLISECONDS_BETW_ADDS; + + mMillisecondsPopulateWorld = PedestrianManager::MILLISECONDS_POPULATE_WORLD; + + mFreePeds.Clear(); + mActivePeds.Clear(); + + for( int i=0; i<PedestrianManager::MAX_MODELS_IN_USE; i++ ) + { + mModelsInUse[i].uid.SetText( NULL ); + mModelsInUse[i].currCount = 0; + } + mNumActiveModels = 0; + + // Recall that at this point, MissionManager has parsed the level script + // which may or may not have populated our model groups. Recall also that + // before that happened, we init all our model groups with a default group + // Therefore, we can safely switch our current model group to group 0 + // (it should exist one way or another). + + // switch to our group zero + UnregisterAllModels(); + + mCurrGroupID = -1; + SwitchModelGroup( mDefaultModelGroup ); // will set mCurrGroupID to 0 + + for( int i=0; i<PedestrianManager::MAX_PEDESTRIANS; i++ ) + { + PedestrianStruct* pedStr = &(mPeds[i]); + rAssert( pedStr != NULL ); + + Pedestrian* ped = pedStr->ped; + rAssert( ped != NULL ); + + char name[5]; + sprintf( name, "ped%d", i ); + name[4] = '\0'; + + // call AddCharacter with an initial, lightweight dummy model so + // we can init all the sim objects, the animation drivers, etc. + Character* character = ::GetCharacterManager()->AddCharacter( + CharacterManager::NPC, name, "npd", "npd" ); + + character->SetRole(Character::ROLE_PEDESTRIAN); + + rAssert( character != NULL ); + character->AddRef(); + character->SetController( pedStr->ped ); + character->mbAllowUnload = false; + pedStr->ped->SetCharacter( character ); + + int nameLen = strlen("npd"); + rAssert( nameLen <= PedestrianManager::MAX_STRING_LEN ); + strncpy( pedStr->modelName, "npd", PedestrianManager::MAX_STRING_LEN ); + pedStr->modelName[nameLen] = '\0'; + + ped->InitZero(); + + // add ped to list of free peds + pedStr->listIndex = mFreePeds.AddLast( pedStr ); + rAssert( pedStr->listIndex != -1 ); + + } +} + + + + +void PedestrianManager::Update( unsigned int milliseconds ) +{ +BEGIN_PROFILE( "Pedestrian Manager" ); + + //rTunePrintf( "<<<<<<<<<<<<<<<<<<<<<<< NUM ACTIVE PEDS %d >>>>>>>>>>>>>>>>>>>>>>\n", mActivePeds.GetNumElems() ); + + PedestrianStruct* pedStr = NULL; + int i = 0; + + // + // ================================ + // Get information about the player + // ================================ + // + 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(); + /* + SuperCam* superCam = ::GetSuperCamManager()->GetSCC(0)->GetActiveSuperCam(); + rAssert( superCam != NULL ); + + superCam->GetPosition( &pPos ); + superCam->GetVelocity( &pVel ); + pSpeed = pVel.Magnitude(); // *** SQUARE ROOT! *** + */ + + int debugCount, debugExpCount; + + // 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 * PedestrianManager::CENTER_OFFSET; + } + + + + // ==================================== + // Update PedestrianManager properties + // ====================================== + + if( mMillisecondsPopulateWorld > milliseconds ) + { + mMillisecondsPopulateWorld -= milliseconds; + } + else + { + mMillisecondsPopulateWorld = 0; + } + + + static bool remove = true; + remove = !remove; + + // + // ================================================== + // Remove active pedestrians that are outside radius + // ================================================== + // + if( remove && (mMillisecondsPopulateWorld == 0)) + { + mMillisecondsTillRemove = PedestrianManager::MILLISECONDS_BETW_REMOVES; + + debugCount = 0; + debugExpCount = mActivePeds.GetNumElems(); + + int j = mActivePeds.GetHead(); + while( 0 <= j && j < DListArray::MAX_ELEMS ) + { + debugCount++; + + pedStr = (PedestrianStruct*) mActivePeds.GetDataAt( j ); + rAssert( pedStr != NULL ); + + // Do radius test on character's bounding sphere + // versus pedestrian radius... + // + bool remove = false; + + rmt::Vector pedPos; + pedStr->ped->GetCharacter()->GetPosition( pedPos ); + + float distSqr = (center - pedPos).MagnitudeSqr(); + float zoneDistSqr = PedestrianManager::REMOVE_RADIUS * PedestrianManager::REMOVE_RADIUS; + if( distSqr >= zoneDistSqr ) + { + remove = true; + } + + // Do frustrum test too... if outside our frustrum, remove it within n seconds + + const float MAX_OUT_OF_SIGHT_MILLISECONDS = 2000; + + if( !pedStr->ped->GetCharacter()->mbInAnyonesFrustrum && + pedStr->ped->mMillisecondsOutOfSight > MAX_OUT_OF_SIGHT_MILLISECONDS ) + { + remove = true; + pedStr->ped->mMillisecondsOutOfSight = 0; + } + + j = mActivePeds.GetNextOf( j ); + + if( remove ) + { + #ifdef RAD_DEBUG + CheckModelCounts(); + #endif + DeactivatePed( pedStr ); + #ifdef RAD_DEBUG + CheckModelCounts(); + #endif + break; + } + } +// rAssert( debugCount == debugExpCount ); + } + else + { + // + // ==================== + // Add Pedestrians + // ==================== + // + if( mMillisecondsPopulateWorld > 0 ) + { + AddPeds( center, PedestrianManager::INITIAL_ADD_RADIUS ); + } + else + { + AddPeds( center, PedestrianManager::ADD_RADIUS ); + } + } + + + // + // ================================= + // Update active pedestrians: + // - fade them as necessary + // ================================= + // + i = mActivePeds.GetHead(); + while( 0 <= i && i < DListArray::MAX_ELEMS ) + { + pedStr = (PedestrianStruct*) mActivePeds.GetDataAt(i); + rAssert( pedStr != NULL ); + + if( pedStr->ped->GetIsActive() ) + { + //////////////////////////////////////////////////// + // Update out of sight stuff + if( !pedStr->ped->GetCharacter()->mbInAnyonesFrustrum ) + { + pedStr->ped->mMillisecondsOutOfSight += milliseconds; + } + else + { + pedStr->ped->mMillisecondsOutOfSight = 0; + } + + + //////////////////////////////////////////////////// + // Set fade if getting near fringe... + // + rmt::Vector myPos; + pedStr->ped->GetCharacter()->GetPosition( myPos ); + + float dist2Player = (pPos - myPos).Length(); // *** SQUARE ROOT! *** + + float fadeMinLimit = PedestrianManager::FADE_RADIUS; + float fadeMaxLimit = PedestrianManager::ADD_RADIUS + PedestrianManager::CENTER_OFFSET; + int fadeAlpha = 255; + if( fadeMinLimit <= dist2Player && dist2Player <= fadeMaxLimit ) + { + // if we're in the fading zone, gotta change fade alpha + float fadeRatio = (dist2Player - fadeMinLimit)/(fadeMaxLimit - fadeMinLimit); + fadeAlpha = (int) (255.0f * (1.0f - fadeRatio)); + } + else if( dist2Player > fadeMaxLimit ) + { + fadeAlpha = 0; + } + pedStr->ped->GetCharacter()->SetFadeAlpha( fadeAlpha ); + } + i = mActivePeds.GetNextOf( i ); + } + + + +END_PROFILE( "Pedestrian Manager" ); + +} + +void PedestrianManager::RemovePed( Character* character ) +{ + if( character == NULL ) + { + return; + } + int j = mActivePeds.GetHead(); + while( 0 <= j && j < DListArray::MAX_ELEMS ) + { + PedestrianStruct* pedStr = (PedestrianStruct*) mActivePeds.GetDataAt( j ); + rAssert( pedStr != NULL ); + + if( pedStr->ped->GetCharacter() == character ) + { + #ifdef RAD_DEBUG + CheckModelCounts(); + #endif + DeactivatePed( pedStr ); + #ifdef RAD_DEBUG + CheckModelCounts(); + #endif + return; + } + j = mActivePeds.GetNextOf( j ); + } +} + +void PedestrianManager::RemoveAllPeds() +{ + int j = mActivePeds.GetHead(); + while( 0 <= j && j < DListArray::MAX_ELEMS ) + { + PedestrianStruct* pedStr = (PedestrianStruct*) mActivePeds.GetDataAt( j ); + rAssert( pedStr != NULL ); + + j = mActivePeds.GetNextOf( j ); + + #ifdef RAD_DEBUG + CheckModelCounts(); + #endif + DeactivatePed( pedStr ); + #ifdef RAD_DEBUG + CheckModelCounts(); + #endif + } +} + +void PedestrianManager::AllowAddingPeds( bool allowed ) +{ + mAllowAdd = allowed; +} + + +// ************************ PRIVATES ************************* + +PedestrianManager::PedestrianManager() : + mMillisecondsTillRemove( 0 ), + mMillisecondsTillAdd( 0 ), + mMillisecondsPopulateWorld( 0 ), + mNumRegisteredModels( 0 ), + mCurrGroupID( -1 ), + mNumActiveModels( 0 ), + mAllowAdd( true ) +{ + // check some stuff once per compile.. + rAssert( PedestrianManager::ADD_RADIUS > 0 ); + rAssert( PedestrianManager::REMOVE_RADIUS > 0 ); + rAssert( PedestrianManager::MAX_STRING_LEN >= 6 ); + rAssert( 1 <= PedestrianManager::MAX_MODELS ); + rAssert( 0 <= PedestrianManager::MAX_PEDESTRIANS && + PedestrianManager::MAX_PEDESTRIANS <= DListArray::MAX_ELEMS ); + + // listen to appropriate events + GetEventManager()->AddListener( this, (EventEnum)( EVENT_LOCATOR + LocatorEvent::LOAD_PED_MODEL_GROUP ) ); + GetEventManager()->AddListener( this, EVENT_VEHICLE_DESTROYED ); + GetEventManager()->AddListener( this, EVENT_PLAYER_CAR_HIT_NPC ); + GetEventManager()->AddListener( this, EVENT_OBJECT_KICKED ); + GetEventManager()->AddListener( this, EVENT_PLAYER_VEHICLE_HORN ); + + /*** DEBUG *** + GetEventManager()->AddListener( this, (EventEnum)(EVENT_GETOUTOFVEHICLE_END) ); + *** DEBUG ***/ + + for( int i=0; i<PedestrianManager::MAX_PEDESTRIANS; i++ ) + { + mPeds[i].ped = new (GMA_LEVEL_OTHER) Pedestrian(); + mPeds[i].ped->AddRef(); + mPeds[i].listIndex = -1; + } +} + +PedestrianManager::~PedestrianManager() +{ + GetEventManager()->RemoveAll( this ); + + int i; + for( i=0; i<PedestrianManager::MAX_PEDESTRIANS; i++ ) + { + if( mPeds[i].ped != NULL ) + { + Character* character = mPeds[i].ped->GetCharacter(); + if( character != NULL ) + { + // Just release the pointer; don't bother calling + // RemoveCharacter(). The character gets cleaned up + // later by CharacterManager's destructor + character->Release(); + } + mPeds[i].ped->Release(); + } + } +} + +void PedestrianManager::DeactivatePed( PedestrianStruct* pedStr ) +{ + rAssert( pedStr != NULL ); + rAssert( pedStr->ped != NULL ); + + rAssert( 1 <= mNumActiveModels && mNumActiveModels <= PedestrianManager::MAX_MODELS_IN_USE ); + + //rDebugPrintf( "*** Removing %s\n", pedStr->modelName ); + + // remove ped from path + Path* path = pedStr->ped->GetPath(); + rAssert( path != NULL ); + bool res = path->RemovePedestrian(); + rAssert( res ); + + // reset ped parameters + pedStr->ped->Deactivate(); + +#ifdef RAD_DEBUG + CheckModelCounts(); +#endif + // If we can find the model in list of currently registered models, + // decrement count. The reason we need to search is that the model + // can be unregistered and re-registered in a different location. + // If we can't find it anywhere, then it must have been unregistered. + int modelID = GetModelIDByName( pedStr->modelName ); + if( modelID != -1 ) + { + mModels[modelID].currCount--; + } + else + { + rDebugPrintf( " Not found in model group, so I'll swap in npd!\n" ); + + #ifdef RAD_DEBUG + SanityCheck(); + #endif + // we're swapping out the model for npd... better update the modelsinuse list + FindModelInUseAndRemove( pedStr->modelName ); + + // since this model has been unregistered, it's not in the current + // model group, so keeping it around will only waste memory... + // we need to swap in "npd" in its place. + Character* character = pedStr->ped->GetCharacter(); + rAssert( character != NULL ); + ::GetCharacterManager()->SwapData( character, "npd", "npd" ); + sprintf( pedStr->modelName, "npd" ); + + #ifdef RAD_DEBUG + SanityCheck(); + #endif + } + + // manage the ped manager's active/free lists + res = mActivePeds.Remove( pedStr->listIndex ); + rAssert( res ); + pedStr->listIndex = mFreePeds.AddLast( pedStr ); + rAssert( pedStr->listIndex > -1 ); + +#ifdef RAD_DEBUG + CheckModelCounts(); +#endif + + +} + +float PedestrianManager::GetRandomMinDistSqr() +{ + static const float MINDISTSQR_FROM_PED_ON_SAME_PATH_A = 16.0f; + static const float MINDISTSQR_FROM_PED_ON_SAME_PATH_B = 49.0f; + static const float MINDISTSQR_FROM_PED_ON_SAME_PATH_C = 144.0f; + + int coinflip = rand() % 3; + float mindistsqr = 0.0f; + switch( coinflip ) + { + case 0: + { + mindistsqr = MINDISTSQR_FROM_PED_ON_SAME_PATH_A; + } + break; + case 1: + { + mindistsqr = MINDISTSQR_FROM_PED_ON_SAME_PATH_B; + } + break; + case 2: + { + mindistsqr = MINDISTSQR_FROM_PED_ON_SAME_PATH_C; + } + break; + default: + { + mindistsqr = MINDISTSQR_FROM_PED_ON_SAME_PATH_A; + } + break; + } + + return mindistsqr; +} + +bool PedestrianManager::ActivatePed( PathSegment* segment, rmt::Vector spawnPos ) +{ + rAssert( segment != NULL ); + + PedestrianStruct* pedStr = NULL; + Pedestrian* ped = NULL; + + // get the path for this pedestrian & try to add ped to it & + // store index + Path* path = segment->GetParentPath(); + rAssert( path != NULL ); + + // Check to make sure it isn't already at max ped limit + if( path->IsFull() ) + { + return false; + } + + /* + // Make sure we're not too close to another ped... + float minDistSqr = GetRandomMinDistSqr(); + rmt::Vector pedPos; + int i = mActivePeds.GetHead(); + while( 0 <= i && i < DListArray::MAX_ELEMS ) + { + pedStr = (PedestrianStruct*) mActivePeds.GetDataAt(i); + rAssert( pedStr != NULL ); + + pedStr->ped->GetCharacter()->GetPosition( &pedPos ); + pedPos.y = spawnPos.y; // ignore diff in y + + if( (pedPos-spawnPos).MagnitudeSqr() < minDistSqr ) + { + return false; + } + i = mActivePeds.GetNextOf( i ); + } + */ + + // make sure we're not spawning too close to another character + // or vehicle + float minDistSqr = GetRandomMinDistSqr(); + float distSqr = 100000.0f; + rmt::Vector hisPos; + + CharacterManager* cm = GetCharacterManager(); + int maxChars = cm->GetMaxCharacters(); + for( int i=0; i<maxChars; i++ ) + { + Character* c = cm->GetCharacter(i); + if( c ) + { + c->GetPosition( hisPos ); + hisPos.y = spawnPos.y; // ignore diff in y + + distSqr = (hisPos - spawnPos).MagnitudeSqr(); + if( distSqr < minDistSqr ) + { + return false; + } + } + } + + VehicleCentral* vc = GetVehicleCentral(); + for( int i=0; i<VehicleCentral::MAX_ACTIVE_VEHICLES; i++ ) + { + Vehicle* v = vc->GetVehicle(i); + if( v ) + { + v->GetPosition( &hisPos ); + hisPos.y = spawnPos.y; // ignore diff in y + + rmt::Sphere s; + v->GetBoundingSphere( &s ); + + distSqr = (hisPos - spawnPos).MagnitudeSqr(); + + minDistSqr = s.radius + 1.0f; + minDistSqr *= minDistSqr; + + if( distSqr < minDistSqr ) + { + return false; + } + } + } + + + + // Choose a model for our new pedestrian + int modelID = GetRandomModel(); + if( modelID <= -1 ) + { + return false; + } + + //*** Test *** + //rDebugPrintf( "Model randomly chosen: %d, %s\n", modelID, mModels[modelID].name ); + //*** Test *** + + + // if reached maxcount for this model, go to next model and use it instead + bool foundUsable = false; + for( int i=0; i<PedestrianManager::MAX_MODELS; i++ ) + { + if( !mModels[modelID].free && + mModels[modelID].currCount < mModels[modelID].maxCount ) + { + foundUsable = true; + break; + } + // go to next model + modelID = (modelID+1) % PedestrianManager::MAX_MODELS; + } + // if we never found a model with currCount < maxCount, then + // we can't actually do any spawning... + if( !foundUsable ) + { + rTunePrintf( "Can't spawn a new ped. Not enough Ped model " + "instances allowed in leveli.mfk. See Dusit or Sheik.\n" ); + return false; + } + + PedestrianStruct* freeNonNPDNotInCurrModelGroup = NULL; + PedestrianStruct* pedStrFoundInFreeList = NULL; + + // See if this model is already available in our freepeds list... + // If so, we don't need to call SwapData (save some processing time) + bool foundInFreeList = false; + int j = mFreePeds.GetHead(); + while( 0 <= j && j < DListArray::MAX_ELEMS ) + { + pedStr = (PedestrianStruct*) mFreePeds.GetDataAt(j); + rAssert( pedStr != NULL ); + if( !foundInFreeList && strcmp(pedStr->modelName, mModels[modelID].name)==0 ) + { + foundInFreeList = true; + pedStrFoundInFreeList = pedStr; + //rDebugPrintf( "*** Yes! We found an existing model %s in the freelist!\n", pedStr->modelName ); + //break; + } + else + { + if( strcmp(pedStr->modelName, "npd") != 0 && + GetModelIDByName( pedStr->modelName ) == -1 ) + { + freeNonNPDNotInCurrModelGroup = pedStr; + } + } + j = mFreePeds.GetNextOf( j ); + } + + // if none of the freepeds has the model we want, we need + // to swap out one of them and put in the model we want. + if( !foundInFreeList ) + { + // + // Since we never found the model we want in the list of free peds, + // we should try to find a "free" model (npd, or a model not + // currently in our model group, so we don't clobber another real + // model that might be wanted later).. + // + // We could achieve this by searching for models not in current group in free list... + // However, if we never found one, then we have to just pick one, preferrably an "npd" + // Since we always add the most recently removed ped to the END of the freepeds + // list, we should take from the FRONT of the freepeds list... sort of a mini-scheduling + // algorithm, ya know... + // + if( freeNonNPDNotInCurrModelGroup ) + { + pedStr = freeNonNPDNotInCurrModelGroup; + } + else + { + pedStr = (PedestrianStruct*) mFreePeds.GetFirst(); + } + rAssert( pedStr != NULL ); + bool res = SwapModels( modelID, pedStr ); + if( !res ) + { + return false; + } + } + else + { + pedStr = pedStrFoundInFreeList; + } + + ////////////////////////////////////////////////////////// + // NOTE: + // At this point we're ready to do the actual activating (no more bailing out) + + //*** Test *** + //rDebugPrintf( "*** Adding %s\n", mModels[modelID].name ); + //*** Test *** +#ifdef RAD_DEBUG + CheckModelCounts(); +#endif + + mModels[modelID].currCount++; + + rAssert( strcmp( pedStr->modelName, mModels[modelID].name ) == 0 ); + + rAssert( pedStr->ped != NULL ); + + // choose a random swatch + int swatch = rand() % CharacterRenderable::NUM_CHARACTER_SWATCHES; + pedStr->ped->GetCharacter()->SetSwatch( swatch ); + + // add ped to path + bool res = path->AddPedestrian( ); + rAssert( res ); + + /* + // get the segment's index (to the parent path's list of segments) + // this will be needed by Pedestrian::Update() when locomoting. + int pathSegmentIndex = segment->GetIndexToParentPath(); + rAssert( 0 <= pathSegmentIndex && pathSegmentIndex < path->GetNumPathSegments() ); + */ + + // set the ped's parameters + pedStr->ped->Activate( path, segment, spawnPos, pedStr->modelName ); + + // manage the ped manager's active/free list + res = mFreePeds.Remove( pedStr->listIndex ); + rAssert( res ); + + pedStr->listIndex = mActivePeds.AddLast( pedStr ); + rAssert( pedStr->listIndex != -1 ); + +#ifdef RAD_DEBUG + CheckModelCounts(); +#endif + return true; +} + +bool PedestrianManager::SwapModels( int modelID, PedestrianStruct* pedStr ) +{ + rAssert( pedStr != NULL ); + rTuneAssert( 0 <= modelID && modelID < PedestrianManager::MAX_MODELS ); + + Character* character = NULL; + +#ifdef RAD_DEBUG + SanityCheck(); +#endif + FindModelInUseAndRemove( pedStr->modelName ); + bool res = FindModelInUseAndAdd( mModels[modelID].name ); + if( res == false ) + { + // we already removed pedStr->modelName from list, but haven't really + // swapped him out... + // If he's in the list of currently registered models, we don't want + // to really swap him out anyway (so call FindModelInUseAndAdd(pedStr->modelName)) + // But if he's not in the list of currently registered models, we DO + // want to swap him out.. so call SwapData passing in "npd" + if( strcmp( pedStr->modelName, "npd" ) != 0 ) + { + int index = GetModelIDByName( pedStr->modelName ); + if( index == -1 ) + { + character = pedStr->ped->GetCharacter(); + rAssert( character != NULL ); + ::GetCharacterManager()->SwapData( character, "npd", "npd" ); + sprintf( pedStr->modelName, "npd" ); + } + else + { + res = FindModelInUseAndAdd( pedStr->modelName ); + rAssert( res ); + } + } + return false; + } + + character = pedStr->ped->GetCharacter(); + rAssert( character != NULL ); + ::GetCharacterManager()->SwapData( character, mModels[modelID].name, "npd" ); + + int nameLen = strlen( mModels[modelID].name ); + rAssert( nameLen <= PedestrianManager::MAX_STRING_LEN ); + strncpy( pedStr->modelName, mModels[modelID].name, PedestrianManager::MAX_STRING_LEN ); + pedStr->modelName[nameLen] = '\0'; + +#ifdef RAD_DEBUG + SanityCheck(); +#endif + + + return true; +} + +int PedestrianManager::GetRandomModel() +{ + if( mNumRegisteredModels < 1 ) + { + rTunePrintf( "PedestrianManager: You haven't registered a pedestrian model!\n" ); + return -1; + } + + // generate a number between 1 and mNumRegisteredModels + int num = (rand() % mNumRegisteredModels) + 1; + rAssert( 1 <= num && num <= mNumRegisteredModels ); + + // loop through models array counting till we get to num & return this modelID + int count = 0; + for(int i=0; i<PedestrianManager::MAX_MODELS; i++) + { + if( !mModels[i].free ) + { + count++; + } + if( count==num ) + { + return i; + } + } + rAssert( false ); // shouldn't get here + return -1; +} + +bool PedestrianManager::RegisterModel( const char* name, int maxCount ) +{ + rTuneAssert( name != NULL ); + rTuneAssert( maxCount > 0 ); + + // search existing SPARSE list for name + int freeIndex = -1; + for( int i=0; i<PedestrianManager::MAX_MODELS; i++ ) + { + if( mModels[i].free ) + { + freeIndex = i; + continue; + } + + // found an existing model registered under same name, so overwrite w/ new values + if( strcmp(mModels[i].name, name)==0 ) + { + mModels[i].maxCount = maxCount; + // currCount should be the same as there are active instances + #ifdef RAD_DEBUG + CheckModelCounts(); + #endif + return true; + } + } + + // If we haven't found an existing model, then we're adding one. + // See if we can... + if( mNumRegisteredModels >= PedestrianManager::MAX_MODELS ) + { + return false; + } + + if( 0 <= freeIndex && freeIndex < PedestrianManager::MAX_MODELS ) + { + // loop through activelist looking for this model and counting the + // number of instances... init it with this value + int count = 0; + int n = mActivePeds.GetHead(); + while( 0 <= n && n < DListArray::MAX_ELEMS ) + { + PedestrianStruct* pedStr = (PedestrianStruct*) mActivePeds.GetDataAt(n); + rAssert( pedStr ); + + if( strcmp( pedStr->modelName, name )==0 ) + { + count++; + } + + n = mActivePeds.GetNextOf( n ); + } + + mModels[freeIndex].Init( false, name, maxCount, count ); + mNumRegisteredModels++; + return true; + } + + return false; +} + +bool PedestrianManager::UnregisterModel( const char* name ) +{ + rAssert( name != NULL ); + + if( mNumRegisteredModels <= 0 ) + { + return false; + } + + // search existing SPARSE list for name + for( int i=0; i<PedestrianManager::MAX_MODELS; i++ ) + { + if( mModels[i].free ) + { + continue; + } + int nameLen = strlen( name ); + int modelNameLen = strlen( mModels[i].name ); + if( (nameLen == modelNameLen) && + (strncmp(mModels[i].name, name, nameLen)==0) ) + { + mNumRegisteredModels--; + mModels[i].InitZero(); + return true; + } + } + return false; +} + +bool PedestrianManager::UnregisterModel( int modelID ) +{ + rAssert( 0 <= modelID && modelID < PedestrianManager::MAX_MODELS ); + + if( mNumRegisteredModels <= 0 ) + { + return false; + } + if( mModels[modelID].free ) + { + return false; + } + mNumRegisteredModels--; + mModels[modelID].InitZero(); + return true; +} + +void PedestrianManager::UnregisterAllModels() +{ + mNumRegisteredModels = 0; + for( int i=0; i<PedestrianManager::MAX_MODELS; i++ ) + { + mModels[i].InitZero(); + } +} + +void PedestrianManager::SwitchModelGroup( int toGroupID ) +{ + rAssert( 0 <= toGroupID && toGroupID < PedestrianManager::MAX_MODEL_GROUPS ); +#ifdef RAD_DEBUG + CheckModelCounts(); +#endif + + // if already in this group, forget it + if( mCurrGroupID == toGroupID ) + { + return; + } + + // NOTE: + // Don't want to UnregisterAllModels here because if say male1 exists in new group as + // well as in old group, we'll be wiping its currcount. So just go around resetting + // maxcount to zero and let RegisterModel overwrite the ones that are pertinent. + // In the end, unregister everything that doesn't have maxcount > 0 + for( int i=0; i<PedestrianManager::MAX_MODELS; i++ ) + { + mModels[i].maxCount = 0; + } + for( int i=0; i<mModelGroups[toGroupID].numModels; i++ ) + { + RegisterModel( + mModelGroups[toGroupID].models[i].name, + mModelGroups[toGroupID].models[i].maxCount ); + } + for( int i=0; i<PedestrianManager::MAX_MODELS; i++ ) + { + if( mModels[i].maxCount <= 0 ) + { + UnregisterModel( i ); + } + } +#ifdef RAD_DEBUG + CheckModelCounts(); +#endif + mCurrGroupID = toGroupID; + +#ifdef RAD_DEBUG + char msg[256]; + sprintf( msg, "*** SWITCHING TO NEW MODEL GROUP: " ); + for( int i=0; i<mModelGroups[toGroupID].numModels; i++ ) + { + sprintf( msg, "%s%s ", msg, mModelGroups[toGroupID].models[i].name ); + } + sprintf( msg, "%s\n", msg ); + //rDebugPrintf( msg ); +#endif + + +} + +int PedestrianManager::GetModelIDByName( const char* name ) +{ + for( int i=0; i<PedestrianManager::MAX_MODELS; i++ ) + { + if( strcmp( mModels[i].name, name )==0 ) + { + return i; + } + } + return -1; +} + +void PedestrianManager::SetModelGroup( int groupID, const ModelGroup& group ) +{ + rAssert( 0 <= groupID && groupID < PedestrianManager::MAX_MODEL_GROUPS ); + rAssert( 0 <= group.numModels && group.numModels <= PedestrianManager::MAX_MODELS ); + + mModelGroups[groupID].numModels = group.numModels; + + for( int i=0; i<group.numModels; i++ ) + { + mModelGroups[groupID].models[i].Init( + group.models[i].name, group.models[i].maxCount ); + } + + for( int i=group.numModels ; i<PedestrianManager::MAX_MODELS; i++ ) + { + mModelGroups[groupID].models[i].InitZero(); + } +} + +//============================================================================= +// PedestrianManager::IsPed +//============================================================================= +// Description: Comment +// +// Parameters: ( Character* character ) +// +// Return: bool +// +//============================================================================= +bool PedestrianManager::IsPed( Character* character ) +{ + int i; + for (i = 0; i < MAX_PEDESTRIANS; ++i ) + { + if ( mPeds[ i ].ped->GetCharacter() == character ) + { + return true; + } + } + + return false; +} + + +void PedestrianManager::HandleEvent( EventEnum id, void* pEventData ) +{ + /*** DEBUG *** + switch( id ) + { + case EVENT_GETOUTOFVEHICLE_END: + { + static int count = 0; + SwitchModelGroup( count % 2 ); + count++; + } + break; + } + *** DEBUG ***/ + + // Handle the events appropriately: + // - determine which model group we want to switch to and call SwitchToGroup on it + switch ( id ) + { + case EVENT_LOCATOR + LocatorEvent::LOAD_PED_MODEL_GROUP: + { + PedGroupLocator* pLocator = (PedGroupLocator*)pEventData; + rAssert( pLocator != NULL ); + + unsigned int groupID = pLocator->GetGroupNum(); + if( pLocator->GetPlayerEntered() ) + { + SwitchModelGroup( (int)groupID ); + } + break; + } + case EVENT_VEHICLE_DESTROYED: + { + // spawn a pedestrian in th driver's seat when a traffic vehicle is destroyed + Vehicle* vehicle = (Vehicle*)pEventData; + if( (GetGameplayManager()->GetCurrentLevelIndex() != RenderEnums::L7) && + (strcmp(vehicle->GetDriverName(), "phantom") == 0) && + mActivePeds.GetNumElems() > 0 ) + { + int randPedNum = rand() % mActivePeds.GetNumElems(); + + Character* character = NULL; + + int count = 0; + int ind = mActivePeds.GetHead(); + while( 0 <= ind && ind <= MAX_PEDESTRIANS ) + { + if( count == randPedNum ) + { + PedestrianStruct* pedStr = (PedestrianStruct*) mActivePeds.GetDataAt( ind ); + character = pedStr->ped->GetCharacter(); + break; + } + count++; + ind = mActivePeds.GetNextOf( ind ); + } + + //Character* character = mPeds[rand() % MAX_PEDESTRIANS].ped->GetCharacter(); + + if(character) + { + vehicle->SetDriver(character); + + character->SetFadeAlpha( 255 ); // make sure we're not faded. + character->SetTargetVehicle( vehicle ); + character->SetInCar( true ); + character->UpdateTransformToInCar(); + character->GetStateManager()->SetState<CharacterAi::InCar>(); + } + } + } + break; + case EVENT_PLAYER_VEHICLE_HORN: // fall thru + case EVENT_PLAYER_CAR_HIT_NPC: // fall thru + case EVENT_OBJECT_KICKED: + { + static const float TRIGGER_PANIC_RADIUS_SQR = 144.0f; + + // Get player data + Avatar* player = GetAvatarManager()->GetAvatarForPlayer(0); + rmt::Vector playerPos; + player->GetPosition( playerPos ); + + // for any ped nearby, make him run away... + int i = mActivePeds.GetHead(); + while( 0 <= i && i <= DListArray::MAX_ELEMS ) + { + PedestrianStruct* pedStr = (PedestrianStruct*) mActivePeds.GetDataAt( i ); + rAssert( pedStr ); + + if( pedStr->ped->GetIsActive() ) + { + rmt::Vector pedPos; + pedStr->ped->GetCharacter()->GetPosition( pedPos ); + float distSqr = (playerPos - pedPos).MagnitudeSqr(); + + if( distSqr < TRIGGER_PANIC_RADIUS_SQR ) + { + pedStr->ped->IncitePanic(); + } + } + i = mActivePeds.GetNextOf( i ); + } + } + break; + + default: + { + break; + } + } +} + + +void PedestrianManager::AddPeds( rmt::Vector center, float addRadius ) +{ + if( CommandLineOptions::Get( CLO_NO_PEDS ) ) + { + return; + } + + if( !mAllowAdd ) + { + return; + } + + // if no more free pedestrians... + if( mFreePeds.GetNumElems() <= 0 ) + { + return; + } + + // Invoke search on DSG for path segments that intersect with + // our radius + ReserveArray<PathSegment*> orList; + ::GetIntersectManager()->FindPathSegmentElems( center, addRadius, orList ); + + // + // For each segment we "intersect" with (in the loose sense), we + // a) break if we have no more pedestrians to spawn + // b) break if segment (or path?) already has a pedestrian + // c) put pedestrian on the segment at the intersection point. + // d) set pedestrian's facing/velocity/position, whatever's necessary + // + if(orList.mUseSize == 0) + return; + + int which = rand() % orList.mUseSize; + + PathSegment* segment = NULL; + + segment = orList.mpData[which]; + rAssert( segment != NULL ); + + // determine intersection point of the path segment & ped radius + // at normalized value "t" + + rmt::Sphere s; + s.Set( center, addRadius ); + + rmt::Vector start, end; + segment->GetStartPos( start ); + segment->GetEndPos( end ); + + rmt::Vector intPts[2]; + int numIntersections = IntersectLineSphere( start, end, s, intPts ); + + // if no intersection, a mild surprise. + if( numIntersections <= 0 ) + { + return; + } + + // Just use the first intersection point + rmt::Vector spawnPos = intPts[0]; + float t = GetLineSegmentT( start, end, spawnPos ); + + // activate the pedestrian +#ifdef RAD_DEBUG + CheckModelCounts(); +#endif + + bool succeeded = ActivatePed( segment, spawnPos ); +#ifdef RAD_DEBUG + CheckModelCounts(); +#endif +} + +void PedestrianManager::FindModelInUseAndRemove( const char* name ) +{ + if( tName::MakeUID(name) == tName::MakeUID("npd") ) + { + return; + } + // should be able to find it in the list of models being used... + tUID uid = tEntity::MakeUID( name ); + bool foundUid = false; + + rAssert( 0 <= mNumActiveModels && mNumActiveModels <= PedestrianManager::MAX_MODELS_IN_USE ); + for( int i=0; i<mNumActiveModels; i++ ) + { + if( uid == mModelsInUse[i].uid.GetUID() ) + { + // count for this ped should be > 0 if it was found in this list + rAssert( mModelsInUse[i].currCount > 0 ); + mModelsInUse[i].currCount--; + + // if currCount is zero, remove it from list of models currently in use. + if( mModelsInUse[i].currCount == 0 ) + { + // swap last entry into this position + //rDebugPrintf( "*** Removing from ModelsInUse %s\n", name ); + mModelsInUse[i].currCount = mModelsInUse[mNumActiveModels-1].currCount; + mModelsInUse[i].uid = mModelsInUse[mNumActiveModels-1].uid; + mNumActiveModels--; + } + foundUid = true; + break; // NOTE: MUST break here since we may have modified mNumActiveModels + } + } + if( !foundUid ) + { + // should have found it! Something wrong with our code... + rAssert( false ); + } +} + +bool PedestrianManager::FindModelInUseAndAdd( const char* name ) +{ + if( strcmp( name, "npd" ) == 0 ) + { + return true; + } + + // PS2 memory can only support a few (4) models in use at all times... + // see if we have room for this model + bool founduid = false; + tUID uid = tEntity::MakeUID( name ); + + rAssert( 0 <= mNumActiveModels && mNumActiveModels <= PedestrianManager::MAX_MODELS_IN_USE ); + for( int i=0; i<mNumActiveModels; i++ ) + { + if( uid == mModelsInUse[i].uid.GetUID() ) + { + mModelsInUse[i].currCount++; + founduid = true; + break; + } + } + if( !founduid ) + { + // couldn't find it in list of models currently in use, so try to add it + // to the list. If we can't, we don't spawn this model + if( mNumActiveModels == PedestrianManager::MAX_MODELS_IN_USE ) + { + return false; + } + //rDebugPrintf( "*** Adding to ModelsInUse %s\n", name ); + mModelsInUse[mNumActiveModels].uid.SetText( name ); + mModelsInUse[mNumActiveModels].currCount = 1; + mNumActiveModels++; + } + return true; +} + +void PedestrianManager::SanityCheck() +{ +#ifdef RAD_DEBUG + // make sure every non-"npd" model in mPeds exists in ModelsInUse + char msg[256]; + for( int i=0; i<PedestrianManager::MAX_PEDESTRIANS; i++ ) + { + if( strcmp( mPeds[i].modelName, "npd" ) == 0 ) + { + continue; + } + + bool found = false; + for( int j=0; j<mNumActiveModels; j++ ) + { + tUID test = tEntity::MakeUID( mPeds[i].modelName ); + if( test == mModelsInUse[j].uid.GetUID() ) + { + sprintf( msg, "%s already came up once in mModelsInUse list\n", + mPeds[i].modelName ); + rAssertMsg( !found, msg); + + sprintf( msg, "UIDs equal but names are not! %s vs %s\n", + mPeds[i].modelName, mModelsInUse[j].uid.GetText() ); + rAssertMsg( strcmp( mPeds[i].modelName, mModelsInUse[j].uid.GetText() ) == 0, msg ); + found = true; + } + } + sprintf( msg, "%s in mPeds could not be found in mModelsInUse\n", + mPeds[i].modelName ); + rAssertMsg( found, msg ); + } + + // make sure every model in ModelsInUse is non-"npd" and exists in mPeds, with correct count + for( int i=0; i<mNumActiveModels; i++ ) + { + rAssertMsg( strcmp( mModelsInUse[i].uid.GetText(), "npd" ) != 0, + "Found npd in mModelsInUse\n" ); + + bool found = false; + int count = 0; + for( int j=0; j<PedestrianManager::MAX_PEDESTRIANS; j++ ) + { + tUID test = tEntity::MakeUID( mPeds[j].modelName ); + if( test == mModelsInUse[i].uid.GetUID() ) + { + count++; + sprintf( msg, "UIDs equal but names are not! %s vs %s\n", + mPeds[j].modelName, mModelsInUse[i].uid.GetText() ); + rAssertMsg( strcmp( mPeds[j].modelName, mModelsInUse[i].uid.GetText() ) == 0, msg ); + found = true; + } + } + sprintf( msg, "%s in mModelsInUse could not be found in mPeds\n", + mModelsInUse[i].uid.GetText() ); + rAssertMsg( found, msg ); + + sprintf( msg, "Not equals numbers of %s found in mPeds (%d) and mModelsInUse (%d)\n", + mModelsInUse[i].uid.GetText(), count, mModelsInUse[i].currCount ); + rAssertMsg( count == mModelsInUse[i].currCount, msg ); + } +#endif +} + + +void PedestrianManager::CheckModelCounts() +{ + // *** DEBUG *** + // do some checking here to make sure mModels' currcount is + // correct + for( int m=0; m<PedestrianManager::MAX_MODELS; m++ ) + { + if( mModels[m].free ) + { + continue; + } + int expectedCount = mModels[m].currCount; + + int count = 0; + int n = mActivePeds.GetHead(); + while( 0 <= n && n < DListArray::MAX_ELEMS ) + { + PedestrianStruct* pedStr = (PedestrianStruct*) mActivePeds.GetDataAt(n); + rAssert( pedStr ); + + if( strcmp( pedStr->modelName, mModels[m].name )==0 ) + { + count++; + } + + n = mActivePeds.GetNextOf( n ); + } + rAssert( count == expectedCount ); + } + // *** DEBUG *** +} + +void PedestrianManager::DumpAllPedModels() +{ + // deactivate all current peds + RemoveAllPeds(); + + // Swap "npd" in for the other models + for( int i=0; i<PedestrianManager::MAX_PEDESTRIANS; i++ ) + { + PedestrianStruct* pedStr = &(mPeds[i]); + rAssert( pedStr ); + + if( tName::MakeUID( pedStr->modelName ) == tName::MakeUID( "npd" ) ) + { + continue; + } + + #ifdef RAD_DEBUG + SanityCheck(); + #endif + // we're swapping out the model for npd... better update the modelsinuse list + FindModelInUseAndRemove( pedStr->modelName ); + + // since this model has been unregistered, it's not in the current + // model group, so keeping it around will only waste memory... + // we need to swap in "npd" in its place. + Character* character = pedStr->ped->GetCharacter(); + rAssert( character != NULL ); + ::GetCharacterManager()->SwapData( character, "npd", "npd" ); + sprintf( pedStr->modelName, "npd" ); + + #ifdef RAD_DEBUG + SanityCheck(); + #endif + } + +}
\ No newline at end of file diff --git a/game/code/worldsim/ped/pedestrianmanager.h b/game/code/worldsim/ped/pedestrianmanager.h new file mode 100644 index 0000000..41829e5 --- /dev/null +++ b/game/code/worldsim/ped/pedestrianmanager.h @@ -0,0 +1,241 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: pedestrianmanager.h +// +// Description: Defines Pedestrian Manager class +// +// History: 09/18/2002 + Created -- Dusit Eakkachaichanvet +// +//============================================================================= + + +#ifndef PEDESTRIANMANAGER_H +#define PEDESTRIANMANAGER_H + + +#include <worldsim/ped/pedestrian.h> +#include <roads/geometry.h> +#include <events/eventlistener.h> +#include <p3d/entity.hpp> +#include <string.h> + +class PathSegment; + +// Hey look! I'm a singleton! +class PedestrianManager : +public EventListener +{ + +// MEMBERS +public: + // STATICS + static const int MAX_PEDESTRIANS = 7; // should be <= DListArray::MAX_ELEMS; + static const int MAX_MODELS_IN_USE = 4; // number of distinct models we can current support at a time + static const int MAX_MODELS = 10; // num distinct models for 2 groups (while one takes over the other) + static const int MAX_STRING_LEN = 64; + static const int MAX_MODEL_GROUPS = 10; // number of different groups we allow to be defined + + static PedestrianManager* mInstance; + static const float FADE_RADIUS; + static const float CENTER_OFFSET; + static const float ADD_RADIUS; + static const float INITIAL_ADD_RADIUS; + static const float REMOVE_RADIUS; + static const unsigned int MILLISECONDS_PER_GROUND_INTERSECT; + static const unsigned int MILLISECONDS_BETW_ADDS; + static const unsigned int MILLISECONDS_BETW_REMOVES; + static const unsigned int MILLISECONDS_POPULATE_WORLD; + + // NON-STATICS + struct ModelData + { + ModelData() : maxCount( 0 ) {}; + char name[MAX_STRING_LEN+1]; + int maxCount; + void Init( const char* modelName, int max ) + { + rAssert( modelName != NULL ); + rAssert( max > 0 ); + + int nameLen = strlen( modelName ); + rAssert( nameLen <= PedestrianManager::MAX_STRING_LEN ); + strncpy( name, modelName, PedestrianManager::MAX_STRING_LEN ); + name[ nameLen ] = '\0'; + + maxCount = max; + } + void InitZero() + { + name[0] = '\0'; + maxCount = 0; + } + + }; + struct ModelGroup + { + ModelGroup() : numModels( 0 ) {}; + int numModels; + ModelData models[MAX_MODELS]; + }; + + // Need lists to keep track of inactive peds & active peds + // + // Whenever we set mPeds[i] to active, remove it from free list + // and add it to active list: + // + // mFreePeds.Remove(mPeds[i].listIndex); + // mPeds[i].listIndex = mActivePeds.AddLast( &mPeds[i] ); + // + // Whenever we set mPeds[i] to inactive, add it to free list & + // remove it from active list: + // + // mActivePeds.Remove(mPeds[i].listIndex); + // mPeds[i].listIndex = mFreePeds.AddLast(&mPeds[i]); + // + DListArray mFreePeds; + DListArray mActivePeds; + + // Pedestrian alone is not sufficient for our purposes... + struct PedestrianStruct + { + PedestrianStruct() : ped( NULL ), listIndex( -1 ) {}; + Pedestrian* ped; + int listIndex; // index to mActivePeds or mFreePeds depending on whether ped is active + char modelName [MAX_STRING_LEN+1]; // name of model + }; + + +// METHODS +public: + + // STATICS + static PedestrianManager* GetInstance(); + static void DestroyInstance(); + static int GetMaxPedestrians( void ) + { + return MAX_PEDESTRIANS; + } + + // NON-STATICS + + void Init(); // Initializes pedmanager itself; called from GamePlayContext::OnStart() + void Update( unsigned int milliseconds ); // called once per frame by GamePlayContext::Update() + + void RemovePed( Character* character ); + void RemoveAllPeds(); + void AllowAddingPeds( bool allowed ); + + void DumpAllPedModels(); // will remove all peds and dump their models (so revert all to "npd") + + ////////// EVENT LISTENER STUFF ///////////// + virtual void HandleEvent( EventEnum id, void* pEventData ); + ///////////////////////////////////////////// + + // Makes all model groups default to: + // numModels = 4 + // model 0 = male1, 2 + // model 1 = fem1, 1 + // model 2 = boy1, 1 + // model 3 = girl1, 1 + // called from LoadingContext::OnStart() + void InitDefaultModelGroups(); + + void SetModelGroup( int groupID, const ModelGroup& group ); + + bool IsPed( Character* character ); + + void SwitchModelGroup( int toGroupID ); + + static void SetDefaultModelGroup( int toGroupID ); + +// MEMBERS +private: + static int mDefaultModelGroup; + + unsigned int mMillisecondsTillRemove; + unsigned int mMillisecondsTillAdd; + + // all peds we'll ever use/need + PedestrianStruct mPeds[ MAX_PEDESTRIANS ]; + + unsigned int mMillisecondsPopulateWorld; + + struct Model + { + Model() : free( true ), maxCount( 0 ), currCount( 0 ) {}; + + bool free; // whether or not this model slot has been registered + char name[MAX_STRING_LEN+1]; // name of the model (duh) + int maxCount; // max ped instances of this model + int currCount; // current num instances of this model + void InitZero() + { + free = true; + name[0] = '\0'; + maxCount = 0; + currCount = 0; + } + void Init( bool isFree, const char* modelName, int max, int curr ) + { + rAssert( modelName != NULL ); + rAssert( max > 0 ); + rAssert( curr >= 0 ); + + free = isFree; + + int nameLen = strlen( modelName ); + rAssert( nameLen <= PedestrianManager::MAX_STRING_LEN ); + strncpy( name, modelName, PedestrianManager::MAX_STRING_LEN ); + name[ nameLen ] = '\0'; + + maxCount = max; + currCount = curr; + } + }; + Model mModels[MAX_MODELS]; // array of currently registered models (of current group) + int mNumRegisteredModels; // number of currently registerd models + + ModelGroup mModelGroups[MAX_MODEL_GROUPS]; // groups of models for swapping + int mCurrGroupID; + + struct UsedModel + { + UsedModel() : uid( 0 ), currCount( 0 ) {}; + tName uid; + int currCount; + }; + UsedModel mModelsInUse[ MAX_MODELS_IN_USE ]; + int mNumActiveModels; + + bool mAllowAdd; + +// METHODS +private: + PedestrianManager(); + virtual ~PedestrianManager(); + + void AddPeds( rmt::Vector center, float addRadius ); + void DeactivatePed( PedestrianStruct* ped ); + bool ActivatePed( PathSegment* segment, rmt::Vector spawnPos ); + + float GetRandomMinDistSqr(); + + int GetRandomModel(); + bool SwapModels( int modelID, PedestrianStruct* pedStr ); + + bool RegisterModel( const char* name, int maxCount ); + bool UnregisterModel( const char* name ); + bool UnregisterModel( int modelID ); + void UnregisterAllModels(); + int GetModelIDByName( const char* name ); + + void FindModelInUseAndRemove( const char* name ); + bool FindModelInUseAndAdd( const char* name ); + + void SanityCheck(); + void CheckModelCounts(); + +}; + +#endif //PEDESTRIANMANAGER_H diff --git a/game/code/worldsim/physicsairef.h b/game/code/worldsim/physicsairef.h new file mode 100644 index 0000000..bff6d06 --- /dev/null +++ b/game/code/worldsim/physicsairef.h @@ -0,0 +1,51 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: physicsairef.h +// +// Description: Blahblahblah +// +// History: 6/17/2002 + Created -- TBJ +// +//============================================================================= + +#ifndef PHYSICSAIREF_H +#define PHYSICSAIREF_H + +//======================================== +// Nested Includes +//======================================== + +//======================================== +// Forward References +//======================================== + +//============================================================================= +// +// Synopsis: Blahblahblah +// +//============================================================================= + +namespace PhysicsAIRef +{ + enum + { + redBrickPhizDefault = 0, + redBrickVehicle, + redBrickPhizVehicleGroundPlane, + redBrickPhizMoveableGroundPlane, // might not need to differnetiate here! + redBrickPhizFence, // might not need to differnetiate here! + redBrickPhizStatic = 1 << 16, + redBrickPhizMoveable = 1 << 17, // don't think we actually need to differentiate between moveable and moveableinstance + PlayerCharacter, + NPCharacter, + redBrickPhizMoveableAnim = 1 << 18, + CameraSphere, + StateProp, + ActorStateProp, + redBrickPhizLast + }; // phizGround, phizStatic, phizMoveableAnim, phizMoveable, phizCamera, phizLast }; +}; + + +#endif //PHYSICSAIREF_H diff --git a/game/code/worldsim/redbrick/allredbrick.cpp b/game/code/worldsim/redbrick/allredbrick.cpp new file mode 100644 index 0000000..27f9cca --- /dev/null +++ b/game/code/worldsim/redbrick/allredbrick.cpp @@ -0,0 +1,13 @@ +#include <worldsim/redbrick/geometryvehicle.cpp> +#include <worldsim/redbrick/physicslocomotion.cpp> +#include <worldsim/redbrick/physicslocomotioncontrollerforces.cpp> +#include <worldsim/redbrick/redbrickcollisionsolveragent.cpp> +#include <worldsim/redbrick/rootmatrixdriver.cpp> +#include <worldsim/redbrick/suspensionjointdriver.cpp> +#include <worldsim/redbrick/trafficlocomotion.cpp> +#include <worldsim/redbrick/vehicle.cpp> +#include <worldsim/redbrick/vehicleinit.cpp> +#include <worldsim/redbrick/vehiclelocomotion.cpp> +#include <worldsim/redbrick/wheel.cpp> +#include <worldsim/redbrick/vehicleeventlistener.cpp> +#include <worldsim/redbrick/trafficbodydrawable.cpp> diff --git a/game/code/worldsim/redbrick/geometryvehicle.cpp b/game/code/worldsim/redbrick/geometryvehicle.cpp new file mode 100644 index 0000000..8b7472b --- /dev/null +++ b/game/code/worldsim/redbrick/geometryvehicle.cpp @@ -0,0 +1,3384 @@ +/*=========================================================================== + geometryvehicle.cpp + + created Dec 7, 2001 + by Greg Mayer + + Copyright (c) 2001 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ +#include <camera/supercammanager.h> +#include <contexts/bootupcontext.h> +#include <worldsim/character/characterrenderable.h> +#include <worldsim/redbrick/geometryvehicle.h> +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/redbrick/wheel.h> +#include <worldsim/avatar.h> +#include <worldsim/avatarmanager.h> +#include <worldsim/vehiclecentral.h> +#include <render/RenderManager/RenderManager.h> + +#include <p3d/anim/compositedrawable.hpp> +#include <p3d/billboardobject.hpp> +#include <p3d/anim/multicontroller.hpp> +#include <p3d/anim/poseanimation.hpp> +#include <p3d/camera.hpp> +#include <p3d/directionallight.hpp> +#include <p3d/light.hpp> +#include <p3d/matrixstack.hpp> +#include <p3d/shadow.hpp> +#include <p3d/view.hpp> +#include <p3d/effects/particlesystem.hpp> +#include <pddi/pddi.hpp> +#include <p3d/utility.hpp> +#include <typeinfo> +#include <p3d/anim/visibilityanimation.hpp> + +#include <raddebug.hpp> +#include <raddebugwatch.hpp> +#include <mission/statepropcollectible.h> +#include <memory/srrmemory.h> + +#include <mission/gameplaymanager.h> +#include <render/particles/particlemanager.h> +#include <radmath/util.hpp> +#include <debug/debuginfo.h> +#include <worldsim/redbrick/trafficbodydrawable.h> +#include <worldsim/coins/sparkle.h> + +//#define SHADOW_EDITTING + +const int TYPICAL_NUMBER_OF_FRAME_CONTROLLERS = 16; + +// this is by artist design... the value is between 0 and 255 +static const int INCAR_ROOF_ALPHA = 100; +// The velocity at which variable emission particle systems +// reach their peak (13.5 meters/second ~ 50 kph) +static const float PARTICLE_SYSTEM_MAX_VELOCITY = 13.5f; +static const float MAX_Y_OFFSET = 0.25f; // Max distance the shadow will raise to avoid Z Chew. + +// Listing of special frame controllers +// This list might be best moved into some sort of script file or data chunk +// rather than being hardcoded if many more get added +// Currently its got framecontrollers for the flame effects on the hoverbike +static const GeometryVehicle::VehicleFrameController FRAME_CONTROLLER_DATA[] = +{ + // These are reversable framecontrollers + { "BQG_flame4Shape", NULL, true, eNone, 0, 1.0f, 0, 0 }, + { "BQG_flame3Shape", NULL, true, eNone, 0, 1.0f, 0, 0 }, + { "BQG_flame2Shape", NULL, true, eNone, 1.0f, 0, 0, 0 }, + { "BQG_flame1Shape", NULL, true, eNone, 1.0f, 0, 0, 0 }, + { "BQG_flame5Shape", NULL, true, eNone, 1.0f, 0, 0, 0 }, + { "BQG_flame6Shape", NULL, true, eNone, 1.0f, 0, 0, 0 }, + { "EFX_backfireSystShape", NULL, false, eBackfire, 0,0,0, 0 }, + { "BQG_backfireflashGroupShape", NULL, false, eBackfire, 0,0,0 }, + { "PTRN_ship", NULL, false, eNone, 0, 0, 1.0f/25.0f, 0 }, + { "PTRN_redbrick", NULL, false, eNone, 0, 0, 0.08f, 5.0f } + //, +// { "PTRN_ship", NULL, false, eNone, 2.0f,-2.0f,0,0 } +}; + +const rmt::Vector BARRACUDA_COLLECTIBLE_POS( 0, 0.4f, -3.0f ); +const rmt::Vector JEEP_COLLECTIBLE_POS( -0.237f, 0.216f, -2.243f ); +const rmt::Vector DUNE_COLLECTIBLE_ROT( rmt::DegToRadian( -90 ), rmt::DegToRadian( 0 ), rmt::DegToRadian( 0 )); +const rmt::Vector DUNE_COLLECTIBLE_POS( 0, 0.714f, 0); + +const float BARREL_RADIUS = 0.351f; + +const rmt::Vector WITCH_COLLECTIBLE_POS( 0, 0.4f, -1.31f - BARREL_RADIUS ); +const rmt::Vector COFFIN_COLLECTIBLE_POS( 0, 0.4f, -1.59f - BARREL_RADIUS ); +const rmt::Vector HALLO_COLLECTIBLE_POS( 0, 0.4f, -3.467f - BARREL_RADIUS ); + + +const rmt::Matrix DEFAULT_COLLECTIBLE_TRANSFORM( 0.9219f, -0.3875f, 0.0f, 0.0f, + 0.3797f, 0.9034f, -0.1994f, 0.0f, + 0.0773f, 0.1838f, 0.9799f, 0.0f, + 0.0f, 0.1270f, -2.6610f, 1.0f ); + + +static const int NUM_FRAME_CONTROLLERS = sizeof( FRAME_CONTROLLER_DATA ) / sizeof( FRAME_CONTROLLER_DATA[0] ); +static bool sbDrawVehicle = true; +static float refractiveIndex = 10.0f; + +//------------------------------------------------------------------------ +GeometryVehicle::GeometryVehicle(): + m_Collectible( NULL ), + m_EnvRef( 0x40 ) +{ + mCompositeDrawable = 0; + + mChassisGeometry = 0; + + + mAnim = 0; + + mAnimRevPerSecondBase = 1.0f; + + mVehicleOwner = 0; + + //mRevMult = 0.5f; + //mRevMult = 0.4f; + mRevMult = 0.1f; // good for frink - anyone else need this? + + + mHoodShader = 0; + mTrunkShader = 0; + mDoorPShader = 0; + mDoorDShader = 0; + + mHoodTextureDam = 0; + mTrunkTextureDam = 0; + mDoorPTextureDam = 0; + mDoorDTextureDam = 0; + + mHoodTextureNorm = 0; + mTrunkTextureNorm = 0; + mDoorPTextureNorm = 0; + mDoorDTextureNorm = 0; + + + mChassisShader = 0; + mChassisTextureNorm = 0; + mChassisTextureDam = 0; + + mParticleEmitter = 0; + mVariableEmissionParticleSystem = 0; + mEngineParticleAttr.mType = ParticleEnum::eNull; + mLeftWheelParticleAttr.mType = ParticleEnum::eNull; + mRightWheelParticleAttr.mType = ParticleEnum::eNull; + mTailPipeParticleAttr.mType = ParticleEnum::eNull; + + mSkidMarkGenerator = 0; + + mSpecialEffect = ParticleEnum::eNull; + + mLastPosition = rmt::Vector (0.0f, 0.0f, 0.0f); + mCurEnvMapRotation = 0.0f; + + mBrakeLightJoints[0] = -1; + mBrakeLightJoints[1] = -1; + mBrakeLightJoints[2] = -1; + mBrakeLightJoints[3] = -1; + + mReverseLightJoints[0] = -1; + mReverseLightJoints[1] = -1; + mReverseLightJoints[2] = -1; + mReverseLightJoints[3] = -1; + + mTrafficBodyDrawable = NULL; + mTrafficDoorDrawable = NULL; + + for( int i = 0; i < 4; ++i ) + { + mShadowPointAdjustments[ i ][ 0 ] = 0.0f; + mShadowPointAdjustments[ i ][ 1 ] = 0.0f; + } + + mFadeAlpha = 255; + + for( int i=0; i<NUM_BRAKELIGHT_BBQS; i++ ) + { + mBrakeLights[i] = NULL; + } + +#ifdef RAD_WIN32 + mFrinkArc = NULL; +#endif + + mUsingTrafficModel = false; + + /* + for( int i=0; i<NUM_HEADLIGHT_BBQGS; i++ ) + { + mHeadLights[i] = NULL; + } + */ + + mHasGhostGlow = false; + for( int i=0; i<NUM_GHOSTGLOW_BBQGS; i++ ) + { + mGhostGlows[i] = NULL; + } + + mHasNukeGlow = false; + for( int i=0; i<NUM_NUKEGLOW_BBQGS; i++ ) + { + mNukeGlows[i] = NULL; + } + + mBrakeLightsOn = false; + mBrakeLightScale = 0.0f; + mHeadLightScale = 0.0f; + mEnableLights = true; // sets visibility on headlights and brakelights + mLightsOffDueToDamage = false; + + mRoofShader = NULL; + mRoofAlpha = 255; + mRoofTargetAlpha = 255; + + mRoofOpacShape = NULL; + mRoofAlphaShape = NULL; + + for( int i = 0; i < MAX_REFRACTION_SHADERS; ++i ) + { + mRefractionShader[ i ] = NULL; + } + // Lets make a collectible transform + // Hard code it for now. Later use car specific ones from Kevin + m_CollectibleTransform = DEFAULT_COLLECTIBLE_TRANSFORM; + + mFrameControllers.reserve( TYPICAL_NUMBER_OF_FRAME_CONTROLLERS ); + + // + // Add refractive index to the watcher + // + static bool refractiveIndexAdded = false; + if( !refractiveIndexAdded ) + { + radDbgWatchAddFloat( &refractiveIndex, "RefractionShader", "RefraciveIndex", 0, 0, -20.0f, 20.0f ); + refractiveIndexAdded = true; + } +} + + +//------------------------------------------------------------------------ +GeometryVehicle::~GeometryVehicle() +{ +#ifdef SHADOW_EDITTING + for( int i = 0; i < 4; ++i ) + { + radDbgWatchDelete( &(mShadowPointAdjustments[ i ][ 0 ]) ); + radDbgWatchDelete( &(mShadowPointAdjustments[ i ][ 1 ]) ); + } +#endif + if(mCompositeDrawable) + { + GetRenderManager()->mEntityDeletionList.Add((tRefCounted*&)mCompositeDrawable); + mCompositeDrawable = 0; + } + + if(mVariableEmissionParticleSystem) + { + GetRenderManager()->mEntityDeletionList.Add((tRefCounted*&)mVariableEmissionParticleSystem); + mVariableEmissionParticleSystem = 0; + } + + if(mChassisGeometry) + { + mChassisGeometry->Release(); + mChassisGeometry = 0; + } + + if(mAnim) + { + mAnim->Release(); + mAnim = 0; + } + + + + for( int i = 0; i < MAX_REFRACTION_SHADERS; ++i ) + { + tRefCounted::Release( mRefractionShader[ i ] ); + mRefractionShader[ i ] = 0; + } + + // damage state crapola + + if(mHoodShader) mHoodShader->Release(); + if(mTrunkShader) mTrunkShader->Release(); + if(mDoorPShader) mDoorPShader->Release(); + if(mDoorDShader) mDoorDShader->Release(); + + if(mHoodTextureNorm) mHoodTextureNorm->Release(); + if(mTrunkTextureNorm) mTrunkTextureNorm->Release(); + if(mDoorPTextureNorm) mDoorPTextureNorm->Release(); + if(mDoorDTextureNorm) mDoorDTextureNorm->Release(); + + if(mHoodTextureDam) mHoodTextureDam->Release(); + if(mTrunkTextureDam) mTrunkTextureDam->Release(); + if(mDoorPTextureDam) mDoorPTextureDam->Release(); + if(mDoorDTextureDam) mDoorDTextureDam->Release(); + + + if(mChassisShader) mChassisShader->Release(); + if(mChassisTextureNorm) mChassisTextureNorm->Release(); + if(mChassisTextureDam) mChassisTextureDam->Release(); + + delete mParticleEmitter; + if ( mSpecialEffect != ParticleEnum::eNull ) + { + GetParticleManager()->DeleteSystem( mSpecialEffect ); + } + + if ( mVariableEmissionParticleSystem != 0 ) + { + mVariableEmissionParticleSystem->ReleaseParticles(); + mVariableEmissionParticleSystem = 0; + } + + if(mSkidMarkGenerator) + { + delete mSkidMarkGenerator; + } + + if( mTrafficBodyDrawable ) + { + mTrafficBodyDrawable->Release(); + mTrafficBodyDrawable = NULL; + } + + if( mTrafficDoorDrawable ) + { + mTrafficDoorDrawable->Release(); + mTrafficDoorDrawable = NULL; + } + + for( int i=0; i<NUM_BRAKELIGHT_BBQS; i++ ) + { + if( mBrakeLights[i] ) + { + mBrakeLights[i]->Release(); + mBrakeLights[i] = NULL; + } + } + +#ifdef RAD_WIN32 + if( mFrinkArc ) + { + mFrinkArc->Release(); + mFrinkArc = NULL; + } +#endif + /* + for( int i=0; i<NUM_HEADLIGHT_BBQGS; i++ ) + { + if( mHeadLights[i] ) + { + mHeadLights[i]->Release(); + mHeadLights[i] = NULL; + } + } + */ + for( int i=0; i<NUM_GHOSTGLOW_BBQGS; i++ ) + { + if( mGhostGlows[i] ) + { + mGhostGlows[i]->Release(); + mGhostGlows[i] = NULL; + } + } + for( int i=0; i<NUM_NUKEGLOW_BBQGS; i++ ) + { + if( mNukeGlows[i] ) + { + mNukeGlows[i]->Release(); + mNukeGlows[i] = NULL; + } + } + if( mRoofShader ) + { + mRoofShader->Release(); + mRoofShader = NULL; + } + if( mRoofOpacShape ) + { + mRoofOpacShape = NULL; + } + if( mRoofAlphaShape ) + { + mRoofAlphaShape = NULL; + } + + + for ( unsigned int fc = 0 ; fc < mFrameControllers.size() ; fc++ ) + { + mFrameControllers[fc].frameController->Release(); + } + + if ( m_Collectible != NULL ) + { + m_Collectible->Release(); + m_Collectible = NULL; + } +} + + +//------------------------------------------------------------------------ +bool GeometryVehicle::Init( const char* name, Vehicle* owner, int num) +{ +#ifdef SHADOW_EDITTING + for( int i = 0; i < 4; ++i ) + { + mShadowPointAdjustments[ i ][ 0 ] = 0.0f; + mShadowPointAdjustments[ i ][ 1 ] = 0.0f; + char text[ 128 ]; + sprintf( text, "%s Shadow point %d X", name, i ); + radDbgWatchAddFloat( &(mShadowPointAdjustments[ i ][ 0 ]), text, "VehicleShadow", 0, 0, -5.0f, 5.0f ); + sprintf( text, "%s Shadow point %d Y", name, i ); + radDbgWatchAddFloat( &(mShadowPointAdjustments[ i ][ 1 ]), text, "VehicleShadow", 0, 0, -5.0f, 5.0f ); + } +#endif + mVehicleOwner = owner; + + mCurEnvMapRotation = 0.0f; + + return GetArt(name); +} + + +//============================================================================= +// GeometryVehicle::InitSkidMarks +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::InitSkidMarks() +{ + // called from vehicle Init + // only for user vehicles + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + mSkidMarkGenerator = new(gma)SkidMarkGenerator; + + int i; + for(i = 0; i < 4; i++) + { + // initial value + + rmt::Vector offset = mVehicleOwner->mSuspensionRestPoints[i]; + offset.y -= mVehicleOwner->mWheels[i]->mRadius; + + mSkidMarkGenerator->SetWheelOffset(i, offset); // need to do this everyframe + + float length = mVehicleOwner->mWheels[i]->mRadius * 0.6f; + //float width = mVehicleOwner->mWheels[i]->mRadius * 0.5f; //?? + float width = mVehicleOwner->mWheels[i]->mRadius * 0.8f; //?? + + mSkidMarkGenerator->SetWheelDimensions(i, width, length); + + //void GenerateSkid( int wheel, const SkidData& ); + + //void Update(); + + } + + +} + + +//============================================================================= +// GeometryVehicle::InitParticles +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::InitParticles() +{ +MEMTRACK_PUSH_GROUP( "GeometryVehicle" ); + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + mParticleEmitter = new(gma)VehicleParticleEmitter; + + rmt::Vector smokeOffset = mVehicleOwner->GetSmokeOffset(); + + mParticleEmitter->SetPartLocation(VehicleParticleEmitter::eEngine, smokeOffset); + + rmt::Vector wheelOffset = mVehicleOwner->GetWheel0Offset(); + mParticleEmitter->SetPartLocation(VehicleParticleEmitter::eRightBackTire, wheelOffset); + + wheelOffset = mVehicleOwner->GetWheel1Offset(); + mParticleEmitter->SetPartLocation(VehicleParticleEmitter::eLeftBackTire, wheelOffset); +MEMTRACK_POP_GROUP("GeometryVehicle"); +} + + + +//============================================================================= +// GeometryVehicle::GetHeadlightScale +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: float +// +//============================================================================= +float GeometryVehicle::GetHeadlightScale() +{ + return mHeadLightScale; +} + + +//============================================================================= +// GeometryVehicle::SetHeadlightScale +//============================================================================= +// Description: Comment +// +// Parameters: ( float scale ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::SetHeadlightScale( float scale ) +{ + rAssert( 0.0f <= scale ); + this->mHeadLightScale = scale; +} + + +//============================================================================= +// GeometryVehicle::AttachCollectible +//============================================================================= +// Description: Comment +// +// Parameters: ( StatePropCollectible* drawable ) +// +// Return: bool +// +//============================================================================= +bool GeometryVehicle::AttachCollectible( StatePropCollectible* drawable ) +{ + bool wasAttached; + // We only have one slot for a collectible per vehicle + // check that this vehicle's slot is currently empty + if ( m_Collectible == NULL ) + { + tRefCounted::Assign( m_Collectible, drawable ); + rmt::Matrix identity; + identity.Identity(); + drawable->SetTransform( identity ); + m_Collectible->EnableCollisionVolume( false ); + m_Collectible->SetState( 1 ); + m_Collectible->RemoveFromDSG(); + m_Collectible->EnableHudIcon( false ); + m_Collectible->EnableCollisionTesting( false ); + wasAttached = true; + } + else + { + wasAttached = false; + } + + return wasAttached; +} + + +//============================================================================= +// GeometryVehicle::GetAttachedCollectible +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: StatePropCollectible +// +//============================================================================= +StatePropCollectible* GeometryVehicle::GetAttachedCollectible() +{ + return m_Collectible; +} + + +//============================================================================= +// GeometryVehicle::DetachCollectible +//============================================================================= +// Description: Comment +// +// Parameters: ( const rmt::Vector& velocity, bool explode ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::DetachCollectible( const rmt::Vector& velocity, bool explode ) +{ + if ( m_Collectible ) + { + m_Collectible->EnableHudIcon( false ); + m_Collectible->EnableCollisionVolume( false ); + m_Collectible->SetState( 0 ); + sim::SimState* sim = m_Collectible->GetSimState(); + if ( sim ) + { + sim->ResetVelocities(); + } + m_Collectible->AddToDSG(); + // Lets set the transform to have no rotation, but position remains the same + rmt::Matrix transform; + transform.Identity(); + transform.FillTranslate( m_CollectibleTransform.Row(3) + mVehicleOwner->GetPosition() ); + m_Collectible->SetTransform(transform); + + m_Collectible->Release(); + if ( explode ) + m_Collectible->Explode(); + + m_Collectible = NULL; + } +} + + +//============================================================================= +// GeometryVehicle::Display +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::Display() +{ +BEGIN_PROFILE("GeometryVehicle::Display SetUp") + rmt::Vector pos = mVehicleOwner->GetPosition (); + rmt::Vector movement = pos - mLastPosition; + bool smokeFirst = true; // Used to fix draw order problem with the smoke and a vehicle's windshield. + tCamera* camera = p3d::context->GetView()->GetCamera(); + rAssert( camera ); + const rmt::Matrix& camTrans = camera->GetCameraToWorldMatrix(); + + if( mVehicleOwner->mVehicleType == VT_USER ) + { + // Set the rotation vector on all the car's shaders. + // Base the rotation on how far the car has moved in the last known direction, + // so that the reflection map moves across the car when the car moves. -- jdy + // + + bool fucked = false; + if ( mLastPosition.MagnitudeSqr() != 0.0f && + ( rmt::Fabs( movement.x ) > 1000.0f || + rmt::Fabs( movement.y ) > 1000.0f || + rmt::Fabs( movement.z ) > 1000.0f ) ) + { + //TODO: What the hell is causing this? + //mVehicleOwner position and facing are fucked. + //We've moved too far. Clamp. + pos = mLastPosition; + fucked = true; + } + + if ( !fucked ) + { + float distance = movement.DotProduct( mVehicleOwner->GetFacing() ); + rAssert( !rmt::IsNan( distance ) ); + if( rmt::IsNan( distance ) ) + { + distance = 0.0f; + } + if( rmt::Fabs( distance ) > 0.01f && rmt::Fabs( distance ) < 50.0f ) // Car must move at least 1 cm for an adjustment to be made + { + // Find the normal of the plane defined by the direction of movement and the camera facing. + //This will be the axis we rotate around. + // Note that the reflection map is relative to the object so we need to transform everything + //into the object's space. + rmt::Matrix carOri = mVehicleOwner->GetTransform(); + carOri.Invert(); + carOri.FillTranslate( rmt::Vector( 0.0f, 0.0f, 0.0f ) ); + rmt::Vector camRelCar; + carOri.Transform( camTrans.Row( 2 ), &camRelCar ); + rmt::Vector carRelMovement; + carOri.Transform( movement, &carRelMovement ); + rmt::Vector axis; + float camMovementFacing = carRelMovement.Dot( camRelCar ); + if( camMovementFacing < 0.0f ) + { + axis.CrossProduct(camRelCar, carRelMovement); + } + else + { + axis.CrossProduct(carRelMovement, camRelCar); + } + // Check if the car is moving towards or away from the camera. + mCurEnvMapRotation += distance * 0.05f; + while( mCurEnvMapRotation >= rmt::PI_2 ) + { + mCurEnvMapRotation -= rmt::PI_2; + } + while( mCurEnvMapRotation < 0.0f ) + { + mCurEnvMapRotation += rmt::PI_2; + } + float mag; + float angle; + CartesianToPolar( axis.x, axis.y, &mag, &angle ); + + tShaderVectorBroadcast cbPos( PDDI_SP_ROTVEC, rmt::Vector( mCurEnvMapRotation, 0.0f, -angle ) ); + int count = mCompositeDrawable->GetNumDrawableElement (); + for (int i = 0; i < count; i++) + { + tCompositeDrawable::DrawableElement* pElement = mCompositeDrawable->GetDrawableElement( i ); + tDrawable* pDrawable = pElement->GetDrawable(); + pDrawable->ProcessShaders( cbPos ); + } + } + // Remember the position of the car for next time + // + mLastPosition = pos; + } + } + else + { + mLastPosition = pos; + } + + rmt::Vector vehicleVelocity; + mVehicleOwner->GetVelocity( &vehicleVelocity ); + + if(mEngineParticleAttr.mType != ParticleEnum::eNull) + { + // Figure out which to draw first, the smoke or the car. + //We do this by transforming the SmokeOffset into world space and then dot product with the + //the camera's facing vector. If it is towards the camera we draw the smoke after the car. + //Crude, but quick and hopefully there won't be too many visual artifacts. + rmt::Matrix facing = mVehicleOwner->mTransform; + facing.FillTranslate( rmt::Vector( 0.0f, 0.0f, 0.0f ) ); + rmt::Vector smoke = mVehicleOwner->GetSmokeOffset(); + facing.Transform( smoke, &smoke ); + float dot = smoke.DotProduct( camTrans.Row( 2 ) ); + smokeFirst = dot > 0.6f; + + if( ( mEngineParticleAttr.mType == ParticleEnum::eEngineSmokeLight ) || + ( mEngineParticleAttr.mType == ParticleEnum::eEngineSmokeMedium ) ) + { + GetSparkleManager()->AddSmoke( mVehicleOwner->mTransform, mVehicleOwner->GetSmokeOffset(), movement, ( ( 0.4f - mVehicleOwner->GetVehicleLifePercentage( mVehicleOwner->mHitPoints ) ) * 2.5f ) ); + } + else + { + // draw some particles + //mEngineParticleAttr.mVelocity = vehicleVelocity / 45.0f; + mParticleEmitter->Generate( VehicleParticleEmitter::eEngine, + mEngineParticleAttr, mVehicleOwner->mTransform); + + + // TODO - HOW ABOUT TRAFFIC - WITH THE ONE-TIME POOF!! DON'T DRAW CONTINUOUSLY + } + } + if ( mSpecialEffect != ParticleEnum::eNull ) + { + ParticleAttributes attr; + attr.mType = mSpecialEffect; + + mParticleEmitter->Generate( VehicleParticleEmitter::eSpecialEmitter, + attr, mVehicleOwner->mTransform); + + } + // If this vehicle has tailpipe particles, generate them, always + + // 31.25 (trails too long) + //mTailPipeParticleAttr.mVelocity = vehicleVelocity / 29.0f; + + if ( mTailPipeParticleAttr.mType != ParticleEnum::eNull ) + { + mParticleEmitter->Generate( VehicleParticleEmitter::eRightTailPipe, + mTailPipeParticleAttr, mVehicleOwner->mTransform ); + + mParticleEmitter->Generate( VehicleParticleEmitter::eLeftTailPipe, + mTailPipeParticleAttr, mVehicleOwner->mTransform ); + } + + if(mLeftWheelParticleAttr.mType != ParticleEnum::eNull) + { + mParticleEmitter->Generate( VehicleParticleEmitter::eLeftBackTire, + mLeftWheelParticleAttr, mVehicleOwner->mTransform); + } + if(mRightWheelParticleAttr.mType != ParticleEnum::eNull) + { + mParticleEmitter->Generate( VehicleParticleEmitter::eRightBackTire, + mRightWheelParticleAttr, mVehicleOwner->mTransform); + } + +// if( mVehicleOwner->mVehicleType == VT_USER ) + { + // Change the vehicle environment reflection + //depending on if it is interior or damaged. + unsigned char blend; + if( !mVehicleOwner->mInterior ) + { + blend = m_EnvRef; + } + else + { + blend = m_EnvRef >> 2; // Quarter value for interior. + } + float damage = mVehicleOwner->GetVehicleLifePercentage(mVehicleOwner->mHitPoints); + damage = rmt::Clamp( ( damage * 0.75f ) + 0.25f, 0.0f, 1.0f ); + blend = (unsigned char)( blend * damage ); + tShaderColourBroadcast envBlend( PDDI_SP_ENVBLEND, tColour( blend, blend, blend ) ); + int count = mCompositeDrawable->GetNumDrawableElement (); + for (int i = 0; i < count; i++) + { + tCompositeDrawable::DrawableElement* pElement = mCompositeDrawable->GetDrawableElement (i); + tDrawable* pDrawable = pElement->GetDrawable (); + pDrawable->ProcessShaders( envBlend ); + } + } + +END_PROFILE("GeometryVehicle::Display SetUp") +BEGIN_PROFILE("GeometryVehicle::Display Render") + + if( mUsingTrafficModel ) + { + if( mTrafficBodyDrawable != NULL ) + { + if( mFadeAlpha != 255 ) + { + mTrafficBodyDrawable->mFading = true; + } + else + { + mTrafficBodyDrawable->mFading = false; + } + mTrafficBodyDrawable->mFadeAlpha = mFadeAlpha; + } + if( mTrafficDoorDrawable != NULL ) + { + if( mFadeAlpha != 255 ) + { + mTrafficDoorDrawable->mFading = true; + } + else + { + mTrafficDoorDrawable->mFading = false; + } + mTrafficDoorDrawable->mFadeAlpha = mFadeAlpha; + } + + + // NOTE: + // This is a big cause for major grievances. It will force the blend mode of + // all the shaders of the compositedrawable's elements to be BLEND_ALPHA... + // bad news for billboards that are BLEND_NONE or BLEND_ADD... + // + tShaderIntBroadcast blendModeAlpha( PDDI_SP_BLENDMODE, PDDI_BLEND_ALPHA ); + mCompositeDrawable->ProcessShaders( blendModeAlpha ); + + tShaderIntBroadcast emissiveAlpha( PDDI_SP_EMISSIVEALPHA, mFadeAlpha ); + mCompositeDrawable->ProcessShaders( emissiveAlpha ); + } + + +#ifdef RAD_WIN32 + const tName& name = mVehicleOwner->GetNameObject(); + if( name == "frink_v" ) + { + float topSpeed = mVehicleOwner->GetTopSpeed(); + float percentOfTopSpeed = vehicleVelocity.Magnitude() / topSpeed; + float refraction = percentOfTopSpeed; + refraction = rmt::Clamp( refraction, 0.0f, 1.0f ); + + int emissiveAlpha = (int)( 255.0f * (1.0f - refraction) ); + + tShaderIntBroadcast blendMode( PDDI_SP_BLENDMODE, PDDI_BLEND_ALPHA ); + tShaderIntBroadcast emissive( PDDI_SP_EMISSIVEALPHA, emissiveAlpha ); + int count = mCompositeDrawable->GetNumDrawableElement (); + for (int j = 0; j < count; j++) + { + tCompositeDrawable::DrawableElement* pElement = mCompositeDrawable->GetDrawableElement( j ); + tDrawable* pDrawable = pElement->GetDrawable(); + pDrawable->ProcessShaders( blendMode ); + pDrawable->ProcessShaders( emissive ); + } + + // so since we just made EVERY SHADER alpha blend, we need to take + // care of the BBQGs (that need to be additive blended) + if( mFrinkArc ) + { + tColour arcColour; + for( int j=0; j< NUM_FRINKARC_BBQS; j++ ) + { + tBillboardQuad* bbq = mFrinkArc->GetQuad(j); + rAssert( bbq ); + + arcColour.Set( + rmt::Clamp( (int)(mOriginalFrinkArcColour[j].Red()*(1.0f - refraction)), 0, 255 ), + rmt::Clamp( (int)(mOriginalFrinkArcColour[j].Green()*(1.0f - refraction)), 0, 255 ), + rmt::Clamp( (int)(mOriginalFrinkArcColour[j].Blue()*(1.0f - refraction)), 0, 255 ), + rmt::Clamp( (int)(mOriginalFrinkArcColour[j].Alpha()*(1.0f - refraction)), 0, 255 ) + ); + bbq->SetColour( arcColour ); + } + mFrinkArc->GetShader()->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ADD ); + } + } +#endif + + + + + + tColour brakecolour; + + // if not turning on brakelights, we need to take into account the daytime + // running lights settings + float dayReduce = 2.5f; // by default, if braking is on, we magnify this value + if( !mBrakeLightsOn ) + { + dayReduce = mBrakeLightScale; + } + float fadeFactor = mFadeAlpha / 255.0f; +BEGIN_PROFILE("GeometryVehicle::Breaklights") + for( int i=0; i<NUM_BRAKELIGHT_BBQS; i++ ) + { + if( mBrakeLights[i] != NULL ) + { + brakecolour.Set( + rmt::Clamp( (int)(mOriginalBrakeLightColours[i].Red()*fadeFactor*dayReduce), 0, 255 ), + rmt::Clamp( (int)(mOriginalBrakeLightColours[i].Green()*fadeFactor*dayReduce), 0, 255 ), + rmt::Clamp( (int)(mOriginalBrakeLightColours[i].Blue()*fadeFactor*dayReduce), 0, 255 ), + rmt::Clamp( (int)(mOriginalBrakeLightColours[i].Alpha()*fadeFactor*dayReduce), 0, 255 ) + ); + for( int j=0; j<mBrakeLights[i]->GetNumQuads(); j++ ) + { + tBillboardQuad* bbq = mBrakeLights[i]->GetQuad( j ); + rAssert( bbq != NULL ); + bbq->SetColour( brakecolour ); + bbq->SetVisibility( mEnableLights ); + } + + // ProcessShaders screwed us by causing us to lose the additive blend for + // brakelights... We set it back. + mBrakeLights[i]->GetShader()->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ADD ); + } + } +END_PROFILE("GeometryVehicle::Breaklights") + +BEGIN_PROFILE("GeometryVehicle::Headlights") + tColour headlightcolour; + int count = 0; + for( int i=0; i<VehicleCentral::NUM_HEADLIGHT_BBQGS; i++ ) + { + if( GetVehicleCentral()->mHeadLights[i] != NULL ) + { + for( int j=0; j<GetVehicleCentral()->mHeadLights[i]->GetNumQuads(); j++ ) + { + headlightcolour.Set( + rmt::Clamp( (int)(GetVehicleCentral()->mOriginalHeadLightColours[count].Red()*fadeFactor*mHeadLightScale), 0, 255 ), + rmt::Clamp( (int)(GetVehicleCentral()->mOriginalHeadLightColours[count].Green()*fadeFactor*mHeadLightScale), 0, 255 ), + rmt::Clamp( (int)(GetVehicleCentral()->mOriginalHeadLightColours[count].Blue()*fadeFactor*mHeadLightScale), 0, 255 ), + rmt::Clamp( (int)(GetVehicleCentral()->mOriginalHeadLightColours[count].Alpha()*fadeFactor*mHeadLightScale), 0, 255 ) + ); + tBillboardQuad* bbq = GetVehicleCentral()->mHeadLights[i]->GetQuad( j ); + rAssert( bbq != NULL ); + bbq->SetColour( headlightcolour ); + bbq->SetVisibility( mEnableLights ); + count++; + } + + // ProcessShaders screwed us by causing us to lose the additive blend + // We set it back. + GetVehicleCentral()->mHeadLights[i]->GetShader()->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ADD ); + } + } +END_PROFILE("GeometryVehicle::Headlights") + +BEGIN_PROFILE("GeometryVehicle::Ghost") + // this means we're the ghost pirate ship... The glow must be ADDITIVE as well... + if( mHasGhostGlow ) + { + count = 0; + tColour glowcolour; + for( int i=0; i<NUM_GHOSTGLOW_BBQGS; i++ ) + { + if( mGhostGlows[i] != NULL ) + { + for( int j=0; j<mGhostGlows[i]->GetNumQuads(); j++ ) + { + glowcolour.Set( + rmt::Clamp( (int)(mOriginalGhostGlowColours[count].Red()*fadeFactor), 0, 255 ), + rmt::Clamp( (int)(mOriginalGhostGlowColours[count].Green()*fadeFactor), 0, 255 ), + rmt::Clamp( (int)(mOriginalGhostGlowColours[count].Blue()*fadeFactor), 0, 255 ), + rmt::Clamp( (int)(mOriginalGhostGlowColours[count].Alpha()*fadeFactor), 0, 255 ) + ); + tBillboardQuad* bbq = mGhostGlows[i]->GetQuad( j ); + rAssert( bbq != NULL ); + bbq->SetColour( glowcolour ); + count++; + } + + // ProcessShaders screwed us by causing us to lose the additive blend + // We set it back. + mGhostGlows[i]->GetShader()->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ADD ); + } + } + } +END_PROFILE("GeometryVehicle::Ghost") + + // this means we're the ghost pirate ship... The glow must be ADDITIVE as well... + if( mHasNukeGlow ) + { + count = 0; + tColour glowcolour; + for( int i=0; i<NUM_NUKEGLOW_BBQGS; i++ ) + { + if( mNukeGlows[i] != NULL ) + { + for( int j=0; j<mNukeGlows[i]->GetNumQuads(); j++ ) + { + glowcolour.Set( + rmt::Clamp( (int)(mOriginalNukeGlowColours[count].Red()*fadeFactor), 0, 255 ), + rmt::Clamp( (int)(mOriginalNukeGlowColours[count].Green()*fadeFactor), 0, 255 ), + rmt::Clamp( (int)(mOriginalNukeGlowColours[count].Blue()*fadeFactor), 0, 255 ), + rmt::Clamp( (int)(mOriginalNukeGlowColours[count].Alpha()*fadeFactor), 0, 255 ) + ); + tBillboardQuad* bbq = mNukeGlows[i]->GetQuad( j ); + rAssert( bbq != NULL ); + bbq->SetColour( glowcolour ); + count++; + } + + // ProcessShaders screwed us by causing us to lose the additive blend + // We set it back. + mNukeGlows[i]->GetShader()->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ADD ); + } + } + } + + // fade roof... + if( mRoofShader ) + { + if( mRoofAlpha > mRoofTargetAlpha ) + { + mRoofAlpha--; + } + else if( mRoofAlpha < mRoofTargetAlpha ) + { + mRoofAlpha++; + } + //mRoofShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ALPHA ); + mRoofShader->SetInt( PDDI_SP_EMISSIVEALPHA, mRoofAlpha ); + if( mRoofOpacShape && mRoofAlphaShape ) + { + if( mRoofAlpha == 255 ) + { + mRoofOpacShape->SetVisibility( true ); + mRoofAlphaShape->SetVisibility( false ); + } + else + { + mRoofOpacShape->SetVisibility( false ); + mRoofAlphaShape->SetVisibility( true ); + } + } + } + +BEGIN_PROFILE("GeometryVehicle::CompDraw->Disp") + if( smokeFirst ) + { + GetSparkleManager()->Render( Sparkle::SRM_SortedOnly ); + } + if( sbDrawVehicle ) + { + mCompositeDrawable->Display(); + } + if( !smokeFirst ) + { + GetSparkleManager()->Render( Sparkle::SRM_SortedOnly ); + } + if( m_Collectible ) + { + rmt::Matrix transform = mVehicleOwner->GetTransform(); + + p3d::stack->PushMultiply( transform ); + p3d::stack->PushMultiply( m_CollectibleTransform ); + m_Collectible->Display(); + p3d::stack->Pop(); + p3d::stack->Pop(); + } + +END_PROFILE("GeometryVehicle::CompDraw->Disp") +END_PROFILE("GeometryVehicle::Display Render") +} + + +//============================================================================= +// GeometryVehicle::DisplaySkids +//============================================================================= +// Description: Comment +// +// Parameters: void GeometryVehicle::DisplaySkids(int wheel + +// +// Return: void +// +//============================================================================= +void GeometryVehicle::SetSkidValues(int wheel, float intensity, rmt::Vector& groundPlaneNormal, eTerrainType terrainType ) +{ + // if this got called, intensity is > 0.0 + + if(mSkidMarkGenerator)// && mDrawSkids) // test here though this should only be getting called for vehicles that have it... ie. VT_USER + { + /* + struct SkidData + { + // Direction that the vehicle is moving + rmt::Vector velocityDirection; + // Orientation and position of the vehicle + rmt::Matrix transform; + // Other data????? + float intensity; // 1 most intense, 0 nonexistent skid + }; + */ + + rmt::Vector offset = mVehicleOwner->mSuspensionRestPoints[wheel]; + offset.y -= mVehicleOwner->mWheels[wheel]->mRadius; + offset.y += mVehicleOwner->mWheels[wheel]->mYOffset; // hmmm.... + + //if(mVehicleOwner->mWheels[wheel]->mYOffset != 0.0f) + //{ + // int stophere = 1; + //} + + mSkidMarkGenerator->SetWheelOffset(wheel, offset); // need to do this everyframe + + SkidMarkGenerator::SkidData sd; + sd.intensity = intensity; + sd.transform = mVehicleOwner->mTransform; + sd.terrainType = terrainType; + + sd.groundPlaneNormal = groundPlaneNormal; + + + rmt::Vector velocityDir = mVehicleOwner->mVelocityCM; + velocityDir.NormalizeSafe(); + + // TODO + // Michael - would this blow up if I passed in 0,0,0 ? + sd.velocityDirection = velocityDir; + + mSkidMarkGenerator->GenerateSkid(wheel, sd); + + } + +} + +//============================================================================= +// GeometryVehicle::DisplaySkids +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::UpdateSkids() +{ + if(mSkidMarkGenerator) + { + mSkidMarkGenerator->Update(); + } +} + + +////////////////////////////////////////////////////////////////////////// +int GeometryVehicle::CastsShadow() +{ + return 2; +} +//------------------------------------------------------------------------ +void GeometryVehicle::DisplayShadow( BlobShadowParams* BlobParams ) +{ + BEGIN_PROFILE("GeometryVehicle::DisplayShadow") + rAssert(BlobParams); + tColour OutsideColour, insideColour; + + // Hack for brightly colored shadows for special game mode + rmt::Vector camPos; + tCamera* camera = p3d::context->GetView()->GetCamera(); + rAssert(camera); + camera->GetWorldPosition( &camPos ); + camPos.Sub( BlobParams->GroundPos ); + float yOffset = 0.0f; // Move the shadow up a bit with distance to avoid Z chew. + if( ::GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + rAssert( mVehicleOwner ); + Avatar* avatar = ::GetAvatarManager()->GetAvatarForVehicle( mVehicleOwner ); + + OutsideColour.Set( 0, 0, 0, 0 ); // black outline + insideColour.Set( 0, 0, 0 ); // default black if no Avatar + if( avatar ) + { + int id = avatar->GetPlayerId(); + + if ( this->mVehicleOwner->mNumTurbos > 0 ) + { + insideColour = ::GetGameplayManager()->GetControllerColour( id ); + } + else + { + insideColour = tColour( 0, 0, 0 ); + } + } + } + else + { + // Camera culling. + float camDirDot = camPos.DotProduct( camera->GetCameraToWorldMatrix().Row( 2 ) ); + // Note that the camPos vector is towards the camera so the camDirDot will be negative when the camera is facing + //the object. So if it's positive then we can assume it's behind the camera. + if( camDirDot > 0.0f ) + { + END_PROFILE("GeometryVehicle::DisplayShadow") + return; + } + + // Blobby shadow for vehicle. + OutsideColour.Set( 255, 255, 255, 255 ); + const int Inside = 128; + + float fadeFactor = 1.0f; + if( mVehicleOwner->mVehicleType == VT_TRAFFIC && + mFadeAlpha != 255 ) + { + fadeFactor = 1.0f - (mFadeAlpha/255.0f); + } + else + { + fadeFactor = 1.0f - BlobParams->ShadowAlpha; + } + // DistanceAlpha: over 30 is 1, under 10 is 0, linear between. + float distanceAlpha = rmt::Clamp((camPos.Magnitude() - 10.0f) * 0.05f, 0.0f, 1.0f); + + fadeFactor *= 1.0f - distanceAlpha; + + int c = rmt::Clamp( int( Inside + ( 255 - Inside ) * fadeFactor ), 0, 255 ); + if( c == 255) + { + return; + } + insideColour.Set( c, c, c, c ); + yOffset = MAX_Y_OFFSET * distanceAlpha; + } + const int NumPoints = 6; + const float BlobLength = 2.2f; + const float BlobWidth = 1.2f; + const float BlobCant = 0.2f; + const float BlobFade = 0.4f; + static float Points[ NumPoints ][ 2 ]; + static float Fades[ NumPoints ][ 2 ]; + + static bool DoOnce = true; + if( DoOnce ) + { + DoOnce = false; + /* Here are the points we'll use for the car: + + ....----0 + . 1 + . | + . | + . + 2 + . 3 + . | + . 4 + ....----5 + We'll mirror the blob along the Z axis. + The shadow point adjusts work like this: + 0 - affects points 0 and 1, the top corner. + 1 - affects point 2, the position for the top division. + 2 - affects point 3, the position for the bottom division. + 3 - affects point 4 and 5, the bottom corner. + */ + Points[ 0 ][ 0 ] = BlobWidth - BlobCant; + Points[ 0 ][ 1 ] = BlobLength; + Points[ 1 ][ 0 ] = BlobWidth; + Points[ 1 ][ 1 ] = BlobLength - BlobCant; + Points[ 2 ][ 0 ] = BlobWidth; + Points[ 2 ][ 1 ] = BlobCant; + Points[ 3 ][ 0 ] = BlobWidth; + Points[ 3 ][ 1 ] = -BlobCant; + Points[ 4 ][ 0 ] = BlobWidth; + Points[ 4 ][ 1 ] = -BlobLength + BlobCant; + Points[ 5 ][ 0 ] = BlobWidth - BlobCant; + Points[ 5 ][ 1 ] = -BlobLength; + Fades[ 0 ][ 0 ] = 0.5f * BlobFade; // Sin( 30 ) + Fades[ 0 ][ 1 ] = 0.866f * BlobFade; // Cos( 30 ) + Fades[ 1 ][ 0 ] = 0.866f * BlobFade; // Sin( 60 ) + Fades[ 1 ][ 1 ] = 0.5f * BlobFade; // Cos( 60 ) + Fades[ 2 ][ 0 ] = 0.866f * BlobFade; // Sin( 60 ) + Fades[ 2 ][ 1 ] = 0.5f * BlobFade; // Cos( 60 ) + Fades[ 3 ][ 0 ] = 0.866f * BlobFade; // Sin( 60 ) + Fades[ 3 ][ 1 ] = 0.5f * BlobFade; // Cos( 60 ) + Fades[ 4 ][ 0 ] = 0.866f * BlobFade; // Sin( 120 ) + Fades[ 4 ][ 1 ] = -0.5f * BlobFade; // Cos( 120 ) + Fades[ 5 ][ 0 ] = 0.5f * BlobFade; // Sin( 150 ) + Fades[ 5 ][ 1 ] = -0.866f * BlobFade; // Cos( 150 ) + } + + camPos.Normalize(); + rmt::Matrix transform; + transform.Identity(); + transform.FillTranslate( BlobParams->GroundPos ); + transform.FillHeading( BlobParams->GroundNormal, BlobParams->ShadowFacing ); + transform.Row( 3 ).Add( camPos ); + transform.Row(3).y += yOffset; + p3d::stack->PushMultiply( transform ); + + pddiShader* blobShader = BootupContext::GetInstance()->GetSharedShader(); + + if( ::GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + blobShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_NONE ); + } + else + { + blobShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_MODULATE ); + } + blobShader->SetInt( PDDI_SP_ISLIT, 0 ); + blobShader->SetInt( PDDI_SP_ALPHATEST, 0 ); + blobShader->SetInt( PDDI_SP_SHADEMODE, PDDI_SHADE_GOURAUD ); + pddiPrimStream* blob = p3d::pddi->BeginPrims( blobShader, PDDI_PRIM_TRIANGLES, PDDI_V_C, 6 * NumPoints ); + for( int i = 0; i < NumPoints * 2; ++i ) + { + int index = i; + int nextIndex = ( i + 1 ) % ( NumPoints * 2 ); + int mirrorX = int( ( index * ( 1.0f / NumPoints ) ) ); + int nextMirrorX = int( ( nextIndex * ( 1.0f / NumPoints ) ) ); + index %= NumPoints; + nextIndex %= NumPoints; + index = rmt::Abs( ( mirrorX * 5 ) - index ); + nextIndex = rmt::Abs( ( nextMirrorX * 5 ) - nextIndex ); + mirrorX = 1 - ( mirrorX * 2 ); + nextMirrorX = 1 - ( nextMirrorX * 2 ); + int adjust = rmt::Clamp( index - 1, 0, NumPoints - 3 ); + int nextAdjust = rmt::Clamp( nextIndex - 1, 0, NumPoints - 3 ); + + float x, y; + blob->Colour( insideColour ); + blob->Coord( 0.0f, 0.0f, 0.0f ); + x = ( Points[ nextIndex ][ 0 ] + mShadowPointAdjustments[ nextAdjust ][ 0 ] ) * BlobParams->ShadowScale; + y = ( Points[ nextIndex ][ 1 ] + mShadowPointAdjustments[ nextAdjust ][ 1 ] ) * BlobParams->ShadowScale; + blob->Colour( insideColour ); + blob->Coord( x * nextMirrorX, y, 0.0f ); + x = ( Points[ index ][ 0 ] + mShadowPointAdjustments[ adjust ][ 0 ] ) * BlobParams->ShadowScale; + y = ( Points[ index ][ 1 ] + mShadowPointAdjustments[ adjust ][ 1 ] ) * BlobParams->ShadowScale; + blob->Colour( insideColour ); + blob->Coord( x * mirrorX, y, 0.0f ); + } + p3d::pddi->EndPrims( blob ); + // How the soft edge. + blob = p3d::pddi->BeginPrims( blobShader, PDDI_PRIM_TRISTRIP, PDDI_V_C, ( ( 2 * NumPoints ) + 1 ) * 2 ); + for( int i = 0; i <= NumPoints * 2; ++i ) + { + int index = i; + int mirrorX = int( ( index * ( 1.0f / NumPoints ) ) ) % 2; + index %= NumPoints; + index = rmt::Abs( ( mirrorX * 5 ) - index ); + mirrorX = 1 - ( mirrorX * 2 ); + int adjust = rmt::Clamp( index - 1, 0, NumPoints - 3 ); + + float inX, inY; + float outX, outY; + inX = ( Points[ index ][ 0 ] + mShadowPointAdjustments[ adjust ][ 0 ] ) * BlobParams->ShadowScale; + inY = ( Points[ index ][ 1 ] + mShadowPointAdjustments[ adjust ][ 1 ] ) * BlobParams->ShadowScale; + inX *= mirrorX; + outX = inX + ( Fades[ index ][ 0 ] * mirrorX * BlobParams->ShadowScale ); + outY = inY + ( Fades[ index ][ 1 ] * BlobParams->ShadowScale ); + blob->Colour( OutsideColour ); + blob->Coord( outX, outY, 0.0f ); + blob->Colour( insideColour ); + blob->Coord( inX, inY, 0.0f ); + } + p3d::pddi->EndPrims( blob ); + p3d::stack->Pop(); + END_PROFILE("GeometryVehicle::DisplayShadow") +} + +//============================================================================= +// GeometryVehicle::DisplayLights +//============================================================================= +// Description: Comment +// +// Parameters: rmt::Vector& GroundPos - Position of car. +// rmt::Vector& GroundNormal - Ground normal at car position. +// rmt::Vector& GroundUp - Up direction at ground position +// +// Return: void +// +//============================================================================= +void GeometryVehicle::DisplayLights( const HeadLightParams& LightParams ) +{ + BEGIN_PROFILE("GeometryVehicle::DisplayLights") + const int NumPoints = 20; + const float LightXSize = 1.0f; + const float LightZSize = 0.125f; + const float FadeSize = 0.3f; + const float LightLength = NumPoints * LightZSize; + static bool DoOnce = true; + static float PointsX[ NumPoints ]; + static tColour Colours[ NumPoints ]; + static float FadePoints[ NumPoints ][ 2 ]; + const tColour Colour( 65, 70, 46 ); + const tColour Black( 0, 0, 0 ); + + if( DoOnce ) + { + DoOnce = false; + // The light cone is made of of two tri strips one is the inner bright parabula + // which fades out with distance, and the other is the soft edge around it. + for( int i = 0; i < NumPoints; ++i ) + { + float z = ( i + 1 ) * LightZSize; + float x = rmt::Sqrt( ( i + 1 ) * 0.5f ); + PointsX[ i ] = x; + z -= LightLength; + float mag = rmt::Sqrt( ( x * x ) + ( z * z ) ); + mag = ( ( mag != 0.0f ) ? 1.0f / mag : 0.0f ) * FadeSize; + x *= mag; + z *= mag; + FadePoints[ i ][ 0 ] = x; + FadePoints[ i ][ 1 ] = z; + float colourScale = ( 1.0f - ( (float)i / (float)NumPoints ) ); + int red = rmt::Clamp( int( Colour.Red() * colourScale ), 0, 255 ); + int green = rmt::Clamp( int( Colour.Green() * colourScale ), 0, 255 ); + int blue = rmt::Clamp( int( Colour.Blue() * colourScale ), 0, 255 ); + Colours[ i ].Set( red, green, blue ); + } + // Now calculate the offset for the fade points. + } + + float xScale = 1.0f; + float zScale = 1.0f; + float scale = 1.0f; + + scale = LightParams.LightReach; + float offset = ( 1.0f - LightParams.LightReach ) * LightLength; + + rmt::Vector camPos; + rmt::Vector groundPos = LightParams.GroundPos; + groundPos.ScaleAdd( offset, LightParams.VehicleHeading ); + tCamera* camera = p3d::context->GetView()->GetCamera(); + rAssert( camera ); + camera->GetWorldPosition( &camPos ); + camPos.Sub( groundPos ); + float cameraToLight = camPos.Magnitude(); + camPos.Normalize(); + rmt::Matrix toCamera; + toCamera.Identity(); + toCamera.FillTranslate( camPos ); + rmt::Matrix transform; + transform.Identity(); + transform.FillTranslate( groundPos ); + transform.FillHeading( LightParams.GroundNormal, LightParams.VehicleHeading ); + transform.Mult( toCamera ); + p3d::stack->PushMultiply( transform ); + + float dist = LightParams.VehicleHeading.Dot( LightParams.GroundNormal ); + rmt::Vector groundByVehicle( LightParams.GroundNormal ); + if( dist != 0.0f ) + { + groundByVehicle.ScaleAdd( -dist, LightParams.VehicleHeading ); + groundByVehicle.Normalize(); + } + float vehicleRoll = groundByVehicle.Dot( LightParams.VehicleUp ); + vehicleRoll *= vehicleRoll; +#ifdef RAD_DEBUG + if( this->mVehicleOwner->mVehicleType != VT_TRAFFIC ) + { + char text[128]; + sprintf( text, "Ground to vehicle ratio: %.4f", vehicleRoll ); + DebugInfo::GetInstance()->Push( "Vehicle lights" ); + DebugInfo::GetInstance()->AddScreenText( text ); + DebugInfo::GetInstance()->Pop(); + } +#endif + + xScale *= scale * LightXSize * vehicleRoll; + zScale *= scale * LightZSize; + + pddiShader* lightShader = BootupContext::GetInstance()->GetSharedShader(); + lightShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ADD ); + lightShader->SetInt( PDDI_SP_ISLIT, 0 ); + lightShader->SetInt( PDDI_SP_ALPHATEST, 0 ); + lightShader->SetInt( PDDI_SP_SHADEMODE, PDDI_SHADE_GOURAUD ); + + // Center light. + pddiPrimStream* light = p3d::pddi->BeginPrims( lightShader, PDDI_PRIM_TRISTRIP, PDDI_V_C, ( NumPoints << 1 ) + 1 ); + light->Colour( Colour ); + light->Coord( 0.0f, 0.0f, 0.0f ); + for( int i = 0; i < NumPoints; ++i ) + { + float inX = PointsX[ i ] * xScale; + float inZ = ( i + 1 ) * zScale; + light->Colour( Colours[ i ] ); + light->Coord( inX, inZ, 0.0f ); + light->Colour( Colours[ i ] ); + light->Coord( -inX, inZ, 0.0f ); + } + p3d::pddi->EndPrims( light ); + // Fade on the negative side. + pddiPrimStream* nFade = p3d::pddi->BeginPrims( lightShader, PDDI_PRIM_TRISTRIP, PDDI_V_C, ( NumPoints + 1 ) << 1 ); + nFade->Colour( Black ); + nFade->Coord( 0.0f, -FadeSize, 0.0f ); + nFade->Colour( Colour); + nFade->Coord( 0.0f, 0.0f, 0.0f ); + for( int i = 0; i < NumPoints; ++i ) + { + float inX = -PointsX[ i ] * xScale; + float inZ = ( i + 1 ) * zScale; + float outX = inX - FadePoints[ i ][ 0 ]; + float outZ = inZ + FadePoints[ i ][ 1 ]; + nFade->Colour( Black ); + nFade->Coord( outX, outZ, 0.0f ); + nFade->Colour( Colours[ i ] ); + nFade->Coord( inX, inZ, 0.0f ); + } + p3d::pddi->EndPrims( nFade ); + + // Fade on the positive side. + pddiPrimStream* pFade = p3d::pddi->BeginPrims( lightShader, PDDI_PRIM_TRISTRIP, PDDI_V_C, ( NumPoints + 1 ) << 1 ); + pFade->Colour( Colour); + pFade->Coord( 0.0f, 0.0f, 0.0f ); + pFade->Colour( Black ); + pFade->Coord( 0.0f, -FadeSize, 0.0f ); + for( int i = 0; i < NumPoints; ++i ) + { + float inX = PointsX[ i ] * xScale; + float inZ = ( i + 1 ) * zScale; + float outX = inX + FadePoints[ i ][ 0 ]; + float outZ = inZ + FadePoints[ i ][ 1 ]; + pFade->Colour( Colours[ i ] ); + pFade->Coord( inX, inZ, 0.0f ); + pFade->Colour( Black ); + pFade->Coord( outX, outZ, 0.0f ); + } + p3d::pddi->EndPrims( pFade ); + + p3d::stack->Pop(); + END_PROFILE("GeometryVehicle::DisplayLights") +} + +//============================================================================= +// GeometryVehicle::FindAndTurnOffWheels +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::FindAndTurnOffWheels() +{ + int i; + for(i = 0; i < 4; i++) + { + tPose* p3dPose = mCompositeDrawable->GetPose(); + + char buffy[128]; + sprintf(buffy, "w%d", i); + int jointIndex = p3dPose->FindJointIndex(buffy); + + int j; + for(j = 0; j < mCompositeDrawable->GetNumDrawableElement(); j++) + { + tCompositeDrawable::DrawablePropElement* prop = (tCompositeDrawable::DrawablePropElement*)(mCompositeDrawable->GetDrawableElement(j)); + if(prop && prop->GetPoseIndex() == jointIndex) + { + prop->SetVisibility(false); + } + } + + } +} + + +//============================================================================= +// GeometryVehicle::FindAndTurnOffFrontWheelsOnly +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::FindAndTurnOffFrontWheelsOnly() +{ + int i; + for(i = 2; i < 4; i++) + { + tPose* p3dPose = mCompositeDrawable->GetPose(); + + char buffy[128]; + sprintf(buffy, "w%d", i); + int jointIndex = p3dPose->FindJointIndex(buffy); + + int j; + for(j = 0; j < mCompositeDrawable->GetNumDrawableElement(); j++) + { + tCompositeDrawable::DrawablePropElement* prop = (tCompositeDrawable::DrawablePropElement*)(mCompositeDrawable->GetDrawableElement(j)); + if(prop && prop->GetPoseIndex() == jointIndex) + { + prop->SetVisibility(false); + } + } + + } +} + + +//============================================================================= +// GeometryVehicle::FindHeadLightBillboardJoints +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::FindHeadLightBillboardJoints() +{ + // grab the pose + tPose* p3dPose = mCompositeDrawable->GetPose(); + + // grab the headlight bbqgs from common section + + // put in left headlight + char buffy[128]; + sprintf( buffy, "hll" ); + int left = p3dPose->FindJointIndex( buffy ); + if( left >= 0 ) + { + for( int i=0; i<VehicleCentral::NUM_HEADLIGHT_BBQGS; i++ ) + { + rAssert( GetVehicleCentral()->mHeadLights[i] ); + mCompositeDrawable->AddProp( GetVehicleCentral()->mHeadLights[i], left ); + } + } + + // now the right side + sprintf( buffy, "hlr" ); + int right = p3dPose->FindJointIndex( buffy ); + if( right >= 0 ) + { + for( int i=0; i<VehicleCentral::NUM_HEADLIGHT_BBQGS; i++ ) + { + rAssert( GetVehicleCentral()->mHeadLights[i] ); + mCompositeDrawable->AddProp( GetVehicleCentral()->mHeadLights[i], right ); + } + } + +} +//============================================================================= +// GeometryVehicle::FindBrakeLightBillboardJoints +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::FindBrakeLightBillboardJoints() +{ + int i; + for(i = 1; i < 5; i++) // 'cause Kevin starts counting at 1, not 0 + { + tPose* p3dPose = mCompositeDrawable->GetPose(); + + char buffy[128]; + sprintf(buffy, "brake%d", i); + int jointIndex = p3dPose->FindJointIndex(buffy); + + int j; + for(j = 0; j < mCompositeDrawable->GetNumDrawableElement(); j++) + { + tCompositeDrawable::DrawablePropElement* prop = (tCompositeDrawable::DrawablePropElement*)(mCompositeDrawable->GetDrawableElement(j)); + if(prop && prop->GetPoseIndex() == jointIndex) + { + //mBrakeLights[i - 1] = (tBillboardQuadGroup*) prop->GetDrawable(); + tBillboardQuadGroup* bbqg = dynamic_cast<tBillboardQuadGroup*>( prop->GetDrawable() ); + if( bbqg != NULL ) + { + tRefCounted::Assign( mBrakeLights[i - 1], bbqg ); + + // the structure for storing the original colors only supports 1 quad per quadgroup + rAssert( mBrakeLights[i-1]->GetNumQuads() == 1 ); + tBillboardQuad* quad = mBrakeLights[i-1]->GetQuad( 0 ); + mOriginalBrakeLightColours[i-1] = quad->GetColour(); + mBrakeLightJoints[i - 1] = jointIndex; + } + } + } + + } + + for(i = 1; i < 5; i++) // 'cause Kevin starts counting at 1, not 0 + { + tPose* p3dPose = mCompositeDrawable->GetPose(); + + char buffy[128]; + sprintf(buffy, "rev%d", i); + int jointIndex = p3dPose->FindJointIndex(buffy); + + int j; + for(j = 0; j < mCompositeDrawable->GetNumDrawableElement(); j++) + { + tCompositeDrawable::DrawablePropElement* prop = (tCompositeDrawable::DrawablePropElement*)(mCompositeDrawable->GetDrawableElement(j)); + if(prop && prop->GetPoseIndex() == jointIndex) + { + prop->SetVisibility(false); + mReverseLightJoints[i - 1] = jointIndex; + } + } + + } +} + + +//============================================================================= +// GeometryVehicle::FindGhostGlowBillboards +//============================================================================= +// Description: Comment +// +// Parameters: ( const char* model ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::FindGhostGlowBillboards( const char* model ) +{ + int count = 0; + for( int i = 0; i<NUM_GHOSTGLOW_BBQGS; i++ ) + { + char name[64]; + sprintf( name, "%sGlow%dShape", model, i+1 ); + tUID test = tEntity::MakeUID( name ); + + for( int j=0; j<mCompositeDrawable->GetNumDrawableElement(); j++ ) + { + tCompositeDrawable::DrawablePropElement* prop = + (tCompositeDrawable::DrawablePropElement*)(mCompositeDrawable->GetDrawableElement(j)); + + if( prop && prop->GetUID() == test ) + { + tBillboardQuadGroup* bbqg = dynamic_cast<tBillboardQuadGroup*>( prop->GetDrawable() ); + if( bbqg != NULL ) + { + tRefCounted::Assign( mGhostGlows[i], bbqg ); + + for( int j=0; j<mGhostGlows[i]->GetNumQuads(); j++ ) + { + tBillboardQuad* quad = mGhostGlows[i]->GetQuad( j ); + rAssert( quad ); + mOriginalGhostGlowColours[count] = quad->GetColour(); + count++; + } + } + } + } + } +} + + +//============================================================================= +// GeometryVehicle::FindNukeGlowBillboards +//============================================================================= +// Description: Comment +// +// Parameters: ( const char* model ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::FindNukeGlowBillboards( const char* model ) +{ + int count = 0; + for( int i = 0; i<NUM_NUKEGLOW_BBQGS; i++ ) + { + char name[64]; + sprintf( name, "nucGlowGroupShape" ); + tUID test = tEntity::MakeUID( name ); + + for( int j=0; j<mCompositeDrawable->GetNumDrawableElement(); j++ ) + { + tCompositeDrawable::DrawablePropElement* prop = + (tCompositeDrawable::DrawablePropElement*)(mCompositeDrawable->GetDrawableElement(j)); + + if( prop && prop->GetUID() == test ) + { + tBillboardQuadGroup* bbqg = dynamic_cast<tBillboardQuadGroup*>( prop->GetDrawable() ); + if( bbqg != NULL ) + { + tRefCounted::Assign( mNukeGlows[i], bbqg ); + + for( int j=0; j<mNukeGlows[i]->GetNumQuads(); j++ ) + { + tBillboardQuad* quad = mNukeGlows[i]->GetQuad( j ); + rAssert( quad ); + mOriginalNukeGlowColours[count] = quad->GetColour(); + count++; + } + } + } + } + } +} + + + +//============================================================================= +// GeometryVehicle::SetCollectibleHardpointPosition +//============================================================================= +// Description: Comment +// +// Parameters: ( const rmt::Vector& position ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::SetCollectibleHardpointPosition( const rmt::Vector& position ) +{ + // Use the default orientation, but replace the translational compoonent + m_CollectibleTransform = DEFAULT_COLLECTIBLE_TRANSFORM; + m_CollectibleTransform.FillTranslate( position ); + +} + + +//============================================================================= +// GeometryVehicle::SetCollectibleHardpointTransform +//============================================================================= +// Description: Comment +// +// Parameters: ( float rotx ,float roty ,float rotz, const rmt::Vector& position ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::SetCollectibleHardpointTransform( float rotx ,float roty ,float rotz, + const rmt::Vector& position ) +{ + m_CollectibleTransform.Identity(); + m_CollectibleTransform.FillRotateXYZ( rotx, roty, rotz ); + m_CollectibleTransform.FillTranslate( position ); +} + + +//============================================================================= +// GeometryVehicle::ShowBrakeLights +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::ShowBrakeLights() +{ + mBrakeLightsOn = true; + /* + int i; + for(i = 0; i < 4; i++) + { + if(mBrakeLightJoints[i] != -1) + { + this->HideFlappingPiece(mBrakeLightJoints[i], false); + } + } + */ +} + +//============================================================================= +// GeometryVehicle::HideBrakeLights +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::HideBrakeLights() +{ + mBrakeLightsOn = false; + /* + int i; + for(i = 0; i < 4; i++) + { + if(mBrakeLightJoints[i] != -1) + { + this->HideFlappingPiece(mBrakeLightJoints[i], true); + } + } + */ +} + + +//============================================================================= +// GeometryVehicle::ShowReverseLights +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::ShowReverseLights() +{ + int i; + for(i = 0; i < 4; i++) + { + if(mReverseLightJoints[i] != -1) + { + this->HideFlappingPiece(mReverseLightJoints[i], false); + } + } + +} + + +//============================================================================= +// GeometryVehicle::HideReverseLights +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void GeometryVehicle::HideReverseLights() +{ + int i; + for(i = 0; i < 4; i++) + { + if(mReverseLightJoints[i] != -1) + { + this->HideFlappingPiece(mReverseLightJoints[i], true); + } + } + +} + + +//============================================================================= +// GeometryVehicle::GetArt +//============================================================================= +// Description: Comment +// +// Parameters: (char* name) +// +// Return: bool +// +//============================================================================= +bool GeometryVehicle::GetArt( const char* name) +{ + // expect a composite drawable named "name" in the file. + + tCompositeDrawable* master = p3d::find<tCompositeDrawable>(name); + rTuneAssert( master != NULL ); + +#ifndef FINAL + if (master == NULL) + { + char buffer [255]; + sprintf(buffer,"Script ERROR: Can't Find Vehicle:%s Make sure you loaded it!\n",name); + rTuneAssertMsg(0,buffer); + } +#endif + + mCompositeDrawable = master->Clone(); + + //mCompositeDrawable = p3d::find<tCompositeDrawable>(name); + rAssert(mCompositeDrawable); + mCompositeDrawable->AddRef(); + + { + char controllerName[255]; + strcpy( controllerName, "EFX_" ); + strcat( controllerName, name ); + strcat( controllerName, "velocpartSystShape" ); + + tEffectController* particleSysController = p3d::find < tEffectController >( controllerName ); + if ( particleSysController != NULL ) + { + tEffect* effect = particleSysController->GetEffect(); + rAssert( dynamic_cast< tParticleSystem* >( effect ) != NULL ); + tParticleSystem* particleSystem = static_cast< tParticleSystem* > ( effect ); + mVariableEmissionParticleSystem = particleSystem; + mVariableEmissionParticleSystem->AddRef(); + } + } + + ///////////////////////////////////////////////////////////////////////////////////// + // Traffic "swatches" (in a sense) + + // Any car using traffic model (regardless of whether they are in fact traffic cars) + // Must contain a trafficbodydrawable so that it will render correctly (due to fading + // and swatch tricks done for traffic models). + + if( IsTrafficModel() ) + { + mUsingTrafficModel = true; + + // Grab the shader to be used for swatches + char shadername[64]; + sprintf( shadername, "%s_m", name ); + tShader* bodyShade = p3d::find<tShader>( shadername ); + + // Find the body prop in compositedrawable's list of elements by name + // e.g. compactAShape + char propname[64]; + sprintf( propname, "%sShape", name ); + tUID uid = tEntity::MakeUID(propname); + + int poseIndex = -1; + tCompositeDrawable::DrawablePropElement* body = NULL; + + for( int i=0; i<mCompositeDrawable->GetNumDrawableElement(); i++ ) + { + body = (tCompositeDrawable::DrawablePropElement*) (mCompositeDrawable->GetDrawableElement(i)); + if( body != NULL ) + { + if( body->GetDrawable()->GetUID() == uid ) + { + poseIndex = i; + break; + } + } + } + + if( poseIndex > -1 && body != NULL ) + { + // create a trafficbodydrawable + tRefCounted::Assign( mTrafficBodyDrawable, new TrafficBodyDrawable() ); + rAssert( mTrafficBodyDrawable ); + + // store away the body prop's old drawable + rAssert( body->GetDrawable() != NULL ); + mTrafficBodyDrawable->SetBodyPropDrawable( body->GetDrawable() ); + + // Grab the shader for the vehicle chassis and store it too + if( bodyShade == NULL ) + { + rDebugPrintf( "Warning: Couldn't find shader \"%s\" for traffic vehicle %s. Chassis color randomization will not apply to this vehicle.\n", + shadername, name ); + } + mTrafficBodyDrawable->SetBodyShader( bodyShade ); + + // make the new traffic body drawable the drawable for the body prop + body->SetDrawable( mTrafficBodyDrawable ); + } + else + { + rDebugPrintf( "Warning: Couldn't find tCompositeDrawable prop \"%s\" for traffic vehicle %s. Chassis color randomization will not apply to this vehicle.\n", + propname, name ); + } + + // Find the door prop in compositedrawable's list of elements by name + // e.g. compactAShape + sprintf( propname, "doorPRotShape" ); + uid = tEntity::MakeUID(propname); + + poseIndex = -1; + body = NULL; + for( int i=0; i<mCompositeDrawable->GetNumDrawableElement(); i++ ) + { + body = (tCompositeDrawable::DrawablePropElement*) + (mCompositeDrawable->GetDrawableElement(i)); + if( body != NULL ) + { + if( body->GetDrawable()->GetUID() == uid ) + { + poseIndex = i; + break; + } + } + } + + if( poseIndex > -1 && body != NULL ) + { + // create a trafficbodydrawable + tRefCounted::Assign( mTrafficDoorDrawable, new TrafficBodyDrawable() ); + rAssert( mTrafficDoorDrawable ); + + // store away the body prop's old drawable + rAssert( body->GetDrawable() != NULL ); + mTrafficDoorDrawable->SetBodyPropDrawable( body->GetDrawable() ); + + // Grab the shader for the vehicle chassis and store it too + if( bodyShade == NULL ) + { + rDebugPrintf( "Warning: Couldn't find shader \"%s\" for traffic vehicle %s. Chassis color randomization will not apply to this vehicle.\n", + shadername, name ); + } + mTrafficDoorDrawable->SetBodyShader( bodyShade ); + + // make the new traffic body drawable the drawable for the body prop + body->SetDrawable( mTrafficDoorDrawable ); + } + else + { + rDebugPrintf( "Warning: Couldn't find tCompositeDrawable prop \"%s\" for traffic vehicle %s. Chassis color randomization will not apply to this vehicle.\n", + propname, name ); + } + + } + // is this ok for release? + // I think so + + // little hack just for frink + // no wheels + if( strcmp(name, "frink_v") == 0 || strcmp(name, "honor_v") == 0 || strcmp(name, "hbike_v") == 0 || + strcmp(name, "witchcar") == 0 || strcmp(name, "ship") == 0 || strcmp(name, "mono_v") == 0 ) + { + FindAndTurnOffWheels(); + } + + if( strcmp( name, "frink_v" ) == 0 ) + { + int count = 0; + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "special_m" ) ); //must be first + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "frink_vInt_m" ) ); + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "frink_vTrim_m" ) ); + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "frink_vDoorPNorm_m" ) ); + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "frink_vExtras_m" ) ); + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "frink_vHoodNorm_m" ) ); + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "frink_vVent_m" ) ); + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "frink_vcoil_m" ) ); + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "BottomA_m" ) ); + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "frink_vDoorDNorm_m" ) ); + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "engine_m" ) ); + tRefCounted::Assign( mRefractionShader[ count++ ], p3d::find< tShader >( "frink_vBackNorm_m" ) ); + + int size = count; + int i; + for( i = 0; i < size; ++i ) + { + rAssert( mRefractionShader[ i ] != NULL ); + } + +#ifdef RAD_WIN32 + tPose* p3dPose = mCompositeDrawable->GetPose(); + + char buffy[128]; + sprintf(buffy, "frinkArcGroup", i); + int jointIndex = p3dPose->FindJointIndex(buffy); + + for(int j = 0; j < mCompositeDrawable->GetNumDrawableElement(); j++) + { + tCompositeDrawable::DrawablePropElement* prop = (tCompositeDrawable::DrawablePropElement*)(mCompositeDrawable->GetDrawableElement(j)); + if(prop && prop->GetPoseIndex() == jointIndex) + { + tBillboardQuadGroup* bbqg = dynamic_cast<tBillboardQuadGroup*>( prop->GetDrawable() ); + if( bbqg ) + { + tRefCounted::Assign( mFrinkArc, bbqg ); + for( int k = 0; k < NUM_FRINKARC_BBQS; k++ ) + { + tBillboardQuad* quad = mFrinkArc->GetQuad( k ); + mOriginalFrinkArcColour[k] = quad->GetColour(); + } + } + } + } +#endif + } + + if(strcmp(name, "rocke_v") == 0) + { + this->FindAndTurnOffFrontWheelsOnly(); + } + + // store brake light joint indices + FindBrakeLightBillboardJoints(); + FindHeadLightBillboardJoints(); + + if( GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + mHeadLightScale = 1.0f; + mBrakeLightScale = 0.4f; + if( mVehicleOwner->mVehicleType == VT_USER || mVehicleOwner->mVehicleType == VT_AI ) + { + EnableLights( true ); + } + } + else + { + // if we are on a level other than 4 or 7, we want to turn the headlights off + if( GetGameplayManager()->GetCurrentLevelIndex() == RenderEnums::L1 || // BROAD DAYLIGHT + GetGameplayManager()->GetCurrentLevelIndex() == RenderEnums::L2 ) + { + // Which values should we use for headlights and brakelights + // when we just have running lights (when not braking) + mHeadLightScale = 0.0f; + mBrakeLightScale = 0.0f;//0.4f; + } + else if( GetGameplayManager()->GetCurrentLevelIndex() == RenderEnums::L3 ) // SUNSET + { + mHeadLightScale = 0.2f; + mBrakeLightScale = 0.3f; + } + else if( GetGameplayManager()->GetCurrentLevelIndex() == RenderEnums::L5 ) // DUSK + { + mHeadLightScale = 0.4f; + mBrakeLightScale = 0.4f; + } + else if( GetGameplayManager()->GetCurrentLevelIndex() == RenderEnums::L6 ) // TWILIGHT + { + mHeadLightScale = 0.4f; + mBrakeLightScale = 0.4f; + } + else if( GetGameplayManager()->GetCurrentLevelIndex() == RenderEnums::L4 || // NIGHT + GetGameplayManager()->GetCurrentLevelIndex() == RenderEnums::L7 ) + { + // full headlights on night levels + mHeadLightScale = 0.6f; + mBrakeLightScale = 0.4f; + } + if( mVehicleOwner->mVehicleType == VT_USER ) + { + EnableLights( false ); + } + } + + + + // hack for ghost ship + if( strcmp(name, "ship") == 0 ) + { + mHasGhostGlow = true; + FindGhostGlowBillboards( name ); + } + + if( strcmp( name, "nuctruck" ) == 0 ) + { + mHasNukeGlow = true; + FindNukeGlowBillboards( name ); + } + + + // TODO - might need to clone this too +// mAnimController = p3d::find<tPoseAnimationController>(buffy); + + // We will advance the multicontroller, not just the posecontroller + + FindAnimationControllers( name ); + + char buffy[128]; + sprintf(buffy, "PTRN_%s", name); + + tPoseAnimationController* poseController = p3d::find<tPoseAnimationController>(buffy); + + // only play an anim if it's there: + + if( poseController ) + { + poseController->SetPose(mCompositeDrawable->GetPose()); + + // TODO + // cast here might be unsafe in the general case + //mAnim = (tPoseAnimation*)(poseController->GetAnimation()); + mAnim = poseController->GetAnimation(); + mAnim->AddRef(); // ? already addref'd by controller? + mAnim->SetCyclic(true); + + float numFrames = mAnim->GetNumFrames(); + float speed = mAnim->GetSpeed(); + + rAssert( numFrames != 0 ); + + mAnimRevPerSecondBase = speed / (float)numFrames; + } + + char roofShaderName[64]; + sprintf( roofShaderName, "%sRoof_m", name ); + tRefCounted::Assign( mRoofShader, p3d::find<tShader>( tEntity::MakeUID( roofShaderName ) ) ); + + FindRoofGeometry( name ); + if( mRoofOpacShape && mRoofAlphaShape ) + { + mRoofOpacShape->SetVisibility( true ); + mRoofAlphaShape->SetVisibility( false ); + } + + if ( mVehicleOwner->GetNameObject() == "gramR_v" ) + { + SetCollectibleHardpointPosition( JEEP_COLLECTIBLE_POS ); + } + else if ( mVehicleOwner->GetNameObject() == "homer_v" ) + { + SetCollectibleHardpointPosition( BARRACUDA_COLLECTIBLE_POS ); + } + else if ( mVehicleOwner->GetNameObject() == "dune_v" ) + { + SetCollectibleHardpointTransform( DUNE_COLLECTIBLE_ROT.x, + DUNE_COLLECTIBLE_ROT.y, + DUNE_COLLECTIBLE_ROT.z, + DUNE_COLLECTIBLE_POS); + } + else if ( mVehicleOwner->GetNameObject() == "hallo" ) + { + SetCollectibleHardpointPosition( HALLO_COLLECTIBLE_POS ); + } + else if ( mVehicleOwner->GetNameObject() == "coffin" ) + { + SetCollectibleHardpointPosition( COFFIN_COLLECTIBLE_POS ); + } + else if ( mVehicleOwner->GetNameObject() == "witchcar" ) + { + SetCollectibleHardpointPosition( WITCH_COLLECTIBLE_POS ); + } + + if(mVehicleOwner->mVehicleType == VT_USER) + { + tCompositeDrawable::DrawableElement* agnes = mCompositeDrawable->FindNode("agnusGeoShape"); + if(agnes) + { + if(GetVehicleCentral()->IsDriverSuppressed("skinner")) + { + agnes->SetVisibility(false); + } + } + } + + //THERE'S AN UNTRACKED ALLOCATION HERE! + HeapMgr()->PushHeap( GMA_LEVEL_OTHER ); + HeapMgr()->PopHeap(GMA_LEVEL_OTHER); + + + return FindDamageShadersAndTextures(name); + //return true; + +} + + +//============================================================================= +// GeometryVehicle::FindDamageShadersAndTextures +//============================================================================= +// Description: Comment +// +// Parameters: (char* name) +// +// Return: void +// +//============================================================================= +bool GeometryVehicle::FindDamageShadersAndTextures( const char* carname) +{ + // names we expect to have: + // + // for shaders + // <carname>DoorDNorm_m + // <carname>DoorPNorm_m + // <carname>HoodNorm_m + // <carname>BackNorm_m + + + // for normal textures: + // <carname>DoorDNorm.bmp + // <carname>DoorPNorm.bmp + // <carname>HoodNorm.bmp + // <carname>BackNorm.bmp current: BarTrunk.bmp + + + // for damage textures: + // <carname>DoorPDam.bmp current: BarracudaDoorPDam.bmp + // <carname>HoodDam.bmp current: BarracudaHoodDam.bmp + // <carname>DoorDDam.bmp current: BarracudaDoorDDam.bmp + // <carname>BackDam.bmp current: BarracudaBackDam.bmp + + // !! + // TODO - search a particular inventory section only? + + char buffy[128]; + + + // change logic here - if we find at least one set of shader, normal texture, damage texture + // return true. + + + + bool result = false; + + bool doorD = true; + bool doorP = true; + bool hood = true; + bool trunk = true; + + + //------- + //shaders + //------- + + sprintf(buffy, "%sDoorDNorm_m", carname); + mDoorDShader = p3d::find<tShader>(buffy); + if(mDoorDShader) + { + mDoorDShader->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + doorD = false; + } + + sprintf(buffy, "%sDoorPNorm_m", carname); + mDoorPShader = p3d::find<tShader>(buffy); + if(mDoorPShader) + { + mDoorPShader->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + doorP = false; + } + + + sprintf(buffy, "%sHoodNorm_m", carname); + mHoodShader = p3d::find<tShader>(buffy); + if(mHoodShader) + { + mHoodShader->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + hood = false; + } + + + sprintf(buffy, "%sBackNorm_m", carname); + mTrunkShader = p3d::find<tShader>(buffy); + if(mTrunkShader) + { + mTrunkShader->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + trunk = false; + } + + + + //---------------- + // normal textures + //---------------- + + sprintf(buffy, "%sDoorDNorm.bmp", carname); + mDoorDTextureNorm = p3d::find<tTexture>(buffy); + if(mDoorDTextureNorm) + { + mDoorDTextureNorm->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + doorD = false; + } + + + sprintf(buffy, "%sDoorPNorm.bmp", carname); + mDoorPTextureNorm = p3d::find<tTexture>(buffy); + if(mDoorPTextureNorm) + { + mDoorPTextureNorm->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + doorP = false; + } + + + sprintf(buffy, "%sHoodNorm.bmp", carname); + mHoodTextureNorm = p3d::find<tTexture>(buffy); + if(mHoodTextureNorm) + { + mHoodTextureNorm->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + hood = false; + } + + + sprintf(buffy, "%sBackNorm.bmp", carname); + mTrunkTextureNorm = p3d::find<tTexture>(buffy); + if(mTrunkTextureNorm) + { + mTrunkTextureNorm->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + trunk = false; + } + + + + //---------------- + // damage textures + //---------------- + + sprintf(buffy, "%sDoorDDam.bmp", carname); + mDoorDTextureDam = p3d::find<tTexture>(buffy); + if(mDoorDTextureDam) + { + mDoorDTextureDam->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + doorD = false; + } + + + sprintf(buffy, "%sDoorPDam.bmp", carname); + mDoorPTextureDam = p3d::find<tTexture>(buffy); + if(mDoorPTextureDam) + { + mDoorPTextureDam->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + doorP = false; + } + + + sprintf(buffy, "%sHoodDam.bmp", carname); + mHoodTextureDam = p3d::find<tTexture>(buffy); + if(mHoodTextureDam) + { + mHoodTextureDam->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + hood = false; + } + + + sprintf(buffy, "%sBackDam.bmp", carname); + mTrunkTextureDam = p3d::find<tTexture>(buffy); + if(mTrunkTextureDam) + { + mTrunkTextureDam->AddRef(); + } + else + { + rTunePrintf("vehicle damage: cant find %s\n", buffy); + //result = false; + trunk = false; + } + + + // now if any of doorD, doorP, hood, or trunk are true - return true + + result = doorD | doorP | hood | trunk; + + + + //---------------------------------------------------- + // all of the above was if we are a type 1 or 2 damage + // + // for type 3 we just expect the following: + //---------------------------------------------------- + + //------- + // shader + //------- + /* + + + // ancient fucking history + + + sprintf(buffy, "%sChassisNorm_m", carname); + mChassisShader = p3d::find<tShader>(buffy); + if(mChassisShader) + { + mChassisShader->AddRef(); + } + + //--------------- + // normal texture + //--------------- + + sprintf(buffy, "%sChassisNorm.bmp", carname); + mChassisTextureNorm = p3d::find<tTexture>(buffy); + if(mChassisTextureNorm) + { + mChassisTextureNorm->AddRef(); + } + + //--------------- + // damage texture + //--------------- + + sprintf(buffy, "%sChassisDam.bmp", carname); + mChassisTextureDam = p3d::find<tTexture>(buffy); + if(mChassisTextureDam) + { + mChassisTextureDam->AddRef(); + } + */ + + + return result; + +} + + + +//============================================================================= +// GeometryVehicle::SetSmoke +//============================================================================= +// Description: Comment +// +// Parameters: (ParticleEnum::ParticleID pid) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::SetEngineSmoke(ParticleEnum::ParticleID pid) +{ + mEngineParticleAttr.mType = pid; +} + + +/* +ParticleEnum::ParticleID mType; + + // only one bias so far, the emission bias + // other biases would be simple to put in + // NumParticles/Life/Speed/Weight/Gravity/Drag/Size/Spin + float mEmissionBias; +*/ + + +//============================================================================= +// GeometryVehicle::SetWheelSmoke +//============================================================================= +// Description: Comment +// +// Parameters: (ParticleEnum::ParticleID pid, float bias) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::SetWheelSmoke( int wheel, ParticleEnum::ParticleID pid, float bias) +{ + switch( wheel ) + { + case 0: + mRightWheelParticleAttr.mType = pid; + mRightWheelParticleAttr.mEmissionBias = bias; + break; + case 1: + mLeftWheelParticleAttr.mType = pid; + mLeftWheelParticleAttr.mEmissionBias = bias; + break; + default: + mRightWheelParticleAttr.mType = pid; + mRightWheelParticleAttr.mEmissionBias = bias; + + mLeftWheelParticleAttr.mType = pid; + mLeftWheelParticleAttr.mEmissionBias = bias; + break; + }; +} + + +//============================================================================= +// GeometryVehicle::DamageTextureChassis +//============================================================================= +// Description: Comment +// +// Parameters: (bool on) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::DamageTextureChassis(bool on) +{ + rAssert(0); // this is fucking history + if(mChassisShader) + { + if(on) + { + mChassisShader->SetTexture(PDDI_SP_BASETEX, mChassisTextureDam); + } + else + { + mChassisShader->SetTexture(PDDI_SP_BASETEX, mChassisTextureNorm); + } + } +} + + +//============================================================================= +// GeometryVehicle::DamageTextureTrunk +//============================================================================= +// Description: Comment +// +// Parameters: (bool on) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::DamageTextureTrunk(bool on) +{ + if(mTrunkShader && mTrunkTextureDam && mTrunkTextureNorm) + { + if(on) + { + mTrunkShader->SetTexture(PDDI_SP_BASETEX, mTrunkTextureDam); + } + else + { + mTrunkShader->SetTexture(PDDI_SP_BASETEX, mTrunkTextureNorm); + } + } +} + +//============================================================================= +// GeometryVehicle::DamageTextureHood +//============================================================================= +// Description: Comment +// +// Parameters: (bool on) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::DamageTextureHood(bool on) +{ + if(mHoodShader && mHoodTextureDam && mHoodTextureNorm) + { + if(on) + { + mHoodShader->SetTexture(PDDI_SP_BASETEX, mHoodTextureDam); + } + else + { + mHoodShader->SetTexture(PDDI_SP_BASETEX, mHoodTextureNorm); + } + } +} + +//============================================================================= +// GeometryVehicle::DamageTextureDoorP +//============================================================================= +// Description: Comment +// +// Parameters: (bool on) +// +// Return: +// +//============================================================================= +void GeometryVehicle::DamageTextureDoorP(bool on) +{ + if(mDoorPShader && mDoorPTextureDam && mDoorPTextureNorm) + { + if(on) + { + mDoorPShader->SetTexture(PDDI_SP_BASETEX, mDoorPTextureDam); + } + else + { + mDoorPShader->SetTexture(PDDI_SP_BASETEX, mDoorPTextureNorm); + } + } +} + +//============================================================================= +// GeometryVehicle::DamageTextureDoorD +//============================================================================= +// Description: Comment +// +// Parameters: (bool on) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::DamageTextureDoorD(bool on) +{ + if(mDoorDShader && mDoorDTextureDam && mDoorDTextureNorm) + { + if(on) + { + mDoorDShader->SetTexture(PDDI_SP_BASETEX, mDoorDTextureDam); + } + else + { + mDoorDShader->SetTexture(PDDI_SP_BASETEX, mDoorDTextureNorm); + } + } +} + + +//============================================================================= +// GeometryVehicle::HideFlappingPiece +//============================================================================= +// Description: Comment +// +// Parameters: (int jointindex) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::HideFlappingPiece(int jointindex, bool doit) +{ + + //tCompositeDrawable::DrawablePropElement* prop = (tCompositeDrawable::DrawablePropElement*)(mCompositeDrawable->GetDrawableElement(jointindex)); + + int i; + for(i = 0; i < mCompositeDrawable->GetNumDrawableElement(); i++) + { + tCompositeDrawable::DrawablePropElement* prop = (tCompositeDrawable::DrawablePropElement*)(mCompositeDrawable->GetDrawableElement(i)); + if(prop && prop->GetPoseIndex() == jointindex) + { + //prop->SetVisibility(false); + prop->SetVisibility(!doit); + } + } + +} + + +//------------------------------------------------------------------------ +void GeometryVehicle::Update(float dt) +{ + AdvanceAnimationControllers( dt * 1000.0f ); + + + + rmt::Vector vehicleVelocity; + mVehicleOwner->GetVelocity( &vehicleVelocity ); + // Certain animations and particle systems are scaled by the velocity + // Calculate what % the scalar should be + float animationSpeedBias = vehicleVelocity.Magnitude() / PARTICLE_SYSTEM_MAX_VELOCITY; + animationSpeedBias = animationSpeedBias > 1.0f ? 1.0f : animationSpeedBias; + animationSpeedBias = animationSpeedBias < 0.0f ? 0.0f : animationSpeedBias; + + if ( mVariableEmissionParticleSystem != NULL ) + { + mVariableEmissionParticleSystem->SetBias( p3dParticleSystemConstants::EmitterBias::EMISSION, animationSpeedBias ); + } + + + // + // Vary prof frink's refraction shader every frame + // + + const tName& name = mVehicleOwner->GetNameObject(); + if( name == "frink_v" ) + { + float topSpeed = mVehicleOwner->GetTopSpeed(); + float percentOfTopSpeed = vehicleVelocity.Magnitude() / topSpeed; + float refraction = percentOfTopSpeed; + refraction = rmt::Clamp( refraction, 0.0f, 1.0f ); + + /* + //we adjust the refractive index based on distance from the camera + rmt::Vector pos = mVehicleOwner->GetPosition (); + rmt::Vector cameraPosition; + unsigned int index = GetSuperCamManager()->GetSCC( 0 )->GetActiveSuperCamIndex(); + SuperCam* camera = GetSuperCamManager()->GetSCC( 0 )->GetSuperCam( index ); + camera->GetPosition( &cameraPosition ); + float distance = ( pos - cameraPosition ).Magnitude(); + + float refractionAmount = refractiveIndex; + //if( distance > 20 ) + { + refractionAmount *= 5; + refractionAmount /= distance; + } + */ + + + // + // special case for the windshield shader + // + if( mRefractionShader[ 0 ] != NULL ) + { + #ifdef RAD_GAMECUBE + float adjustedRefraction = refraction; + #else + float adjustedRefraction = 0.5f + (0.5f * refraction); + #endif + + #ifndef RAD_WIN32 + mRefractionShader[ 0 ]->SetFloat( PDDI_SP_REFRACTBLEND, adjustedRefraction ); + mRefractionShader[ 0 ]->SetFloat( PDDI_SP_REFRACTINDEX, refractiveIndex ); + #endif + } + + // + // all other shaders + // + int i; + for( i = 1; i < MAX_REFRACTION_SHADERS; ++i ) + { + if( mRefractionShader[ i ] != NULL ) + { + #ifndef RAD_WIN32 + mRefractionShader[ i ]->SetFloat( PDDI_SP_REFRACTBLEND, refraction ); + mRefractionShader[ i ]->SetFloat( PDDI_SP_REFRACTINDEX, refractiveIndex ); + #endif + } + } + } + + // Update the collectible's position + if ( m_Collectible != NULL ) + { + rmt::Matrix collectibleTransform; + collectibleTransform.Mult( m_CollectibleTransform, mVehicleOwner->mTransform ); + m_Collectible->SetTransform( collectibleTransform ); + } +} + + +//============================================================================= +// GeometryVehicle::SetTrafficBodyColour +//============================================================================= +// Description: Comment +// +// Parameters: ( pddiColour colour ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::SetTrafficBodyColour( pddiColour colour ) +{ + if( mVehicleOwner->mVehicleType == VT_TRAFFIC || mVehicleOwner->mVehicleType == VT_USER ) + { + if( mTrafficBodyDrawable != NULL ) + { + mTrafficBodyDrawable->SetDesiredColour( colour ); + } + if( mTrafficDoorDrawable != NULL ) + { + mTrafficDoorDrawable->SetDesiredColour( colour ); + } + } +} + + +//============================================================================= +// GeometryVehicle::GetP3DPose +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: tPose +// +//============================================================================= +tPose* GeometryVehicle::GetP3DPose() +{ + if(mCompositeDrawable) + { + return mCompositeDrawable->GetPose(); + } + else + { + return 0; + } +} + + +//============================================================================= +// GeometryVehicle::SetShadowAdjustments +//============================================================================= +// Description: Comment +// +// Parameters: ( float Adjustments[ 4 ][ 2 ] ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::SetShadowAdjustments( float Adjustments[ 4 ][ 2 ] ) +{ + for( int i = 0; i < 4; ++i ) + { + mShadowPointAdjustments[ i ][ 0 ] = Adjustments[ i ][ 0 ]; + mShadowPointAdjustments[ i ][ 1 ] = Adjustments[ i ][ 1 ]; + } +} + + +//============================================================================= +// GeometryVehicle::SetFadeAlpha +//============================================================================= +// Description: Comment +// +// Parameters: ( int fade ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::SetFadeAlpha( int fade ) +{ + rAssert( 0 <= fade && fade <= 255 ); + mFadeAlpha = fade; +} + +// Hack: +// We hard code the traffic models so we can tell which vehicle +// needs special treatment in terms of fading... Be sure to add +// any new traffic models here or they won't fade properly. +// +// The problem is that the intbroadcast on all the shaders in +// this composite drawable is going to reset everyone's blend +// mode to BLEND_ALPHA... Brakelights, however, need BLEND_ADDITIVE +// and some cars (cCells, cPolice) have special light effects that +// have similar problems. Setting these all to BLEND_ALPHA will +// cause the billboards to not display properly, so we make sure +// we do an intbroadcast (using ProcessShaders()) only for cars +// that are using traffic models (including non-traffic cars such +// as mission AI cars). +// +// The alternative to this is to avoid using ProcessShaders() and +// set the appropriate shader for each composite drawable's element +// that need to fade independently... Could be messy... +// This hard-coded list is not so bad.. +// +//============================================================================= +// GeometryVehicle::IsTrafficModel +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: bool +// +//============================================================================= +bool GeometryVehicle::IsTrafficModel() +{ + rAssert( mVehicleOwner ); + + VehicleEnum::VehicleID ID = mVehicleOwner->mVehicleID; + + if( ID == VehicleEnum::HUSKA || + + ID == VehicleEnum::COMPACTA || + ID == VehicleEnum::PICKUPA || + ID == VehicleEnum::MINIVANA || + ID == VehicleEnum::SUVA || + ID == VehicleEnum::SPORTSA || + + ID == VehicleEnum::SPORTSB || + ID == VehicleEnum::SEDANA || + ID == VehicleEnum::SEDANB || + ID == VehicleEnum::TAXIA || + ID == VehicleEnum::WAGONA || + + ID == VehicleEnum::COFFIN || + ID == VehicleEnum::HALLO || + ID == VehicleEnum::WITCHCAR || + ID == VehicleEnum::SHIP || + ID == VehicleEnum::AMBUL || + + ID == VehicleEnum::BURNSARM || + ID == VehicleEnum::FISHTRUC || + ID == VehicleEnum::GARBAGE || + ID == VehicleEnum::ICECREAM || + ID == VehicleEnum::ISTRUCK || + + ID == VehicleEnum::NUCTRUCK || + ID == VehicleEnum::PIZZA || + ID == VehicleEnum::SCHOOLBU || + ID == VehicleEnum::VOTETRUC || + ID == VehicleEnum::GLASTRUC ) + + { + return true; + } + return false; +} + + +//============================================================================= +// GeometryVehicle::FadeRoof +//============================================================================= +// Description: Comment +// +// Parameters: ( bool fade ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::FadeRoof( bool fade ) +{ + rAssert( 0 <= INCAR_ROOF_ALPHA && INCAR_ROOF_ALPHA <= 255 ); + mRoofTargetAlpha = (fade)? INCAR_ROOF_ALPHA : 255; +} + + +//============================================================================= +// GeometryVehicle::FindRoofGeometry +//============================================================================= +// Description: Comment +// +// Parameters: ( const char* model ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::FindRoofGeometry( const char* model ) +{ + char opac[64]; + sprintf(opac, "%sRoofOpacShape", model ); + tUID opacID = tEntity::MakeUID( opac ); + + char alpha[64]; + sprintf(alpha, "%sRoofAlphaShape", model ); + tUID alphaID = tEntity::MakeUID( alpha ); + + for( int j = 0; j < mCompositeDrawable->GetNumDrawableElement(); j++) + { + tCompositeDrawable::DrawablePropElement* prop = + (tCompositeDrawable::DrawablePropElement*) + (mCompositeDrawable->GetDrawableElement(j)); + + if( prop ) + { + tUID uid = prop->GetUID(); + if( uid == opacID ) + { + mRoofOpacShape = prop; + } + else if( uid == alphaID ) + { + mRoofAlphaShape = prop; + } + } + } +} + + +//============================================================================= +// GeometryVehicle::EnableLights +//============================================================================= +// Description: Comment +// +// Parameters: ( bool enable ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::EnableLights( bool enable ) +{ + mEnableLights = enable; +} + + +//============================================================================= +// GeometryVehicle::GetVehicleColour +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: tColour +// +//============================================================================= +tColour GeometryVehicle::GetVehicleColour()const +{ + if ( mTrafficBodyDrawable != NULL ) + { + return mTrafficBodyDrawable->GetDesiredColour(); + } + else + { + const tColour DEFAULT_COLOUR( 128,128,128 ); + return DEFAULT_COLOUR; + } +} + + +//============================================================================= +// GeometryVehicle::FindAnimationControllers +//============================================================================= +// Description: Comment +// +// Parameters: ( const char* multicontrollername ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::FindAnimationControllers( const char* multicontrollername ) +{ + + tMultiController* mc = p3d::find< tMultiController >( multicontrollername ); + if( mc == NULL ) + return; + + HeapMgr()->PushHeap(GMA_LEVEL_OTHER); + + // Iterate through the frame controllers and add them to our own list + for ( unsigned int i = 0 ; i < mc->GetNumTracks() ; i++ ) + { + tFrameController* fc = mc->GetTrack( i )->Clone(); + + VehicleFrameController vfc; + + vfc.trigger = eNone; + vfc.brakeBias = vfc.gasBias = vfc.speedBias = 0; + vfc.timeBias = 1.0f; + vfc.frameSetter = false; + + GetSpecialController( fc->GetUID(), &vfc ); + vfc.frameController = fc; + vfc.frameController->AddRef(); + vfc.frameController->Reset(); + vfc.frameController->SetCycleMode( FORCE_CYCLIC ); + + // What kind of animation controller are we dealing with? + // Possibly a pose controller? + tPoseAnimationController* poseController = dynamic_cast< tPoseAnimationController* >( vfc.frameController ); + if ( poseController ) + { + // The compositeDrawble::clone function allocates a brand new pose + // set the frame controller that this pose is the one to update upon + // Advance() + poseController->SetPose( mCompositeDrawable->GetPose() ); + + } + + mFrameControllers.push_back( vfc ); + } + HeapMgr()->PopHeap(GMA_LEVEL_OTHER); +} + + +//============================================================================= +// GeometryVehicle::GetSpecialController +//============================================================================= +// Description: Comment +// +// Parameters: ( tUID name, VehicleFrameController* outSpecialController ) +// +// Return: bool +// +//============================================================================= +bool GeometryVehicle::GetSpecialController( tUID name, VehicleFrameController* outSpecialController ) +{ + bool wasFound = false; + // iterate through the preset "special" controllers and find any with the same name + for ( int i = 0 ; i < NUM_FRAME_CONTROLLERS ; i++ ) + { + if ( name == tName::MakeUID( FRAME_CONTROLLER_DATA[i].name ) ) + { + *outSpecialController = FRAME_CONTROLLER_DATA[i]; + wasFound = true; + break; + } + } + return wasFound; +} + + +//============================================================================= +// GeometryVehicle::AdvanceAnimationControllers +//============================================================================= +// Description: Comment +// +// Parameters: ( float deltaTime ) +// +// Return: void +// +//============================================================================= +void GeometryVehicle::AdvanceAnimationControllers( float deltaTime ) +{ + int numFCs = mFrameControllers.size(); + + float gas = mVehicleOwner->GetGas(); + float speed = mVehicleOwner->GetSpeedKmh(); + float brake = mVehicleOwner->GetBrake(); + + if ( mVehicleOwner->IsMovingBackward() ) + speed *= -1; + + FCTrigger trigger = eNone; + + if ( mVehicleOwner->GetDeltaGas() > 0.001f ) + { + trigger = eBackfire; + } + for ( int i = 0 ; i < numFCs ; i++ ) + { + VehicleFrameController& vfc = mFrameControllers[i]; + if ( vfc.frameSetter ) + { + // This thing doesn't ever run linearly, it only sets the frame + // it uses specifically. + // Figure out which frame to set based upon vehicle attributes + float relativeframe; + float numFrames = vfc.frameController->GetNumFrames(); + relativeframe = vfc.brakeBias * brake + vfc.gasBias * gas + + vfc.speedBias * speed; + + vfc.frameController->SetFrame( relativeframe * numFrames ); + vfc.frameController->Advance( 0.0f ); + } + else + { + // Is the animation triggered by something? + // Check to see if it is or isn't + if ( trigger != eNone && trigger == vfc.trigger ) + { + // Its a triggered animation thats been triggered + // If the animation isnt playing already then restart it + if ( vfc.frameController->GetRelativeSpeed() < 0.001f ) + { + vfc.frameController->Reset(); + vfc.frameController->SetRelativeSpeed( 1.0f ); + } + if ( vfc.frameController->LastFrameReached() ) + { + vfc.frameController->SetRelativeSpeed( 0 ); + } + } + // Standard animation, apply biases and play + float advanceTime = vfc.timeBias * deltaTime; + if ( advanceTime < 0 ) advanceTime = 0; + + float advanceGas = gas * vfc.gasBias * deltaTime; + if ( advanceGas < 0 ) advanceGas = 0; + + float advanceBrake = brake * vfc.brakeBias * deltaTime; + if ( advanceBrake < 0 ) advanceBrake = 0; + + float advanceSpeed = speed * vfc.speedBias * deltaTime; + if ( advanceSpeed < 0 ) advanceSpeed = 0; + + float totalAdvanceTime = advanceTime + advanceGas - advanceBrake + advanceSpeed; + + vfc.frameController->Advance( totalAdvanceTime ); + } + } +} + diff --git a/game/code/worldsim/redbrick/geometryvehicle.h b/game/code/worldsim/redbrick/geometryvehicle.h new file mode 100644 index 0000000..4da0a8b --- /dev/null +++ b/game/code/worldsim/redbrick/geometryvehicle.h @@ -0,0 +1,322 @@ +/*=========================================================================== + geometryvehicle.h + + created Dec 7, 2001 + by Greg Mayer + + Copyright (c) 2001 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ + +#ifndef _GEOMETRYVEHICLE_HPP +#define _GEOMETRYVEHICLE_HPP + +class tPose; +class tGeometry; +class tTexture; +class tShader; +class tPoseAnimationController; +class tParticleSystem; +class tAnimation; +class Vehicle; +class TrafficBodyDrawable; +class tBillboardQuadGroup; +class tFrameController; +class SkidmarkGenerator; +class tMultiController; +class StatePropCollectible; + +#include <p3d/anim/compositedrawable.hpp> +#include <render/particles/vehicleparticleemitter.h> +#include <worldsim/SkidMarks/SkidMarkGenerator.h> +#include <constants/particleenum.h> +#include <vector> +#include <memory/stlallocators.h> + +#define MAX_REFRACTION_SHADERS 16 + +struct BlobShadowParams; + +struct HeadLightParams +{ + HeadLightParams( const rmt::Vector& Pos, const rmt::Vector& Normal, const rmt::Vector& Forward, const rmt::Vector& Up ) : + GroundPos( Pos ), GroundNormal( Normal ), VehicleHeading( Forward ), VehicleUp( Up ) {}; + const rmt::Vector& GroundPos; + const rmt::Vector& GroundNormal; + const rmt::Vector& VehicleHeading; + const rmt::Vector& VehicleUp; + float LightReach; // A value of 1 is the light right at the car (i.e. car on the ground), a zero is light max reach from the car (i.e. car high in the air) +}; + +enum FCTrigger +{ + eNone, + eBackfire +}; + +class GeometryVehicle +{ +public: + + GeometryVehicle(); + ~GeometryVehicle(); + + bool Init( const char* name, Vehicle* owner, int i); + + void Display(); + //void DisplaySkids(); + void UpdateSkids(); + void SetSkidValues(int wheel, float intensity, rmt::Vector& normal, eTerrainType terrainType ); + + + void Update(float dt); + + tPose* GetP3DPose(); + + int CastsShadow(); + void DisplayShadow( BlobShadowParams* BlobParams = 0 ); + + // The additive lights. Headlights, etc. + void DisplayLights( const HeadLightParams& LightParams ); + + void ShowBrakeLights(); + void HideBrakeLights(); + + void ShowReverseLights(); + void HideReverseLights(); + + void SetTrafficBodyColour( pddiColour colour ); + void SetShadowAdjustments( float Adjustments[ 4 ][ 2 ] ); + void SetShininess( unsigned char EnvRef ) { m_EnvRef = EnvRef; } + + void SetFadeAlpha( int fade ); + + bool IsTrafficModel(); + + void FadeRoof( bool fade ); + + void EnableLights( bool enable ); + void SetLightsOffDueToDamage(bool lightsOffDueToDamage) {mLightsOffDueToDamage = lightsOffDueToDamage;} + // Get the vehicle body colour, currently only valid for traffic vehicles + tColour GetVehicleColour()const; + bool HasVehicleColour()const { return (mTrafficBodyDrawable != NULL); } + + // Structure that holds data on how to manipulate frame controllers + // that are tied to vehicle specific things like gas and velocity + // The animation frame of such an object is determined by + // frame = clamp( gasBias * currGas * brakeBias * brake + speedBias * currSpeed ) + // controller->SetFrame( frame ); + // note that brake values act as reverse gas and are in the range [0,1] like gas is + + + struct VehicleFrameController + { + const char* name; + tFrameController* frameController; + bool frameSetter; + FCTrigger trigger; + float gasBias; + float brakeBias; + float speedBias; + float timeBias; + }; + + float GetHeadlightScale(); + void SetHeadlightScale( float scale ); + + // Attach a tDrawable collectible to the vehicle + // returns true if attached or false if not ( false happens + // when a collectible is already attached to the vehicle) + bool AttachCollectible( StatePropCollectible* ); + StatePropCollectible* GetAttachedCollectible(); + void DetachCollectible( const rmt::Vector& velocity, bool explode = true ); + +private: + + // TODO - keep like this? + friend class Vehicle; + + Vehicle* mVehicleOwner; + + bool GetArt( const char* name); // a bit obscure, but use return value to say true for localized damage textures and shaders present + void FindAndTurnOffWheels(); + void FindAndTurnOffFrontWheelsOnly(); // for rocket car + void FindBrakeLightBillboardJoints(); + void FindHeadLightBillboardJoints(); + void FindGhostGlowBillboards( const char* model ); + void FindRoofGeometry( const char* model ); + void FindNukeGlowBillboards( const char* model ); + + void SetCollectibleHardpointPosition( const rmt::Vector& position ); + void SetCollectibleHardpointTransform( float rotx ,float roty ,float rotz, const rmt::Vector& position ); + + tCompositeDrawable* mCompositeDrawable; + + // some debug shit to make the car change colour when you bottom out + tGeometry* mChassisGeometry; + + + tShader* mRefractionShader[ MAX_REFRACTION_SHADERS ]; + + //------------------------------------------------------- + // damage texture stuff + // + // get specific test working then make a general solution + // + //------------------------------------------------------- + + // need pointers to the tShaders so we can swap the texture they use + // need pointers to the textures to swap in + // need pointers to the textures to swap back? + + // we should find these for type 1 and 2 damage + + + tShader* mHoodShader; + tShader* mTrunkShader; + tShader* mDoorPShader; + tShader* mDoorDShader; + + tTexture* mHoodTextureDam; + tTexture* mTrunkTextureDam; + tTexture* mDoorPTextureDam; + tTexture* mDoorDTextureDam; + + tTexture* mHoodTextureNorm; + tTexture* mTrunkTextureNorm; + tTexture* mDoorPTextureNorm; + tTexture* mDoorDTextureNorm; + + bool FindDamageShadersAndTextures( const char* name); // only returns true if localized damage textures and shaders are present + //void TriggerDamage(); + + void DamageTextureDoorD(bool on); + void DamageTextureDoorP(bool on); + void DamageTextureHood(bool on); + void DamageTextureTrunk(bool on); + + void HideFlappingPiece(int jointindex, bool doit); + + void InitParticles(); + + void SetEngineSmoke(ParticleEnum::ParticleID pid); + void SetWheelSmoke( int wheel, ParticleEnum::ParticleID pid, float bias); + + tShader* mChassisShader; + tTexture* mChassisTextureNorm; + tTexture* mChassisTextureDam; + void DamageTextureChassis(bool on); + + + VehicleParticleEmitter* mParticleEmitter; + + ParticleAttributes mEngineParticleAttr; // eNull + ParticleAttributes mLeftWheelParticleAttr; + ParticleAttributes mRightWheelParticleAttr; + ParticleAttributes mTailPipeParticleAttr; // eNull + + //eEngineSmokeLight, + //eEngineSmokeHeavy, + + // Particle system with variable emission rate depending on speed + tParticleSystem* mVariableEmissionParticleSystem; + + + SkidMarkGenerator* mSkidMarkGenerator; + void InitSkidMarks(); + + // ? + // tPoseAnimationController* mAnimController; + + //tPoseAnimation* mAnim; + + + //tPoseAnimationController* mAnimController; + tAnimation* mAnim; + + + float mAnimRevPerSecondBase; + + float mRevMult; + + // Special particle effects (only present on a few vehicles, like Frink or + // the zombie car) + ParticleEnum::ParticleID mSpecialEffect; + + // Cache the last position of the vehicle for + // spherical environment map rotation based on distance travelled + // + rmt::Vector mLastPosition; + float mCurEnvMapRotation; + + int mBrakeLightJoints[4]; + int mReverseLightJoints[4]; + + TrafficBodyDrawable* mTrafficBodyDrawable; + TrafficBodyDrawable* mTrafficDoorDrawable; + + float mShadowPointAdjustments[ 4 ][ 2 ]; // Nudge around the shadow points. See notes in the shadow display. + + int mFadeAlpha; + + enum { + NUM_BRAKELIGHT_BBQS = 4, +#ifdef RAD_WIN32 + NUM_FRINKARC_BBQS = 3, +#endif + NUM_GHOSTGLOW_BBQGS = 6, + NUM_GHOSTGLOW_BBQS = 6, + NUM_NUKEGLOW_BBQGS = 1, + NUM_NUKEGLOW_BBQS = 3 + }; + +#ifdef RAD_WIN32 + tBillboardQuadGroup* mFrinkArc; + tColour mOriginalFrinkArcColour[ NUM_FRINKARC_BBQS ]; +#endif + + tBillboardQuadGroup* mBrakeLights[NUM_BRAKELIGHT_BBQS]; + // This relies on each quadgroup containing only one quad + // & will store color of that quad + tColour mOriginalBrakeLightColours[NUM_BRAKELIGHT_BBQS]; + + bool mUsingTrafficModel; + + bool mHasGhostGlow; + tBillboardQuadGroup* mGhostGlows[NUM_GHOSTGLOW_BBQGS]; + tColour mOriginalGhostGlowColours[NUM_GHOSTGLOW_BBQS]; + + bool mHasNukeGlow; + tBillboardQuadGroup* mNukeGlows[NUM_NUKEGLOW_BBQGS]; + tColour mOriginalNukeGlowColours[NUM_NUKEGLOW_BBQS]; + + + bool mBrakeLightsOn; + float mBrakeLightScale; + float mHeadLightScale; + bool mEnableLights; + bool mLightsOffDueToDamage; + + tCompositeDrawable::DrawablePropElement* mRoofOpacShape; + tCompositeDrawable::DrawablePropElement* mRoofAlphaShape; + tShader* mRoofShader; + int mRoofAlpha; + int mRoofTargetAlpha; + + + std::vector< VehicleFrameController, s2alloc< VehicleFrameController > > mFrameControllers; + void FindAnimationControllers( const char* multicontrollername ); + bool GetSpecialController( tUID name, VehicleFrameController* outSpecialController ); + void AdvanceAnimationControllers( float deltaTime ); + + // A collectible that can be attached to the car + // Used for l7m5 mission + StatePropCollectible* m_Collectible; + rmt::Matrix m_CollectibleTransform; + + unsigned char m_EnvRef; +}; + +#endif // _GEOMETRYVEHICLE_HPP
\ No newline at end of file diff --git a/game/code/worldsim/redbrick/physicslocomotion.cpp b/game/code/worldsim/redbrick/physicslocomotion.cpp new file mode 100644 index 0000000..1ab2899 --- /dev/null +++ b/game/code/worldsim/redbrick/physicslocomotion.cpp @@ -0,0 +1,2069 @@ +/*=========================================================================== + physicslocomotion.cpp + + created April 24, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ + +#include <simcommon/simstatearticulated.hpp> +#include <simphysics/articulatedphysicsobject.hpp> +#include <worldsim/redbrick/physicslocomotion.h> +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/redbrick/wheel.h> +#include <worldsim/worldphysicsmanager.h> + + +#include <poser/pose.hpp> +#include <poser/poseengine.hpp> +#include <poser/posedriver.hpp> + +#include <render/IntersectManager/IntersectManager.h> + +#include <cheats/cheatinputsystem.h> + +using namespace sim; + +/* + data that should move from Vehicle to here... + + struct TerrainIntersectCache + { + rmt::Vector closestTriPosn; + rmt::Vector closestTriNormal; + rmt::Vector planePosn; + rmt::Vector planeNorm; + }; + + TerrainIntersectCache mTerrainIntersectCache[4]; // is this wasting too much memory? + + +??? + float mRollingFrictionForce; + float mTireLateralResistance; + float mSlipGasModifier; +??? + + + + + +*/ + + +//------------------------------------------------------------------------ +PhysicsLocomotion::PhysicsLocomotion(Vehicle* vehicle) : VehicleLocomotion(vehicle) +{ + mVehicle = vehicle; + // + int i; + for(i = 0; i < 4; i++) + { + mForceApplicationPoints[i].Clear(); + mSuspensionPointVelocities[i].Clear(); + + mCachedSuspensionForceResults[i] = 0.0f; + + // TODO - initialize better? + // everytime control is switched to VL_PHYSICS + //mWheelTerrainCollisionFixDepth[i] = 0.0f; + + //mWheelTerrainCollisionNormals[i].x = 0.0f; + //mWheelTerrainCollisionNormals[i].y = 1.0f; + //mWheelTerrainCollisionNormals[i].z = 0.0f; + + //mWheelTerrainCollisionPoints[i].Clear(); + + //mGoodWheelTerrainCollisionValue[i] = false; + } + + mCurrentSteeringForce = mVehicle->mDesignerParams.mDpTireLateralResistanceNormal; + +} + + + +//------------------------------------------------------------------------ +PhysicsLocomotion::~PhysicsLocomotion() +{ + // +} + + + +//============================================================================= +// PhysicsLocomotion::SetTerrainIntersectCachePointsForNewTransform +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::SetTerrainIntersectCachePointsForNewTransform() +{ + rmt::Vector tempVec; + + int i; + for(i = 0; i < 4; i++) + { + tempVec = mVehicle->mSuspensionWorldSpacePoints[i]; + tempVec.y -= 2.0f * mVehicle->mWheels[i]->mRadius; + + mTerrainIntersectCache[i].closestTriPosn = tempVec; + mTerrainIntersectCache[i].closestTriNormal.Set(0.0f, 1.0f, 0.0f); + + mTerrainIntersectCache[i].planePosn = tempVec; + mTerrainIntersectCache[i].planeNorm.Set(0.0f, 1.0f, 0.0f); + + mTerrainIntersectCache[i].mTerrainType = TT_Road; + mTerrainIntersectCache[i].mInteriorTerrain = false; + + mIntersectNormalUsed[i].Set(0.0f, 1.0f, 0.0f); + } + +} + + +//============================================================================= +// PhysicsLocomotion::MoveWheelsToBottomOfSuspension +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::MoveWheelsToBottomOfSuspension() +{ + + poser::Pose* pose = mVehicle->mPoseEngine->GetPose(); + + // want to call false here because we want the movement of the hierarchy based on animation to also be available for collision + // TODO - make sure this is done in the traffic locomotion case as well. + mVehicle->mPoseEngine->Begin(false); //Cary Here - I've moved it. + //mVehicle->mPoseEngine->Begin(true); //Cary Here - I've moved it. + // TODO - even need to do this? + + + // TODO + // note: + // are we making the assumption that the animation doesn't move the suspension points around? + // for now, yes. + + + // TODO - only do this if wheelInCollision? + + + int i; + for(i = 0; i < 4; i++) + { + Wheel* wheel = mVehicle->mWheels[i]; + + poser::Joint* joint = pose->GetJoint(mVehicle->mWheelToJointIndexMapping[i]); + rmt::Vector trans = joint->GetObjectTranslation(); + + //trans.y -= mVehicle->mWheels[i]->mLimit; + // TODO - verify that the -= is the thing to do here + trans.y -= wheel->mLimit; + + trans.y += mVehicle->mDesignerParams.mDpSuspensionYOffset; + + joint->SetObjectTranslation(trans); + + + + // TODO - if this method actually works, make nicer interface with wheel to do this + + // + // remember, mYOffset is the offset (upwards) from the bottom of the suspension limit, to fix wheel out of collision (or just barely in collision) + wheel->mYOffset = -1.0f * wheel->mLimit; + + wheel->mWheelInCollision = false; + wheel->mWheelBottomedOutThisFrame = false; + + + } + + // just in case + // + // TODO - review why the hell you're doing this? - this one's pretty important I think + CollisionObject* collObj = mVehicle->mSimStateArticulated->GetCollisionObject(); + collObj->SetHasMoved(true); + + +} + + +//============================================================================= +// PhysicsLocomotion::UpdateVehicleGroundPlane +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::UpdateVehicleGroundPlane() +{ + // create a new transform for the ground plane sim state and set it. + + rmt::Vector p, n; + p.Set(0.0f, 0.0f, 0.0f); + n.Set(0.0f, 0.0f, 0.0f); + + /* + int i; + int count = 0; + for(i = 0; i < 4; i++) + { + //if(mFoundPlane[i]) + + // try doing this with whatever's in there + // old or new + // + // TODO - is this safe? + + { + p.Add(mVehicle->mTerrainIntersectCache[i].planePosn); + n.Add(mVehicle->mTerrainIntersectCache[i].planeNorm); + } + + + } + + p.Scale(0.25f); + n.Scale(0.25f); + */ + + // new idea for ground plane + // pick the most upright normal, and the lowest height point + // ?? + + p = mTerrainIntersectCache[0].planePosn; + n = mTerrainIntersectCache[0].planeNorm; + + int i; + for(i = 1; i < 4; i++) + { + if(mTerrainIntersectCache[i].planePosn.y < p.y) + { + p = mTerrainIntersectCache[i].planePosn; + } + + if( GetWorldPhysicsManager()->mWorldUp.DotProduct(mTerrainIntersectCache[i].planeNorm) > + GetWorldPhysicsManager()->mWorldUp.DotProduct(n) ) + { + n = mTerrainIntersectCache[i].planeNorm; + } + + } + + + // new approach with manual sim state + + mVehicle->mGroundPlaneWallVolume->mPosition = p; + mVehicle->mGroundPlaneWallVolume->mNormal = n; + + + sim::CollisionObject* co = mVehicle->mGroundPlaneSimState->GetCollisionObject(); + co->PostManualUpdate(); + + + + + /* + rmt::Matrix groundPlaneTransform; + groundPlaneTransform.Identity(); + + // TODO - optimize this call since we know what the heading is all the time + //rmt::Vector heading(0.0f, 0.0f, 1.0f); + rmt::Vector up(0.0f, 0.0f, 1.0f); + + // note: + //!!! + // + // new approach - use our n as heading + + //groundPlaneTransform.FillHeading(heading, n); + groundPlaneTransform.FillHeading(n, up); + + groundPlaneTransform.FillTranslate(p); + + //PushSimState(bool inReset=true) = 0; // better name for this would be Sync + // ? mSimStateArticulated->SetControl(simAICtrl); + + mVehicle->mGroundPlaneSimState->SetTransform(groundPlaneTransform); + + + // ? mSimStateArticulated->SetControl(simSimulationCtrl); + // TODO - need to do this? + // double check with martin!! + //mVehicle->mGroundPlaneSimState->GetCollisionObject()->Update(); - don't seem to need this? + */ + +} + +//------------------------------------------------------------------------ +void PhysicsLocomotion::PreCollisionPrep(bool firstSubstep) +{ + + BEGIN_PROFILE("MoveWheelsToBottomOfSuspension") + MoveWheelsToBottomOfSuspension(); + END_PROFILE("MoveWheelsToBottomOfSuspension") + + // this is in substep, so comment out if we don't want it to happen + if(firstSubstep) // only do this once per update, but it must be inside the loop the first time + { + + if(mVehicle->mSimStateArticulated->GetControl() != sim::simAICtrl) + { + + BEGIN_PROFILE("FetchWheelTerrainCollisionInfo") + FetchWheelTerrainCollisionInfo(); + END_PROFILE("FetchWheelTerrainCollisionInfo") + } + } + else + { + int stophere = 1; + } + + BEGIN_PROFILE("UpdateVehicleGroundPlane") + UpdateVehicleGroundPlane(); + END_PROFILE("UpdateVehicleGroundPlane") + +} + + +//============================================================================= +// PhysicsLocomotion::CompareNormalAndHeight +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector& normalPointingAtCar, rmt::Vector& groundContactPoint) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::CompareNormalAndHeight(int index, rmt::Vector& normalPointingAtCar, rmt::Vector& groundContactPoint) +{ + + // important! + // + // assume this is being called from the collision solver agent _after_ we've already fetched new wheel terrain collision info + + // so.... + // how to decide? + + // first try? + // y value of contact points? + rAssert(index >= 0 && index < 4); + + + if(mFoundPlane[index]) + { + + if(groundContactPoint.y > mTerrainIntersectCache[index].planePosn.y) + { + + //mTerrainIntersectCache[index].planePosn = groundContactPoint; + //mTerrainIntersectCache[index].planeNorm = normalPointingAtCar; + + // note this used to be in here?? + mIntersectNormalUsed[index] = normalPointingAtCar; + + } + } + else + { + + // take the more uprigth normal + if(GetWorldPhysicsManager()->mWorldUp.DotProduct(normalPointingAtCar) > + GetWorldPhysicsManager()->mWorldUp.DotProduct(mIntersectNormalUsed[index]) ) + + { + mIntersectNormalUsed[index] = normalPointingAtCar; + + + } + + } +/* + struct TerrainIntersectCache + { + rmt::Vector closestTriPosn; + rmt::Vector closestTriNormal; + rmt::Vector planePosn; + rmt::Vector planeNorm; + }; + + TerrainIntersectCache mTerrainIntersectCache[4]; // is this wasting too much memory? + + // need this because if we're driving on a static collision volume the terrain normal could be way off! + rmt::Vector mIntersectNormalUsed[4]; +*/ + + +} + +//============================================================================= +// PhysicsLocomotion::PreSubstepUpdate +//============================================================================= +// Description: Comment +// +// Parameters: (Vehicle* vehicle) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::PreSubstepUpdate() +{ + + //MoveWheelsToBottomOfSuspension(); + + //FetchWheelTerrainCollisionInfo(); + + //UpdateVehicleGroundPlane(); + +} + + +//------------------------------------------------------------------------ +void PhysicsLocomotion::SetForceApplicationPoints() +{ + int i; + for(i = 0; i < 4; i++) + { + // TODO - clean up + // for force application points should also add in wheel - nope, tried that, not so good + + // TODO + // why do I have copies of thsi stuff here + mForceApplicationPoints[i] = mVehicle->mSuspensionWorldSpacePoints[i]; + + // try something new: + + if(mVehicle->mVehicleType == VT_USER && (mVehicle->mVehicleState == VS_SLIP || mVehicle->mVehicleState == VS_EBRAKE_SLIP)) + { + + rmt::Vector tireFix = mVehicle->mVehicleUp; + + + // new test - decrease depending on % of top speed + //tireFix.Scale(mVehicle->mWheels[i]->mRadius * -1.0f * (1.0f - (mVehicle->mPercentOfTopSpeed * 0.5f))); + + //tireFix.Scale(mVehicle->mWheels[i]->mRadius * -1.0f * (1.0f - (mVehicle->mPercentOfTopSpeed * 0.75f))); + + + //tireFix.Scale(mVehicle->mWheels[i]->mRadius * -1.0f * (1.0f - (mVehicle->mPercentOfTopSpeed * mVehicle->mPercentOfTopSpeed))); + tireFix.Scale(mVehicle->mWheels[i]->mRadius * mVehicle->mPercentOfTopSpeed);// * 0.5f); + + + //mForceApplicationPoints[i].y -= mVehicle->mWheels[i]->mRadius; + + + mForceApplicationPoints[i].Add(tireFix); + } + + mSuspensionPointVelocities[i] = mVehicle->mSuspensionPointVelocities[i]; + + } + +} + + +//------------------------------------------------------------------------ +void PhysicsLocomotion::ApplySuspensionForces(float dt, bool atrest) +{ + + int i; + for(i = 0; i < 4; i++) + { + //rmt::Vector force = mVehicle->mVehicleUp; + //rmt::Vector force = mTerrainIntersectCache[i].planeNorm; + rmt::Vector force; + if(atrest) + { + force = mVehicle->mVehicleUp; + } + else + { + force = mIntersectNormalUsed[i]; + } + + //?? + /* + // not helping + if( GetWorldPhysicsManager()->mWorldUp.DotProduct(mVehicle->mVehicleUp) > + GetWorldPhysicsManager()->mWorldUp.DotProduct(force) && mVehicle->mDoingJumpBoost) + + { + + force = mVehicle->mVehicleUp; + } + */ + + // debug + //float cos30 = 0.866f; + //float cos40 = 0.766f; + //if(GetWorldPhysicsManager()->mWorldUp.DotProduct(force) < cos30) + + + float yVelocityAlongSuspensionAxis = force.DotProduct(mSuspensionPointVelocities[i]); + + //mCachedSuspensionForceResults[i] = mWheels[i]->CalculateSuspensionForce(mSuspensionPointVelocities[i].y, dt); + Wheel* wheel = mVehicle->mWheels[i]; + + float forceToUse = 0.0f; + mCachedSuspensionForceResults[i] = CalculateSuspensionForce(wheel, yVelocityAlongSuspensionAxis, dt, forceToUse); + + + force.Scale(forceToUse); + + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->AddForce(force, &(mForceApplicationPoints[i])); + } + + +} + +//============================================================================= +// PhysicsLocomotion::CalculateSuspensionForce +//============================================================================= +// Description: Comment +// +// note - moving thsi functionality from Wheel class to here because wheel +// shouldn't do physics calculations, and this class shouldn't hold the +// info about wheels - like k and c +// +// +// Parameters: (Wheel* wheel, float suspensionPointYVelocity, float dt) +// +// Return: float +// +//============================================================================= +float PhysicsLocomotion::CalculateSuspensionForce(Wheel* wheel, float suspensionPointYVelocity, float dt, float& forceToUse) +{ + + // crazy shit goin' on here! + // the value we return we'll cachce for slip calculations and stuff + // + // the value we stuff in forceToUse is the one to make the car bank. + // + // the one we cache should not include the quadSpringForce + + + float force = 0.0f; + + + rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; + float tip = mVehicle->mVehicleUp.DotProduct(up); + + float cos85 = 0.087f; + float cos80 = 0.17365f; + float cos65 = 0.4226f; + + if(wheel->mWheelInCollision && tip > cos65) // minor TODO - should this number match any others? + { + // test + // + // make the spring also only push up + + //float springForce = wheel->mk * wheel->mYOffset; + float springForce = 0.0f; + //if(wheel->mYOffset > 0.0f) + + if(!(mVehicle->mDoingJumpBoost && wheel->mYOffset < 0.0f)) + { + springForce = wheel->mk * wheel->mYOffset; + } + + + + float quadSpringForce = 0.0f; + if(wheel->mYOffset > 0.0f) + { + quadSpringForce = wheel->mqk * wheel->mYOffset * wheel->mYOffset; + //springForce += quadSpringForce; + } + + + // + // or... topping out! + // + if((wheel->mLimit - rmt::Fabs(wheel->mYOffset)) < wheel->mLimit * 0.25f) + { + //springForce *= 3.0f; + } + + // for now, only add damping if chassis is trying to compress suspension - ie. y velocity is down, -ve + float damperForce = 0.0f; + + + if(suspensionPointYVelocity > 0.0f)// this would make the dampe pull down + { + if((mVehicle->mDamperShouldNotPullDown[wheel->mWheelNum]) || mVehicle->mDoingJumpBoost) + { + // no suspension force down + } + else + { + // regular + damperForce = wheel->mc * -1.0f * suspensionPointYVelocity; + } + + } + else + { + // no worries + damperForce = wheel->mc * -1.0f * suspensionPointYVelocity; + } + + + + force = springForce + damperForce; + forceToUse = springForce + damperForce + quadSpringForce; + } + else + { + // need to relax spring somehow + + // this is essentially useless.... + + float relaxDistance = wheel->mLimit / wheel->mSpringRelaxRate * dt; + if(wheel->mYOffset >= relaxDistance) + { + wheel->mYOffset -= relaxDistance; + } + else if(wheel->mYOffset <= -relaxDistance) + { + wheel->mYOffset += relaxDistance; + } + + } + + return force; + +} + + +//============================================================================= +// PhysicsLocomotion::ApplyDragForce +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::ApplyDragForce() +{ + if(mVehicle->mSpeed > 1.0f) + { + rmt::Vector force = mVehicle->mVelocityCM; + + // new test - + //force.y = 0.0f; + + force.NormalizeSafe(); + + float mag = mVehicle->mSpeed * mVehicle->mSpeed * 0.5f * mVehicle->mDragCoeff; + + // new + // car slows down too much when you let off gas + if(mVehicle->mGas == 0.0f && mVehicle->mSpeedKmh > 50.0f && mVehicle->mBrake == 0.0f) + { + mag *= 0.25f; + } + + + force.Scale(-1.0f * mag); + + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->AddForce(force); + } + +} + + +//============================================================================= +// PhysicsLocomotion::LowSpeedTest +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::LowSpeedTest() +{ + + //static float test = 0.8f; + static float test = 0.8f; + + static float linearWhittle = 0.6f; + static float angularWhittle = 0.1f; + + //const float lowspeed = 1.0f; + //static float lowspeed = 2.0f; + float lowspeed = 2.0f; + float reallylowspeed = 0.05f; + + // only apply if at least 2 wheels in contact with the ground: + + int i; + int count = 0; + for(i = 0; i < 4; i++) + { + if(mVehicle->mWheels[i]->mWheelInCollision) + { + count++; + } + } + + if(count > 1) + { + + if(mVehicle->mSpeed < lowspeed && mVehicle->mGas < 0.1f /*&& mVehicle->mReverse < 0.1f*/ && mVehicle->mBrake < 0.1f) // new... brake + { + // whittle away speed, or just reset? + //mVehicle->mSimStateArticulated->ResetVelocities(); + // I knew this was a bad idea + + if(mVehicle->mSpeed < reallylowspeed) + { + mVehicle->mSimStateArticulated->ResetVelocities(); + } + else + { + + rmt::Vector& linearVel = mVehicle->mSimStateArticulated->GetLinearVelocity(); + rmt::Vector& angularVel = mVehicle->mSimStateArticulated->GetAngularVelocity(); + + linearVel.Scale(linearWhittle); + angularVel.Scale(angularWhittle); + } + } + } + + +} + + + +//============================================================================= +// PhysicsLocomotion::ApplyAngularDrag +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::ApplyAngularDrag() +{ + if(mVehicle->mPercentOfTopSpeed > 0.01f) + { + const float magicshit = 3.0f; + + rmt::Vector angularDragForce = mVehicle->mSimStateArticulated->GetAngularVelocity(); + angularDragForce.Scale(-1.0f * magicshit * mVehicle->mDesignerParams.mDpMass); + + sim::PhysicsObject* phobj = (sim::PhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject()); + rAssert(phobj); + + phobj->AddTorque(angularDragForce); + } + +} + +//============================================================================= +// PhysicsLocomotion::ApplyRollingFriction +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::ApplyRollingFriction() +{ + // to start with simplest possible model - constant? + //static float test = 1.0f; + //static float test2 = 2000.0f; + + //static float test = 0.9f; + //static float test = 0.8f; + static float test = 0.8f; + + //const float lowspeed = 1.0f; + //static float lowspeed = 2.0f; + static float lowspeed = 2.0f; + static float reallylowspeed = 0.05f; + + static float torquemod = 0.001f; + + // only apply if at least 2 wheels in contact with the ground: + + int i; + int count = 0; + for(i = 0; i < 4; i++) + { + if(mVehicle->mWheels[i]->mWheelInCollision) + { + count++; + } + } + + if(count > 1) + { + + if(mVehicle->mSpeed > reallylowspeed && mVehicle->mGas == 0.0f) + { + rmt::Vector force = mVehicle->mVelocityCM; + force.Normalize(); + + force.Scale(-1.0f * mVehicle->mRollingFrictionForce * mVehicle->mDesignerParams.mDpMass); + + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->AddForce(force); + + + } + + else if(0)//mVehicle->mGas < 0.1f && mVehicle->mReverse < 0.1f && mVehicle->mBrake < 0.1f) // new... brake + { + // whittle away speed, or just reset? + //mVehicle->mSimStateArticulated->ResetVelocities(); + // I knew this was a bad idea + + //if(1)//mVehicle->mSpeed < reallylowspeed) + /* + if(mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->IsAtRest()) + { + + mVehicle->mSimStateArticulated->ResetVelocities(); + mVehicle->mSimStateArticulated->SetControl(sim::simAICtrl); + + //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->ResetAppliedForces(); + + } + else + { + //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->ResetAppliedForces(); + + + //rmt::Vector& linearVel = mVehicle->mSimStateArticulated->GetLinearVelocity(); + //rmt::Vector& angularVel = mVehicle->mSimStateArticulated->GetAngularVelocity(); + + //linearVel.Scale(test); + //angularVel.Scale(test); + } + */ + + } + } + +} + + +//============================================================================= +// PhysicsLocomotion::HighSpeedInstabilityTest +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::HighSpeedInstabilityTest() +{ + const float threshold = 0.5f; + + // from threshold to 100%, increase y cm offset + + if(mVehicle->mPercentOfTopSpeed > threshold) + { + // should do ... + if(mVehicle->mInstabilityOffsetOn) + { + // already set + // + // nothing to do here + } + else + { + // set it + + rmt::Vector unstableAdjust = mVehicle->mCMOffset; + + //static float weeblemagic = 1.0f; + /*static*/ float magic = 1.0f; + + // scale by point in range + // set range of 0 to 1 for start to limit + /* + float range = 1.0f - ((tip - end) / (start - end)); + + if(range < 0.0f) + { + range = 0.0f; + //rAssert(0); + } + if(range > 1.0f) + { + range = 1.0f; + } + + float modifier = weeblemagic * range; + */ + //weeble.y -= modifier; + + unstableAdjust.y += magic; + + //weeble.y -= 3.0f; + + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(unstableAdjust); + + mVehicle->mInstabilityOffsetOn = true; + } + } + else + { + // if it's on, shut it off + if(mVehicle->mInstabilityOffsetOn) + { + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mCMOffset); + + mVehicle->mInstabilityOffsetOn = false; + } + } + +} + + +//============================================================================= +// PhysicsLocomotion::Weeble +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::Weeble() +{ + bool doit = false; + + // only do this for sideways tipping + + rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; + float tip = mVehicle->mVehicleUp.DotProduct(up); + + float cos35 = 0.8192f; + float cos45 = 0.7071f; + float cos55 = 0.5736f; + float cos40 = 0.766f; + + float start = cos40; + float end = cos55; + + + // new test - + // if we are completely airborn - set cmoffset to 0,0,0 + + //if(mVehicle->mAirBorn) + //{ + // if(!(mVehicle->mCMOffsetSetToOriginal)) + // { + // //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mCMOffset); + // mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mOriginalCMOffset); + // mVehicle->mCMOffsetSetToOriginal = true; + // } + //} + //else + { + /* + if(mVehicle->mCMOffsetSetToOriginal) + { + //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mOriginalCMOffset); + mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mCMOffset); + mVehicle->mCMOffsetSetToOriginal = false; + } + */ + + //if(!mVehicle->mAirBorn && tip < start) + if(1)//tip < start) + { + bool slowdowntipping = false; + if( !(mVehicle->mWheels[0]->mWheelInCollision) && !(mVehicle->mWheels[3]->mWheelInCollision) ) + { + doit = true; + + if(mVehicle->mWheels[1]->mWheelInCollision && mVehicle->mWheels[2]->mWheelInCollision) + { + slowdowntipping = true; + } + } + + + if( !(mVehicle->mWheels[1]->mWheelInCollision) && !(mVehicle->mWheels[2]->mWheelInCollision) ) + { + doit = true; + + if(mVehicle->mWheels[0]->mWheelInCollision && mVehicle->mWheels[3]->mWheelInCollision) + { + slowdowntipping = true; + } + } + + if(0)//(slowdowntipping) + { + // only resist tipping UP! + + + + //static float magicshit = 3.0f; + const float magicshit = 2.5f; + + float facingAngVel = (mVehicle->mSimStateArticulated->GetAngularVelocity()).DotProduct(mVehicle->mVehicleFacing); + + // a positive value means tipping left. + // + // so....... + // we want to apply the torque if the transverse.dot.worldup is positive + + // or... if tipping right and transverse.dot.worldup is negative + + if( (facingAngVel > 0.0f && mVehicle->mVehicleTransverse.DotProduct(up) > 0.0f) || + (facingAngVel < 0.0f && mVehicle->mVehicleTransverse.DotProduct(up) < 0.0f) ) + { + rmt::Vector torque = mVehicle->mVehicleFacing; + + + torque.Scale(-1.0f * magicshit * mVehicle->mDesignerParams.mDpMass * facingAngVel); + + sim::PhysicsObject* phobj = (sim::PhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject()); + rAssert(phobj); + + phobj->AddTorque(torque); + } + } + + } + if(mVehicle->mAirBorn) + { + doit = true; + } + + + if(doit) + { + + if(!(mVehicle->mWeebleOn)) + { + + // weeble + rmt::Vector weeble = mVehicle->mCMOffset; + + //static float weeblemagic = 1.0f; + float weeblemagic = 1.0f; + + // scale by point in range + // set range of 0 to 1 for start to limit + /* + float range = 1.0f - ((tip - end) / (start - end)); + + if(range < 0.0f) + { + range = 0.0f; + //rAssert(0); + } + if(range > 1.0f) + { + range = 1.0f; + } + + float modifier = weeblemagic * range; + */ + //weeble.y -= modifier; + + //weeble.y -= weeblemagic; + weeble.y += mVehicle->mDesignerParams.mDpWeebleOffset; + + //weeble.y -= 3.0f; + + rmt::Vector current = ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->GetExternalCMOffset(); + + if(current.Equals(weeble)) + { + int stophere = 1; + } + else + { + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(weeble); + } + + mVehicle->mWeebleOn = true; + } + + } + else + { + if(mVehicle->mWeebleOn) + { + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mCMOffset); + } + mVehicle->mWeebleOn = false; + } + + } + +} + + +//============================================================================= +// PhysicsLocomotion::PreUpdate +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::PreUpdate() +{ + //mVehicle->CalculateSuspensionLocationAndVelocity(); + +} + + +//============================================================================= +// PhysicsLocomotion::Update +//============================================================================= +// Description: Comment +// +// Parameters: (float dt) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::Update(float dt) +{ + + + // recall + // this is the set of function calls to physicsvehicle from vehicle in the old + // system + + UseWheelTerrainCollisionInfo(dt); + + CorrectWheelYPositions(); // old name: PreUpdate(dt); + + mVehicle->CalculateSuspensionLocationAndVelocity(); + + + + //mVehicle->mSimStateArticulated->SetControl(sim::simSimulationCtrl); -- move this into SelfRestTest + + + //bool rest = mVehicle->SelfRestTest(); + + + bool rest = false; + rest = mVehicle->SelfRestTest(); + + + if(mVehicle->mSimStateArticulated->GetControl() != sim::simAICtrl) + { + + + // TODO - this call, and UpdateJointState, should be in the same place + // mVehicle->mSimStateArticulated->StoreJointState(dt); + + + // the update guts from physicsvehicle + // + // kind of a fucking mess + //mVehicle->CalculateSuspensionLocationAndVelocity(); + + SetForceApplicationPoints(); + + + ApplySuspensionForces(dt, rest); + + if(!(mVehicle->mAirBorn) && !rest) + { + ApplyControllerForces2(dt); + } + else + { + // at least do this + mVehicle->mBurnoutLevel = 0.0f; + + } + + if(GetCheatInputSystem()->IsCheatEnabled(CHEAT_ID_NO_TOP_SPEED) && mVehicle->mVehicleType == VT_USER) + { + // no drag for you! + } + else + { + if(!(mVehicle->mSteeringWheelsOutOfContact) && !(mVehicle->mDoSpeedBurst) && !(mVehicle->mDoingJumpBoost)) + { + ApplyDragForce(); + } + } + + if(!(mVehicle->mDoingJumpBoost)) + { + ApplyRollingFriction(); // move low speed portion of this into a separate function to follow update... + } + + //TipTest(); + + if(mVehicle->mAirBorn && mVehicle->mVehicleType == VT_USER ) + { + ApplyAngularDrag(); + } + + Weeble(); + + if(mVehicle->mVehicleType == VT_USER) + { + //HighSpeedInstabilityTest(); + } + + if(mVehicle->mVehicleType == VT_USER) + { + StuckOnSideTest(dt); + } + + + if(mVehicle->mAirBorn)// && mVehicle->mVehicleType == VT_USER) + { + DurangoStyleStabilizer(dt); + } + + + + // + // **** + // + // THE Update + // + // **** + + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->Update(dt); + + //LowSpeedTest(); + + //rest = mVehicle->SelfRestTest(); + } + + + + //Update(dt); + //PostUpdate(dt); + + // maybe for first pass implementation, implement methods with the same name? + + + // ??????? + // + // do the pose driver updates here!!!! or back in vehicle <- for some reason + // the second way seems much nicer to me. + + + + // the net effect on Vehicle result when this function returns is to have + // modified the joint-space y values in the wheel joints to correct + // the wheel positions, and to have applied forces and torques to the + // simstate and had it update itself so there is a new world space transform + // AND make sure the world space transform of joint 0 is in sync with this + + // also - make sure wheels have enough info for suspensionjointdriver to + // do it's thing + // + // ie - cumulative rot + // wheel turn angle + // that's about it? + + // are we gonna have to make physicslocomotion a friend of the Vehicle? + // is that the correct thing to do - make all the locomotions friends?? + + + +} + + + +//============================================================================= +// PhysicsLocomotion::DurangoStyleStabilizer +//============================================================================= +// Description: Comment +// +// Parameters: (float dt) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::DurangoStyleStabilizer(float dt) +{ + // assume airborn + + rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; + float tip = mVehicle->mVehicleUp.DotProduct(up); + + // we want a fix torque proportional to 1.0 - tip. + + // 'direction' of the toruqe is vehicleup crossed into world up + float cos10 = 0.9848f; + if(tip < cos10) + { + + + rmt::Vector fixTorque; + fixTorque.CrossProduct(mVehicle->mVehicleUp, up); + + // need this? + fixTorque.NormalizeSafe(); + + const float hackmagicshit = 200.0f; + + float fixscale = 1.0f - tip; + + fixTorque.Scale(hackmagicshit * mVehicle->mDesignerParams.mDpMass * fixscale); + + sim::PhysicsObject* phobj = (sim::PhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject()); + rAssert(phobj); + + phobj->AddTorque(fixTorque); + + + // try some damping too + const float dampingShit = 7.0f; + + rmt::Vector angularDragForce = mVehicle->mSimStateArticulated->GetAngularVelocity(); + angularDragForce.Scale(-1.0f * dampingShit * mVehicle->mDesignerParams.mDpMass); + + phobj->AddTorque(angularDragForce); + + + } + +} + +//============================================================================= +// PhysicsLocomotion::StuckOnSideTest +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::StuckOnSideTest(float dt) +{ + // only for user vehicle in physics locomotion + + const float lowspeed = 1.0f; + if(mVehicle->mSpeed < lowspeed) + { + + rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; + float tip = mVehicle->mVehicleUp.DotProduct(up); + + //float cos85 = 0.0872f; + float test = 0.2f; + if(tip < test) + { + //static float magicshit = 1.0f; + const float magicshit = 130.0f; + + float side = up.DotProduct(mVehicle->mVehicleTransverse); + if(side > 0.0f) + { + // lying on it's left side - right side pointint to sky + //if(mVehicle->mUnmodifiedInputWheelTurnAngle > 0.0f) + if(mVehicle->mUnmodifiedInputWheelTurnAngle < 0.0f) + { + // apply some torque + rmt::Vector torque = mVehicle->mVehicleFacing; + //torque.Scale(mVehicle->mUnmodifiedInputWheelTurnAngle * -1.0f * mVehicle->mDesignerParams.mDpMass * magicshit); + torque.Scale(mVehicle->mUnmodifiedInputWheelTurnAngle * 1.0f * mVehicle->mDesignerParams.mDpMass * magicshit); + + sim::PhysicsObject* phobj = (sim::PhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject()); + rAssert(phobj); + + phobj->AddTorque(torque); + + } + + + } + else + { + // lying on it's right side - left side pointint to sky + //if(mVehicle->mUnmodifiedInputWheelTurnAngle < 0.0f) + if(mVehicle->mUnmodifiedInputWheelTurnAngle > 0.0f) + { + // apply some torque + rmt::Vector torque = mVehicle->mVehicleFacing; + //torque.Scale(mVehicle->mUnmodifiedInputWheelTurnAngle * -1.0f * mVehicle->mDesignerParams.mDpMass * magicshit); + torque.Scale(mVehicle->mUnmodifiedInputWheelTurnAngle * 1.0f * mVehicle->mDesignerParams.mDpMass * magicshit); + + sim::PhysicsObject* phobj = (sim::PhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject()); + rAssert(phobj); + + phobj->AddTorque(torque); + + } + + + } + + } + + } + +} + +//============================================================================= +// PhysicsLocomotion::TipTest +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::TipTest() +{ + + + rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; + float tip = mVehicle->mVehicleUp.DotProduct(up); + + float cos45 = 0.7071f; + float cos80 = 0.17365f; + float cos65 = 0.4226f; + // need to figure out if the angular velocity is trying to tip us over more, or right us + + float start = cos45; + float end = cos65; + + //static float tipRecoverPercentage = 75.0f; + float tipRecoverPercentage = 75.0f; + + if(tip < start && tip > end) + { + // set range of 0 to 1 for start to limit + float range = 1.0f - ((tip - end) / (start - end)); + + + float lean = mVehicle->mVehicleTransverse.DotProduct(up); + + if(lean > cos45) + { + // tipping left + + rmt::Vector& angularVel = mVehicle->mSimStateArticulated->GetAngularVelocity(); + + float proj = angularVel.DotProduct(mVehicle->mVehicleFacing); + + //if(proj > 0.0f) + if(proj > 1.0f) + { + // the velocity is trying to tip us more + + //rmt::Vector recover = mVehicle->mVehicleFacing; + //recover.Scale(-1.0f * proj * tipRecoverPercentage); + + //angularVel.Add(recover); + + //angularVel.Scale(tipRecoverPercentage); + //angularVel.Scale(1.0f - range); + angularVel.Scale(0.0f); + + //rmt::Vector recover = mVehicle->mVehicleFacing; + //recover.Scale(-1.0f * proj * tipRecoverPercentage * mVehicle->mDesignerParams.mDpMass * range); + + //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->AddTorque(recover); + + + + } + } + else if(lean < -cos45) + { + + //rmt::Vector& linearVel = mVehicle->mSimStateArticulated->GetLinearVelocity(); + rmt::Vector& angularVel = mVehicle->mSimStateArticulated->GetAngularVelocity(); + + float proj = angularVel.DotProduct(mVehicle->mVehicleFacing); + + + // tipping over right + //if(proj < 0.0f) + if(proj < -1.0f) + { + + //rmt::Vector recover = mVehicle->mVehicleFacing; + //recover.Scale(-1.0f * proj * tipRecoverPercentage); + + //angularVel.Add(recover); + + //angularVel.Scale(tipRecoverPercentage); + //angularVel.Scale(1.0f - range); + angularVel.Scale(0.0f); + + //rmt::Vector recover = mVehicle->mVehicleFacing; + //recover.Scale(-1.0f * proj * tipRecoverPercentage * mVehicle->mDesignerParams.mDpMass * range); + + //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->AddTorque(recover); + + } + + } + + // else don't do shit + + + + + + + // what is our tendency to hold and recover from tipping? + + + } + +} + +//============================================================================= +// PhysicsLocomotion::PostUpdate +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::PostUpdate() +{ + // this whole method is kind of annyoing + // + // TODO + //??? shoudl be in Vehicle + + mVehicle->mTransform = mVehicle->mSimStateArticulated->GetTransform(-1); + + + mVehicle->mVelocityCM = mVehicle->mSimStateArticulated->GetLinearVelocity(); + mVehicle->mSpeed = mVehicle->mVelocityCM.Magnitude(); + mVehicle->mPercentOfTopSpeed = mVehicle->mSpeed / (mVehicle->mDesignerParams.mDpTopSpeedKmh / 3.6f); + if(mVehicle->mPercentOfTopSpeed > 1.0f) + { + mVehicle->mPercentOfTopSpeed = 1.0f; + } + + // Roll up the terrain type and interior flag; + // As soon as we get two tires or more of a terrain type, that's the type we'll consider the car on. + // This isn't very exact since the rear two tires will have precidence over the other tires. + int terrainCounts = 0; + int interiorCount = 0; + eTerrainType newType = TT_Road; + for( int i = 0; i < 4; i++ ) + { + int index = i ^ 2; // We do this to change i from 0, 1, 2, 3 into 2, 3, 0, 1 so that the rear wheels are processed last. + interiorCount += mTerrainIntersectCache[ index ].mInteriorTerrain ? 1 : 0; +//? Do we want to mask out roads? int mask = ( 1 << (int)mTerrainIntersectCache[ index ].mTerrainType ) & ~(int)TT_Road; // Mask out roads so everything else takes precidence over it. + int mask = 1 << (int)mTerrainIntersectCache[ index ].mTerrainType; + if( ( terrainCounts & mask ) ) + { + // We've already got one, so this is number two. + newType = mTerrainIntersectCache[ index ].mTerrainType; + } + else + { + terrainCounts |= mask; + } + } + mVehicle->mTerrainType = newType; + mVehicle->mInterior = ( interiorCount >= 2 ); + +} + +//------------------------------------------------------------------------ +void PhysicsLocomotion::CorrectWheelYPositions() +{ + + + poser::Pose* pose = mVehicle->mPoseEngine->GetPose(); + + + int i; + for(i = 0; i < 4; i++) + { + // we don't want yOffset, we want the correction value!! + + // now just use mYOffset + //float yOffset = mWheels[i]->mYOffset; fuck no!!! + Wheel* wheel = mVehicle->mWheels[i]; + + float yCorrectionValue = wheel->GetYCorrectionValue(); + + poser::Joint* joint = pose->GetJoint(mVehicle->mWheelToJointIndexMapping[i]); + rmt::Vector trans = joint->GetObjectTranslation(); + + // these trans.y should still be at the bottom of their suspension range + + // new test so wheels don't hang so low + if(wheel->mWheelInCollision) + { + trans.y += yCorrectionValue; + } + else if(mVehicle->mAirBorn) // add this here to remove the snap/bounce when turning hard and two wheels come out of contact + { + trans.y += wheel->mLimit; + } + + // test + //trans.y += mVehicle->mDesignerParams.mDpSuspensionYOffset; + + joint->SetObjectTranslation(trans); + + wheel->ResolveOffset(); + + } + + +} + + +//------------------------------------------------------------------------ +// temp... +//------------------------------------------------------------------------ +inline void TempGetTerrainIntersects( rmt::Vector& trans, + float radius, + bool& foundtri, + rmt::Vector& closestTriNormal, + rmt::Vector& closestTriPosn, + bool& foundplane, + rmt::Vector& planeNormal, + rmt::Vector& planePosn) +{ + const float y = 3.0f; + + + foundtri = false; + foundplane = true; + planeNormal.Set(0.0f, 1.0f, 0.0f); + planePosn.x = trans.x; + planePosn.y = y; + planePosn.z = trans.z; + + +} + +//============================================================================= +// PhysicsLocomotion::FetchWheelTerrainCollisionInfo +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::FetchWheelTerrainCollisionInfo() +{ + poser::Pose* pose = mVehicle->mPoseEngine->GetPose(); + + int i; + for(i = 0; i < 4; i++) + { + BEGIN_PROFILE("PreInter") + Wheel* wheel = mVehicle->mWheels[i]; + + // for each wheel, based on the world xz, get a y and a normal, ..... and a ground type + + //? could also do based on suspension points, no? + // I guess this is more accurate + + poser::Joint* joint = pose->GetJoint(mVehicle->mWheelToJointIndexMapping[i]); + rmt::Vector trans = joint->GetWorldTranslation(); + + + rmt::Vector closestTriNormal, closestTriPosn; + rmt::Vector planeNormal, planePosn; + int terrainType; + //bool bFoundTri, bFoundPlane; + + mFoundTri[i] = false; + mFoundPlane[i] = false; + END_PROFILE("PreInter") + + BEGIN_PROFILE("GetIntersectManager()->FindIntersection") + + terrainType = GetIntersectManager()->FindIntersection( trans, + mFoundPlane[i], + planeNormal, + planePosn ); + + + /* + TempGetTerrainIntersects( trans, + wheel->mRadius, + mFoundTri[i], + closestTriNormal, + closestTriPosn, + mFoundPlane[i], + planeNormal, + planePosn); + */ + + END_PROFILE("GetIntersectManager()->FindIntersection") + + + BEGIN_PROFILE("PostInter") + // fill in pieces of cache + if(mFoundPlane[i]) + { + + rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; + if(up.DotProduct(planeNormal) < 0.0f) + { + //rAssert(0); + } + + + mTerrainIntersectCache[i].planePosn = planePosn; + mTerrainIntersectCache[i].planeNorm = planeNormal; + + mIntersectNormalUsed[i] = mTerrainIntersectCache[i].planeNorm; + } + else + { + // no plane + //rAssert(0); + } + + + + + //Devin says this is not used. Cleaning up the warnings. +// if(mFoundTri[i]) +// { +// mTerrainIntersectCache[i].closestTriPosn = closestTriPosn; +// mTerrainIntersectCache[i].closestTriNormal = closestTriNormal; +// } + + mTerrainIntersectCache[ i ].mTerrainType = (eTerrainType)( terrainType & ~0x80 ); + mTerrainIntersectCache[ i ].mInteriorTerrain = ( terrainType & 0x80 ) == 0x80; + + // stuff in value for later comparison + //mIntersectNormalUsed[i] = mTerrainIntersectCache[i].planeNorm; + END_PROFILE("PostInter") + } + +} + + +//============================================================================= +// PhysicsLocomotion::UseWheelTerrainCollisionInfo +//============================================================================= +// Description: Comment +// +// per-wheel, calculate and call Wheel::SetYOffsetFromCurrentPosition +// +// Parameters: (float dt) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::UseWheelTerrainCollisionInfo(float dt) +{ + + poser::Pose* pose = mVehicle->mPoseEngine->GetPose(); + + // take the highest value for this from all 4 wheels + float bottomOutFix = 0.0f; + + int i; + for(i = 0; i < 4; i++) + { + + // make copies 'cause we may want to modify local result, but not cached value + bool foundPlane = mFoundPlane[i]; + bool foundTri = mFoundTri[i]; + + float fixHeight = 0.0f; + bool thisWheelInCollision = false; + rmt::Vector fixHeightNormal(0.0f, 1.0f, 0.0f); + + float fixAlongSuspensionAxis = 0.0f; + + Wheel* wheel = mVehicle->mWheels[i]; + poser::Joint* joint = pose->GetJoint(mVehicle->mWheelToJointIndexMapping[i]); + rmt::Vector trans = joint->GetWorldTranslation(); + + + if(!mFoundPlane[i] && !mFoundTri[i]) + { + // we didn't get any good data back, so we need + // to use last cached value + //rAssert(0); // curious to see if we hit this - yes, a lot + + // just use plane y + foundPlane = true; + } + + if(foundPlane) + { + float y = mTerrainIntersectCache[i].planePosn.y; + + // first check + // the pathologically bad case: + if(mVehicle->mSuspensionWorldSpacePoints[i].y < y) + { + // this means the suspension rest point on the car is below the terrain + // this is fucking bad. + + // TODO: take this out? + //rAssert(0); + + // ?? actually need to do anything here? + + // TODO - bump car up? + + } + + //else + + { + // hopefully sane case + + // in planePosn, we only really care about the y + + float penetratingDepth = y - (trans.y - wheel->mRadius); + + if(penetratingDepth > 0.0f) + { + thisWheelInCollision = true; + fixHeight = penetratingDepth; + + // fixHeight is going pure up + fixHeightNormal.Set(0.0f, 1.0f, 0.0f); + + fixAlongSuspensionAxis = fixHeight / rmt::Fabs( (mVehicle->mVehicleUp).DotProduct(fixHeightNormal)); + + } + } + + } + + + // TODO! revisit this! + + if(0)//foundTri) + { + // calculate fixAlongSuspensionAxis based on this also, and if it's greater than above, use it instead + + rmt::Vector penetrationVector; + //penetrationVector.Sub(trans, mWheelTerrainCollisionPoints[i]); + + penetrationVector = trans; + penetrationVector.Sub(mTerrainIntersectCache[i].closestTriPosn); + + // TODO - is this safe? + float penMag = penetrationVector.Magnitude(); + + bool checkBump = false; + + if(trans.y < mTerrainIntersectCache[i].closestTriPosn.y) + { + // definately need to fix! + fixHeight = wheel->mRadius + penMag; + thisWheelInCollision = true; + fixHeightNormal = mTerrainIntersectCache[i].closestTriNormal; + + float tempFixAlongSuspensionAxis = fixHeight / rmt::Fabs( (mVehicle->mVehicleUp).DotProduct(fixHeightNormal)); + + if(tempFixAlongSuspensionAxis > fixAlongSuspensionAxis) + { + fixAlongSuspensionAxis = tempFixAlongSuspensionAxis; + } + + checkBump = true; + + } + else if(penMag < wheel->mRadius) // normal case + { + fixHeight = wheel->mRadius - penMag; + thisWheelInCollision = true; + fixHeightNormal = mTerrainIntersectCache[i].closestTriNormal; + + float tempFixAlongSuspensionAxis = fixHeight / rmt::Fabs( (mVehicle->mVehicleUp).DotProduct(fixHeightNormal)); + + if(tempFixAlongSuspensionAxis > fixAlongSuspensionAxis) + { + fixAlongSuspensionAxis = tempFixAlongSuspensionAxis; + } + + checkBump = true; + + } + + if(checkBump) + { + + // if this is too horizontal, we need to deal with it specially + float sin10 = 0.1736f; + float sin20 = 0.342f; + float sin30 = 0.5f; + + // TODO - what angle? + if(rmt::Fabs(fixHeightNormal.DotProduct(mVehicle->mVehicleUp)) < sin20)// && fixHeight > wheel->mRadius) + { + // just bump the whole car back by penMag? + + rmt::Vector bump = fixHeightNormal; + bump.NormalizeSafe(); + bump.Scale(fixHeight); + + // TODO - true or false or tapered down or what? + //mVehicle->mSimStateArticulated->DynamicPositionAdjustment(bump, dt, true); + //mVehicle->mSimStateArticulated->DynamicPositionAdjustment(bump, dt, false); + + mVehicle->mBottomedOutThisFrame = true; + + continue; + + + } + } + } + + + if(thisWheelInCollision) + { + + // perhaps this method should be renamed + // it sets the wheelInCollision flag to true! + float bottomedOut = wheel->SetYOffsetFromCurrentPosition(fixAlongSuspensionAxis); + + if(bottomedOut > bottomOutFix) + { + bottomOutFix = bottomedOut; + wheel->mWheelBottomedOutThisFrame = true; // not sure if we'll use yet. + + //char buffy[128]; + //sprintf(buffy, "wheel %d bottomed out this frame\n", i); + //rDebugPrintf(buffy); + } + + } + + } // end for(i + + + if(bottomOutFix > 0.0f) + { + + // this is the amount the suspension couldn't deal with. + // + // what to do? + // in srr1 we just bumped the whole chassis up by this amount - seemed to work fairly well actually. + //rmt::Vector inDeltaPos = mVehicle->mVehicleUp; + + // Note! + // we hav to be careful how to deal with this 'cause we can get some weird ass results that make + // the car go flying after seemingly minor crashes + + // hack, but might work well - only correct the vehicle straight up! + + //rmt::Vector inDeltaPos = fixHeightNormal; + + rmt::Vector inDeltaPos; + + inDeltaPos = GetWorldPhysicsManager()->mWorldUp; + + + // sanity test? + if(bottomOutFix > (mVehicle->mWheels[0]->mRadius * 0.5f)) + { + bottomOutFix = (mVehicle->mWheels[0]->mRadius * 0.5f); + } + + + + + if(inDeltaPos.y > 0.0f) // this test is pointless right now + { + + inDeltaPos.Scale(bottomOutFix); + + // TODO - revisit this + + + +///you are here +//take this out - see what f1 car does + +// no this was not the problem. + +// get kevin to raise the bv + + mVehicle->mSimStateArticulated->DynamicPositionAdjustment(inDeltaPos, dt, false); + + + + // ? + // how 'bout just applying some impulse to the sucker? + + /* + if(0)//mVehicle->mOkToCrashLand) + { + rmt::Matrix groundPlaneMatrix = mVehicle->mGroundPlaneSimState->GetTransform(-1); + rmt::Vector groundPlaneUp = groundPlaneMatrix.Row(2); // recall - ground plane is oriented weird + + rmt::Vector& linearVel = mVehicle->mSimStateArticulated->GetLinearVelocity(); + + static float test = 10.0f; + groundPlaneUp.Scale(test); + + linearVel.Add(groundPlaneUp); + + mVehicle->mOkToCrashLand = false; + + } + */ + + } + + mVehicle->mBottomedOutThisFrame = true; + } + + + +} + + + diff --git a/game/code/worldsim/redbrick/physicslocomotion.h b/game/code/worldsim/redbrick/physicslocomotion.h new file mode 100644 index 0000000..446e42a --- /dev/null +++ b/game/code/worldsim/redbrick/physicslocomotion.h @@ -0,0 +1,135 @@ +/*=========================================================================== + physicslocomotion.h + + created April 24, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ + +#ifndef _PHYSICSLOCOMOTION_H +#define _PHYSICSLOCOMOTION_H + +#include <worldsim/redbrick/vehiclelocomotion.h> +#include <render/intersectmanager/intersectmanager.h> // For the terrain type enumeration. +#include <radmath/radmath.hpp> + +class Vehicle; +class Wheel; + +class PhysicsLocomotion : public VehicleLocomotion +{ +public: + + PhysicsLocomotion(Vehicle* vehicle); + ~PhysicsLocomotion(); + + virtual void PreSubstepUpdate(); + virtual void PreCollisionPrep(bool firstSubstep); + + virtual void UpdateVehicleGroundPlane(); + + virtual void PreUpdate(); + virtual void Update(float dt); + virtual void PostUpdate(); + + + // call this from Vehicle::SetTransform + void SetTerrainIntersectCachePointsForNewTransform(); + + virtual void CompareNormalAndHeight(int index, rmt::Vector& normalPointingAtCar, rmt::Vector& groundContactPoint); + +//private: for the purposes of debug printout + + void MoveWheelsToBottomOfSuspension(); + + void FetchWheelTerrainCollisionInfo(); + void UseWheelTerrainCollisionInfo(float dt); + + void TipTest(); + void ApplyAngularDrag(); + + void StuckOnSideTest(float dt); + + void DurangoStyleStabilizer(float dt); + + + // delete all this crap soon + + //float mWheelTerrainCollisionFixDepth[4]; + //rmt::Vector mWheelTerrainCollisionPoints[4]; + //rmt::Vector mWheelTerrainCollisionNormals[4]; + //bool mGoodWheelTerrainCollisionValue[4]; + + bool mFoundTri[4]; + bool mFoundPlane[4]; + + + void CorrectWheelYPositions(); // used to be preupdate + + void SetForceApplicationPoints(); + rmt::Vector mForceApplicationPoints[4]; + rmt::Vector mSuspensionPointVelocities[4]; + + void ApplySuspensionForces(float dt, bool atrest); + float mCachedSuspensionForceResults[4]; + float CalculateSuspensionForce(Wheel* wheel, float suspensionPointYVelocity, float dt, float& forceToUse); + + void ApplyControllerForces2(float dt); + void GasForce2(rmt::Vector& forceResult, int wheelNum, float dt); + void BrakeForce2(rmt::Vector& forceResult, int wheelNum, float dt); + void ReverseForce2(rmt::Vector& forceResult, int wheelNum); + void SteeringForce2(rmt::Vector& forceResult, int wheelNum, float dt); + //void LimitTireForce(rmt::Vector& totalForce, int wheelNum); + void TestControllerForces(rmt::Vector* totalForce); + void SetSkidLevel(); + + void UpdateSteeringForce(float dt); + float mCurrentSteeringForce; + + void ApplyTerrainFriction(rmt::Vector* totalForce); + + + void TestSteeringForce(rmt::Vector& force, int index); + + void EBrakeEffect(rmt::Vector& forceResult, int wheelNum); + void DoughnutTest(); + + void ApplyDragForce(); + void Weeble(); + + void HighSpeedInstabilityTest(); + + void ApplyRollingFriction(); + + void LowSpeedTest(); + + // just for convenience in internal methods + Vehicle* mVehicle; + + struct TerrainIntersectCache + { + rmt::Vector closestTriPosn; + rmt::Vector closestTriNormal; + rmt::Vector planePosn; + rmt::Vector planeNorm; + eTerrainType mTerrainType; + bool mInteriorTerrain; + }; + + TerrainIntersectCache mTerrainIntersectCache[4]; // is this wasting too much memory? + + // need this because if we're driving on a static collision volume the terrain normal could be way off! + rmt::Vector mIntersectNormalUsed[4]; + + friend class Vehicle; + + + +}; + + +#endif // _PHYSICSLOCOMOTION_H diff --git a/game/code/worldsim/redbrick/physicslocomotioncontrollerforces.cpp b/game/code/worldsim/redbrick/physicslocomotioncontrollerforces.cpp new file mode 100644 index 0000000..00ae0aa --- /dev/null +++ b/game/code/worldsim/redbrick/physicslocomotioncontrollerforces.cpp @@ -0,0 +1,1686 @@ +//============================================================================= +// Copyright (C) 2001 Radical Entertainment Ltd. All rights reserved. +// +// File: physicslocomotioncontrollerforces.cpp +// +// Description: moved some methods into a different cpp file 'cause it was getting +// too big +// +// History: May 13, 2001 + Created -- gmayer +// +//============================================================================= + + +//======================================== +// System Includes +//======================================== + +#include <simcommon/simstate.hpp> +#include <simcommon/simstatearticulated.hpp> +#include <simcommon/simulatedobject.hpp> +#include <simphysics/articulatedphysicsobject.hpp> +#include <simphysics/physicsobject.hpp> +#include <simcommon/simenvironment.hpp> +#include <simcollision/collisionobject.hpp> +#include <simcollision/collisionvolume.hpp> + +#include <raddebug.hpp> + +#include <gameflow/gameflow.h> +#include <worldsim/vehiclecentral.h> +#include <worldsim/avatarmanager.h> + + +#include <cheats/cheatinputsystem.h> + +#include <poser/pose.hpp> +#include <poser/poseengine.hpp> +#include <poser/posedriver.hpp> + + +//======================================== +// Project Includes +//======================================== + +#include <worldsim/redbrick/physicslocomotion.h> +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/redbrick/wheel.h> + +#include <camera/supercammanager.h> +#include <camera/supercamcentral.h> + +#ifdef WORKING_ON_GREG_PHIZTEST_RIGHT_NOW +#include "../../../../users/greg/phiztest/code/worldsim/redbrick/phizsim.h" // just for a temp hack using world up +#else +#include <worldsim/worldphysicsmanager.h> +#endif + +#include <render/IntersectManager/IntersectManager.h> + +using namespace sim; + + +// test of terrain friction + // enum value +const float TF_Road = 1.0f; // 0 +const float TF_Grass = 0.7f; // 1 +const float TF_Sand = 0.6f; // 2 +const float TF_Gravel = 0.6f; // 3 +const float TF_Water = 0.6f; // 4 +const float TF_Wood = 1.0f; // 5 +const float TF_Metal = 1.0f; // 6 +const float TF_Dirt = 0.6f; // 7 + + + + + + +//============================================================================= +// PhysicsLocomotion::ApplyControllerForces2 +//============================================================================= +// Description: Comment +// +// Parameters: (float dt) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::ApplyControllerForces2(float dt) +{ + // for each wheel, calculate the total requested force, and then + // chop it based on the force circle? + + mVehicle->mBurnoutLevel = 0.0f; + + int i; + rmt::Vector totalForce[4]; + for(i = 0; i < 4; i++) + { + totalForce[i].Clear(); + + + if(/*mVehicle->mWheels[i]->mWheelInCollision &&*/ !(mVehicle->mWheels[i]->mWheelBottomedOutThisFrame)) + { + + // TODO + // only do loop if wheel in collision? + // else reset some shit like the wheel state + + // TODO + // how about wheel bottomed out this frame? + // only affects steering? or all? + + //rmt::Vector totalForce(0.0f, 0.0f, 0.0f); + + GasForce2(totalForce[i], i, dt); + // TODO - make sure you set skid level and renderspinuprate and shit like that. + + BrakeForce2(totalForce[i], i, dt); + // TODO - somewhere else add code to bring car to a standstill + + ReverseForce2(totalForce[i], i); + + EBrakeEffect(totalForce[i], i); + + SteeringForce2(totalForce[i], i, dt); + + + + } + //else + { + // reset wheel state? + } + + } + + // DoughnutTest(); I think we need a state for this. + + + // test if we should change vehicle state + // TODO - test for airborn etc.. before calling this? + + TestControllerForces(totalForce); // <- mVehicleState only changed in here! + + // new - Mar 12, 2003 + // + // use state to set "target" steering force. + // + // move towards target based on time rate + // + // + + // next step - make let-off gas get you out of slip state + + // how does gasreducinggrip thing fit into all of this? + + // next step - make skid level 0 to 1 based on have we gotten to our target level yet? + + //UpdateSteeringForce(dt); + + + // not doing this anymore - only step is really to set good skid level + + + + SetSkidLevel(); + + // TODO - actually limit locomotive and other forces?????? + + // cheap and easy test of terrain friction: + //ApplyTerrainFriction(totalForce); + + + for(i = 0; i < 4; i++) + { + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->AddForce(totalForce[i], &(mForceApplicationPoints[i])); + } +} + + + +//============================================================================= +// PhysicsLocomotion::UpdateSteeringForce +//============================================================================= +// Description: Comment +// +// Parameters: (float dt) +// +// Return: void +// +//============================================================================= +/* + + // I don't think plum wants this now + +void PhysicsLocomotion::UpdateSteeringForce(float dt) +{ + // default changerate: 50 units / second + //static float changeRate = 10.0f; + //static float changeRate = 5.0f; + static float changeRate = 1.0f; + + float changethisframe = changeRate * dt; + + float target = 80.0f; // just some default - should never use + + + // if we are not VT_USER, switch immediately. + + + if(mVehicle->mVehicleState == VS_NORMAL) + { + // "target" - mVehicle->mDesignerParams.mDpTireLateralResistanceNormal; + + target = mVehicle->mDesignerParams.mDpTireLateralResistanceNormal; + + } + else if(mVehicle->mVehicleState == VS_EBRAKE_SLIP) + { + // "target" - mVehicle->mDesignerParams.mDpTireLateralResistanceSlip + + target = mVehicle->mDesignerParams.mDpTireLateralResistanceSlip; + + } + else + { + // regular slip + // "target" - mVehicle->mDesignerParams.mDpTireLateralResistanceSlipNoEBrake + + target = mVehicle->mDesignerParams.mDpTireLateralResistanceSlipNoEBrake; + + } + + if(mCurrentSteeringForce > target) + { + if( (mCurrentSteeringForce - changethisframe) > target) + { + mCurrentSteeringForce -= changethisframe; + } + else + { + mCurrentSteeringForce = target; + } + } + else if(mCurrentSteeringForce < target) + { + if((mCurrentSteeringForce + changethisframe) < target) + { + mCurrentSteeringForce += changethisframe; + } + else + { + mCurrentSteeringForce = target; + } + } + + + + +} + +*/ + +//============================================================================= +// PhysicsLocomotion::ApplyTerrainFriction +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector* totalForce) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::ApplyTerrainFriction(rmt::Vector* totalForce) +{ + int i; + for(i = 0; i < 4; i++) + { + switch (mTerrainIntersectCache[i].mTerrainType) + { + + case TT_Road: + totalForce[i].Scale(TF_Road); + break; + + case TT_Grass: + totalForce[i].Scale(TF_Grass); + break; + + case TT_Dirt: + totalForce[i].Scale(TF_Dirt); + break; + + case TT_Water: + totalForce[i].Scale(TF_Water); + break; + + case TT_Gravel: + totalForce[i].Scale(TF_Gravel); + break; + + case TT_Wood: + totalForce[i].Scale(TF_Wood); + break; + + case TT_Sand: + totalForce[i].Scale(TF_Sand); + break; + + case TT_Metal: + totalForce[i].Scale(TF_Metal); + break; + + default: + break; + + } + + } +} + + +//============================================================================= +// PhysicsLocomotion::GasForce2 +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector& forceResult, float dt, int wheelNum) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::GasForce2(rmt::Vector& forceResult, int wheelNum, float dt) +{ + if(mVehicle->mWheels[wheelNum]->mDriveWheel) + { + float desiredForceMag = 0.0f; + + // burnout check + // + // TODO + // only high powered vehicles should do this? or do for longer? + //if(mVehicle->mDesignerParams.mDpGasScale > + + // maybe the % topspeed for which you do it is greater for high powered vehicles + + // try mapping 5.0 (average value) to %20 topspeed + //static float burnouttunebullshit = 0.04f; + const float burnouttunebullshit = 0.03f; + + //static float gasdecrease = 0.5f; + const float gasdecrease = 0.75f; + + //static float burnoutsound = 0.7f; + const float gasvalue = 0.99f; + + //float burnoutRange = mVehicle->mDesignerParams.mDpGasScale * burnouttunebullshit; + + float burnoutRange = mVehicle->mDesignerParams.mDpBurnoutRange; + + // need new designer param for this percent of top speed + // + // use same value for doing doughnut? + + // !! TODO - get hard/soft press on ps2! + // + // this is not gonna happen + // if you put the ps2 button into analog mode, you gotta mash it to get _anything_! + + if(mVehicle->mPercentOfTopSpeed < burnoutRange && mVehicle->mGas > gasvalue && mVehicle->mVehicleType == VT_USER) + { + // burnout + // + // skid, smoke, spin and non loco + + // the regular calculation would give this: + //desiredForceMag = mVehicle->mDesignerParams.mDpGasScale * mVehicle->mSlipGasModifier * mVehicle->mGas * mVehicle->mDesignerParams.mDpMass; + + const rmt::Vector& gravity = GetWorldPhysicsManager()->mSimEnvironment->Gravity(); + float gravFactor = gravity.y * -1.0f / 9.81f; + + //desiredForceMag = mVehicle->mDesignerParams.mDpGasScale * mVehicle->mSlipGasModifier * /*mVehicle->mGas*/ 1.0f * mVehicle->mDesignerParams.mDpMass;// * gravFactor; + desiredForceMag = mVehicle->mDesignerParams.mDpGasScale * mVehicle->mSlipGasModifier * mVehicle->mGas * mVehicle->mDesignerParams.mDpMass;// * gravFactor; + + desiredForceMag *= gasdecrease; + + //mVehicle->mBurnoutLevel = burnoutsound; + mVehicle->mBurnoutLevel = (1.0f - (0.5f * (mVehicle->mPercentOfTopSpeed / burnoutRange))); + if(mVehicle->mBurnoutLevel < 0.0f) + { + mVehicle->mBurnoutLevel = 0.0f; + } + + + + // additional test for donut? + // here? + if(rmt::Fabs(mVehicle->mUnmodifiedInputWheelTurnAngle) > 0.6) + { + //const float magicshit = 4.0f; + //float magicshit = 9.0f; + float magicshit = mVehicle->mDesignerParams.mDpDonutTorque; + + rmt::Vector etorque = mVehicle->mVehicleUp; + float dir = 0.0f; + if(mVehicle->mWheelTurnAngle > 0.0f) + { + dir = 1.0f; + } + if(mVehicle->mWheelTurnAngle < 0.0f) + { + dir = -1.0f; + } + + //float speedeffect = mVehicle->mPercentOfTopSpeed * 2.0f; + //if(speedeffect > 1.0f) + //{ + // speedeffect = 1.0f; + //} + + float speedmodifier = 1.0f - mVehicle->mPercentOfTopSpeed; + + etorque.Scale(magicshit * dir * mVehicle->mDesignerParams.mDpMass * speedmodifier); + + //mVehicle->mPhObj->AddTorque(etorque); + + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->AddTorque(etorque); + + } + else + { + // speed burst build up here.... + + if(mVehicle->mEBrake > 0.5f) + { + mVehicle->mSpeedBurstTimer += dt; + mVehicle->mBuildingUpSpeedBurst = true; + mVehicle->mDoSpeedBurst = false; + + //if(mVehicle->mSpeedBurstTimer > 3.0f) + if(mVehicle->mSpeedBurstTimer > mVehicle->mDesignerParams.mDpMaxSpeedBurstTime) + { + mVehicle->mSpeedBurstTimer = mVehicle->mDesignerParams.mDpMaxSpeedBurstTime; + } + } + else + { + if(mVehicle->mBuildingUpSpeedBurst) + { + rAssert(mVehicle->mDoSpeedBurst == false); + // just let off the button + mVehicle->mBuildingUpSpeedBurst = false; + mVehicle->mDoSpeedBurst = true; + + SuperCamCentral* scc = GetSuperCamManager()->GetSCC(0); + SuperCam* sc = scc->GetActiveSuperCam(); + + mVehicle->mFOVToRestore = sc->GetFOV(); + + mVehicle->mSpeedBurstTimerHalf = mVehicle->mSpeedBurstTimer * 0.5f; + + + } + } + + } + + + } + else + { + + //! + // TODO + // + // take analog gas usage out of here? + + if(mVehicle->mGas > 0.0f) + { + + + const rmt::Vector& gravity = GetWorldPhysicsManager()->mSimEnvironment->Gravity(); + float gravFactor = gravity.y * -1.0f / 9.81f; + + + if(mVehicle->mVehicleState == VS_NORMAL) + { + if(mVehicle->mVehicleType == VT_USER && mVehicle->mPercentOfTopSpeed > mVehicle->mDesignerParams.mDpGasScaleSpeedThreshold) + { + // use high speed gas scale + desiredForceMag = mVehicle->mDesignerParams.mDpHighSpeedGasScale * mVehicle->mSlipGasModifier * mVehicle->mGas * mVehicle->mDesignerParams.mDpMass;// * gravFactor; + + } + else + { + desiredForceMag = mVehicle->mDesignerParams.mDpGasScale * mVehicle->mSlipGasModifier * mVehicle->mGas * mVehicle->mDesignerParams.mDpMass;// * gravFactor; + + } + + } + else + { + // slip + + desiredForceMag = mVehicle->mDesignerParams.mDpSlipGasScale * mVehicle->mSlipGasModifier * mVehicle->mGas* mVehicle->mDesignerParams.mDpMass;// * gravFactor; + + } + } + + // if we get out of speedburst range we should probably null out + + + //mVehicle->mBuildingUpSpeedBurst = false; + //mVehicle->mSpeedBurstTimer = 0.0f; + //mVehicle->mDoSpeedBurst = false; + + } + + + // regardless of burnout or normal + // test for wheelie down here + if(mVehicle->mPercentOfTopSpeed < mVehicle->mDesignerParams.mDpWheelieRange && mVehicle->mGas > gasvalue && mVehicle->mVehicleType == VT_USER) + { + // set wheelie offsets + + if(!(mVehicle->mDoingWheelie) && !(mVehicle->mWeebleOn)) + { + rmt::Vector wheelie = mVehicle->mCMOffset; + + wheelie.y += mVehicle->mDesignerParams.mDpWheelieYOffset; + wheelie.z += mVehicle->mDesignerParams.mDpWheelieZOffset; + + //mVehicle->mPhObj->SetExternalCMOffset(wheelie); + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(wheelie); + + mVehicle->mDoingWheelie = true; + } + + + } + else if(mVehicle->mDoingWheelie) + { + //mVehicle->mPhObj->SetExternalCMOffset(mVehicle->mCMOffset); + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mCMOffset); + mVehicle->mDoingWheelie = false; + } + + + + rmt::Vector force = mVehicle->mVehicleTransverse; + //force.CrossProduct(mTerrainIntersectCache[wheelNum].planeNorm); + force.CrossProduct(mIntersectNormalUsed[wheelNum]); + + // just in case + force.NormalizeSafe(); + + + if(mVehicle->mDoSpeedBurst) + { + desiredForceMag *= 2.0f; + mVehicle->mSpeedBurstTimer -= dt; + + SuperCamCentral* scc = GetSuperCamManager()->GetSCC(0); + SuperCam* sc = scc->GetActiveSuperCam(); + + + if(mVehicle->mSpeedBurstTimer <= 0.0f) + { + mVehicle->mSpeedBurstTimer = 0.0f; + mVehicle->mDoSpeedBurst = false; + + //sc->SetFOV(mVehicle->mFOVToRestore); - this shoudl all be moved to super cam + } + else + { + //static float camhack = 1.0f; + //static float camhack = 0.015f; + float camhack = 0.015f; + float fov; + + if(mVehicle->mSpeedBurstTimer > (mVehicle->mSpeedBurstTimerHalf * 1.5f)) + { + fov = sc->GetFOV(); + fov = fov + camhack; + //sc->SetFOV(fov); this shoudl all be moved to super cam + } + else if(mVehicle->mSpeedBurstTimer < (mVehicle->mSpeedBurstTimerHalf * 0.5f)) + { + fov = sc->GetFOV(); + fov = fov - camhack; + //sc->SetFOV(fov); this shoudl all be moved to super cam + } + // else do nothing in the mid-range + } + + } + + if(mVehicle->mDoingJumpBoost) + { + // want to boost the jump + // proportioal to the cars power, and current speed, and should have gas held down + if(mVehicle->mGas > 0.5f && mVehicle->mPercentOfTopSpeed > 0.3f && mVehicle->GetSpeedKmh() < 140.0f) + { + //static float gasscaleboosteffect = 0.1f; + //const float gasscaleboosteffect = 0.08f; + float boostEffect = 0.0f; + if( GetGameFlow()->GetCurrentContext() == CONTEXT_SUPERSPRINT ) + { + // do special boost only for AI in supersprint + rAssert( mVehicle->mVehicleType == VT_AI ); + boostEffect = 1.4f; + } + else + { + boostEffect = 0.07f; + } + const float gasscaleboosteffect = boostEffect; + + //float multiplier = 1.0f + /* mVehicle->mPercentOfTopSpeed */ + (mVehicle->mDesignerParams.mDpGasScale * gasscaleboosteffect); + const float avgGasScale = 7.0f; + + float multiplier = 1.0f + /* mVehicle->mPercentOfTopSpeed */ + (avgGasScale * gasscaleboosteffect); + + desiredForceMag *= multiplier; + } + + } + + + //rmt::Vector force = mVehicle->mVehicleFacing; + + // just in case + // you only get speed burst of buildup if you hold down gas + if(mVehicle->mGas == 0.0f && (mVehicle->mDoSpeedBurst || mVehicle->mSpeedBurstTimer > 0.0f)) + { + mVehicle->mSpeedBurstTimer = 0.0f; + mVehicle->mDoSpeedBurst = false; + } + + if(GetAvatarManager()->GetAvatarForPlayer(0)->GetVehicle() == mVehicle) + { + if(GetCheatInputSystem()->IsCheatEnabled(CHEAT_ID_HIGH_ACCELERATION)) + { + desiredForceMag *= 2.0f; + } + } + + + + force.Scale(desiredForceMag); + + forceResult.Add(force); + } + +} + + +//============================================================================= +// PhysicsLocomotion::DoughnutTest +//============================================================================= +// Description: Comment +// +// Parameters: (int wheelNum) +// +// Return: float +// +//============================================================================= +void PhysicsLocomotion::DoughnutTest() +{ + // already know this is a drive wheel +/* + static float test1 = 0.3f; + static float test2 = 0.8f; + static float gastest = 0.9f; + static float amount = 18.0f; + + if( mVehicle->mPercentOfTopSpeed < test1 && rmt::Fabs(mVehicle->mUnmodifiedInputWheelTurnAngle) > test2 && mVehicle->mGas > gastest) + { + rmt::Vector doughnutTorque = mVehicle->mVehicleUp; + + // nasty hardcoded logic + // -ve is left turn + + if(mVehicle->mUnmodifiedInputWheelTurnAngle < 0.0f) + { + doughnutTorque.Scale(-amount * mVehicle->mDesignerParams.mDpMass); + } + else + { + doughnutTorque.Scale(amount* mVehicle->mDesignerParams.mDpMass); + } + + mVehicle->mPhObj->AddTorque(doughnutTorque); + + } +*/ +} + +//============================================================================= +// PhysicsLocomotion::BrakeForce2 +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector& forceResult, int wheelNum) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::BrakeForce2(rmt::Vector& forceResult, int wheelNum, float dt) +{ + + //mBrakeTimer = 0.0f; + //mBrakeActingAsReverse = false; + + // this is exactly reverse force + + if(mVehicle->mWheels[wheelNum]->mDriveWheel) + { + + const rmt::Vector& gravity = GetWorldPhysicsManager()->mSimEnvironment->Gravity(); + float gravFactor = gravity.y * -1.0f / 9.81f; + + // if we're coming to a stop we want to use brakescale + // if we're going backwards want to use gasscale + + float desiredForceMag = 0.0f; + + + float proj = mVehicle->mVelocityCM.DotProduct(mVehicle->mVehicleFacing); + + // don't want just any slight backwards motion to trigger + const float cos120 = -0.5f; + + //if(proj < cos120) + if(proj < 0.0f) + { + // going backwards + + // same accel and top speed reverse as forward + + if(mVehicle->mVehicleType == VT_USER && mVehicle->mPercentOfTopSpeed > mVehicle->mDesignerParams.mDpGasScaleSpeedThreshold) + { + desiredForceMag = mVehicle->mDesignerParams.mDpHighSpeedGasScale * mVehicle->mBrake * mVehicle->mDesignerParams.mDpMass; + } + else + { + desiredForceMag = mVehicle->mDesignerParams.mDpGasScale * mVehicle->mBrake * mVehicle->mDesignerParams.mDpMass; + } + + if(mVehicle->mSpeedKmh < 50.0f) + { + mVehicle->mBurnoutLevel = mVehicle->mBrake; + } + } + else + { + if(mVehicle->mGas > 0.1f) + { + desiredForceMag = (mVehicle->mDesignerParams.mDpGasScale) * mVehicle->mBrake * mVehicle->mDesignerParams.mDpMass * 0.75f;// magic number is gasdecrease when doing burnout + } + else + { + desiredForceMag = (mVehicle->mDesignerParams.mDpBrakeScale + 3.0f) * mVehicle->mBrake * mVehicle->mDesignerParams.mDpMass;// * gravFactor; + } + } + + + + + + + + + + rmt::Vector force = mVehicle->mVehicleFacing; + force.Scale(-1.0f * desiredForceMag); + + // TODO - make this a state? + // so that we can reduce the grip on the rear wheels and totally up the front? + + forceResult.Add(force); + + // if we are actually going in reverse + //if(mVehicle->mVehicleFacing.DotProduct(mVehicle->mVelocityCM) < 0.0f) + //{ + // mVehicle->mBurnoutLevel = mVehicle->mBrake; + //} + + } + + + + /* + //static float test = 0.8f; + const float test = 0.8f; + + //const float lowspeed = 1.0f; + const float lowspeed = 1.0f; + + + + + // copy of test from rolling friction - + // holding brake down should hold car perfectly still.... + if(mVehicle->mBrakeActingAsReverse) + { + if(mVehicle->mSpeed > lowspeed) + { + float desiredForceMag = mVehicle->mBrake * mVehicle->mDesignerParams.mDpBrakeScale * mVehicle->mDesignerParams.mDpMass; + + rmt::Vector force = mSuspensionPointVelocities[wheelNum]; + + force.NormalizeSafe(); + force.Scale(-1.0f * desiredForceMag); + + forceResult.Add(force); + } + else if(mVehicle->mBrake > 0.7f) + { + // TODO - this may have to change with brakestands! + + // whittle away speed + rmt::Vector& linearVel = mVehicle->mSimStateArticulated->GetLinearVelocity(); + rmt::Vector& angularVel = mVehicle->mSimStateArticulated->GetAngularVelocity(); + + linearVel.Scale(test); + angularVel.Scale(test); + + + } + } + */ +} + + +//============================================================================= +// PhysicsLocomotion::ReverseForce2 +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector& forceResult, int wheelNum) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::ReverseForce2(rmt::Vector& forceResult, int wheelNum) +{ + if(mVehicle->mWheels[wheelNum]->mDriveWheel) + { + float desiredForceMag = mVehicle->mDesignerParams.mDpGasScale * mVehicle->mReverse * mVehicle->mDesignerParams.mDpMass; + + rmt::Vector force = mVehicle->mVehicleFacing; + force.Scale(-1.0f * desiredForceMag); + + // TODO - make this a state? + // so that we can reduce the grip on the rear wheels and totally up the front? + + forceResult.Add(force); + } + +} + + +//============================================================================= +// PhysicsLocomotion::EBrakeEffect +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::EBrakeEffect(rmt::Vector& forceResult, int wheelNum) +{ + // in addition to reducing the rear grip, we want to apply (weak?) brakes too? + + // since people are using the handbrake like a parking brake, I guess I better acknowledge... + static const float reallylowspeed = 0.5f; + if(mVehicle->mSpeed < reallylowspeed && mVehicle->mEBrake == 1.0f) + { + // someone's trying to use it to hold the car in place, so just let the rest test do it's thing + return; + } + + if(mVehicle->mWheels[wheelNum]->mDriveWheel && mVehicle->mWheels[wheelNum]->mWheelTurnAngle == 0.0f) + { + // just lock up steer/drive wheels????? + + const rmt::Vector& gravity = GetWorldPhysicsManager()->mSimEnvironment->Gravity(); + float gravFactor = gravity.y * -1.0f / 9.81f; + + //float desiredForceMag = mVehicle->mEBrake * mVehicle->mDesignerParams.mDpBrakeScale * mVehicle->mDesignerParams.mDpMass;// * gravFactor; + const float defaultebrakebrakescalethatplumishappywith = 3.0f; + float desiredForceMag = mVehicle->mEBrake * defaultebrakebrakescalethatplumishappywith /*mVehicle->mDesignerParams.mDpBrakeScale*/ * mVehicle->mDesignerParams.mDpMass;// * gravFactor; + + rmt::Vector force = mSuspensionPointVelocities[wheelNum]; + + force.NormalizeSafe(); + force.Scale(-1.0f * desiredForceMag); + + forceResult.Add(force); + + } + + + + + //mVehicle->mVehicleState = VS_SLIP; + + // instead of adding another state variable, just put this calculation right in steering code? + //mVehicle->mTireLateralResistance = mVehicle->mDesignerParams.mDpTireLateralResistanceSlip * (1.0f - mVehicle->mEBrake); + +} + +//============================================================================= +// PhysicsLocomotion::SteeringForce2 +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector& forceResult, int wheelNum) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::SteeringForce2(rmt::Vector& forceResult, int wheelNum, float dt) +{ + rmt::Vector lateralDirection = mVehicle->mVehicleTransverse; + + + if(mVehicle->mWheels[wheelNum]->mSteerWheel) + { + rmt::Matrix steeringMatrix; // TODO - make this a class member? + steeringMatrix.Identity(); + steeringMatrix.FillRotateY(mVehicle->mWheels[wheelNum]->mWheelTurnAngle); + + lateralDirection.Transform(steeringMatrix); + } + + //float lateralVelocityProjection = lateralDirection.DotProduct(mSuspensionPointVelocities[wheelNum]); + + + + rmt::Vector temp = mSuspensionPointVelocities[wheelNum]; + + //rmt::Vector temp = mVehicle->mVelocityCM; + + + float desiredLateralResistanceForce = 0.0f; + + //const float threshold = 0.15f; + //static float threshold = 0.15f; + const float threshold = 0.15f; + if(mVehicle->mPercentOfTopSpeed > threshold) + { + //------- + // normal + //------- + + // this is the normal thing to do for regular and high-speed driving + temp.NormalizeSafe(); + + float lateralVelocityProjection = lateralDirection.DotProduct(temp); + // the higher this is the more we're asking this tire to do for us! + + + const rmt::Vector& gravity = GetWorldPhysicsManager()->mSimEnvironment->Gravity(); + float gravFactor = gravity.y * -1.0f / 9.81f; + + if(mVehicle->mVehicleState == VS_NORMAL) + { + desiredLateralResistanceForce = (mVehicle->mDesignerParams.mDpMass * lateralVelocityProjection * + mVehicle->mDesignerParams.mDpTireLateralResistanceNormal) /*mCurrentSteeringForce)*/ * 0.25f; + } + else if(mVehicle->mVehicleState == VS_EBRAKE_SLIP) + { + // slip + { + + desiredLateralResistanceForce = (mVehicle->mDesignerParams.mDpMass * lateralVelocityProjection * + mVehicle->mDesignerParams.mDpTireLateralResistanceSlip) /*mCurrentSteeringForce)*/ * 0.25f; + + + + } + } + else // just VS_SLIP + { + // slip without ebrake! + desiredLateralResistanceForce = (mVehicle->mDesignerParams.mDpMass * lateralVelocityProjection * + mVehicle->mDesignerParams.mDpTireLateralResistanceSlipNoEBrake /*mCurrentSteeringForce*/) * 0.25f; + + + if(mVehicle->mWheels[wheelNum]->mSteerWheel) + { + desiredLateralResistanceForce *= (1.0f + mVehicle->mDesignerParams.mDpSlipEffectNoEBrake); + + } + else + { + desiredLateralResistanceForce *= (1.0f - mVehicle->mDesignerParams.mDpSlipEffectNoEBrake); + } + + } + + +/* + + // put ebrake effect outside of any state + + if(mVehicle->mEBrake > 0.1) // note: we can be in this state without always holding the button down + { + if(mVehicle->mWheels[wheelNum]->mSteerWheel) + { + // increase resistance on steer wheels + //desiredLateralResistanceForce *= (1.0f + mVehicle->mEBrake * mVehicle->mDesignerParams.mDpEBrakeEffect); + desiredLateralResistanceForce *= (1.0f + mVehicle->mDesignerParams.mDpEBrakeEffect); + + } + else + { + // decrease resistance on rear + //desiredLateralResistanceForce *= (1.0f - mVehicle->mEBrake * mVehicle->mDesignerParams.mDpEBrakeEffect); + desiredLateralResistanceForce *= (1.0f - mVehicle->mDesignerParams.mDpEBrakeEffect); + } + + } + +*/ + + + + // rockford...? + + + // this is the condition for beginning a rockford + if( mVehicle->mVehicleFacing.DotProduct(mVehicle->mVelocityCM) < 0.0f && mVehicle->mEBrake < 0.1 && rmt::Fabs(mVehicle->mUnmodifiedInputWheelTurnAngle) > 0.5f && mVehicle->mGas == 0.0f )//|| + //mVehicle->mDoingRockford ) + { + + mVehicle->mDoingRockford = true; + // rockford in proportion to ebrake setting + + // note - the + and - are inverted from regular ebrake effect + + + //static float magicshit = 20.0f; + //static float mDpRockfordEffect = 0.5f; + float mDpRockfordEffect = 0.5f; + + + + if(mVehicle->mWheels[wheelNum]->mSteerWheel) + { + // increase resistance on steer wheels + //desiredLateralResistanceForce *= (1.0f + mVehicle->mEBrake * mVehicle->mDesignerParams.mDpEBrakeEffect); + desiredLateralResistanceForce *= (1.0f - mDpRockfordEffect); + } + else + { + // decrease resistance on rear + //desiredLateralResistanceForce *= (1.0f - mVehicle->mEBrake * mVehicle->mDesignerParams.mDpEBrakeEffect); + desiredLateralResistanceForce *= (1.0f + mDpRockfordEffect); + } + + + float hackbullshit = 10.0f; + float magicshit = hackbullshit * mDpRockfordEffect; + + rmt::Vector rockfordtorque = mVehicle->mVehicleUp; + float dir = 0.0f; + if(mVehicle->mWheelTurnAngle > 0.0f) + { + dir = -1.0f; + } + if(mVehicle->mWheelTurnAngle < 0.0f) + { + dir = 1.0f; + } + + + float speedeffect = mVehicle->mPercentOfTopSpeed; + + rockfordtorque.Scale(magicshit * dir * mVehicle->mDesignerParams.mDpMass * speedeffect); + + //mVehicle->mPhObj->AddTorque(rockfordtorque); + ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->AddTorque(rockfordtorque); + + + mVehicle->mBurnoutLevel = 1.0f; + + + + + //hmmm.... + //static float resdrop = 0.001f; + float resdrop = 0.001f; + //desiredLateralResistanceForce *= resdrop; + + // new place to set this + mVehicle->mCollisionLateralResistanceDropFactor = resdrop; + + } + + + // new - reduce traction on drive wheels if givin 'er gas + const float gasmodifierthreshold = 0.7f; + const float absoluteSpeedThreshold = 90.0f; + if( mVehicle->mWheels[wheelNum]->mDriveWheel && mVehicle->mVehicleType == VT_USER && mVehicle->mGas > 0.0f && + mVehicle->mPercentOfTopSpeed > gasmodifierthreshold && rmt::Fabs(mVehicle->mWheelTurnAngleInputValue) > 0.1f && + mVehicle->mSpeedKmh > absoluteSpeedThreshold) + { + const float maxPenalty = 0.2f; + float modifier = 1.0f - (maxPenalty * mVehicle->mGas); // ? also include speed?? + + desiredLateralResistanceForce *= modifier; + + mVehicle->mDrawWireFrame = true; + mVehicle->mLosingTractionDueToAccel = true; // for plum + //mVehicle->mBurnoutLevel = 1.0f; + + + } + + + + + } + else + { + //---------- + // low speed + //---------- + + temp.NormalizeSafe(); + + float lateralVelocityProjection = lateralDirection.DotProduct(temp); + + // at low speeds we want to account for the (small) magnitude of the velocity + lateralVelocityProjection *= (mVehicle->mPercentOfTopSpeed / threshold); + + + const rmt::Vector& gravity = GetWorldPhysicsManager()->mSimEnvironment->Gravity(); + float gravFactor = gravity.y * -1.0f / 9.81f; + + if(mVehicle->mVehicleState == VS_NORMAL) + { + desiredLateralResistanceForce = (mVehicle->mDesignerParams.mDpMass * lateralVelocityProjection * mVehicle->mDesignerParams.mDpTireLateralResistanceNormal) * 0.25f;// * gravFactor; + } + else + { + // slip + desiredLateralResistanceForce = (mVehicle->mDesignerParams.mDpMass * lateralVelocityProjection * mVehicle->mDesignerParams.mDpTireLateralResistanceSlip) * 0.25f;// * gravFactor; + + /* + if(mVehicle->mEBrake > 0.0) + { + if(mVehicle->mWheels[wheelNum]->mSteerWheel) + { + // increase resistance on steer wheels + desiredLateralResistanceForce *= (1.0f + mVehicle->mEBrake * mVehicle->mDesignerParams.mDpEBrakeEffect); + + } + else + { + // decrease resistance on rear + desiredLateralResistanceForce *= (1.0f - mVehicle->mEBrake * mVehicle->mDesignerParams.mDpEBrakeEffect); + + } + + } + */ + + + } + + } + + + + // put ebrake effect outside of any state + if(mVehicle->mPercentOfTopSpeed > 0.02f) + { + if(mVehicle->mEBrake > 0.1) // note: we can be in this state without always holding the button down + { + if(mVehicle->mWheels[wheelNum]->mSteerWheel) + { + // increase resistance on steer wheels + //desiredLateralResistanceForce *= (1.0f + mVehicle->mEBrake * mVehicle->mDesignerParams.mDpEBrakeEffect); + desiredLateralResistanceForce *= (1.0f + mVehicle->mDesignerParams.mDpEBrakeEffect); + + } + else + { + // decrease resistance on rear + //desiredLateralResistanceForce *= (1.0f - mVehicle->mEBrake * mVehicle->mDesignerParams.mDpEBrakeEffect); + desiredLateralResistanceForce *= (1.0f - mVehicle->mDesignerParams.mDpEBrakeEffect); + } + + } + } + + + //rmt::Vector force = lateralDirection; + + // assumign these are good? + + //mTerrainIntersectCache[i].planePosn = planePosn; + //mTerrainIntersectCache[i].planeNorm = planeNormal; + + + //rmt::Vector force = mTerrainIntersectCache[wheelNum].planeNorm; + + rmt::Vector force = mIntersectNormalUsed[wheelNum]; + force.CrossProduct(mVehicle->mVehicleFacing); + + // just in case + force.NormalizeSafe(); + + //rmt::Vector force = mVehicle->mVehicleTransverse; // I think this is better! + + + + + + /// new quick terrain based friction test + // only affect steering + + switch (mTerrainIntersectCache[wheelNum].mTerrainType) + { + case TT_Road: + desiredLateralResistanceForce *= TF_Road; + break; + + case TT_Grass: + desiredLateralResistanceForce *= TF_Grass; + break; + + case TT_Dirt: + desiredLateralResistanceForce *= TF_Dirt; + break; + + case TT_Water: + desiredLateralResistanceForce *= TF_Water; + break; + + case TT_Gravel: + desiredLateralResistanceForce *= TF_Gravel; + break; + + case TT_Wood: + desiredLateralResistanceForce *= TF_Wood; + break; + + case TT_Sand: + desiredLateralResistanceForce *= TF_Sand; + break; + + case TT_Metal: + desiredLateralResistanceForce *= TF_Metal; + break; + + default: + break; + + } + + + force.Scale(-1.0f * desiredLateralResistanceForce); + + + if(1)//mVehicle->mBurnoutLevel == 0.0f) + { + // test + // scale lateral resistance way down if we have just had a collision + force.Scale(mVehicle->mCollisionLateralResistanceDropFactor); + } + + + // test before or after the above drop? + //TestSteeringForce(force, wheelNum); + + + + forceResult.Add(force); + + + + // new test + // faked high speed instability + /* + const float highSpeedInstabilityThreshold = 0.6f; + if(mVehicle->mPercentOfTopSpeed > highSpeedInstabilityThreshold) + { + // add some magic torque depending on the input wheel turn angle. + if(mVehicle->mWheelTurnAngleInputValue != 0.0f && !(mVehicle->mAirBorn)) + { + static float magicshit = 10.0f; + + rmt::Vector instabilityTorque = mVehicle->mVehicleFacing; + instabilityTorque.Scale(magicshit * mVehicle->mDesignerParams.mDpMass * mVehicle->mPercentOfTopSpeed * mVehicle->mWheelTurnAngleInputValue); + + sim::PhysicsObject* phobj = (sim::PhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject()); + rAssert(phobj); + + phobj->AddTorque(instabilityTorque); + + + } + } + */ + +} + + +//============================================================================= +// PhysicsLocomotion::TestSteeringForce +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector& force) +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::TestSteeringForce(rmt::Vector& force, int index) +{ + //mVehicleState not changed here - this is just used to limit the lateral force + + //static float forceCap = 1.0f; + + + rAssert(0); + + +/* + static float rearWheelMult = 1.0f; + + if(mVehicle->mVehicleState == VS_SLIP) + { + + float suspensionEffect = mCachedSuspensionForceResults[index]; + + float suspensionMinEffect = mVehicle->mSuspensionRestValue; + + if(suspensionEffect < suspensionMinEffect) + { + suspensionEffect = suspensionMinEffect; + } + + float maxTireForce = mVehicle->mDesignerParams.mDpTireLateralStaticGrip * suspensionEffect; + + + // cap all forces + int j; + for(j = 0; j < 4; j++) + { + + //if(totalForce[j].Magnitude() > forceCap) + //{ + // totalForce[j].NormalizeSafe(); + // totalForce[j].Scale(forceCap); + //} + + if(j < 2) + { + + if(force.Magnitude() > maxTireForce * rearWheelMult) + { + force.NormalizeSafe(); + force.Scale(maxTireForce * rearWheelMult); + } + } + else + { + if(force.Magnitude() > maxTireForce) + { + force.NormalizeSafe(); + force.Scale(maxTireForce); + } + + } + + } + + } + +*/ +} + +//============================================================================= +// PhysicsLocomotion::TestControllerForces +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector* totalForce) -- points to array of 4 +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::TestControllerForces(rmt::Vector* totalForce) +{ + + // mVehicleState only changed in here! + + + // find wheel with highest down force + int i; + int index = 0; + for(i = 1; i < 4; i++) + { + if(mCachedSuspensionForceResults[i] > mCachedSuspensionForceResults[index]) + { + index = i; + } + } + + // TODO - fix this for airborn! + /* + float suspensionEffect = mCachedSuspensionForceResults[index]; + + float suspensionMinEffect = mVehicle->mSuspensionRestValue; + + if(suspensionEffect < suspensionMinEffect) + { + suspensionEffect = suspensionMinEffect; + } + */ + + // new test + float suspensionEffect = mVehicle->mSuspensionRestValue; + + + float maxTireForce = mVehicle->mDesignerParams.mDpTireLateralStaticGrip * suspensionEffect; + + /* + // new Mar 12, 2003 + // affect the maxTireForce calculation by gas + const float gasmodifierthreshold = 0.7f; + const float absoluteSpeedThreshold = 90.0f; + + if(mVehicle->mVehicleType == VT_USER && mVehicle->mGas > 0.0f && + mVehicle->mPercentOfTopSpeed > gasmodifierthreshold && rmt::Fabs(mVehicle->mWheelTurnAngleInputValue) > 0.1f && + mVehicle->mSpeedKmh > absoluteSpeedThreshold) + { + //const float maxPenalty = 0.2f; + static float maxPenalty = 0.4f; + float modifier = 1.0f - (maxPenalty * mVehicle->mGas); // ? also include speed?? + + // *** desiredLateralResistanceForce *= modifier; + maxTireForce *= modifier; + + mVehicle->mDrawWireFrame = true; + mVehicle->mLosingTractionDueToAccel = true; // for plum + //mVehicle->mBurnoutLevel = 1.0f; + + + } + */ + + + if(mVehicle->mVehicleState == VS_NORMAL) + { + // if the wheel with the greatest down force is slipping + // make whole car slip + + if(mVehicle->mEBrake > 0.1f && mVehicle->mPercentOfTopSpeed > 0.05f) + { + // change state to controlled skid + mVehicle->mVehicleState = VS_EBRAKE_SLIP; + } + else + { + // now if wheel index is slipping, whole fucking car is slipping + float totalForceMag = totalForce[index].Magnitude(); + //float maxTireForce = mVehicle->mDesignerParams.mDpTireLateralStaticGrip * suspensionEffect; + if(totalForceMag > maxTireForce) + { + // fucker is sliding + mVehicle->mVehicleState = VS_SLIP; + //mVehicle->mTireLateralResistance = mVehicle->mDesignerParams.mDpTireLateralResistanceSlip; + + } + } + + } + else + { + if( !mVehicle->mOutOfControl ) + { + // need heruistic to go back to normal + rmt::Vector velDir = mVehicle->mVelocityCM; + velDir.NormalizeSafe(); + + float cos5 = 0.9962f; + float cos10 = 0.9848f; + float cos20 = 0.9397f; + float cos15 = 0.9659f; + + // in addition to the alignment test, should not go into VS_SLIP if you're going very slow, + // ebrake or not. + + if( (velDir.DotProduct(mVehicle->mVehicleFacing) > cos15 && rmt::Fabs(mVehicle->mWheelTurnAngleInputValue) < 0.1f) || + mVehicle->mSpeed < 1.0f || + mVehicle->mGas == 0.0f) + { + mVehicle->mVehicleState = VS_NORMAL; + //mVehicle->mTireLateralResistance = mVehicle->mDesignerParams.mDpTireLateralResistanceNormal; + } + + /* + // duplicate this here in case we only got in here cause the ebrake is held down. + if(mVehicle->mEBrake > 0.1f && mVehicle->mSpeed > 1.0f) + { + mVehicle->mVehicleState = VS_SLIP; + //mVehicle->mTireLateralResistance = mVehicle->mDesignerParams.mDpTireLateralResistanceSlip; + // return; + } + */ + } + } + + + + + +} + + +//============================================================================= +// PhysicsLocomotion::SetSkidLevel +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void PhysicsLocomotion::SetSkidLevel() +{ + mVehicle->mSkidLevel = 0.0f; + mVehicle->mSlipGasModifier = 1.0f; + + //static float hack = 0.7f; + //mVehicle->mSkidLevel = hack; + + + float skid = 0.0f; + float speedfactor = mVehicle->mPercentOfTopSpeed; + + + + rmt::Vector velDir = mVehicle->mVelocityCM; + velDir.NormalizeSafe(); + + + float projectionOnly = rmt::Fabs(velDir.DotProduct(mVehicle->mVehicleTransverse)); + + + if(mVehicle->mVehicleState == VS_SLIP || mVehicle->mVehicleState == VS_EBRAKE_SLIP) + { + // static float hack = 0.7f; + // mVehicle->mSkidLevel = hack; + + + if(1)//projectionOnly > 0.7f) + { + // this actually works quite well + skid = projectionOnly; + } + else + { + skid = projectionOnly * speedfactor; + } + + + // if they're using the ebrake, we want whichever is loudest: + if(mVehicle->mEBrake * 0.5f > skid) + { + skid = mVehicle->mEBrake * 0.5f; + } + + } + + if(mVehicle->mBrake > 0.05f && speedfactor > 0.07f) + { + /* + rmt::Vector velDir = mVehicle->mVelocityCM; + velDir.NormalizeSafe(); + + float proj = velDir.DotProduct(mVehicle->mVehicleFacing); + + // hmm.. + if(mVehicle->mBrake * 0.5f > skid && proj > 0.0f) + { + skid = mVehicle->mBrake * 0.5f; + } + */ + + // not sure if values lower than 0.5 are working... + // + // + float forward = velDir.DotProduct(mVehicle->mVehicleFacing); + if(forward > 0.0f && speedfactor < 0.5f) + { + skid = 1.0f; + } + + + } + + if(mVehicle->mEBrake > 0.85f && speedfactor > 0.07f)// && mVehicle->mWheelTurnAngle == 0.0f) + { + // hmm.. + if(mVehicle->mEBrake * 0.5f > skid) + { + skid = mVehicle->mEBrake * 0.5f; + } + } + + + // compare to burnout level + if(mVehicle->mBurnoutLevel > skid) + { + mVehicle->mSkidLevel = mVehicle->mBurnoutLevel; + } + else + { + mVehicle->mSkidLevel = skid; + } + + + // still too quiet + if(mVehicle->mSkidLevel > 0.0f) + { + if(mVehicle->mSkidLevel < 0.5f) + { + mVehicle->mSkidLevel = 0.5f; + } + } + + +/* + + // override any of the above shit if wheels are not in contact with ground + int i; + if(mBurnoutLevel > 0.0f) + { + if(mWheels[0]->mWheelInCollision + + + for(i = 0; i < 2; i++) + { + if(mWheels[i]->mWheelInCollision) // will this value still be valid here? + { + mGeometryVehicle->SetSkidValues(i, mSkidLevel); + } + } + } + else + { + for(i = 0; i < 4; i++) + { + if(mWheels[i]->mWheelInCollision) // will this value still be valid here? + { + mGeometryVehicle->SetSkidValues(i, mSkidLevel); + } + } + } + + +*/ + + +} + + + + + + + + + diff --git a/game/code/worldsim/redbrick/redbrickcollisionsolveragent.cpp b/game/code/worldsim/redbrick/redbrickcollisionsolveragent.cpp new file mode 100644 index 0000000..4543079 --- /dev/null +++ b/game/code/worldsim/redbrick/redbrickcollisionsolveragent.cpp @@ -0,0 +1,927 @@ +/*=========================================================================== + redbrickcollisionsolveragent.cpp + + created Jan 28, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + +===========================================================================*/ +#include <simcommon/simstatearticulated.hpp> +#include <simcommon/simulatedobject.hpp> +#include <worldsim/redbrick/redbrickcollisionsolveragent.h> +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/redbrick/wheel.h> +#include <worldsim/worldphysicsmanager.h> +#include <mission/gameplaymanager.h> +#include <worldsim/avatarmanager.h> +#include <worldsim/avatar.h> + +#include <events/eventmanager.h> +#include <sound/soundcollisiondata.h> + +#ifdef RAD_XBOX +#include <float.h> +#endif + + +//------------------------------------------------------------------------ +RedBrickCollisionSolverAgent::RedBrickCollisionSolverAgent() +{ + // + mBottomedOut = false; + + //mBottomedOutScale = 1.0e-5f; + //mBottomedOutScale = 1.0f; + //mBottomedOutScale = 0.0002f; + mBottomedOutScale = 0.0003f; + + + mWheelSidewaysHit = false; + + mWheelSidewaysHitScale = 0.002f; + +} + + +//------------------------------------------------------------------------ +RedBrickCollisionSolverAgent::~RedBrickCollisionSolverAgent() +{ + // +} + + +//------------------------------------------------------------------------------------------------ +void RedBrickCollisionSolverAgent::ResetCollisionFlags() +{ + // TODO - how to properly tie this to rest of vehicle module???? + + // TODO - need these flags on a car by car basis + + mBottomedOut = false; + mWheelSidewaysHit = false; + +} + + +//------------------------------------------------------------------------------------------------ +Solving_Answer RedBrickCollisionSolverAgent::TestImpulse(rmt::Vector& impulse, Collision& inCollision) +{ + + // currently this method does nothing... + // + // test some collision results... + + + + + + // TODO - + // something more efficient here so we don't have to test this shit every time + + CollisionObject* collObjA = inCollision.mCollisionObjectA; + CollisionObject* collObjB = inCollision.mCollisionObjectB; + + SimState* simStateA = collObjA->GetSimState(); + SimState* simStateB = collObjB->GetSimState(); + + // + // SOUND EVENT HERE? + // + + // need to convert the impulse to a nice friendly float between 0.0 and 1.0 + // + // just looking at some empirical data, the range of impulse magnitude seems to be from around + // 0.0 to 100000.0 - at the high end would be a heavy vehicle (3000 kg) hitting a static wall at 122kmh + // a 2000kg car hitting a wall at 150kmh gave 78706.7 + + + + + +#ifdef RAD_DEBUG + if(rmt::IsNan(impulse.x) || rmt::IsNan(impulse.y) || rmt::IsNan(impulse.z)) + { + rAssert(0); + } +#endif + + + + + + float impulseMagnitude = impulse.Magnitude(); + + if(impulseMagnitude > 100000.0f) + { + int stophere = 1; + } + + + + const float maxIntensity = 100000.0f; + + float soundScale = impulseMagnitude / maxIntensity; + if(soundScale > 1.0f) + { + soundScale = 1.0f; + } + if(soundScale < 0.0f) + { + rAssert(0); + } + +// SoundCollisionData soundData( soundScale, simStateA->mAIRefPointer, simStateB->mAIRefPointer ); +// GetEventManager()->TriggerEvent( EVENT_COLLISION, &soundData ); + // up to this point is pretty generic for sound stuff - ie. the guts of PostReactToCollision might look a lot like this? + + + // here is more vehicle intensive + + //if(simStateA->mAIRefIndex == Vehicle::GetAIRef() && simStateB->mAIRefIndex == Vehicle::GetAIRef()) + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickVehicle && simStateB->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + // car on car + + //static float magic = 1.0f; + + // fun test + //impulse.Scale(magic); + + //int stophere = 1; + + if( ((Vehicle*)(simStateA->mAIRefPointer))->mVehicleType == VT_TRAFFIC ) + { + + } + + } + + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickVehicle || simStateB->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + //PhysicsVehicle* physicsVehicle; + Vehicle* vehicle; + + float test = impulse.Magnitude(); + + + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickVehicle ) + { + vehicle = (Vehicle*)(simStateA->mAIRefPointer); + } + else + { + vehicle = (Vehicle*)(simStateB->mAIRefPointer); + } + + + // TODO - more detailed test... + + // TODO - I'm not sure I like this system... + + if(mBottomedOut) // todo - more detailed test for if we are dealing with a wheel + { + //mImpulse.Scale(mBottomedOutScale); + } + if(mWheelSidewaysHit) + { + //mImpulse.Scale(mWheelSidewaysHitScale); + + //mImpulse.y = 0.0f; // TODO - expand to below later... + + } + + } + + return Solving_Continue; +} + +//============================================================================= +// RedBrickCollisionSolverAgent::CarOnCarAction +//============================================================================= +// Description: Comment +// +// Parameters: (Collision& inCollision) +// +// Return: Solving_Answer +// +//============================================================================= +Solving_Answer RedBrickCollisionSolverAgent::CarOnCarPreTest(Collision& inCollision, int inPass) +{ + CollisionObject* collObjA = inCollision.mCollisionObjectA; + CollisionObject* collObjB = inCollision.mCollisionObjectB; + + SimState* simStateA = collObjA->GetSimState(); + SimState* simStateB = collObjB->GetSimState(); + + Vehicle* vehicleA = (Vehicle*)(simStateA->mAIRefPointer); + Vehicle* vehicleB = (Vehicle*)(simStateB->mAIRefPointer); + + int jointIndexA = inCollision.mIndexA; + int jointIndexB = inCollision.mIndexB; + + + + //vehicleA->TriggerDamage(); + //vehicleB->TriggerDamage(); - move the damage triggering to Vehicle::PostReactToCollision + +// vehicleA->SetHitJoint(jointIndexA); +// vehicleB->SetHitJoint(jointIndexB); + + + // for now... + if(1)//vehicleA->mUsingInCarPhysics) + { + if(vehicleA->IsJointAWheel(jointIndexA) || vehicleB->IsJointAWheel(jointIndexB)) + { + return Solving_Aborted; + } + + if(vehicleA->IsAFlappingJoint(jointIndexA) || vehicleB->IsAFlappingJoint(jointIndexB)) + { + // TODO - try taking this out? + return Solving_Aborted; + } + } + + //hmmm + // is this the place to switch loco? + if(vehicleA->GetLocomotionType() == VL_TRAFFIC) + { + vehicleA->SetLocomotion(VL_PHYSICS); + } + + if(vehicleB->GetLocomotionType() == VL_TRAFFIC) + { + vehicleB->SetLocomotion(VL_PHYSICS); + } + + // just in case it has come to rest.... + + if(simStateA->GetControl() == sim::simAICtrl) + { + simStateA->SetControl(sim::simSimulationCtrl); + } + + if(simStateB->GetControl() == sim::simAICtrl) + { + simStateB->SetControl(sim::simSimulationCtrl); + } + + + return CollisionSolverAgent::PreCollisionEvent(inCollision, inPass); +} + +//============================================================================= +// RedBrickCollisionSolverAgent::PreCollisionEvent +//============================================================================= +// Description: Comment +// +// Parameters: (Collision& inCollision, int inPass) +// +// Return: Solving_Answer +// +//============================================================================= +Solving_Answer RedBrickCollisionSolverAgent::PreCollisionEvent(Collision& inCollision, int inPass) +{ + // have a look at objects A and B and see if we care about them: + + // TODO + // also, depending how steeply we've hit something, we also want to let the solver do it's thing... + // ? mabye cancel out the y component? + + // also may skip this depending how tipped we are + + + CollisionObject* collObjA = inCollision.mCollisionObjectA; + CollisionObject* collObjB = inCollision.mCollisionObjectB; + + SimState* simStateA = collObjA->GetSimState(); + SimState* simStateB = collObjB->GetSimState(); + + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickVehicle || simStateB->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + //-------------------------------------- + // deal specially with car on car action + //-------------------------------------- + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickVehicle && simStateB->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + // test for hitting a wheel or a flapping joint in here + // + return CarOnCarPreTest(inCollision, inPass); + } + + Vehicle* vehicle; + + int jointIndex; + + rmt::Vector groundContactPoint; + rmt::Vector normalPointingAtCar; + + bool carisA = true; + + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + vehicle = (Vehicle*)(simStateA->mAIRefPointer); + jointIndex = inCollision.mIndexA; + + groundContactPoint = inCollision.GetPositionB(); + normalPointingAtCar = inCollision.mNormal; + + } + else + { + vehicle = (Vehicle*)(simStateB->mAIRefPointer); + jointIndex = inCollision.mIndexB; + + groundContactPoint = inCollision.GetPositionA(); + normalPointingAtCar = inCollision.mNormal; + normalPointingAtCar.Scale(-1.0f); + carisA = false; + } + + + // temp hack cause I think we're hitting our driver every frame: +/* + if(carisA) + { + if(simStateB->mAIRefIndex == PhysicsAIRef::redBrickPhizMoveable) + { + return Solving_Aborted; + } + } + else + { + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickPhizMoveable) + { + return Solving_Aborted; + } + + } +*/ + + + /* + + this now encompassed below... + + if(carisA) + { + if(simStateB->mAIRefIndex == PhysicsAIRef::StateProp) + { + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + if(playerAvatar->GetVehicle() == vehicle) + { + GetEventManager()->TriggerEvent( EVENT_HIT_MOVEABLE, (void*)simStateB ); + } + } + } + else + { + if(simStateA->mAIRefIndex == PhysicsAIRef::StateProp) + { + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + if(playerAvatar->GetVehicle() == vehicle) + { + GetEventManager()->TriggerEvent( EVENT_HIT_MOVEABLE, (void*)simStateA ); + } + } + } + */ + + if(carisA) + { + if(simStateB->mAIRefIndex == PhysicsAIRef::redBrickPhizMoveable || simStateB->mAIRefIndex == PhysicsAIRef::StateProp) + { + sim::SimControlEnum control = simStateB->GetControl(); + bool ishit = ((DynaPhysDSG*)(simStateB->mAIRefPointer))->mIsHit; + enClasstypeID classid = (enClasstypeID)((DynaPhysDSG*)(simStateB->mAIRefPointer))->GetCollisionAttributes()->GetClasstypeid(); + + + if(simStateB->GetControl() == sim::simAICtrl && ((DynaPhysDSG*)(simStateB->mAIRefPointer))->mIsHit && + ((DynaPhysDSG*)(simStateB->mAIRefPointer))->GetCollisionAttributes()->GetClasstypeid() == PROP_ONETIME_MOVEABLE) + { + return Solving_Aborted; + } + else + { + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + if(playerAvatar->GetVehicle() == vehicle) + { + GetEventManager()->TriggerEvent( EVENT_HIT_MOVEABLE, (void*)simStateB ); + } + } + } + } + else + { + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickPhizMoveable || simStateA->mAIRefIndex == PhysicsAIRef::StateProp) + { + + sim::SimControlEnum control = simStateA->GetControl(); + bool ishit = ((DynaPhysDSG*)(simStateA->mAIRefPointer))->mIsHit; + enClasstypeID classid = (enClasstypeID)((DynaPhysDSG*)(simStateA->mAIRefPointer))->GetCollisionAttributes()->GetClasstypeid(); + + + + if(simStateA->GetControl() == sim::simAICtrl && ((DynaPhysDSG*)(simStateA->mAIRefPointer))->mIsHit && + ((DynaPhysDSG*)(simStateA->mAIRefPointer))->GetCollisionAttributes()->GetClasstypeid() == PROP_ONETIME_MOVEABLE) // safe cast? + { + return Solving_Aborted; + } + else + { + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + if(playerAvatar->GetVehicle() == vehicle) + { + GetEventManager()->TriggerEvent( EVENT_HIT_MOVEABLE, (void*)simStateA ); + } + } + + } + + } + + if(vehicle->mUsingInCarPhysics) + { + if(vehicle->IsAFlappingJoint(jointIndex)) + { + + // vehicle->SetHitJoint(jointIndex); + + return Solving_Aborted; + } + } + + + + if(vehicle->IsJointAWheel(jointIndex)) + { + + if(carisA) + { + if(simStateB->mAIRefIndex == PhysicsAIRef::redBrickPhizFence) + { + int stophere = 1; + + // the old tip test + rmt::Vector worldUp = GetWorldPhysicsManager()->mWorldUp; + + float cos10 = 0.9848f; + //if(worldUp.DotProduct(vehicle->mVehicleUp) < cos10) + { + // will this work?? + inCollision.mIndexA = 0; + return Solving_Continue; + + } + + } + + + + if(simStateB->mAIRefIndex != PhysicsAIRef::redBrickPhizStatic) // redBrickPhizMoveableAnim? -> don't think we need to + { + if(vehicle->mUsingInCarPhysics) + { + return Solving_Aborted; + } + else + { + return Solving_Continue; + } + } + + + } + else + { + + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickPhizFence) + { + // the old tip test + rmt::Vector worldUp = GetWorldPhysicsManager()->mWorldUp; + + float cos10 = 0.9848f; + //if(worldUp.DotProduct(vehicle->mVehicleUp) < cos10) + { + // will this work?? + inCollision.mIndexB = 0; + return Solving_Continue; + + } + + int stophere = 1; + } + + + if(simStateA->mAIRefIndex != PhysicsAIRef::redBrickPhizStatic) + { + if(vehicle->mUsingInCarPhysics) + { + return Solving_Aborted; + } + else + { + return Solving_Continue; + } + } + } + + + /* + // if this is a wheel, but the thing we hit is a vehicle ground plane, then we want to ignore it. + if(carisA) + { + if(simStateB->mAIRefIndex == vehicle->GetGroundPlaneAIRef() || + simStateB->mAIRefIndex == PhysicsAIRef::redBrickPhizMoveable) // new - do we want to keep this? + { + return Solving_Aborted; + } + } + else + { + if(simStateA->mAIRefIndex == vehicle->GetGroundPlaneAIRef() || + simStateA->mAIRefIndex == PhysicsAIRef::redBrickPhizMoveable) // new - do we want to keep this? + { + return Solving_Aborted; + } + + } + */ + + // recall: + // mPositionA = mPositionB + mNormal * mDistance + + // SG::phizSim.mCollisionDistanceCGS; + + // amount we want to keep in collision + // TODO - even want to do this? + //float slightlyColliding = (SG::phizSim.mCollisionDistanceCGS * 0.1f) * 0.01f; // last factor to make sure we're in meters... + + //float slightlyColliding = (SG::phizSim.mCollisionDistanceCGS * 1.0f) * 0.01f; // last factor to make sure we're in meters... + + // TODO + // hack to fix for no phizsim + float collisionDistanceCGS = 2.0f; + + //float slightlyColliding = (SG::phizSim.mCollisionDistanceCGS * 1.0f) * 0.01f; // last factor to make sure we're in meters... + float slightlyColliding = (collisionDistanceCGS * 1.0f) * 0.01f; // last factor to make sure we're in meters... + + // TODO - choose right value here based on coefficient of restituion blah blah + // initial random guessof 0.1 was so low it was never used since the impulses + // kept shit apart. + // + // hmmmm... maybe want to use that? but then how does suspension compress? + // + + + // if we hit something sideways with the wheel, just apply impulse + float cos80 = 0.1736f; + float cos70 = 0.342f; + + // TODO - this isn't nearly adequate! + + // TODO + // temp fix + + rmt::Vector worldUp = GetWorldPhysicsManager()->mWorldUp; + + // new test + //if(vehicle->mPercentOfTopSpeed > 0.3f) + if(vehicle->mPercentOfTopSpeed > 0.1f) + { + int wheelIndex = vehicle->mJointIndexToWheelMapping[jointIndex]; + rAssert(wheelIndex >= 0); + rAssert(wheelIndex < 5); + + rmt::Vector normVel = vehicle->mSuspensionPointVelocities[wheelIndex]; + + normVel.NormalizeSafe(); + float cos45 = 0.7071f; + if(normalPointingAtCar.DotProduct(normVel) > cos45) + //if(normalPointingAtCar.DotProduct(normVel) > cos70) + { + // the collision is pointing the way we're going so fuck it + //char buffy[128]; + //sprintf(buffy, "aborting wheel-static collision fix - pointing too much along velocity\n"); + //rDebugPrintf(buffy); + + + vehicle->SetNoDamperDownFlagOnWheel(wheelIndex); + + return Solving_Aborted; + } + } + + + + float cos55 = 0.5736f; + + //if( rmt::Fabs((inCollision.mNormal).DotProduct(vehicle->mVehicleUp)) < cos70 || // recall - this is the new line + if( rmt::Fabs((inCollision.mNormal).DotProduct(vehicle->mVehicleUp)) < cos55 || // recall - this is the new line + worldUp.DotProduct(vehicle->mVehicleUp) < cos80) // leave this on at 80 + { + + //char buffy[128]; + //sprintf(buffy, "aborting wheel-static collision fix - too horizontal\n"); + //rDebugPrintf(buffy); + + int wheelIndex = vehicle->mJointIndexToWheelMapping[jointIndex]; + rAssert(wheelIndex >= 0); + rAssert(wheelIndex < 5); + + vehicle->SetNoDamperDownFlagOnWheel(wheelIndex); + + // temp + // TODO - something? + return Solving_Aborted; + + // let collision solver just apply some impulse + // + // TODO - scale down impulse?? + + mWheelSidewaysHit = true; + + if(carisA) + { + inCollision.mIndexA = 0; + } + else + { + inCollision.mIndexB = 0; + } + + return CollisionSolverAgent::PreCollisionEvent(inCollision, inPass); + + } + + + + + if(inCollision.mDistance < slightlyColliding) + { + // need to move along normal by: + float fixAlongCollisionNormal = slightlyColliding - inCollision.mDistance; + + float fixAlongSuspensionAxis = fixAlongCollisionNormal / rmt::Fabs( (vehicle->mVehicleUp).DotProduct(inCollision.mNormal) ); + + // now fixAlongSuspensionAxis is the object space y offset that we should correct to + + // this will return true for bottom out + if(vehicle->SetWheelCorrectionOffset(jointIndex, fixAlongSuspensionAxis, normalPointingAtCar, groundContactPoint)) + //if(fixAlongSuspensionAxis > 2.0f * physicsVehicle->GetWheelByJoint(jointIndex)->GetSuspensionLimit() ) + { + // we've bottomed out + // + // so also let solver apply impulse + mBottomedOut = true; // TODO - should also store the joint! - ...later...why? + + // if we've bottomed out, we only want to transfer impulse to the chassis if + // it's more or less up + + const float cos20 = 0.9397f; + + //if( rmt::Fabs((inCollision.mNormal).DotProduct(vehicle->mVehicleUp)) > cos20 ) + if(normalPointingAtCar.DotProduct(vehicle->mVehicleUp) > cos20) + { + /* + if(carisA) + { + inCollision.mIndexA = 0; + } + else + { + inCollision.mIndexB = 0; + } + + return CollisionSolverAgent::PreCollisionEvent(inCollision, inPass); + + + april 11, 2003 + this makes an absolute world of difference in jumps!!!! + + + */ + + return Solving_Aborted; + } + else + { + return Solving_Aborted; + } + + } + + return Solving_Aborted; + + } + + + + } // end of the IsJointAWheel block... + + + // if we got here we are the chassis hitting a static or our own groudn plane + if(carisA) + { + if(simStateB->mAIRefIndex == PhysicsAIRef::redBrickPhizVehicleGroundPlane) + { + + rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; + float tip = vehicle->mVehicleUp.DotProduct(up); + + //float cos10 = 0.9848f; + float cos16 = 0.9613f; + + if(vehicle->mAirBorn && tip > cos16) + { + //int stophere = 1; + return Solving_Aborted; + } + + + //vehicle->mChassisHitGroundPlaneThisFrame = true; + } + } + else + { + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickPhizVehicleGroundPlane) + { + + + rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; + float tip = vehicle->mVehicleUp.DotProduct(up); + + //float cos10 = 0.9848f; + float cos16 = 0.9613f; + + if(vehicle->mAirBorn && tip > cos16) + { + //int stophere = 1; + return Solving_Aborted; + } + + + + //vehicle->mChassisHitGroundPlaneThisFrame = true; + } + + } + + + + + if(inCollision.mDistance < -0.5f) + //if(inCollision.mDistance < -0.25f) + //if(inCollision.mDistance < -0.1f) + { + //if(vehicle->mVehicleID == VehicleEnum::FAMIL_V) + //{ + // int stophere = 1; // motherfucking goddamn data conditions on breakpoints not working!! + //} + + // greg + // jan 15, 2003 + + // did you know, that to penetrate something 0.5m in 32ms, you only have to be going 56 kmh ? + + // live and learn + + //return Solving_Aborted; + //inCollision.mDistance = -0.1f; // fuck + } + + // this is just chassis hitting something here... + + // test - if all wheels are out of collision, transffer impulse to root...? + + + } + return Solving_Continue; +} + + + +Solving_Answer RedBrickCollisionSolverAgent::EndObjectCollision(SimState* inSimState, int inIndex) +{ + + if(inSimState->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + Vehicle* vehicle = (Vehicle*)(inSimState->mAIRefPointer); + + if(vehicle->IsAFlappingJoint(inIndex)) + { + //vehicle->mSimStateArticulated->GetSimulatedObject()->ResetCache(); + } + } + + return CollisionSolverAgent::EndObjectCollision(inSimState, inIndex); +} + + + +Solving_Answer RedBrickCollisionSolverAgent::TestCache(SimState* inSimState, int inIndex) +{ + // this is called everytime an impulse is added on an object. Getting the cache give information + +/* + if(inSimState->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + // car on car + + //static float magic = 1.0f; + + // fun test + //impulse.Scale(magic); + + //int stophere = 1; + + if( ((Vehicle*)(inSimState->mAIRefPointer))->mVehicleType == VT_TRAFFIC ) + { + static float magic = 5.0f; + + // fun test + impulse.Scale(magic); + + SimulatedObject* simobj = inSimState->GetSimulatedObject(-1); + + rmt::Vector cachev, cachew; + simobj->GetCollisionCache(cachev, cachew, -1); //retrieve the collision cache of the physical object. + + + if (cachev.MagnitudeSqr() > maxDv2) + { + bool fuck = true; + } + + + } + } + +*/ + + +#if 0 + SimulatedObject* simobj = inSimState->GetSimulatedObject(-1); + + + if ( simobj && (simobj->Type() == RigidObjectType || simobj->Type() == ArticulatedObjectType) ) + { + /* + static float thresholdfactor1 = 100.0f; + Vector currentv, vcmv, cachev, cachew; + currentv = inSimState->GetLinearVelocity(); //current speed of the object + if (inSimState->GetVirtualCM()==NULL) + vcmv.Clear(); + else + vcmv = inSimState->GetVirtualCM()->GetVelocity(); //The current speed of the vcm of the object + simobj->GetCollisionCache(cachev, cachew, -1); //retrieve the collision cache of the physical object. + cachev.Add(vcmv); //The combined vcm's speed and the cached speed. + float testSpeed = Max(currentv.DotProduct(currentv),Sqr(simobj->GetMinimumLinSpeed())); + if ( cachev.DotProduct(cachev) > thresholdfactor1*testSpeed ) + { + //inSimState->SetControl(simSimulationCtrl); + return Solving_Aborted; + } + */ + Vector cachev, cachew; + simobj->GetCollisionCache(cachev, cachew, -1); //retrieve the collision cache of the physical object. + + static float maxDv2 = 27.0f*27.0f; + if (cachev.MagnitudeSqr() > maxDv2) + { + bool fuck = true; + } + } +#endif + + /* + if (simobj && simobj->Type() == ArticulatedObjectType) + { + //Looks if there is a vcm installed on this joint: + SimStateArticulated *simStateArt = (SimStateArticulated*)inSimState; + JointVCMpArray *vcma = &simStateArt->mVirtualCMList; + for (int i=0 ; i<vcma->GetSize() ; i++) + { + JointVirtualCM *vcm = vcma->GetAt(i); + if (vcm->GetIndex() == inIndex) + { + static float thresholdfactor2=100.0f; + Vector currentv, vcmv, cachev, cachew; + simStateArt->GetVelocity(vcm->GetPosition(), currentv, inIndex); + vcmv = vcm->GetVelocity(); + simobj->GetCollisionCache(cachev, cachew, inIndex); + if ( cachev.DotProduct(cachev) > thresholdfactor2*currentv.DotProduct(currentv) ) + { + simStateArt->SetControl(simSimulationCtrl); + return Solving_Aborted; + } + } + } + } + */ + return Solving_Continue; +} + diff --git a/game/code/worldsim/redbrick/redbrickcollisionsolveragent.h b/game/code/worldsim/redbrick/redbrickcollisionsolveragent.h new file mode 100644 index 0000000..ed88531 --- /dev/null +++ b/game/code/worldsim/redbrick/redbrickcollisionsolveragent.h @@ -0,0 +1,57 @@ +/*=========================================================================== + redbrickcollisionsolveragent.h + + created Jan 28, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + +===========================================================================*/ + +#ifndef _REDBRICKCOLLISIONSOLVERAGENT_H +#define _REDBRICKCOLLISIONSOLVERAGENT_H + +#include <worldsim/worldcollisionsolveragent.h> + +#include <simcommon/tlist.hpp> +#include <simcollision/collisionanalyser.hpp> +#include <simcollision/collisionanalyserdata.hpp> + +using namespace sim; + +// TODO - looking for the best place to put these +// a necessary part of the RedBrick interface + +// TBJ moved 'em to <worldsim/physicsairef.h> +// +//enum RedBrickPhizAITypes { redBrickPhizDefault = 0, redBrickVehicle, redBrickPhizVehicleGroundPlane, redBrickPhizStatic, redBrickPhizMoveable, redBrickPhizMoveableAnim, redBrickPhizLast }; // phizGround, phizStatic, phizMoveableAnim, phizMoveable, phizCamera, phizLast }; + +class RedBrickCollisionSolverAgent : public WorldCollisionSolverAgent +{ +public: + + RedBrickCollisionSolverAgent(); + ~RedBrickCollisionSolverAgent(); + + // the key method to override + Solving_Answer PreCollisionEvent(Collision& inCollision, int inPass); + Solving_Answer TestImpulse(rmt::Vector& mImpulse, Collision& inCollision); + Solving_Answer TestCache(SimState* inSimState, int inIndex); + + Solving_Answer EndObjectCollision(SimState* inSimState, int inIndex); + + Solving_Answer CarOnCarPreTest(Collision& inCollision, int inPass); + + virtual void ResetCollisionFlags(); + + bool mBottomedOut; + float mBottomedOutScale; + + bool mWheelSidewaysHit; + float mWheelSidewaysHitScale; + + +}; + +#endif // _REDBRICKCOLLISIONSOLVERAGENT_H diff --git a/game/code/worldsim/redbrick/rootmatrixdriver.cpp b/game/code/worldsim/redbrick/rootmatrixdriver.cpp new file mode 100644 index 0000000..ccaf57e --- /dev/null +++ b/game/code/worldsim/redbrick/rootmatrixdriver.cpp @@ -0,0 +1,29 @@ +/*=========================================================================== + rootmatrixdriver.cpp + + created Jan 24, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ + + +#include <poser/pose.hpp> +#include <poser/poseengine.hpp> +#include <poser/posedriver.hpp> +#include <poser/joint.hpp> + +#include <worldsim/redbrick/rootmatrixdriver.h> + +//------------------------------------------------------------------------ +void RootMatrixDriver::Update(poser::Pose* pose) +{ + poser::Joint* j = pose->GetJoint(0); + //rmt::Matrix m = j->GetObjectMatrix(); + //m.Mult(*mRootMatrix); + j->SetWorldMatrix(*mRootMatrix); // recall, mRootMatrix is a pointer to mTransform of the physics vehicle +} + diff --git a/game/code/worldsim/redbrick/rootmatrixdriver.h b/game/code/worldsim/redbrick/rootmatrixdriver.h new file mode 100644 index 0000000..bbebab8 --- /dev/null +++ b/game/code/worldsim/redbrick/rootmatrixdriver.h @@ -0,0 +1,77 @@ +/*=========================================================================== + rootmatrixdriver.h + + created Jan 24, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ + +#ifndef _ROOTMATRIXDRIVER_H +#define _ROOTMATRIXDRIVER_H + + +#include <poser/pose.hpp> +#include <poser/poseengine.hpp> +#include <poser/posedriver.hpp> + +/* +class PoseDriver: public tEntity +{ +public: + + PoseDriver(); + + virtual int GetMinimumJointIndex() const + { return 0; } + virtual int GetPriority() const + { return PRIORITY_DEFAULT; } + + bool IsEnabled() const + { return m_IsEnabled; } + void SetIsEnabled(bool isEnabled) + { m_IsEnabled = isEnabled; } + + virtual void Advance(float dt) = 0; + virtual void Update(Pose* pose) = 0; + +protected: + + virtual ~PoseDriver(); + +private: + + bool m_IsEnabled; +}; +*/ + + +class RootMatrixDriver : public poser::PoseDriver +{ +public: + RootMatrixDriver(rmt::Matrix* inRootMatrix) : mRootMatrix(inRootMatrix) {} + virtual int GetMinimumJointIndex() const { return 0; } + virtual int GetPriority() const { return 0; } + virtual void Advance(float dt) {} + virtual void Update(poser::Pose* pose); + + // move definition to cpp file + /* + { + poser::Joint* j = pose->GetJoint(0); + rmt::Matrix m = j->GetObjectMatrix(); + m.Mult(*mRootMatrix); + j->SetWorldMatrix(m); + } + */ + +private: + rmt::Matrix* mRootMatrix; +}; + +#endif // #ifndef _ROOTMATRIXDRIVER_H + + diff --git a/game/code/worldsim/redbrick/suspensionjointdriver.cpp b/game/code/worldsim/redbrick/suspensionjointdriver.cpp new file mode 100644 index 0000000..d9bca77 --- /dev/null +++ b/game/code/worldsim/redbrick/suspensionjointdriver.cpp @@ -0,0 +1,130 @@ +/*=========================================================================== + suspensionjointdriver.cpp + + created Feb 1, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ + +#include <poser/pose.hpp> +#include <poser/poseengine.hpp> +#include <poser/posedriver.hpp> +#include <poser/joint.hpp> + +#include <worldsim/redbrick/suspensionjointdriver.h> +#include <worldsim/redbrick/wheel.h> + + + +//------------------------------------------------------------------------ +SuspensionJointDriver::SuspensionJointDriver(Wheel* wheel, int jointIndex) +{ + mWheel = wheel; + mJointIndex = jointIndex; + +} + + +//------------------------------------------------------------------------ +SuspensionJointDriver::~SuspensionJointDriver() +{ + // +} + + + +//------------------------------------------------------------------------ +void SuspensionJointDriver::Update(poser::Pose* pose) +{ + // process results of last time's collision detection and solving... + + + //mWheel->ResolveOffset(); // I think this call is redundant - bug + + // now just use mYOffset + //float yOffset = mWheel->mYOffset; // need to reapply this because we called PoseEngine::Begin and overwrote the suspension results... + + + //! + // BUT + // + // this mYOffset is from the bottomed out location set in precollisionprep, not the neutral point + // + // try not calling PoseEngine::Begin in PostUpdate + + + + + + + + poser::Joint* joint = pose->GetJoint(mJointIndex); + + // TODO - is this call inefficient? + // I'm gonna have to make a copy of the matrix at some point so I can change some values.... + rmt::Matrix matrix = joint->GetObjectMatrix(); + + + + +//Matrix& FillRotateXYZ(float anglex, float angley, float anglez); + + /* + + + rmt::Matrix rot; + rot.Identity(); + rot.FillRotateX(mWheel->mRotAngle * 100.0f); + + matrix.Mult(rot); + + + */ + + + //matrix.m[3][1] += yOffset; + + // !! TODO - safe to just apply this? + //matrix.m[3][1] += mWheel->mPhysicsVehicleOwner->mGravitySettleYCorrection; // this is insignificant + + if(mWheel->mSteerWheel) + { + // this is one of the two front wheels, so turn it + float angle = mWheel->mWheelTurnAngle; + + // should this be done before the addition of the yOffset? + // shouldn't matter too much.... + matrix.FillRotateXYZ(mWheel->mCumulativeRot, mWheel->mWheelTurnAngle, 0.0f); + + } + else + { + matrix.FillRotateX(mWheel->mCumulativeRot); + } + + joint->SetObjectMatrix(matrix); + + /* + + rmt::Vector trans = joint->GetObjectTranslation(); + + trans.y += yOffset; + + joint->SetObjectTranslation(trans); + + if(mWheel->GetNum() < 1) + { + // this is one of the two front wheels, so rotate it + + const rmt::Matrix& GetObjectMatrix() const + { return GetObjectTransform().GetMatrix(); } + void SetObjectMatrix(const rmt::Matrix& matrix); + + + } + */ +} diff --git a/game/code/worldsim/redbrick/suspensionjointdriver.h b/game/code/worldsim/redbrick/suspensionjointdriver.h new file mode 100644 index 0000000..43d0902 --- /dev/null +++ b/game/code/worldsim/redbrick/suspensionjointdriver.h @@ -0,0 +1,71 @@ +/*=========================================================================== + suspensionjointdriver.h + + created Feb 1, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ + +#ifndef _SUSPENSIONJOINTDRIVER_H +#define _SUSPENSIONJOINTDRIVER_H + + +#include <poser/pose.hpp> +#include <poser/poseengine.hpp> +#include <poser/posedriver.hpp> + +class Wheel; + +/* +class PoseDriver: public tEntity +{ +public: + + PoseDriver(); + + virtual int GetMinimumJointIndex() const + { return 0; } + virtual int GetPriority() const + { return PRIORITY_DEFAULT; } + + bool IsEnabled() const + { return m_IsEnabled; } + void SetIsEnabled(bool isEnabled) + { m_IsEnabled = isEnabled; } + + virtual void Advance(float dt) = 0; + virtual void Update(Pose* pose) = 0; + +protected: + + virtual ~PoseDriver(); + +private: + + bool m_IsEnabled; +}; +*/ + + +class SuspensionJointDriver : public poser::PoseDriver +{ +public: + SuspensionJointDriver(Wheel* wheel, int jointIndex); + ~SuspensionJointDriver(); + + virtual void Advance(float dt) {} + virtual void Update(poser::Pose* pose); + +private: + Wheel* mWheel; + int mJointIndex; + +}; + +#endif // #ifndef _SUSPENSIONJOINTDRIVER_H + + diff --git a/game/code/worldsim/redbrick/trafficbodydrawable.cpp b/game/code/worldsim/redbrick/trafficbodydrawable.cpp new file mode 100644 index 0000000..56725ef --- /dev/null +++ b/game/code/worldsim/redbrick/trafficbodydrawable.cpp @@ -0,0 +1,82 @@ +#include <worldsim/redbrick/trafficbodydrawable.h> +#include <p3d/shader.hpp> +#include <debug/profiler.h> + +TrafficBodyDrawable::TrafficBodyDrawable() +{ + mBodyPropDrawable = NULL; + mBodyShader = NULL; + mDesiredColour.Set( 255, 255, 255, 255 ); + mFading = false; +} + +TrafficBodyDrawable::~TrafficBodyDrawable() +{ + if( mBodyPropDrawable != NULL ) + { + mBodyPropDrawable->Release();//delete mBodyPropDrawable; + mBodyPropDrawable = NULL; + } + if( mBodyShader != NULL ) + { + mBodyShader->Release(); + mBodyShader = NULL; + } +} +void TrafficBodyDrawable::SetBodyPropDrawable( tDrawable* drawable ) +{ + tRefCounted::Assign( mBodyPropDrawable, drawable ); +} +void TrafficBodyDrawable::SetBodyShader( tShader* shader ) +{ + tRefCounted::Assign( mBodyShader, shader ); +} + +/////////////////////////////////////////////////// +// Implementing tDrawable +void TrafficBodyDrawable::Display() +{ + BEGIN_PROFILE("TrafficBodyDrawable::Display") + rAssert( mBodyPropDrawable != NULL ); + if( mBodyPropDrawable != NULL ) + { + if( mBodyShader != NULL ) + { + // display with desired colour first, then we'll go over it with a gloss + // put the old settings back + if(!mFading) + { + mBodyShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_NONE ); + } + else + { + mBodyShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ALPHA ); + } + mBodyShader->SetColour( PDDI_SP_DIFFUSE, mDesiredColour ); + mBodyShader->SetInt( PDDI_SP_EMISSIVEALPHA, mFadeAlpha ); + mBodyPropDrawable->Display(); + + pddiColour white( 255,255,255,255 ); + mBodyShader->SetColour( PDDI_SP_DIFFUSE, white ); + mBodyShader->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ALPHA ); + mBodyShader->SetInt( PDDI_SP_EMISSIVEALPHA, mFadeAlpha ); + mBodyShader->SetInt( PDDI_SP_ALPHATEST, 1 ); + mBodyShader->SetFloat( PDDI_SP_ALPHACOMPARE_THRESHOLD, (250.0f * (float(mFadeAlpha) / 255.0f)) / 255.0f ); + mBodyPropDrawable->Display(); + + + mBodyShader->SetInt( PDDI_SP_ALPHATEST, 0 ); + } + else + { + mBodyPropDrawable->Display(); + } + } + END_PROFILE("TrafficBodyDrawable::Display") +} + +void TrafficBodyDrawable::ProcessShaders(ShaderCallback& callback) +{ + rAssert( mBodyPropDrawable != NULL ); + mBodyPropDrawable->ProcessShaders(callback); +} diff --git a/game/code/worldsim/redbrick/trafficbodydrawable.h b/game/code/worldsim/redbrick/trafficbodydrawable.h new file mode 100644 index 0000000..4ee7594 --- /dev/null +++ b/game/code/worldsim/redbrick/trafficbodydrawable.h @@ -0,0 +1,39 @@ +#ifndef TRAFFICBODYDRAWABLE_H +#define TRAFFICBODYDRAWABLE_H + +#include <p3d/drawable.hpp> + +class tShader; + +class TrafficBodyDrawable : + public tDrawable +{ +public: + TrafficBodyDrawable(); + ~TrafficBodyDrawable(); + + void SetBodyPropDrawable( tDrawable* drawable ); + void SetBodyShader( tShader* shader ); + void SetDesiredColour( pddiColour colour ) + { + mDesiredColour = colour; + } + pddiColour GetDesiredColour() const { return mDesiredColour; } + + int mFadeAlpha; + bool mFading; + +public: + /////////////////////////////////////////////////// + // Implementing tDrawable + virtual void Display(); + virtual void ProcessShaders(ShaderCallback&); + +private: + tDrawable* mBodyPropDrawable; + tShader* mBodyShader; + pddiColour mDesiredColour; + +}; + +#endif
\ No newline at end of file diff --git a/game/code/worldsim/redbrick/trafficlocomotion.cpp b/game/code/worldsim/redbrick/trafficlocomotion.cpp new file mode 100644 index 0000000..20bd77c --- /dev/null +++ b/game/code/worldsim/redbrick/trafficlocomotion.cpp @@ -0,0 +1,1421 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: trafficlocomotion.cpp +// +// Description: Blahblahblah +// +// History: 09/09/2002 + Locomote through intersections -- Dusit Eakkachaichanvet +// 04/24/2002 + Created -- Greg Mayer +// +//============================================================================= +#include <poser/poseengine.hpp> +#include <raddebugwatch.hpp> +#include <radtime.hpp> +#include <simcommon/simstatearticulated.hpp> +#include <worldsim/redbrick/trafficlocomotion.h> +#include <worldsim/redbrick/vehicle.h> + +#include <debug/profiler.h> +#include <worldsim/worldphysicsmanager.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/geometry.h> + +const float TrafficLocomotion::SECONDS_BETW_HISTORY_UPDATES = 0.005f; +static const float SECONDS_BETW_CHECKS_FOR_FREE_LANE = 5.0f; +static const float TRAFFIC_WHEEL_GAP_HACK = 0.1f; + +#define FACING_HISTORY +// Need to use pos history to smooth out motion along curve +// Fortunately, this also causes the actual (apparent) position +// to be located further back from the calculated position, so +// turning looks better. +// Unfortunately, averaging means we can't stop on a dime, +// like we want to ... +#define POS_HISTORY + +//------------------------------------------------------------------------ +TrafficLocomotion::TrafficLocomotion(Vehicle* vehicle) : + VehicleLocomotion(vehicle), + mMyAI( NULL ) +{ + + mMyAI = new TrafficAI( vehicle ); + mMyAI->AddRef(); + +#ifdef DEBUGWATCH + if( vehicle && vehicle->mVehicleType == VT_TRAFFIC ) + { + mMyAI->RegisterAI(); + } +#endif + + + mVehicle = vehicle; + mIsInIntersection = false; + mCurrWay = -1; + mCurrPathLength = 0.0f; + mCurrPathLocation = 0.0f; + mActualSpeed = 0.0f; + mWays = NULL; + mNumWays = 0; + mSecondsTillCheckForFreeLane = SECONDS_BETW_CHECKS_FOR_FREE_LANE; + + //////////////////////////////////////////////// + // HISTORY stuff + //////////////////////////////////////////////// +#ifdef FACING_HISTORY + rmt::Vector heading( 0.0f, 0.0f, 0.0f ); + /* + if( mVehicle != NULL ) + { + mVehicle->GetHeading( &heading ); + } + */ + mFacingHistory.Init( heading ); +#endif + +#ifdef POS_HISTORY + rmt::Vector pos( 0.0f, 0.0f, 0.0f ); + mPosHistory.Init( pos ); +#endif + //////////////////////////////////////////////// + + mLaneChangeProgress = 0.0f; + mLaneChangeDist = 0.0f; + mLaneChangingFrom = 0; + mOutLaneT = 0.0f; + + mSecondsSinceLastAddToHistory = 0.0f; +} + + +//------------------------------------------------------------------------ +TrafficLocomotion::~TrafficLocomotion() +{ + mMyAI->ReleaseVerified(); +} + +void TrafficLocomotion::InitPos( const rmt::Vector& pos ) +{ +#ifdef POS_HISTORY + mPosHistory.Init( pos ); +#endif + + mSecondsSinceLastAddToHistory = 0.0f; + + ///////////////////////////////////////////// + // Adjust ground height (unfortunately we need to do this + // because we use pos averaging (so our y value is off) + + mPrevPos = pos; + + rmt::Vector groundPosition, outnorm; + bool bFoundPlane = false; + + groundPosition = pos; + outnorm.Set( 0.0f, 1.0f, 0.0f ); + + GetIntersectManager()->FindIntersection( + groundPosition, // IN + bFoundPlane, // OUT + outnorm, // OUT + groundPosition // OUT + ); + + if( bFoundPlane ) + { + mPrevPos.y = groundPosition.y; + } + /////////////////////////////////////////////// + +} + + +void TrafficLocomotion::InitFacing( const rmt::Vector& facing ) +{ + rAssert( rmt::Epsilon( facing.MagnitudeSqr(), 1.0f, 0.005f ) ); +#ifdef FACING_HISTORY + mFacingHistory.Init( facing ); +#endif + mSecondsSinceLastAddToHistory = 0.0f; + +} +//============================================================================= +// TrafficLocomotion::Init +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void TrafficLocomotion::Init() +{ + mMyAI->Init(); +} + +//============================================================================= +// TrafficLocomotion::Init +//============================================================================= +// Description: Comment +// +// Parameters: ( TrafficAI::Behaviour behaviour, +// unsigned int behaviourModifiers, +// Vehicle* vehicle, +// Lane* lane, +// unsigned int laneIndex, +// RoadSegment* segment, +// unsigned int segmentIndex, +// float t, +// float kmh ) +// +// Return: void +// +//============================================================================= +void TrafficLocomotion::Init( Vehicle* vehicle, + Lane* lane, + unsigned int laneIndex, + RoadSegment* segment, + unsigned int segmentIndex, + float t, + float mps ) +{ + mMyAI->Init( mVehicle, lane, laneIndex, segment, segmentIndex, t, mps ); +} + +//============================================================================= +// TrafficLocomotion::InitVehicleAI +//============================================================================= +// Description: Comment +// +// Parameters: ( Behaviour behaviour, unsigned int behaviourModifiers, Vehicle* vehicle ) +// +// Return: void +// +//============================================================================= +void TrafficLocomotion::InitVehicleAI( Vehicle* vehicle ) +{ + mMyAI->SetVehicle( vehicle ); +} + +//============================================================================= +// TrafficLocomotion::InitLane +//============================================================================= +// Description: Comment +// +// Parameters: ( Lane* lane, unsigned int laneIndex, float mps ) +// +// Return: void +// +//============================================================================= +void TrafficLocomotion::InitLane( Lane* lane, unsigned int laneIndex, float mps ) +{ + mMyAI->SetLane( lane ); + mMyAI->SetLaneIndex( laneIndex ); + mMyAI->SetAISpeed( mps ); +} + +//============================================================================= +// TrafficLocomotion::InitSegment +//============================================================================= +// Description: Comment +// +// Parameters: ( RoadSegment* segment, unsigned int segmentIndex, float t ) +// +// Return: void +// +//============================================================================= +void TrafficLocomotion::InitSegment( RoadSegment* segment, unsigned int segmentIndex, float t ) +{ + mMyAI->SetSegment( segment ); + mMyAI->SetSegmentIndex( segmentIndex ); + mMyAI->SetLanePosition( t ); + mMyAI->SetIsInIntersection( false ); + mIsInIntersection = false; +} + + +//============================================================================= +// TrafficLocomotion::UpdateVehicleGroundPlane +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void TrafficLocomotion::UpdateVehicleGroundPlane() +{ + rmt::Vector p, n; + p.Set(0.0f, 0.0f, 0.0f); + n.Set(0.0f, 1.0f, 0.0f); + + p = mVehicle->GetPosition(); + p.y -= 5.0f; // just to be safe + + + mVehicle->mGroundPlaneWallVolume->mPosition = p; + mVehicle->mGroundPlaneWallVolume->mNormal = n; + + + sim::CollisionObject* co = mVehicle->mGroundPlaneSimState->GetCollisionObject(); + co->PostManualUpdate(); + + +} + + + +//------------------------------------------------------------------------ +void TrafficLocomotion::PreCollisionPrep(bool firstSubstep) +{ + UpdateVehicleGroundPlane(); +} + +//============================================================================= +// TrafficLocomotion::PreSubstepUpdate +//============================================================================= +// Description: Comment +// +// Parameters: (mVehicle* mVehicle) +// +// Return: void +// +//============================================================================= +void TrafficLocomotion::PreSubstepUpdate() +{ + if( !mMyAI->mIsActive ) + { + return; + } + + // perform whatever update here +} + + +//============================================================================= +// TrafficLocomotion::PreUpdate +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void TrafficLocomotion::PreUpdate() +{ + poser::Pose* pose = mVehicle->mPoseEngine->GetPose(); + + mVehicle->mPoseEngine->Begin( false ); + //mVehicle->mPoseEngine->Begin(true); + + + int i; + for(i = 0; i < 4; i++) + { + Wheel* wheel = mVehicle->mWheels[i]; + + poser::Joint* joint = pose->GetJoint(mVehicle->mWheelToJointIndexMapping[i]); + rmt::Vector trans = joint->GetObjectTranslation(); + + //trans.y -= mVehicle->mWheels[i]->mLimit; + // TODO - verify that the -= is the thing to do here + //trans.y -= wheel->mLimit; + trans.y += TRAFFIC_WHEEL_GAP_HACK; + + joint->SetObjectTranslation(trans); + + } + + +} + + +//============================================================================= +// TrafficLocomotion::UpdateAI +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void TrafficLocomotion::UpdateAI(unsigned int ms) +{ + // TrafficAI::Update takes timestep in seconds + mMyAI->Update(float(ms)/1000.0f); +} + + + +void TrafficLocomotion::StopSuddenly( rmt::Vector& pos, rmt::Vector& facing ) +{ +#ifdef POS_HISTORY + // re-init history with pos + mPosHistory.GetAverage( pos ); + mPosHistory.Init( pos ); +#else + pos = mPrevPos; +#endif + +#ifdef FACING_HISTORY + mFacingHistory.GetNormalizedAverage( facing ); + mFacingHistory.Init( facing ); +#endif + + mSecondsSinceLastAddToHistory = 0.0f; + + // TODO: + // When we stop suddenly, our t value is slightly ahead of + // us (because we've clobbered our pos history with the + // vehicle's current position). When we accelerate again, + // we'll dump the t position into the history, causing + // the vehicle to lurch forward inertialessly, throwing off + // the average. + // + // So we reset our t value at this point to where we are. + // This is rather tricky to do: we could be anywhere... + // - If we're on a road segment, that's perfect. + // - If we're not on a road segment, but we're in + // an intersection, there's no way to guarantee that + // our t value is still on the spline (we could already + // be in the OUT lane). We need to create a new spline + // that still takes us from one road to another, but + // we didn't store all this info!! Arrgh... + // + // A BETTER design (from the ground up), would have + // just stored splines from the beginning (even if you're + // traversing roads), making sure that this spline is + // long enough to contain our vehicle's actual position. + // As our t value crawls along a roadsegment, we add it to + // our spline. This way when we need to recompute t, we + // can just search on our spline for the closest segment + // to our current position. The other plus side to this + // is that we don't traverse segments differently from + // intersections. Everything gets stored in our spline. + // + +} + + + +//============================================================================= +// TrafficLocomotion::Update +//============================================================================= +// Description: Comment +// +// Parameters: (float seconds) +// +// Return: void +// +//============================================================================= +void TrafficLocomotion::Update(float seconds) +{ + if( !mMyAI->mIsActive ) + { + return; + } + + mSecondsSinceLastAddToHistory += seconds; + +BEGIN_PROFILE( "Traffic Locomotion" ); + + //mVehicle->mPoseEngine->Begin( false ); + + rmt::Vector pos, facing; + + TrafficAI::State state = mMyAI->GetState(); + + switch ( state ) + { + case TrafficAI::DEAD: + { + pos = mPrevPos; + #ifdef FACING_HISTORY + mFacingHistory.GetNormalizedAverage( facing ); + #endif + } + break; + case TrafficAI::DRIVING: // fall through + case TrafficAI::WAITING_AT_INTERSECTION: + { + if( mMyAI->mNeedToSuddenlyStop ) + { + StopSuddenly( pos, facing ); + break; + } + + bool stayDoggyStay = false; + + //================================================ + // DETERMINE t AND CURRENT WAYPOINT OR ROAD SEGMENT + // (whichever's applicable) + //================================================ + float t = 0.0f; + float pathLength = 0.0f; + + if( !mIsInIntersection ) + { + pathLength = mMyAI->GetLaneLength(); + t = mMyAI->GetLanePosition(); + } + else + { + pathLength = mCurrPathLength; + t = mCurrPathLocation; + } + + float adjustedDt = (GetAISpeed() * seconds) / pathLength; + t += adjustedDt; + + while( t > 1.0f ) + { + t -= 1.0f; + + if( !mIsInIntersection ) // we're not inside an intersection + { + const Road* road = mMyAI->GetLane()->GetRoad(); + unsigned int segmentIndex = mMyAI->GetSegmentIndex(); + + // If there are more segments on this road + if ( segmentIndex < (road->GetNumRoadSegments()-1) ) + { + // we have to move ahead a segment + segmentIndex++; + mMyAI->SetSegmentIndex( segmentIndex ); + float newLength = mMyAI->GetLaneLength(); + t *= pathLength / newLength; + mMyAI->SetLanePosition(t); + pathLength = newLength; + + // ======================== + // We just updated mMyAI: + // - segment & segment index updated to look at next segment + // - lane & lane index not updated + // - lane position updated with the new segment's t + // ======================== + } + // Else we must be at an intersection + else + { + // set up cubic spline + bool succeeded = EnterIntersection(); + // ======================== + // We just updated mMyAI: + // - segment & segment index updated to look at the OUT segment + // - lane & lane index updated to look at the OUT lane + // - lane position not updated + // ======================== + + // Set info of first path on spline + if( succeeded ) + { + rAssert( mWays != NULL ); + + mMyAI->SetIsInIntersection( true ); + mIsInIntersection = true; + + mCurrWay = 0; + mCurrPath.Sub( mWays[mCurrWay+1], mWays[mCurrWay] ); + mCurrPathLength = mCurrPath.Length(); // *** SQUARE ROOT! *** + t *= pathLength / mCurrPathLength; + pathLength = mCurrPathLength; + } + else + { + stayDoggyStay = true; + break; + } + + } + } + else // we ARE inside an intersection + { + // Since we never spawn in an intersection, we never + // get into this case unless we entered via EnterIntersection(), + // and t is > 1.0f... + // Meaning that at this point, mWays has been populated and + // mCurrWay, mCurrPath, and mCurrPathLength are set. + + rAssert( mCurrWay < mNumWays ); + rAssert( mCurrWay >= 0 ); + + // if we still got waypoints to traverse in intersection + if( mCurrWay < (mNumWays - 2) ) + { + mCurrWay++; + + rAssert( mWays != NULL ); + + mCurrPath.Sub( mWays[mCurrWay+1], mWays[mCurrWay] ); + float newLength = mCurrPath.Length(); // *** SQUARE ROOT! *** + t *= pathLength / newLength; + pathLength = newLength; + mCurrPathLength = newLength; + } + else // Else going out of the intersection now + { + mIsInIntersection = false; + mMyAI->SetIsInIntersection( false ); + + // mMyAI information has already been set to look + // at the out lane/segment/etc. + // Must move within the lane... + float newLength = mMyAI->GetLaneLength(); + t *= pathLength / newLength; + pathLength = newLength; + } + } + } + + // if t is not a valid number, it means that we encountered + // an error inside EnterIntersection() + if( stayDoggyStay ) + { + pos = mPrevPos; + #ifdef FACING_HISTORY + mFacingHistory.GetNormalizedAverage( facing ); + #endif + break; // exit this case + } + + + //================================================ + // KNOWING t, DETERMINE pos AND facing + //================================================ + rAssert( 0 <= t && t <= 1.0f ); + + // Depending on whether we're inside an intersection, + // we have different methods for determining pos & facing + if( !mIsInIntersection ) + { + mMyAI->SetLanePosition( t ); + RoadSegment* segment = mMyAI->GetSegment(); + rAssert( segment ); + + segment->GetLaneLocation( mMyAI->GetLanePosition(), mMyAI->GetLaneIndex(), pos, facing ); + if( !rmt::Epsilon( facing.MagnitudeSqr(), 1.0f, 0.005f ) ) + { + facing.NormalizeSafe(); // *** SQUARE ROOT! *** + } + rAssert( rmt::Epsilon( facing.MagnitudeSqr(), 1.0f, 0.005f ) ); + } + else + { + mCurrPathLocation = t; + rmt::Vector temp = facing = mCurrPath; + facing.Scale( 1.0f / mCurrPathLength ); + rAssert( rmt::Epsilon( facing.MagnitudeSqr(), 1.0f, 0.005f ) ); + temp.Scale(t); + + rAssert( mWays != NULL ); + pos.Add( mWays[mCurrWay], temp ); + } + } + break; + + case TrafficAI::WAITING_FOR_FREE_LANE: + { + mSecondsTillCheckForFreeLane -= seconds; + if( mSecondsTillCheckForFreeLane < seconds ) + { + mSecondsTillCheckForFreeLane = SECONDS_BETW_CHECKS_FOR_FREE_LANE; + + // Transit back to driving next frame, so we check again for + // lane availability + mMyAI->SetState( TrafficAI::DRIVING ); + } + + // keep old heading & facing for now (complete and instantaneous stop) + pos = mPrevPos; + #ifdef FACING_HISTORY + mFacingHistory.GetNormalizedAverage( facing ); + #endif + } + break; + + case TrafficAI::SPLINING: // fall thru + case TrafficAI::LANE_CHANGING: + { + rAssert( !mIsInIntersection ); + + // In TrafficAI, if we're SPLINING, we can be told to stop for something. + // (when LANE_CHANGING we don't stop for anything... why? Not sure...) + // What happens if we need to suddenly stop here? + if( mMyAI->mNeedToSuddenlyStop ) + { + StopSuddenly( pos, facing ); + break; + } + + + //================================================ + // DETERMINE t AND CURRENT WAYPOINT OR ROAD SEGMENT + // (whichever's applicable) + //================================================ + float pathLength = mCurrPathLength; + float t = mCurrPathLocation; + + float adjustedDt = (GetAISpeed() * seconds) / pathLength; + t += adjustedDt; + + while( t > 1.0f ) + { + t -= 1.0f; + + // In case we got fed a long "seconds" value and go WAY over + // the lane change spline. Then we're not lane changing + // anymore (we're on normal road now) + if( mMyAI->GetState() != state ) + { + const Road* road = mMyAI->GetLane()->GetRoad(); + unsigned int segmentIndex = mMyAI->GetSegmentIndex(); + + if( segmentIndex < (road->GetNumRoadSegments()-1) ) + { + // we have to move ahead a segment + segmentIndex++; + mMyAI->SetSegmentIndex( segmentIndex ); + float newLength = mMyAI->GetLaneLength(); + t *= pathLength / newLength; + mMyAI->SetLanePosition(t); + pathLength = newLength; + + // ======================== + // We just updated mMyAI: + // - segment & segment index updated to look at next segment + // - lane & lane index not updated + // - lane position updated with the new segment's t + // ======================== + } + else // if we're out of segments.. we are at an intersection... + { + // TODO: + // we should include intersection code here and deal with + // the t-overflow properly... but I'm thinking that's a lot + // of code repeat... so I'll get around to it later... + t = 0.999f; + mMyAI->SetLanePosition(t); + } + } + else // else we are in middle of lane changing... + { + // At this point, mWays has been populated and + // mCurrWay, mCurrPath, and mCurrPathLength are set. + + rAssert( mCurrWay < mNumWays ); + rAssert( mCurrWay >= 0 ); + + // if we still got waypoints to traverse... + if( mCurrWay < (mNumWays - 2) ) + { + mCurrWay++; + + rAssert( mWays != NULL ); + + mCurrPath.Sub( mWays[mCurrWay+1], mWays[mCurrWay] ); + float newLength = mCurrPath.Length(); // *** SQUARE ROOT! *** + t *= pathLength / newLength; + pathLength = newLength; + mCurrPathLength = newLength; + } + else // Else done lane changing now... + { + mMyAI->SetState( TrafficAI::DRIVING ); + + // mMyAI information has already been set to look + // at the target lane/segment/etc. + // Must move within the lane... + float newLength = mMyAI->GetLaneLength(); + t *= pathLength / newLength; + t += mOutLaneT; + pathLength = newLength; + } + } + } + + //================================================ + // KNOWING t, DETERMINE pos AND facing + //================================================ + rAssert( 0 <= t && t <= 1.0f ); + + // Depending on whether we're still lane changing... + // we have different methods for determining pos & facing + if( mMyAI->GetState() != state ) + { + mMyAI->SetLanePosition( t ); + RoadSegment* segment = mMyAI->GetSegment(); + rAssert( segment ); + + segment->GetLaneLocation( mMyAI->GetLanePosition(), mMyAI->GetLaneIndex(), pos, facing ); + if( !rmt::Epsilon( facing.MagnitudeSqr(), 1.0f, 0.005f ) ) + { + facing.NormalizeSafe(); // *** SQUARE ROOT! *** + } + rAssert( rmt::Epsilon( facing.MagnitudeSqr(), 1.0f, 0.005f ) ); + } + else + { + mCurrPathLocation = t; + rmt::Vector temp = facing = mCurrPath; + facing.Scale( 1.0f / mCurrPathLength ); + rAssert( rmt::Epsilon( facing.MagnitudeSqr(), 1.0f, 0.005f ) ); + temp.Scale(t); + + rAssert( mWays != NULL ); + pos.Add( mWays[mCurrWay], temp ); + } + } + break; + + case TrafficAI::SWERVING: + { + // we should be updating TrafficLocomotion if we're swerving because + // we should be in PhysicsLocomotion + rAssert( false ); + } + default: + { + rAssert( false ); + } + break; + } + + + + //================================================ + // KNOWING pos AND facing, UPDATE VEHICLE + //================================================ + + // only need mPrevPos for the transition into Intersection + // since we can't rely on Vehicle->GetPosition to return us + // the RIGHT-ON-THE-GROUND values (Vehicle class does its + // own adjustments to bring the car up from ground level) + rAssert( rmt::Epsilon( facing.MagnitudeSqr(), 1.0f, 0.005f ) ); + +#if defined(FACING_HISTORY) || defined(POS_HISTORY) + + // we need to add this many entries + int maxCount = (int)(mSecondsSinceLastAddToHistory / SECONDS_BETW_HISTORY_UPDATES); + + if( maxCount > 0 ) + { + float secondsConsumed = (float)(maxCount)*SECONDS_BETW_HISTORY_UPDATES; + float tmpT = (secondsConsumed/mSecondsSinceLastAddToHistory); + + // figure out the starting and ending elements to interpolate between + #ifdef POS_HISTORY + rmt::Vector posStart = mPosHistory.GetLastEntry(); + rmt::Vector posDir = pos - posStart; + rmt::Vector posEnd = posStart + posDir * tmpT; + posDir = posEnd - posStart; + #endif + #ifdef FACING_HISTORY + rmt::Vector faceStart = mFacingHistory.GetLastEntry(); + rmt::Vector faceDir = facing - faceStart; + rmt::Vector faceEnd = faceStart + faceDir * tmpT; + faceDir = faceEnd - faceStart; + #endif + + + float tIncrement = 1.0f / (float)(maxCount); + tmpT = 0.0f; + for( int count = 1; count <= maxCount; count++ ) + { + tmpT += tIncrement; + #ifdef POS_HISTORY + mPosHistory.UpdateHistory( posStart + posDir * tmpT ); + #endif + #ifdef FACING_HISTORY + mFacingHistory.UpdateHistory( faceStart + faceDir * tmpT ); + #endif + + } + mSecondsSinceLastAddToHistory -= secondsConsumed; + + } + +#endif + + // Histories are updated, now we get the average out of them. + +#ifdef FACING_HISTORY + mFacingHistory.GetNormalizedAverage( facing ); +#endif +#ifdef POS_HISTORY + mPosHistory.GetAverage( pos ); +#endif + + ///////////////////////////////////////////// + // 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 = pos; + outnorm.Set( 0.0f, 1.0f, 0.0f ); + + GetIntersectManager()->FindIntersection( + groundPosition, // IN + bFoundPlane, // OUT + outnorm, // OUT + groundPosition // OUT + ); + + if( bFoundPlane ) + { + pos.y = groundPosition.y; + } + /////////////////////////////////////////////// + + // compute actual speed + rmt::Vector oldPos; + oldPos = mPrevPos; + mActualSpeed = (pos - oldPos).Magnitude() / seconds; + + // Update vehicle transform... + rmt::Matrix newTransform; + newTransform.Identity(); + + rmt::Vector target; + target = pos; + target.Add( facing ); + rmt::Vector up = UpdateVUP( pos, target ); + newTransform.FillTranslate(pos); + newTransform.FillHeading(facing, up); + mVehicle->TrafficSetTransform(newTransform); + + // Pivot the front wheels... + PivotFrontWheels( facing ); + + mPrevPos = pos; + +END_PROFILE( "Traffic Locomotion" ); +} + +//============================================================================= +// TrafficLocomotion::PostUpdate +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void TrafficLocomotion::PostUpdate() +{ + if( !mMyAI->mIsActive ) + { + return; + } + // make sure values are set for wheel rendering info... + + // set 'artificial suspension point velocities + int i; + for(i = 0; i < 4; i++) + { + rmt::Vector velocity = mVehicle->mVehicleFacing; + velocity.Scale( mActualSpeed ); + mVehicle->mSuspensionPointVelocities[i] = velocity; + } + mVehicle->mVelocityCM = mVehicle->mVehicleFacing; + mVehicle->mVelocityCM.Scale( mActualSpeed ); + + // update mSimStateArticulated speed here? + // hmmm... + rmt::Vector& linearVelocity = mVehicle->mSimStateArticulated->GetLinearVelocity(); + linearVelocity = mVehicle->mVelocityCM; + + mVehicle->mSpeed = mActualSpeed; + + +} + +////////////////////////////////////////////////////////////////////// +//////////////////////////// PRIVATES //////////////////////////////// + +void TrafficLocomotion::FindOutLane ( + const Lane* inLane, + unsigned int inLaneIndex, + Lane*& outLane, + unsigned int& outLaneIndex ) +{ + rAssert( inLane != NULL ); + + const Road* inRoad = inLane->GetRoad(); + rAssert( inRoad != NULL ); + + Intersection* intersection = + (Intersection*) inRoad->GetDestinationIntersection(); + rAssert( intersection ); + + Road* outRoad = NULL; + outLane = NULL; + outLaneIndex = 0; + + switch( mMyAI->DecideTurn() ) + { + case TrafficAI::LEFT: + { + intersection->GetLeftTurnForTraffic( + *inRoad, inLaneIndex, outRoad, outLane, outLaneIndex ); + } + break; + case TrafficAI::RIGHT: + { + intersection->GetRightTurnForTraffic( + *inRoad, inLaneIndex, outRoad, outLane, outLaneIndex ); + } + break; + case TrafficAI::STRAIGHT: + { + intersection->GetStraightForTraffic( + *inRoad, inLaneIndex, outRoad, outLane, outLaneIndex ); + } + break; + default: + { + rAssert( false ); + } + } +} + + +void TrafficLocomotion::BuildCurve ( + RoadSegment* inSegment, + unsigned int inLaneIndex, + RoadSegment* outSegment, + unsigned int outLaneIndex ) +{ + rAssert( inSegment != NULL ); + rAssert( outSegment != NULL ); + + // get the start pos & dir + rmt::Vector startPos, startDir; + inSegment->GetLaneLocation( 1.0f, inLaneIndex, startPos, startDir ); + //startDir.y = 0.0f; + if( !rmt::Epsilon( startDir.MagnitudeSqr(), 1.0f, 0.001f ) ) + { + startDir.Normalize(); // *** SQUARE ROOT! *** + } + + // get end pos & dir + rmt::Vector endPos, endDir; + outSegment->GetLaneLocation( 0.0f, outLaneIndex, endPos, endDir ); + //endDir.y = 0.0f; + if( !rmt::Epsilon( endDir.MagnitudeSqr(), 1.0f, 0.001f ) ) + { + endDir.Normalize(); // *** SQUARE ROOT! *** + } + + // get intermediate points + rmt::Vector startEndTemp, p2, p4; + startEndTemp.Sub( endPos, startPos ); + float distFromStartOrEnd = startEndTemp.Length() / 3.0f; // *** SQUARE ROOT! *** + p2 = startPos + startDir * distFromStartOrEnd; + p4 = endPos - endDir * distFromStartOrEnd; + + // Now we are ready to gather together our points and send it + // to our "curve" finder. + rmt::Vector pts [4]; + pts[0] = startPos; + pts[1] = p2; + pts[2] = p4; + pts[3] = endPos; + + mSplineMaker.SetControlPoint( pts[0], 0 ); + mSplineMaker.SetControlPoint( pts[1], 1 ); + mSplineMaker.SetControlPoint( pts[2], 2 ); + mSplineMaker.SetControlPoint( pts[3], 3 ); + +/* +#if defined(RAD_TUNE) || defined(RAD_DEBUG) + + const Road* inRoad = inSegment->GetRoad(); + rAssert( inRoad ); + + Intersection* intersection = + (Intersection*) inRoad->GetDestinationIntersection(); + rAssert( intersection ); + + // NOTE: + // If you hate this assert, you can comment it out locally... FOR NOW... + // Put it back in once all y-value mismatch errors are gone. + // In other words, once hell freezes over. + rmt::Vector intersectionLoc; + intersection->GetLocation( intersectionLoc ); + char baseMsg[1000]; + sprintf( baseMsg, + "\nMismatching y-values at intersection (%f,%f,%f).\n" + " Check if y values are same for all IN & OUT road segments attached\n" + " to this intersection. Check hypergraph to see if the roadnodes leading\n" + " IN and OUT of this intersection contain all the proper roadsegments.\n" + " If you skip this, Traffic cars will \"hop\" when they transit through\n" + " the intersection between the road segments with mismatching y values.\n" + " Better to report error to me (Dusit) or Sheik. Preferrably Sheik.\n\n", + intersectionLoc.x, + intersectionLoc.y, + -1*intersectionLoc.z ); + rAssert( strlen(baseMsg) < 1000 ); + + float ep = 0.001f; + if( !( rmt::Epsilon( pts[0].y, pts[1].y, ep ) && + rmt::Epsilon( pts[0].y, pts[2].y, ep ) && + rmt::Epsilon( pts[0].y, pts[3].y, ep ) ) ) + { + rTunePrintf( baseMsg ); + } +#endif +*/ + + mSplineMaker.GetCubicBezierCurve( mWays, mNumWays ); + rAssert( mWays != NULL ); + rAssert( mNumWays == CubicBezier::MAX_CURVE_POINTS ); +} + + + +bool TrafficLocomotion::EnterIntersection() +{ + const Lane* inLane = mMyAI->GetLane(); + rAssert( inLane != NULL ); + RoadSegment* inSegment = mMyAI->GetSegment(); + rAssert( inSegment != NULL ); + const unsigned int inLaneIndex = mMyAI->GetLaneIndex(); + + // ============================================== + // Find OUT LANE and OUT LANE INDEX + // ============================================== + + Lane* outLane = NULL; + unsigned int outLaneIndex = 0; + + FindOutLane( inLane, inLaneIndex, outLane, outLaneIndex ); + + // if absolutely can't find a road, transit to waiting state + if( outLane == NULL ) + { + // TODO: Should we slow down? + // Right now we have complete and instantaneous stop, + // which is needed because we are transiting to a diff state + // and must maintain old position & facing for when we transit + // back... + mMyAI->SetState( TrafficAI::WAITING_FOR_FREE_LANE ); + + return false; + } + + rAssert( outLane != NULL ); + + + // ================================================ + // Update IN & OUT lanes' list of traffic vehicles + // ================================================ + UpdateLanes( mVehicle->mTrafficVehicle, (Lane*)inLane, outLane ); + + + // ============================================== + // Build Cubic Spline to navigate intersection + // ============================================== + const Road* outRoad = outLane->GetRoad(); + rAssert( outRoad != NULL ); + unsigned int outSegmentIndex = 0; + RoadSegment* outSegment = outRoad->GetRoadSegment(outSegmentIndex); + rAssert( outSegment != NULL ); + + // create control points, then the spline... store all in mWays + BuildCurve( inSegment, inLaneIndex, outSegment, outLaneIndex ); + + + // ================================================== + // Update mMyAI: + // - to look at the OUT segment & segment index + // - to look at the OUT lane & lane index + // - lane position not updated + // =================================================== + mMyAI->SetLane( outLane ); + mMyAI->SetLaneIndex( outLaneIndex ); + mMyAI->SetSegment( outSegment ); + mMyAI->SetSegmentIndex( outSegmentIndex ); + + return true; +} + +void TrafficLocomotion::PivotFrontWheels( rmt::Vector facing ) +{ + float cosAlpha = 0.0f; + rmt::Vector outPos, outFacing; + if( mIsInIntersection ) + { + // when we're in the intersection, the lane is the OUTLANE, so get the t=0.0f + mMyAI->GetSegment()->GetLaneLocation( 0.0f, (int)mMyAI->GetLaneIndex(), outPos, outFacing ); + outFacing.y = 0.0f; + outFacing.Normalize(); // *** SQUARE ROOT! *** + facing.y = 0.0f; + facing.Normalize(); // *** SQUARE ROOT! *** + cosAlpha = facing.Dot( outFacing ); + } + else + { + // fake wheel turning for lane change... + if( mMyAI->GetState() == TrafficAI::LANE_CHANGING ) + { + float progress = mLaneChangeProgress / mLaneChangeDist; + if( progress < 0.65f ) + { + cosAlpha = 0.9396926f; // cos20 + } + else + { + cosAlpha = -0.9396926f; // cos20 + } + } + else + { + // when we're not in the intersection, the lane is the + // current lane, so get t=1.0f + mMyAI->GetSegment()->GetLaneLocation( 1.0f, + (int)mMyAI->GetLaneIndex(), outPos, outFacing ); + outFacing.y = 0.0f; + outFacing.Normalize(); // *** SQUARE ROOT! *** + facing.y = 0.0f; + facing.Normalize(); // *** SQUARE ROOT! *** + cosAlpha = facing.Dot( outFacing ); + + } + } + + // ensure we are in bounds + if( cosAlpha < -1.0f ) + { + cosAlpha = -1.0f; + } + else if( cosAlpha > 1.0f ) + { + cosAlpha = 1.0f; + } + float alpha = rmt::ACos( cosAlpha ); // *** COSINE! *** + rAssert( !rmt::IsNan( alpha ) ); + + // pivot left or right + if( mMyAI->GetState() != TrafficAI::LANE_CHANGING ) + { + rmt::Vector rightOfFacing = Get90DegreeRightTurn( facing ); + if( rightOfFacing.Dot( outFacing ) < 0.0f ) + { + alpha *= -1.0f; + } + } + // Who am I? I'm Spiderman! No, I'm Dusit. + mVehicle->SetWheelTurnAngleDirectlyInRadiansForDusitOnly( alpha ); + +} + + + +void TrafficLocomotion::UpdateLanes( TrafficVehicle* tv, Lane* oldLane, Lane* newLane ) +{ + rAssert( tv != NULL ); + rAssert( tv->GetIsActive() ); + rAssert( oldLane != NULL ); + rAssert( newLane != NULL ); + + // Remove vehicle from IN lane + bool found = false; + for( int i=0; i<oldLane->mTrafficVehicles.mUseSize; i++ ) + { + if( tv == oldLane->mTrafficVehicles[i] ) + { + oldLane->mTrafficVehicles.Remove( i ); + found = true; + break; + } + } + //rAssert( found ); + + // Add vehicle to OUT lane. + // What happens if AddLast returns -1 because you can't add + // another vehicle to that lane? It WON'T! Because in our + // Intersection::Get<blah>ForTraffic, we make sure we either + // return a lane that can take this car or NULL. If NULL, then + // we shouldn't be at this point in the code. + // + rAssert( newLane->mTrafficVehicles.mUseSize < newLane->mTrafficVehicles.mSize ); + newLane->mTrafficVehicles.Add( tv ); + tv->SetLane( newLane ); +} + +Lane* TrafficLocomotion::GetAIPrevLane() +{ + return mMyAI->mPrevLane; +} + +// will return if there's not enough room to lanechange +bool TrafficLocomotion::BuildLaneChangeCurve( + RoadSegment* oldSegment, + const float oldT, + unsigned int oldLaneIndex, + unsigned int newLaneIndex, + const float dist) +{ + rAssert( oldSegment != NULL ); + + // We have to create control points + rmt::Vector pts[4]; + + // First figure out entry point + rmt::Vector entryFacing; + oldSegment->GetLaneLocation( oldT, oldLaneIndex, pts[0], entryFacing ); + if( !rmt::Epsilon( entryFacing.MagnitudeSqr(), 1.0f, 0.0001f ) ) + { + entryFacing.NormalizeSafe(); // *** SQUARE ROOT! *** + } + rAssert( rmt::Epsilon( entryFacing.MagnitudeSqr(), 1.0f, 0.0001f ) ); + + // Second, figure out the next point in line with facing, + // some dist ahead (percentage of lanechange dist) + float entryOffset = 0.2f * dist; + pts[1] = pts[0] + entryFacing * entryOffset; + + // Third, figure out the exit point. + // we'll have to follow the road for the given lanechange distance + RoadSegment* endSegment = oldSegment; + + float oldLaneLength = oldSegment->GetLaneLength( oldLaneIndex ); + float t = oldT; + float adjustedDt = dist / oldLaneLength; + t += adjustedDt; + + unsigned int endSegIndex = endSegment->GetSegmentIndex(); + while( t > 1.0f ) + { + t -= 1.0f; + // move on to next segment (there must be one) + unsigned int numSegs = endSegment->GetRoad()->GetNumRoadSegments(); + endSegIndex++; + + // if we're out of bounds, it means that there weren't enough segments + // to complete lane-change. Abort! + if( endSegIndex < 0 || endSegIndex >= numSegs ) + { + return false; + } + + endSegment = endSegment->GetRoad()->GetRoadSegment( endSegIndex ); + float nextSegLen = endSegment->GetLaneLength( oldLaneIndex ); + + t *= oldLaneLength / nextSegLen; + + oldLaneLength = nextSegLen; + } + + rAssert( t <= 1.0f ); + + // since the last point of spline won't be the START of the + // segment when we come out of LANE_CHANGING state, we need to + // store away the t so we add it later... + mOutLaneT = t; + + rmt::Vector exitFacing; + endSegment->GetLaneLocation( t, newLaneIndex, pts[3], exitFacing ); + if( !rmt::Epsilon( exitFacing.MagnitudeSqr(), 1.0f, 0.0001f ) ) + { + exitFacing.NormalizeSafe(); // *** SQUARE ROOT! *** + } + rAssert( rmt::Epsilon( exitFacing.MagnitudeSqr(), 1.0f, 0.0001f ) ); + exitFacing.Scale( -1.0f ); + + // Lastly, figure out the previous point, in line with facing, + // some dist ahead (percentage of lanechange dist) + float exitOffset = dist * 0.5f;//0.3f; + pts[2] = pts[3] + exitFacing * exitOffset; + + // + // update AI's segment info: segment, segment index, lane length + // + mMyAI->SetSegmentIndex( endSegment->GetSegmentIndex() ); + + mSplineMaker.SetControlPoint( pts[0], 0 ); + mSplineMaker.SetControlPoint( pts[1], 1 ); + mSplineMaker.SetControlPoint( pts[2], 2 ); + mSplineMaker.SetControlPoint( pts[3], 3 ); + + // use mWays to store the spline + // (since we're not in an intersection anyway) + mSplineMaker.GetCubicBezierCurve( mWays, mNumWays ); + rAssert( mWays != NULL ); + rAssert( mNumWays == CubicBezier::MAX_CURVE_POINTS ); + + // update the currway stuff needed to work with mWays + mCurrWay = 0; + mCurrPath.Sub( mWays[mCurrWay+1], mWays[mCurrWay] ); + mCurrPathLength = mCurrPath.Length(); // *** SQUARE ROOT! *** + mCurrPathLocation = 0.0f; + + return true; +} + +bool TrafficLocomotion::BuildArbitraryCurve( + const rmt::Vector& startPos, + const rmt::Vector& startDir, // is normalized to 1 + const rmt::Vector& endPos, + const rmt::Vector& endDir )// is normalized to 1 +{ + rAssert( rmt::Epsilon( startDir.MagnitudeSqr(), 1.0f, 0.001f ) ); + rAssert( rmt::Epsilon( endDir.MagnitudeSqr(), 1.0f, 0.001f ) ); + + float distScale = (startPos - endPos).Length(); // *** SQUARE ROOT! *** + distScale *= 0.3f; + + rmt::Vector pts[4]; + pts[0] = startPos; + pts[1] = startPos + startDir * distScale; + pts[2] = endPos + endDir * -1 * distScale; + pts[3] = endPos; + + mSplineMaker.SetControlPoint( pts[0], 0 ); + mSplineMaker.SetControlPoint( pts[1], 1 ); + mSplineMaker.SetControlPoint( pts[2], 2 ); + mSplineMaker.SetControlPoint( pts[3], 3 ); + + // use mWays to store the spline + mSplineMaker.GetCubicBezierCurve( mWays, mNumWays ); + rAssert( mWays != NULL ); + rAssert( mNumWays == CubicBezier::MAX_CURVE_POINTS ); + + // update the currway stuff needed to work with mWays + mCurrWay = 0; + mCurrPath.Sub( mWays[mCurrWay+1], mWays[mCurrWay] ); + mCurrPathLength = mCurrPath.Length(); // *** SQUARE ROOT! *** + mCurrPathLocation = 0.0f; + + return true; +} + +void TrafficLocomotion::GetSplineCurve( rmt::Vector*& ways, int& npts, int& currWay ) +{ + if( mWays == NULL ) + { + ways = NULL; + npts = 0; + currWay = -1; + } + else + { + ways = mWays; + npts = mNumWays; + currWay = mCurrWay; + } +} diff --git a/game/code/worldsim/redbrick/trafficlocomotion.h b/game/code/worldsim/redbrick/trafficlocomotion.h new file mode 100644 index 0000000..350f5d0 --- /dev/null +++ b/game/code/worldsim/redbrick/trafficlocomotion.h @@ -0,0 +1,270 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: trafficlocomotion.h +// +// Description: Blahblahblah +// +// History: 09/09/2002 + Locomote through intersections -- Dusit Eakkachaichanvet +// 07/08/2002 + Created -- Cary Brisebois +// +//============================================================================= + +#ifndef TRAFFICLOCOMOTION_H +#define TRAFFICLOCOMOTION_H + +//======================================== +// Nested Includes +//======================================== +#include <worldsim/redbrick/vehiclelocomotion.h> +#include <radmath/radmath.hpp> +#include <ai/vehicle/trafficai.h> +#include <roads/geometry.h> + +//======================================== +// Forward References +//======================================== +class Lane; +class RoadSegment; + +// +// A lightweight statically allocated, singly-linked list +// + +const int MAX_ITEMS = 30; + +struct Item +{ + rmt::Vector p; + int next; +}; + +class StaticList +{ +public: + Item mItems[MAX_ITEMS]; + + void Clear() + { + int i=0; + for(i; i<MAX_ITEMS; i++) + { + mItems[i].next = -1; + } + mFirstFree = 0; + } + + StaticList() + { + Clear(); + } + + int GetNumItems() + { + return mFirstFree; + } + + Item* InsertItem( rmt::Vector p, Item* prevItem ) + { + if( mFirstFree >= MAX_ITEMS ) + { + return NULL; + } + int freeIndex = mFirstFree; + mItems[freeIndex].p = p; + mItems[freeIndex].next = (prevItem != NULL)? prevItem->next : -1; + + if( prevItem != NULL ) + { + prevItem->next = freeIndex; + } + else if( freeIndex > 0 ) + { + mItems[freeIndex-1].next = freeIndex; + } + mFirstFree++; + return &(mItems[freeIndex]); + } + +private: + + int mFirstFree; +}; + + + + + + + + +//============================================================================= +// +// Synopsis: Blahblahblah +// +//============================================================================= + +class TrafficLocomotion : public VehicleLocomotion +{ +public: + + enum { + ON_ROAD_HISTORY_SIZE = 66 + }; + + static const float SECONDS_BETW_HISTORY_UPDATES; + + TrafficLocomotion(Vehicle* vehicle); + virtual ~TrafficLocomotion(); + + void Init(); + void Init( Vehicle* vehicle, + Lane* lane, + unsigned int laneIndex, //0 is curbside + RoadSegment* segment, + unsigned int segmentIndex, + float t, + float kmh ); + + void InitPos( const rmt::Vector& pos ); + void InitFacing( const rmt::Vector& facing ); + void InitVehicleAI( Vehicle* vehicle ); + void InitLane( Lane* lane, unsigned int laneIndex, float mps ); + void InitSegment( RoadSegment* segment, unsigned int segmentIndex, float t ); + + //////////////////// VEHICLELOCOMOTION STUFF //////////////////// + virtual void PreSubstepUpdate(); + virtual void PreCollisionPrep(bool firstSubstep); + virtual void UpdateVehicleGroundPlane(); + virtual void PreUpdate(); + virtual void Update(float seconds); + virtual void PostUpdate(); + virtual void CompareNormalAndHeight(int index, rmt::Vector& normalPointingAtCar, rmt::Vector& groundContactPoint) {}; + /////////////////////////////////////////////////////////////////// + + TrafficAI* GetAI(); + Lane* GetAILane(); + Lane* GetAIPrevLane(); + void SetAISpeed( float mps ); + float GetAISpeed(); + bool IsInIntersection() const; + void UpdateAI(unsigned int ms); + void SetActive( bool isActive ); + bool GetActive() const; + void SetAIState( enum TrafficAI::State state ); + + void UpdateLanes( TrafficVehicle* tv, Lane* oldLane, Lane* newLane ); + + bool BuildLaneChangeCurve( + RoadSegment* oldSegment, + const float oldT, + unsigned int oldLaneIndex, + unsigned int newLaneIndex, + const float dist); + + bool BuildArbitraryCurve( + const rmt::Vector& startPos, + const rmt::Vector& startDir, + const rmt::Vector& endPos, + const rmt::Vector& endDir ); + + void GetSplineCurve( rmt::Vector*& ways, int& npts, int& currWay ); + +public: + float mLaneChangeProgress; // progress in meters while in lane change state + float mLaneChangeDist; // total distance over which we want to lane change + unsigned int mLaneChangingFrom; // index of lane we are lane changing FROM + float mOutLaneT; + + float mCurrPathLocation; + + float mActualSpeed; + +private: + Vehicle* mVehicle; + TrafficAI* mMyAI; + + ///// For building splines at intersections ////// + CubicBezier mSplineMaker; + rmt::Vector* mWays; + int mNumWays; + ////////////////////////////////////////////////// + + rmt::Vector mCurrPath; + float mCurrPathLength; + int mCurrWay; + + bool mIsInIntersection; + + rmt::Vector mPrevPos; + + float mSecondsTillCheckForFreeLane; + + ////////////// History /////////////// + VectorHistory<ON_ROAD_HISTORY_SIZE> mFacingHistory; + VectorHistory<ON_ROAD_HISTORY_SIZE> mPosHistory; + ///////////////////////////////////////////// + + float mSecondsSinceLastAddToHistory; + +private: + // Helpers + void FindOutLane( const Lane* inLane, + unsigned int inLaneIndex, + Lane*& outLane, + unsigned int& outLaneIndex ); + + void BuildCurve ( RoadSegment* inSegment, + unsigned int inLaneIndex, + RoadSegment* outSegment, + unsigned int outLaneIndex ); + + bool EnterIntersection(); + + void PivotFrontWheels( rmt::Vector facing ); + + void StopSuddenly( rmt::Vector& pos, rmt::Vector& facing ); + + ////////////// WASTEFUL CONSTRUCTORS //////////////////// + TrafficLocomotion(); + TrafficLocomotion( const TrafficLocomotion& trafficlocomotion ); + TrafficLocomotion& operator=( const TrafficLocomotion& trafficlocomotion ); + //////////////////////////////////////////////////////////// +}; + +inline bool TrafficLocomotion::IsInIntersection() const +{ + return mIsInIntersection; +} +inline bool TrafficLocomotion::GetActive() const +{ + return mMyAI->mIsActive; +} +inline void TrafficLocomotion::SetActive( bool isActive ) +{ + mMyAI->mIsActive = isActive; +} +inline void TrafficLocomotion::SetAISpeed( float mps ) +{ + mMyAI->SetAISpeed( mps ); +} +inline float TrafficLocomotion::GetAISpeed() +{ + return mMyAI->GetAISpeed(); + } +inline Lane* TrafficLocomotion::GetAILane() +{ + return mMyAI->GetLane(); +} +inline void TrafficLocomotion::SetAIState( enum TrafficAI::State state ) +{ + mMyAI->SetState(state); +} +inline TrafficAI* TrafficLocomotion::GetAI() +{ + return mMyAI; +} + +#endif //TRAFFICLOCOMOTION_H + + diff --git a/game/code/worldsim/redbrick/vehicle.cpp b/game/code/worldsim/redbrick/vehicle.cpp new file mode 100644 index 0000000..5abc941 --- /dev/null +++ b/game/code/worldsim/redbrick/vehicle.cpp @@ -0,0 +1,6483 @@ +//============================================================================= +// Copyright (C) 2001 Radical Entertainment Ltd. All rights reserved. +// +// File: vehicle.cpp +// +// Description: the car +// +// History: Nov 16, 2001 + Created -- gmayer +// +//============================================================================= + + +//======================================== +// System Includes +//======================================== +#include <p3d/anim/skeleton.hpp> +#include <p3d/matrixstack.hpp> +#include <simcommon/simstate.hpp> +#include <simcommon/simstatearticulated.hpp> +#include <simcommon/simulatedobject.hpp> +#include <simcommon/simenvironment.hpp> +#include <simcollision/collisionobject.hpp> +#include <simcollision/collisionvolume.hpp> +#include <simcollision/collisionmanager.hpp> +#include <simphysics/articulatedphysicsobject.hpp> +#include <simphysics/physicsobject.hpp> +#include <mission/charactersheet/charactersheetmanager.h> +#include <simcollision/collisiondisplay.hpp> +#include <render/DSG/StatePropDSG.h> +#include <worldsim/character/characterrenderable.h> +#include <mission/statepropcollectible.h> +#include <mission/objectives/missionobjective.h> + +#include <raddebug.hpp> +#include <raddebugwatch.hpp> + +#include <string.h> + +//======================================== +// Project Includes +//======================================== + +#include <worldsim/redbrick/vehicle.h> + +#include <worldsim/worldphysicsmanager.h> + +#include <worldsim/redbrick/geometryvehicle.h> +#include <worldsim/redbrick/wheel.h> +#include <worldsim/redbrick/redbrickcollisionsolveragent.h> + +#include <worldsim/redbrick/physicslocomotion.h> +#include <worldsim/redbrick/trafficlocomotion.h> +#include <worldsim/traffic/trafficmanager.h> +#include <worldsim/avatarmanager.h> +#include <worldsim/avatar.h> +#include <worldsim/character/character.h> + + +#include <choreo/puppet.hpp> + + +#include <events/eventmanager.h> +#include <events/eventdata.h> + +#include <roads/roadsegment.h> +#include <roads/geometry.h> + + +#include <memory/srrmemory.h> +#include <mission/gameplaymanager.h> + +#include <meta/carstartlocator.h> + +#include <debug/debuginfo.h> + + +#include <ai/actionbuttonhandler.h> +#include <ai/actionbuttonmanager.h> + +#include <meta/eventlocator.h> +#include <meta/spheretriggervolume.h> +#include <meta/recttriggervolume.h> + + +#include <render/RenderManager/RenderManager.h> +#include <render/Culling/WorldScene.h> +#include <render/IntersectManager/IntersectManager.h> +#include <render/breakables/breakablesmanager.h> + +#include <sound/soundcollisiondata.h> + +#include <radmath/radmath.hpp> +#include <worldsim/coins/sparkle.h> + +#include <cheats/cheatinputsystem.h> + +#include <mission/gameplaymanager.h> +#include <supersprint/supersprintmanager.h> +#include <p3d/billboardobject.hpp> +#include <meta/triggervolumetracker.h> + +#include <camera/supercammanager.h> +#include <camera/supercamcentral.h> + +#include <presentation/gui/guisystem.h> + +using namespace sim; + +// note - methods that are only called once at initialization, moved to vehicleinit + +// CONSTANTS +// In what radius the vehicle explosion will affect objects +const float EXPLOSION_EFFECT_RADIUS = 20.0f; +// How much force to apply to objects within this radius +const float EXPLOSION_FORCE = 20000.0f; +// Set the center of the explosion so be below that car position so that objects get hurled upwards +const float EXPLOSION_Y_OFFSET = -5.0f; + +const float HITPOINTS_REMOVED_FROM_VEHICLE_EXPLOSION = 0.5; +const float HITPOINTS_REMOVED_FROM_VEHICLE_EXPLOSION_PLAYER = 0.5; + +static const float REST_LINEAR_TOL = 0.05f; // linear velocity tolerance +static const float REST_ANGULAR_TOL = 0.3f; // angular velocity tolerance + +// Initialize static variables +float Vehicle::s_DamageFromExplosion = HITPOINTS_REMOVED_FROM_VEHICLE_EXPLOSION; +float Vehicle::s_DamageFromExplosionPlayer = HITPOINTS_REMOVED_FROM_VEHICLE_EXPLOSION_PLAYER; + +bool Vehicle::sDoBounce = false; + +void Vehicle::ActivateTriggers( bool activate ) +{ + if(activate == mTriggerActive) + { + return; + } + + if( mVehicleType == VT_AI && activate ) + { + // NOT OK to add triggers for AI car doors, but OK to remove... + // AI triggers remain as they are (so you can't get into the cars) + //rAssert( false, "FALSE, Chuck, FALSE!" ); + return; + } + + if(mVehicleDestroyed && activate) + { + return; + } + + if(!GetVehicleCentral()->GetVehicleTriggersActive() && activate) + { + return; + } + + if( mpEventLocator == NULL ) + { + return; + } + + mTriggerActive = activate; + + if( activate ) + { + for( unsigned j = 0; j < mpEventLocator->GetNumTriggers(); j++ ) + { + GetTriggerVolumeTracker()->AddTrigger( mpEventLocator->GetTriggerVolume( j ) ); + } + } + else + { + for( unsigned j = 0; j < mpEventLocator->GetNumTriggers(); j++ ) + { + GetTriggerVolumeTracker()->RemoveTrigger( mpEventLocator->GetTriggerVolume( j ) ); + } + } +} + +void Vehicle::SetUserDrivingCar( bool b ) +{ + // chooka set's this when a character get's in or out. + mUserDrivingCar = b; + if( mVehicleType != VT_AI && mUserDrivingCar ) + { + mVehicleType = VT_USER; + } +} + + +void Vehicle::TransitToAI() +{ + mVehicleType = VT_AI; + ActivateTriggers( false ); + GetEventManager()->TriggerEvent( EVENT_USER_VEHICLE_REMOVED_FROM_WORLD, this ); +} + +//============================================================================= +// +//============================================================================= +// Description: Comment +// +// Parameters: +// +// Return: void +// +//============================================================================= +void Vehicle::EnteringJumpBoostVolume() +{ + mDoingJumpBoost = true; +} + +/* +void Vehicle::AddRef() +{ + if( mVehicleType == VT_TRAFFIC && TrafficManager::GetInstance()->IsVehicleTrafficVehicle(this) ) + { + rDebugPrintf( "Booya!\n" ); + } + tRefCounted::AddRef(); +} + +void Vehicle::Release() +{ + if( mVehicleType == VT_TRAFFIC && TrafficManager::GetInstance()->IsVehicleTrafficVehicle(this) ) + { + rDebugPrintf( "Yaaaboo!\n" ); + } + tRefCounted::Release(); +} +*/ + +//============================================================================= +// Vehicle:: +//============================================================================= +// Description: Comment +// +// Parameters: +// +// Return: void +// +//============================================================================= +void Vehicle::ExitingJumpBoostVolume() +{ + if(mDoingJumpBoost) + { + // if we were doing one, now might be a good time for this event: + if(mVehicleType == VT_USER) + { + GetEventManager()->TriggerEvent(EVENT_BIG_AIR, (void*)(this->mpDriver)); + } + + } + + mDoingJumpBoost = false; +} + +int Vehicle::CastsShadow() +{ + int retVal; + // Casts a shadow in the 2nd pass, if this vehicle has one (witch and ship don't) + if ( m_IsSimpleShadow ) + { + retVal = 989; + } + else + { + retVal = 0; + } + return retVal; +} +//============================================================================= +// Vehicle::DisplayShadow +//============================================================================= +// Description: Comment +// +// Parameters: ( ) +// +// Return: void +// +//============================================================================= +void Vehicle::DisplayShadow() +{ + if( !mOkToDrawSelf || !mDrawVehicle ) + { + return; + } + + BEGIN_PROFILE("Vehicle::DisplayShadow") + if( !IsSimpleShadow() ) + { + p3d::stack->Push(); + p3d::stack->Multiply(mTransform); + + mGeometryVehicle->DisplayShadow(); + + p3d::stack->Pop(); + } + END_PROFILE("Vehicle::DisplayShadow") +} + +void Vehicle::DisplaySimpleShadow( void ) +{ + if( !mOkToDrawSelf || !mDrawVehicle ) + { + return; + } + + BEGIN_PROFILE("Vehicle::DisplaySimpleShadow") + p3d::pddi->SetZWrite(false); + if( IsSimpleShadow() ) + { + const float HeightRatio = 1.0f / 4.0f; + rmt::Vector pos = mTransform.Row( 3 ); + float carY = pos.y; + pos.y = GetGroundY(); + rmt::Vector norm; + rmt::Vector forward; + forward = mTransform.Row( 2 ); + + if( GetLocomotionType() == VL_TRAFFIC ) + { + // We'll assume the traffic doesn't go jumping through the air...although they + //could theorically get knocked through the air I guess. When that happens however + //they are under physics control and Greg says they aren't VL_TRAFFIC any more. + norm = mTransform.Row( 1 ); + } + else + { + //const rmt::Matrix& groundTrans = mGroundPlaneSimState->GetTransform(); + //norm = groundTrans.Row( 2 ); + + norm = this->mGroundPlaneWallVolume->mNormal; + } + + BlobShadowParams p( pos, norm, forward ); + p.ShadowScale = rmt::Clamp( 1.0f - ( ( carY - ( pos.y + GetRestHeightAboveGround() ) ) * HeightRatio ), 0.0f, 1.0f ); + p.ShadowAlpha = p.ShadowScale * ( mInterior ? 0.5f : 1.0f ); + mGeometryVehicle->DisplayShadow( &p ); + } + p3d::pddi->SetZWrite(true); + END_PROFILE("Vehicle::DisplaySimpleShadow") +} + + + +//============================================================================= +// Vehicle::SetInCarSimState +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::SetInCarSimState() +{ + if(mSimStateArticulatedOutOfCar) + { + if(!mUsingInCarPhysics) + { + if(mCollisionAreaIndex != -1) + { + GetWorldPhysicsManager()->RemoveFromAnyOtherCurrentDynamicsListAndCollisionArea(this); + + RemoveSelfFromCollisionManager(); // deals with our own index + GetWorldPhysicsManager()->EmptyCollisionAreaIndex(this->mCollisionAreaIndex); + + } + + mSimStateArticulatedOutOfCar->ResetVelocities(); + mSimStateArticulatedInCar->ResetVelocities(); + + rmt::Matrix transform = mSimStateArticulatedOutOfCar->GetTransform(); + + mSimStateArticulatedInCar->SetControl(sim::simAICtrl); + mSimStateArticulatedInCar->SetTransform(transform); + mSimStateArticulatedInCar->SetControl(sim::simSimulationCtrl); + + + + mSimStateArticulated = mSimStateArticulatedInCar; + + mUsingInCarPhysics = true; + + if(mCollisionAreaIndex != -1) + { + AddSelfToCollisionManager(); + } + } + + } + else + { + // please God let this be the last fucking hack in this game... + mSimStateArticulated->ResetVelocities(); + + rmt::Matrix transform = mSimStateArticulated->GetTransform(); + + mSimStateArticulated->SetControl(sim::simAICtrl); + mSimStateArticulated->SetTransform(transform); + mSimStateArticulated->SetControl(sim::simSimulationCtrl); + + CalculateSuspensionLocationAndVelocity(); // just in case + + + + } + + +} + + +//============================================================================= +// Vehicle::SetOutOfCarSimState +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::SetOutOfCarSimState() +{ + if(mSimStateArticulatedOutOfCar) + { + if(mUsingInCarPhysics) + { + if(mCollisionAreaIndex != -1) + { + GetWorldPhysicsManager()->RemoveFromAnyOtherCurrentDynamicsListAndCollisionArea(this); + + RemoveSelfFromCollisionManager(); // deals with our own index + + GetWorldPhysicsManager()->EmptyCollisionAreaIndex(this->mCollisionAreaIndex); + + // hmmm + // this is probably at init. + // + // we need to do something to try and make the normal physics update loop run once + // so that the cars settles into place with suspensionYOffset set by designers... + + // try this: + //this->PreSubstepUpdate(); + //this->PreCollisionPrep(0.016f, true); + //this->Update(0.016f); + //this->PostSubstepUpdate(); + + + } + + mSimStateArticulatedOutOfCar->ResetVelocities(); + mSimStateArticulatedInCar->ResetVelocities(); + + + rmt::Matrix transform = mSimStateArticulatedInCar->GetTransform(); + + mSimStateArticulatedOutOfCar->SetControl(sim::simAICtrl); + mSimStateArticulatedOutOfCar->SetTransform(transform); + mSimStateArticulatedOutOfCar->SetControl(sim::simSimulationCtrl); + + mSimStateArticulated = mSimStateArticulatedOutOfCar; + + CalculateSuspensionLocationAndVelocity(); // just in case + + mUsingInCarPhysics = false; + + if(mCollisionAreaIndex != -1) + { + AddSelfToCollisionManager(); + } + } + + } + else + { + // please God let this be the last fucking hack in this game... + mSimStateArticulated->ResetVelocities(); + + rmt::Matrix transform = mSimStateArticulated->GetTransform(); + + mSimStateArticulated->SetControl(sim::simAICtrl); + mSimStateArticulated->SetTransform(transform); + mSimStateArticulated->SetControl(sim::simSimulationCtrl); + + CalculateSuspensionLocationAndVelocity(); // just in case + + + + } + +} + + + + + +//============================================================================= +// Vehicle::SetLocomotion +//============================================================================= +// Description: Comment +// +// Parameters: ( VehicleLocomotionType loco ) +// +// Return: void +// +//============================================================================= +void Vehicle::SetLocomotion( VehicleLocomotionType loco ) +{ + + switch(loco) + { + case VL_PHYSICS: + + if(mLoco == VL_TRAFFIC) + { + // this is a traffic car that has just been hit + // deactivate the AI so that when we get to a rest state + // and return to AI control the car won't move + ::GetEventManager()->TriggerEvent( EVENT_TRAFFIC_GOT_HIT, this ); + TrafficManager::GetInstance()->Deactivate( this ); + } + + mVehicleLocomotion = mPhysicsLocomotion; + + //mSimStateArticulated->ResetVelocities(); //hmmmmmmmmmmmmmmmmmmmmmmmmmm + // looks like we have to do this + + //mSimStateArticulated->StoreJointState(0.016f); + mSimStateArticulated->SetControl(simSimulationCtrl); + + mLocoSwitchedToPhysicsThisFrame = true; + + // hmm.... + // safe to set velocity of vehicle to mVelocityCM? + //mSimStateArticulated->ResetVelocities(); + + break; + + case VL_TRAFFIC: + + mVehicleLocomotion = mTrafficLocomotion; + mSimStateArticulated->SetControl(simAICtrl); + + mSimStateArticulated->ResetVelocities(); + + break; + + default: + rAssert(0); + } + + mLoco = loco; +} + + + +//============================================================================= +// Vehicle::IsAFlappingJoint +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: bool +// +//============================================================================= +bool Vehicle::IsAFlappingJoint(int index) +{ + if( index == mDoorDJoint || + index == mDoorPJoint || + index == mHoodJoint || + index == mTrunkJoint) + { + return true; + } + + return false; + +} + + +//============================================================================= +// Vehicle::CalculateDragCoeffBasedOnTopSpeed +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::CalculateDragCoeffBasedOnTopSpeed() +{ + // this will also have to be called again whenever the desginer params are reloaded + + + // dragforce = 0.5 * coeff * speed * speed; + // + // so, we need a coeff that causes dragforce to match engine force at that speed + // + // for now we know the max engine force - 2 * 1.0f * mDpGasScale + + float topspeedmps = mDesignerParams.mDpTopSpeedKmh / 3.6f; + + // * 2 because this is what we apply at each wheel + // also need to subtract rolling friction for this to be accurate + //float terminalForce = 2.0f * mDesignerParams.mDpGasScale * mDesignerParams.mDpMass - mRollingFrictionForce; + + float terminalForce = 0.0f; + if(mDesignerParams.mDpGasScaleSpeedThreshold == 1.0f) + { + terminalForce = 2.0f * mDesignerParams.mDpGasScale * mDesignerParams.mDpMass; //) - (mRollingFrictionForce * mDesignerParams.mDpMass); + } + else + { + // else threhold is < 1.0 so it is used + + terminalForce = 2.0f * mDesignerParams.mDpHighSpeedGasScale * mDesignerParams.mDpMass; //) - (mRollingFrictionForce * mDesignerParams.mDpMass); + } + + + mDragCoeff = 2.0f* terminalForce / (topspeedmps * topspeedmps); + + +} + + + +//============================================================================= +// Vehicle::IsJointAWheel +//============================================================================= +// Description: Comment +// +// Parameters: (int jointIndex) +// +// Return: bool +// +//============================================================================= +bool Vehicle::IsJointAWheel(int jointIndex) +{ + if(mJointIndexToWheelMapping[jointIndex] >= 0) + { + return true; + } + else + { + return false; + } +} + + +//============================================================================= +// Vehicle::SetWheelCorrectionOffset +//============================================================================= +// Description: Comment +// +// Parameters: (int jointNum, float objectSpaceYOffsetFromCurrentPosition) +// +// Return: bool +// +//============================================================================= +bool Vehicle::SetWheelCorrectionOffset(int jointNum, float objectSpaceYOffsetFromCurrentPosition, rmt::Vector& normalPointingAtCar, rmt::Vector& groundContactPoint) +{ + //bool bottomedOut = mWheels[mJointIndexToWheelMapping[jointNum]]->SetYOffsetFromCurrentPosition(objectSpaceYOffsetFromCurrentPosition); + float bottomedOut = mWheels[mJointIndexToWheelMapping[jointNum]]->SetYOffsetFromCurrentPosition(objectSpaceYOffsetFromCurrentPosition); + + if(mLoco == VL_PHYSICS) // is this method even called if it's not? + { + mVehicleLocomotion->CompareNormalAndHeight(mJointIndexToWheelMapping[jointNum], normalPointingAtCar, groundContactPoint); + } + + + if(bottomedOut > 0.0f) + { + // if any wheel bottoms out we want to set this + + // currently this is just used by the parent Vehicle to do some debug rendering + mBottomedOutThisFrame = true; + + // TODO ? + // what to do with this value + + return true; + } + + //return bottomedOut; + return false; +} + + + +//============================================================================= +// Vehicle::SetVehicleSimEnvironment +//============================================================================= +// Description: Comment +// +// Parameters: (sim::SimEnvironment* se) +// +// Return: void +// +//============================================================================= +void Vehicle::SetVehicleSimEnvironment(sim::SimEnvironment* se) +{ + mPhObj->SetSimEnvironment(se); +} + + +//============================================================================= +// Vehicle::GetCollisionAreaIndexAndAddSelf +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::GetCollisionAreaIndexAndAddSelf() +{ + mCollisionAreaIndex = GetWorldPhysicsManager()->GetVehicleCollisionAreaIndex(); + rAssert(mCollisionAreaIndex != -1); + + AddSelfToCollisionManager(); + +} + + +//============================================================================= +// Vehicle::RemoveSelfAndFreeCollisionAreaIndex +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::RemoveSelfAndFreeCollisionAreaIndex() +{ + RemoveSelfFromCollisionManager(); + + GetWorldPhysicsManager()->FreeCollisionAreaIndex(mCollisionAreaIndex); + mCollisionAreaIndex = -1; + +} + +//============================================================================= +// Vehicle::AddSelfToCollisionManager +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::AddSelfToCollisionManager() +{ + GetWorldPhysicsManager()->mCollisionManager->AddCollisionObject(mSimStateArticulated->GetCollisionObject(), mCollisionAreaIndex); + GetWorldPhysicsManager()->mCollisionManager->AddCollisionObject(mGroundPlaneSimState->GetCollisionObject(), mCollisionAreaIndex); + GetWorldPhysicsManager()->mCollisionManager->AddPair(mGroundPlaneSimState->GetCollisionObject(), mSimStateArticulated->GetCollisionObject(), mCollisionAreaIndex); +} + + +//============================================================================= +// Vehicle::RemoveSelfFromCollisionManager +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::RemoveSelfFromCollisionManager() +{ + rAssert(mCollisionAreaIndex != -1); + GetWorldPhysicsManager()->mCollisionManager->RemoveCollisionObject(mSimStateArticulated->GetCollisionObject(), mCollisionAreaIndex); + GetWorldPhysicsManager()->mCollisionManager->RemoveCollisionObject(mGroundPlaneSimState->GetCollisionObject(), mCollisionAreaIndex); +} + +//============================================================================= +// Vehicle::AddToOtherCollisionArea +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: void +// +//============================================================================= +void Vehicle::AddToOtherCollisionArea(int index) +{ + GetWorldPhysicsManager()->mCollisionManager->AddCollisionObject(mSimStateArticulated->GetCollisionObject(), index); +} + +//============================================================================= +// Vehicle::ReLoadDesignerParams +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::CalculateValuesBasedOnDesignerParams() +{ + // in the future, reload from file or something? + // + // right now, this method can just be called when a gamepad button is hit, to + // pass down new settings (using the Marting-method in msdev) and force + // recalculation in the physicsvehicle and wheel as necessary + + int stophere = 1; // break, and change values in msdev, then continue + + // + //mPhysicsVehicle->SetDesignerParams(&mDesignerParams); + + if(GetCheatInputSystem()->IsCheatEnabled(CHEAT_ID_HIGH_ACCELERATION)) + { + //mDesignerParams.mDpGasScale *= 3.0f; + } + + + // TODO - reimplement as necessary here + + // account for new mass + SetupPhysicsProperties(); + + CalculateDragCoeffBasedOnTopSpeed(); + + + int i; + for(i = 0; i < 4; i++) + { + mSuspensionRestPoints[i] = mSuspensionRestPointsFromFile[i]; + mSuspensionRestPoints[i].y += mDesignerParams.mDpSuspensionYOffset; + + mWheels[i]->SetDesignerParams(&mDesignerParams); + } + + // reset hitpoints to full + mHitPoints = mDesignerParams.mHitPoints; + + float health = GetCharacterSheetManager()->GetCarHealth( mCharacterSheetCarIndex ); + if( ( health >= 0.0f ) && ( health < 1.0f ) ) + { + mHitPoints *= health; + SyncVisualDamage( health ); + } +} + + +//============================================================================= +// Vehicle::TrafficSetTransform +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Matrix &m) +// +// Return: void +// +//============================================================================= +void Vehicle::TrafficSetTransform(rmt::Matrix &m) +{ + // should already be under simAICtrl + rAssert(mSimStateArticulated->GetControl() == simAICtrl); + + // the position in the incoming matrix is on the road surface + // + // need to figure out how much to bump this up along vup + + // ( I think the wheels settling a bit on the suspension will have to be + // handled elsewhere.. eg. suspensionjointdriver) + + float restHeightAboveGround = GetRestHeightAboveGround(); + //restHeightAboveGround = 2.0f; + rmt::Vector fudge = mVehicleUp; + + const float hacktest = 0.1f; + + fudge.Scale(restHeightAboveGround - hacktest); + + rmt::Vector currentTrans = m.Row(3); + currentTrans.Add(fudge); + + m.FillTranslate(currentTrans); + + + mSimStateArticulated->SetTransform(m); + mTransform = m; + + +} + + +//============================================================================= +// Vehicle::GetRestHeightAboveGround +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: float +// +//============================================================================= +float Vehicle::GetRestHeightAboveGround() +{ + // ie. height above ground for skeleton in rest pose + // + // assume all the wheels have same radius and same suspension point y + + // TODO - is this correct? adequate? + float suspensionPointToCenter = -1.0f * mSuspensionRestPoints[0].y; + float wheelRadius = mWheels[0]->mRadius; + + return suspensionPointToCenter + wheelRadius; +} + + +//============================================================================= +// Vehicle::SetTransform +//============================================================================= +// Description: Comment +// +// Parameters: ( rmt::Matrix* m ) +// +// Return: void +// +//============================================================================= +void Vehicle::SetTransform( rmt::Matrix &m ) +{ + if(mSimStateArticulated->GetControl() == simAICtrl) + { + mSimStateArticulated->SetTransform(m); + + // TODO + // MS7 HACK + //mSimStateArticulated->SetControl(simSimulationCtrl); + } + else + { + mSimStateArticulated->SetControl(simAICtrl); + mSimStateArticulated->SetTransform(m); + mSimStateArticulated->SetControl(simSimulationCtrl); + } + + mSimStateArticulated->ResetVelocities(); + + mTransform = mSimStateArticulated->GetTransform(-1); + + mPoseEngine->Begin(true); // TODO - should this be true or false + + int i; + for (i = 0; i < mPoseEngine->GetPassCount(); i++) + { + mPoseEngine->Advance(i, 0); + mPoseEngine->Update(i); + } + mPoseEngine->End(); + mSimStateArticulated->UpdateJointState(0); + + // TODO - reset other state variables + // TODO - need to touch pose engine here? + + + mVelocityCM.Set(0.0f, 0.0f, 0.0f); + mSpeed = 0.0f; + mSpeedKmh = 0.0f; + mPercentOfTopSpeed = 0.0f; + + mBrakeTimer = 0.0f; + mBrakeActingAsReverse = false; + + mVehicleFacing = mTransform.Row(2); + mVehicleUp = mTransform.Row(1); + mVehicleTransverse = mTransform.Row(0); + + + mGear = 0; // neutral? + + // redundant, but just in case + for(i = 0; i < 4; i++) + { + mWheels[i]->Reset(); + } + + + + CalculateSuspensionLocationAndVelocity(); + + mPhysicsLocomotion->SetTerrainIntersectCachePointsForNewTransform(); + + //Only do this if the car is actually IN the DSG + if ( mVehicleCentralIndex > -1 ) + { + DSGUpdateAndMove(); + } +} + +//============================================================================= +// Vehicle::SetPosition +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector* newPos) +// +// Return: void +// +//============================================================================= +void Vehicle::SetPosition(rmt::Vector* newPos) +{ + rmt::Matrix m; + m.Identity(); + + m.FillTranslate(*newPos); + + SetTransform( m ); +} + +// +// Dumbledore here, +// This method is to be used for vehicles that get spawned at a locator position that +// has been SNAPPED TO GROUND. Since Vehicle will assume initial position belongs +// to the car's center of mass, the car will start half-sunken into the ground.. +// that is unless we can do the auto adjustment here... +// +void Vehicle::SetInitialPositionGroundOffsetAutoAdjust( rmt::Vector* newPos ) +{ + mInitialPosition = *newPos; + mInitialPosition.y += GetRestHeightAboveGround(); +} + +//============================================================================= +// Vehicle::SetInitialPosition +//============================================================================= +// Description: Comment +// +// Parameters: ( rmt::Vector* newPos ) +// +// Return: void +// +//============================================================================= +void Vehicle::SetInitialPosition( rmt::Vector* newPos ) +{ + mInitialPosition = *newPos; +} + +//============================================================================= +// Vehicle::SetResetFacingInRadians +//============================================================================= +// Description: Comment +// +// Parameters: ( float rotation ) +// +// Return: void +// +//============================================================================= +void Vehicle::SetResetFacingInRadians( float rotation ) +{ + mResetFacingRadians = rotation; +} + +//============================================================================= +// Vehicle::Reset +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::Reset( bool resetDamage, bool clearTraffic ) +{ + //SetPosition(&mInitialPosition); + + rmt::Matrix m; + + m.Identity(); + m.FillRotateXYZ( 0.0f, mResetFacingRadians, 0.0f ); + m.FillTranslate( mInitialPosition ); + int i; + for(i = 0; i < 4; i++) + { + mWheels[i]->Reset(); + } + + SetTransform( m ); + + /* + // FORCE UPDATE THE AVATAR'S LAST PATH INFO + Avatar* avatar = GetAvatarManager()->GetAvatarForVehicle( this ); + if( avatar ) + { + RoadManager::PathElement elem; + RoadSegment* seg; + float segT, roadT; + avatar->GetLastPathInfo( elem, seg, segT, roadT ); + } + */ + + // if clearTraffic is set to true, we want to use the functionality of ResetOnSpot + // but use the position that was filled into mInitialPosition + if(clearTraffic) + { + this->ResetOnSpot(resetDamage, false); + } + else + { + + + + ResetFlagsOnly( resetDamage ); + + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForVehicle( this ); + if ( playerAvatar ) + { + GetSuperCamManager()->GetSCC( playerAvatar->GetPlayerId() )->DoCameraCut(); + } + } +} + + +//============================================================================= +// Vehicle::ResetFlagsOnly +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::ResetFlagsOnly(bool resetDamage) +{ + mOkToDrawSelf = true; + + mDoingBurnout = false; + //mOkToCrashLand = false; + + mDoingRockford = false; + + mSpeedBurstTimer = 0.0f; + mEBrakeTimer = 0.0f; + mBuildingUpSpeedBurst = false; + mDoSpeedBurst = false; + mDoingJumpBoost = false; + mBrakeLightsOn = false; + mReverseLightsOn = false; + mCMOffsetSetToOriginal = false; + mStuckOnSideTimer = 0.0f; + + if( resetDamage ) + { + mVehicleDestroyed = false; + ResetDamageState(); + mAlreadyPlayedExplosion = false; + + } + + + mDesiredDoorPosition[0] = mDesiredDoorPosition[1] = 0.0f; + mDesiredDoorAction[0] = mDesiredDoorAction[1] = DOORACTION_NONE; + + mDrawWireFrame = false; + + mWasAirborn = false; + mWasAirbornTimer = 0.0f; + + mBottomOutSpeedMaintenance = 0.0f; + + // reset the AI pathfinding information, if we're an AI vehicle + if( mVehicleType == VT_AI && mVehicleCentralIndex != -1 ) + { + // if we're an AI car, dump our pathfinding data + VehicleAI* vAI = dynamic_cast<VehicleAI*>( GetVehicleCentral()->GetVehicleController( mVehicleCentralIndex ) ); + if( vAI ) + { + vAI->Reset(); + } + } +} + +//============================================================================= +// Vehicle::ResetOnSpot +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::ResetOnSpot( bool resetDamage /*=true*/ , bool moveCarOntoRoad) +{ + + // find road segment and put them on it + + // one branch or the other will fill these up + rmt::Vector newVehiclePosition(0.0f, 0.0f, 0.0f); + float ang = 0.0f; + + + rmt::Vector currentVehiclePosition = this->rPosition(); + float radius = 100.0f; // this is the radius of query for nearest road + + RoadSegment* roadSeg = NULL; + float dummy; + + bool useIntersection = false; + Intersection* in = NULL; + + + // We need to do something special for street races because they have race props, + // meaning that resetting onto the "nearest road" can take you outside the race + // bounds. For normal race missions, we just find the closest road. + // + if( GetGameplayManager()->GetCurrentMission() != NULL && + GetGameplayManager()->GetCurrentMission()->IsRaceMission() ) + { + RoadManager::PathElement elem; + RoadSegment* seg = 0; + float segT; + float roadT; + + if( mVehicleType == VT_USER ) + { + //// Use my UBER-search algorithm only when desperate times call for it. + //RoadManager::PathfindingOptions options = RoadManager::PO_SEARCH_INTERSECTIONS | RoadManager::PO_SEARCH_ROADS; + //RoadManager::GetInstance()->FindClosestPathElement( currentVehiclePosition, radius, options, elem, roadSeg, segT, roadT ); + + GetAvatarManager()->GetAvatarForPlayer(0)->GetLastPathInfo(elem, roadSeg, segT, roadT); + if( elem.type == RoadManager::ET_INTERSECTION ) + { + useIntersection = true; + in = (Intersection*) elem.elem; + } + } + else + { + rAssert( mVehicleType == VT_AI ); + VehicleAI* vAI = GetVehicleCentral()->GetVehicleAI( this ); + rAssert( vAI ); + + vAI->GetLastPathInfo( elem, roadSeg, segT, roadT ); + } + } + else + { + GetIntersectManager()->FindClosestRoad( currentVehiclePosition, radius, roadSeg, dummy ); + } + + + if( useIntersection ) + { + rAssert( in ); + in->GetLocation( newVehiclePosition ); + + // bump up position + float h = this->GetRestHeightAboveGround(); + h += 1.0f; + newVehiclePosition.y += h; + + ang = FacingIntoRad(mVehicleFacing); + + } + else + { + if(roadSeg) + { + // from the road + // want two points - centre of baseline and center of top + + rmt::Vector corner0, corner1, corner2, corner3; + + roadSeg->GetCorner(0, corner0); + roadSeg->GetCorner(1, corner1); + roadSeg->GetCorner(2, corner2); + roadSeg->GetCorner(3, corner3); + + rmt::Vector base = corner0; + base.Add(corner3); + base.Scale(0.5f); + + rmt::Vector top = corner1; + top.Add(corner2); + top.Scale(0.5f); + + rmt::Vector centerline = top; + centerline.Sub(base); + + // we want the vehicle's new orientation to be along this vector, and the position + // to be the projection (shortest distance) of vehicle's position onto this line + + rmt::Vector centerlineDir = centerline; + + rAssert(centerline.Magnitude() > 0.0f); + + centerlineDir.NormalizeSafe(); + + rmt::Vector newVehicleFacing = centerlineDir; + + //If this is NOT SuperSprint, do this test. Otherwise the car will + //always face along the road. + if ( !GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + if(mVehicleFacing.DotProduct(centerlineDir) > 0.0f) + { + // leave as is + } + else + { + newVehicleFacing.Scale(-1.0f); + } + } + + ang = FacingIntoRad(newVehicleFacing); + + + // for position, we just use Dusit's magical function + + // the line segment at which this closest point occurs + //float FindClosestPointOnLine( const rmt::Vector& start, + // const rmt::Vector& end, + // const rmt::Vector& p, + // rmt::Vector& closestPt ); + + + FindClosestPointOnLine(base, top, currentVehiclePosition, newVehiclePosition); + + + + + // make sure there's no traffic in the way at that point + rmt::Sphere s; + s.centre = newVehiclePosition; + //s.radius = 10.0f; + s.radius = this->mWheelBase * 2.0f; + + TrafficManager::GetInstance()->ClearTrafficInSphere(s); + + + + + // bump up position + float h = this->GetRestHeightAboveGround(); + h += 1.0f; + + newVehiclePosition.y += h; + + + //GetVehicleCentral()->ClearSpot(newVehiclePosition, this->mWheelBase * 2.0f, this); + + } + else + { + + // this is the old debug version + // + // literally resets on spot. + newVehiclePosition = currentVehiclePosition; + newVehiclePosition.y += 1.0f; + + ang = FacingIntoRad(mVehicleFacing); + } + } + + if(moveCarOntoRoad) + { + + rmt::Matrix m; + + m.Identity(); + + if ( GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + if ( GetSSM()->IsTrackReversed() ) + { + ang += rmt::PI; //Turn, turn around + } + } + + //m.FillRotateXYZ( 0.0f, mResetFacingRadians, 0.0f ); + m.FillRotateXYZ( 0.0f, ang, 0.0f ); + + m.FillTranslate(newVehiclePosition); + int i; + for(i = 0; i < 4; i++) + { + mWheels[i]->Reset(); + } + + SetTransform( m ); + } + + ResetFlagsOnly(resetDamage); + + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForVehicle( this ); + if ( playerAvatar ) + { + GetSuperCamManager()->GetSCC( playerAvatar->GetPlayerId() )->DoCameraCut(); + } + + mAlreadyCalledAutoResetOnSpot = false; +} + + +//============================================================================= +// Vehicle::GetFacingInRadians +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: float +// +//============================================================================= +float Vehicle::GetFacingInRadians() +{ + return FacingIntoRad(mVehicleFacing); +} + + +//============================================================================= +// Vehicle::FacingIntoRad +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: float +// +//============================================================================= +float Vehicle::FacingIntoRad(rmt::Vector facing) +{ + float test = rmt::ATan2(facing.x, facing.z); + + if(rmt::IsNan(test)) + { + return 0.0f; + } + + return test; + //return 0.0f; +} + +//============================================================================= +// Vehicle::SetGas +//============================================================================= +// Description: Comment +// +// Parameters: (float gas) +// +// Return: void +// +//============================================================================= +void Vehicle::SetGas(float gas) +{ + + if(!mVehicleDestroyed && !mGasBrakeDisabled) + { + mGas = gas; + //if(mGas > 0.8f) + //{ + // mGas = 1.0f; + //} + } + else + { + mGas = 0.0f; + } +} + + +//============================================================================= +// Vehicle::SetBrake +//============================================================================= +// Description: Comment +// +// Parameters: (float brake) +// +// Return: void +// +//============================================================================= +void Vehicle::SetBrake(float brake) +{ + + if(!mVehicleDestroyed && !mGasBrakeDisabled) + { + // normal setting of value + + // the max we will set brake to is inverse of percentage of top speed + + float proj = mVelocityCM.DotProduct(mVehicleFacing); + if(proj > 0.0f) + { + // only do all this crap if going forward, and using brake to slow down, not go in reverse + + + float maxbrake = 1.0f - this->mPercentOfTopSpeed; + if(maxbrake < 0.0f) + { + maxbrake = 0.0f; + } + + if(brake > maxbrake) + { + brake = maxbrake; + } + + if(mGas > 0.1f) + { + if(brake > mGas) + { + brake = mGas - 0.1f; + } + } + if(brake < 0.0f) + { + brake = 0.0f; + } + } + + + mBrake = brake; + } + else + { + mBrake = 0.0f; + } + + /* + if(mBrake > 0.0f && !mBrakeLightsOn) + { + mGeometryVehicle->ShowBrakeLights(); + mBrakeLightsOn = true; + } + + if(mBrake == 0.0f && mBrakeLightsOn) + { + mGeometryVehicle->HideBrakeLights(); + mBrakeLightsOn = false; + } + */ + + if(mBrake > 0.0f && !mDontShowBrakeLights) + { + if(IsInReverse()) + { + if(!mReverseLightsOn) + { + mGeometryVehicle->ShowReverseLights(); + mReverseLightsOn = true; + } + if(mBrakeLightsOn) + { + mGeometryVehicle->HideBrakeLights(); + mBrakeLightsOn = false; + } + + } + else + { + if(mReverseLightsOn) + { + mGeometryVehicle->HideReverseLights(); + mReverseLightsOn = false; + } + + if(!mBrakeLightsOn) + { + mGeometryVehicle->ShowBrakeLights(); + mBrakeLightsOn = true; + } + } + + } + + if(mBrake == 0.0f || mDontShowBrakeLights) + { + if(mBrakeLightsOn) + { + mGeometryVehicle->HideBrakeLights(); + mBrakeLightsOn = false; + } + if(mReverseLightsOn) + { + mGeometryVehicle->HideReverseLights(); + mReverseLightsOn = false; + } + } + +} + + +//============================================================================= +// Vehicle::SetEBrake +//============================================================================= +// Description: Comment +// +// Parameters: (float ebrake) +// +// Return: void +// +//============================================================================= +void Vehicle::SetEBrake(float ebrake, float dt) +{ + const float magicEBrakeTimerLimit = 0.5f; // seconds + + if(ebrake > 0.0f) + { + //mEBrake = ebrake; + + if(mEBrake == 0.0f) + { + // ie. the button just went down, reset the timer + mEBrakeTimer = 0.0f; + } + + // hack for digital + mEBrake = 1.0f; + + + } + else if (mEBrakeTimer < magicEBrakeTimerLimit) + { + // leave EBrake at last set value? + } + else + { + mEBrake = 0.0f; + mEBrakeTimer = 0.0f; + + } + + + + mEBrakeTimer += dt; + + + // mEBrake = ebrake; old implementation of this method + + + +} + + +//============================================================================= +// Vehicle::SetWheelTurnAngle +//============================================================================= +// Description: Comment +// +// Parameters: (float wheelTurnAngle, bool doNotModifyInputValue) +// +// Return: void +// +//============================================================================= +void Vehicle::SetWheelTurnAngle(float wheelTurnAngle, bool doNotModifyInputValue, float dt) +{ + + mUnmodifiedInputWheelTurnAngle = wheelTurnAngle; // just store for later use - things like doughnuts... + + // modify the h/w input to be nicer + + //char dbinfo[64]; + //sprintf(dbinfo, "input wheel turn angle: %.2f\n", wheelTurnAngle); + //DEBUGINFO_ADDSCREENTEXT( dbinfo ); + + //doNotModifyInputValue = true; //debug test + if(doNotModifyInputValue) // please ignore the name of the param :) + { + // this is a wheel + // scale up input value + //const float magicWheelScaleUp = 1.2f; + //const float magicWheelScaleUp = 10.2f; + const float magicWheelScaleUp = 2.75f; + //const float magicWheelScaleUp = 1.5f; + + wheelTurnAngle *= magicWheelScaleUp; + + if(wheelTurnAngle < -1.0f) + { + wheelTurnAngle = -1.0f; + } + if(wheelTurnAngle > 1.0f) + { + wheelTurnAngle = 1.0f; + } + + + + + + } + if (1) // plum wants high speed steering drop always + { + /* + if(rmt::Fabs(wheelTurnAngle) < mSteeringInputThreshold) + { + wheelTurnAngle *= mSteeringPreSlope; + } + else + { + float steeringPostSlope = (1.0f - (mSteeringInputThreshold * mSteeringPreSlope)) / (1.0f - mSteeringInputThreshold); + + float pieceBelow = mSteeringInputThreshold * mSteeringPreSlope; + float pieceAbove = (rmt::Fabs(wheelTurnAngle) - mSteeringInputThreshold) * steeringPostSlope; + + float wheelTurnAngleMag = pieceBelow + pieceAbove; + wheelTurnAngle = wheelTurnAngleMag * rmt::Sign(wheelTurnAngle); + } + */ + + // factor in sensitivity drop as speed increases + // + // simple slope-intercept formula + float slope = mDesignerParams.mDpHighSpeedSteeringDrop - 1.0f; // divided by 1.0 implied + float yintercept = 1.0f; + // input x is mPercentOfTopSpeed + + float value = slope * mPercentOfTopSpeed + yintercept; + + // this should still be a positive number + // use to scale the angle (ie. formulat initialy setup for input value of 1.0) + + if(value < 0.0f) + { + value = 0.0f; // should never hit this + } + + wheelTurnAngle *= value; + } + + + + //else + //{ + // float maxWheelTurnInRadians = rmt::DegToRadian(mDesignerParams.mDpMaxWheelTurnAngle); + // mWheelTurnAngle = wheelTurnAngle * maxWheelTurnInRadians; + //} + + + // if we're below some low speed.....??? 60 kmh... have a time lag to reach the desired angle to reduce twichyness + const float thresholdLowSpeed = 90.0f; // kmh + const float timeAtZero = 0.3f; + if(/*this->mSpeedKmh < thresholdLowSpeed && */ this->mVehicleType == VT_USER && !doNotModifyInputValue) + { + // closer to zero, the longer the lag. + + // at 60 it would take no time. + + // standard linear drop: + //float secondsToChangeOneUnitAtCurrentSpeed = (1.0f - mSpeedKmh / thresholdLowSpeed) * timeAtZero; + + // parabolic drop: + float secondsToChangeOneUnitAtCurrentSpeed = ((1.0f - rmt::Sqr(mSpeedKmh / thresholdLowSpeed)) * timeAtZero); + + // new cap for plum + if(secondsToChangeOneUnitAtCurrentSpeed < 0.15f) + { + secondsToChangeOneUnitAtCurrentSpeed = 0.15f; + } + + rAssert(secondsToChangeOneUnitAtCurrentSpeed > 0.0f); + if(secondsToChangeOneUnitAtCurrentSpeed > 0.0f) // 2 lines of defense + { + float unitsPerSecond = 1.0f / secondsToChangeOneUnitAtCurrentSpeed; + + // this is the max amount we can change in this frame + float unitsInThisFrame = unitsPerSecond * dt; + + // at this point wheelTurnAngle is still our 'target', between 0 and 1 + + // we can move a maximum of 'unitsInThisFrame' from mWheelTurnAngleInputValue (last value) towards our target, wheelTurnAngle + + if(wheelTurnAngle > mWheelTurnAngleInputValue) + { + if(wheelTurnAngle > (mWheelTurnAngleInputValue + unitsInThisFrame)) + { + mWheelTurnAngleInputValue += unitsInThisFrame; + } + else + { + mWheelTurnAngleInputValue = wheelTurnAngle; + } + + + } + else if(wheelTurnAngle < mWheelTurnAngleInputValue) + { + if(wheelTurnAngle < (mWheelTurnAngleInputValue - unitsInThisFrame)) + { + mWheelTurnAngleInputValue -= unitsInThisFrame; + } + else + { + mWheelTurnAngleInputValue = wheelTurnAngle; + } + + } + // else if they're == do nothing + + } + + } + else + { + mWheelTurnAngleInputValue = wheelTurnAngle; + } + + + // low speed uturn thingy + if(this->mSpeedKmh < 50.0f && this->mVehicleType == VT_USER) + { + // allow the max wheel turn angel to be higher + // ?? try current + 10? + + // normal case - use limit set by designers + float maxWheelTurnInRadians = rmt::DegToRadian(mDesignerParams.mDpMaxWheelTurnAngle + 10.0f); + //mWheelTurnAngle = wheelTurnAngle * maxWheelTurnInRadians; + mWheelTurnAngle = mWheelTurnAngleInputValue * maxWheelTurnInRadians; + + + } + else + { + // normal case - use limit set by designers + float maxWheelTurnInRadians = rmt::DegToRadian(mDesignerParams.mDpMaxWheelTurnAngle); + //mWheelTurnAngle = wheelTurnAngle * maxWheelTurnInRadians; + mWheelTurnAngle = mWheelTurnAngleInputValue * maxWheelTurnInRadians; + + } + + + int i; + for(i = 0; i < 4; i++) + { + if(mWheels[i]->mSteerWheel) + { + mWheels[i]->mWheelTurnAngle = mWheelTurnAngle; + } + } + + //sprintf(dbinfo, "modified wheel turn angle: %.2f\n", mWheelTurnAngle); + + //DEBUGINFO_ADDSCREENTEXT( dbinfo ); + +} + + +//============================================================================= +// Vehicle::SetWheelTurnAngleDirectlyInRadiansForDusitOnly +//============================================================================= +// Description: Comment +// +// Parameters: (float rad) +// +// Return: void +// +//============================================================================= +void Vehicle::SetWheelTurnAngleDirectlyInRadiansForDusitOnly(float rad) +{ + float maxWheelTurnInRadians = rmt::DegToRadian(mDesignerParams.mDpMaxWheelTurnAngle); + + mWheelTurnAngle = rad; + + if(rad > maxWheelTurnInRadians) + { + mWheelTurnAngle = maxWheelTurnInRadians; + } + if(rad < -maxWheelTurnInRadians) + { + mWheelTurnAngle = -maxWheelTurnInRadians; + } + + + int i; + for(i = 0; i < 4; i++) + { + if(mWheels[i]->mSteerWheel) + { + mWheels[i]->mWheelTurnAngle = mWheelTurnAngle; + } + } + + +} + +//============================================================================= +// Vehicle::SetReverse +//============================================================================= +// Description: Comment +// +// Parameters: (float reverse) +// +// Return: void +// +//============================================================================= +void Vehicle::SetReverse(float reverse) +{ + mReverse = reverse; + if(mReverse > 0.0f) + { + //rAssertMsg(0, "see greg"); + //int stophere = 1; + } +} + + + +//============================================================================= +// Vehicle::PreSubstepUpdate +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::PreSubstepUpdate(float dt) +{ + float dirSpeedKmh = mSpeedKmh * ( (mVelocityCM.DotProduct(mVehicleFacing) > 0.0f) ? 1.0f : -1.0f); + float deltaKmh = dirSpeedKmh - mLastSpeedKmh; + mAccelMss = (deltaKmh / 3600.0f) / (dt / 1000.0f); + mLastSpeedKmh = mSpeedKmh; + + mBottomedOutThisFrame = false; + + /* + if(mWaitingToSwitchToOutOfCar && mCollisionAreaIndex != -1) + { + mOutOfCarSwitchTimer += dt; + if(mOutOfCarSwitchTimer > 2.0f) + { + mOutOfCarSwitchTimer = 0.0f; + mWaitingToSwitchToOutOfCar = false; + + SetOutOfCarSimState(); + + } + + } + */ + + mVelocityCMLag = mVelocityCM; + mPositionCMLag = this->rPosition(); + + mVehicleLocomotion->PreSubstepUpdate(); +} + + + +//============================================================================= +// Vehicle::PreCollisionPrep +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::PreCollisionPrep(float dt, bool firstSubstep) +{ + BEGIN_PROFILE("mGeometryVehicle->Update") + mGeometryVehicle->Update(dt); // seems to work fine before + END_PROFILE("mGeometryVehicle->Update") + + // new + // TODO - how to improve the interfaces + BEGIN_PROFILE("mVehicleLocomotion->PreCollisionPrep") + mVehicleLocomotion->PreCollisionPrep(firstSubstep); + END_PROFILE("mVehicleLocomotion->PreCollisionPrep") + + // used by physicslocomotion in suspension force calculation + // but set by redbrickcollisionsolveragent - yuck + mDamperShouldNotPullDown[0] = false; + mDamperShouldNotPullDown[1] = false; + mDamperShouldNotPullDown[2] = false; + mDamperShouldNotPullDown[3] = false; + + // reset this flag + //mHitJoint = -1; + +} + + +//============================================================================= +// Vehicle::SetNoDamperDownFlagOnWheel +//============================================================================= +// Description: Comment +// +// Parameters: (int wheelIndex) +// +// Return: void +// +//============================================================================= +void Vehicle::SetNoDamperDownFlagOnWheel(int wheelIndex) +{ + mDamperShouldNotPullDown[wheelIndex] = true; // just for this frame +} + + +//============================================================================= +// Vehicle::CalculateSuspensionLocationAndVelocity +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::CalculateSuspensionLocationAndVelocity() +{ + // TODO - is this ok to actually change what we call world space suspension point for this frame if we're airborn??? + + // do airborn calculation here? + // only look at wheels 2,3 + + // recall - right now this is getting called from physicslocomotion::update, after collision detection + + + //if( !(mWheels[2]->mWheelInCollision) && !(mWheels[3]->mWheelInCollision)) + if( !(mWheels[2]->mWheelInCollision) || !(mWheels[3]->mWheelInCollision)) + { + // consider this airbor + // even if we're just cresting hill. + mSteeringWheelsOutOfContact = true; + } + else + { + mSteeringWheelsOutOfContact = false; + } + + + // air born test + int i; + int count = 0; + for(i = 0; i < 4; i++) + { + if(!(mWheels[i]->mWheelInCollision)) + { + count++; + } + } + + if(count > 3) + { + mAirBorn = true; + mWasAirborn = true; + mWasAirbornTimer = 0.0f; + } + else + { + mAirBorn = false; + } + + + + + + + // TODO - how 'bout flipped? + + + for(i = 0; i < 4; i++) + { + + mSuspensionWorldSpacePoints[i] = mSuspensionRestPoints[i]; + + + //if(1)//mSteeringWheelsOutOfContact) + if(mSteeringWheelsOutOfContact)// || mVehicleState == VS_NORMAL) + { + // TODO - revisit this - I don't like it + + // ok, this was a serious fuck up + // revisit during the tipping likelihood thing + + //mSuspensionWorldSpacePoints[i].y = mCMOffset.y; + } + + mSuspensionWorldSpacePoints[i].Transform(mTransform); + + mSimStateArticulated->GetVelocity(mSuspensionWorldSpacePoints[i], mSuspensionPointVelocities[i]); + + } + + + // note: + // mSteeringWheelsOutOfContact is a bit of a misnomer + + // do a test here for real airborn, all 4 wheels off, and vehicle upright + // + // this is the only place where mOkToCrashLand will get set to true + // + /* + if( !(mWheels[0]->mWheelInCollision) && !(mWheels[1]->mWheelInCollision) && + !(mWheels[2]->mWheelInCollision) && !(mWheels[3]->mWheelInCollision) && + mVehicleUp.y > 0.0f) + { + mOkToCrashLand = true; + } + */ + + // TODO - around here somewhere is the place to calculate and set a big-air event + +} + + + +//============================================================================= +// Vehicle::JumpOnHorn +//============================================================================= +// Description: Comment +// +// Parameters: (float test) +// +// Return: void +// +//============================================================================= +void Vehicle::JumpOnHorn(float test) +{ + if(!(this->mAirBorn)) + { + + if(GetSimState()->GetControl() == sim::simAICtrl) + { + GetSimState()->SetControl(sim::simSimulationCtrl); + } + + rmt::Vector boost = mVehicleFacing; + boost.y += 2.0f; + + static float hack = 5.0f; + //const float hack = 2.0f; + + boost.Scale(test * hack); + + rmt::Vector& linearVel = mSimStateArticulated->GetLinearVelocity(); + linearVel.Add(boost); + + if(GetSimState()->GetControl() == sim::simAICtrl) + { + GetSimState()->SetControl(sim::simSimulationCtrl); + } + } +} + + +void Vehicle::TurboOnHorn() +{ + /*static*/ const float SECONDS_TURBO_RECHARGE = 0.5f; + /*static*/ const float TURBO_SPEED_MPS = 10.0f; + + if( mSecondsTillNextTurbo <= 0.0f ) + { + if( mNumTurbos > 0 ) + { + // apply turbo speed + if(GetSimState()->GetControl() == sim::simAICtrl) + { + GetSimState()->SetControl(sim::simSimulationCtrl); + } + rmt::Vector turbo = mVehicleFacing * TURBO_SPEED_MPS; + rmt::Vector& linearVel = mSimStateArticulated->GetLinearVelocity(); + linearVel.Add( turbo ); + + /* + // TODO: + // Maybe play a sound effect? Pass in the vehicle pointer? Ask Esan + ::GetEventManager()->TriggerEvent( EVENT_USE_NITRO, this ); + */ + + // decrement number of turbos + mNumTurbos--; + + // reset mSecondsTillNextTurbo + mSecondsTillNextTurbo = SECONDS_TURBO_RECHARGE; + } + } +} + + +//============================================================================= +// Vehicle::Update +//============================================================================= +// Description: Comment +// +// Parameters: (float dt) +// +// Return: void +// +//============================================================================= +void Vehicle::Update(float dt) +{ + // Update gas, if the change in gas is significant + + if(mLocoSwitchedToPhysicsThisFrame) + { + this->PreCollisionPrep(dt, true); + } + mLocoSwitchedToPhysicsThisFrame = false; + + + + mDeltaGas = (mGas - mLastGas) / dt; + mLastGas = mGas; + + // update turbo wind down time + if( mSecondsTillNextTurbo > 0.0f ) + { + mSecondsTillNextTurbo -= dt; + } + + mNoDamageTimer -= dt; + if(mNoDamageTimer < 0.0f) + { + mNoDamageTimer = 0.0f; + } + + // TODO - before or after physics? + //mGeometryVehicle->Update(dt); // seems to work fine before + + // don't need this if traffic! + // + // actually, need a different one if this is traffic + //CalculateSuspensionLocationAndVelocity(); // TODO - ? shouldn't this also be done before FetchingWheelTerrainCollisionInfo + // moved to PhysicsLocomotion::PreUpdate + + // break up of work into pre and post update, outside of update, is a bit arbitrary + // I suppose + + mSimStateArticulated->StoreJointState(dt); + + mVehicleLocomotion->PreUpdate(); + + if(mVehicleType != VT_AI && GetGameplayManager()->GetGameType() != GameplayManager::GT_SUPERSPRINT) + { + if(mCollisionLateralResistanceDropFactor < 1.0f) + { + mCollisionLateralResistanceDropFactor += dt; + if(mCollisionLateralResistanceDropFactor > 1.0f) + { + mCollisionLateralResistanceDropFactor = 1.0f; + } + } + } + else + { + mCollisionLateralResistanceDropFactor = 1.0f; + } + + mVehicleLocomotion->Update(dt); + // basically - the only thing that is different after this is the mTransform, + // whether traffic or physics + // + // ?? maybe traffic should just set mTransform directly + // also set mSpeed??? + + + mVehicleLocomotion->PostUpdate(); + + + // TODO - trust these are normalized? + mVehicleFacing = mTransform.Row(2); + mVehicleUp = mTransform.Row(1); + mVehicleTransverse = mTransform.Row(0); + + // is this gonna hurt framrate? + mVehicleFacing.Normalize(); + mVehicleUp.Normalize(); + mVehicleTransverse.Normalize(); + + + // this is good + // can leave as is for both traffic and physics + // + // just have to make sure that mSuspensionPointVelocities are correct + // and it will use mSteeringWheelAngle if set. + UpdateWheelRenderingInfo(dt); + + SetGeometryVehicleWheelSmokeLevel(); + + if(mBurnoutLevel > 0.0f) + { + if ( !mDoingBurnout ) + { + GetEventManager()->TriggerEvent( EVENT_BURNOUT ); + mDoingBurnout = true; + } + } + else + { + if ( mDoingBurnout ) + { + GetEventManager()->TriggerEvent( EVENT_BURNOUT_END ); + mDoingBurnout = false; + } + } + + + + mSpeedKmh = mSpeed * 3.6f; // for watcher... + + + int i; + for (i = 0; i < mPoseEngine->GetPassCount(); i++) + { + mPoseEngine->Advance(i, dt); + mPoseEngine->Update(i); + } + + // calculate forced door opening as a result of character getting into/out of car + // TODO : this should probably be doen in the poseengine , but I don't yet understand + // enough about what poseengine/posedriver are actually doing to do that + CalcDoors(); + + // cap on huge impulses + const float hugeSpeed = 200.0f; // that's about 720 kmh + if(this->mSpeed > hugeSpeed) + { + // this is really fucking bad + //rAssertMsg(0,"tell greg how you did this"); + mSimStateArticulated->ResetVelocities(); + mSimStateArticulated->GetSimulatedObject()->ResetCache(); + + this->ResetOnSpot(false); + // mVehicleLocomotion->PostUpdate(); + + + } + + if(1)//mLoco == VL_PHYSICS) + { + mSimStateArticulated->UpdateJointState(dt); + } + + + // for debugging: + // we want the collision objects to draw in the correct places + // TODO - want to call this here! every frame!! + CollisionObject* collObj = mSimStateArticulated->GetCollisionObject(); + collObj->Update(); + + DSGUpdateAndMove(); + + //-------------------------- + // do some gear and rpm shit + //-------------------------- + + UpdateGearAndRPM(); + + // Update the trigger volume. + mpTriggerVolume->SetTransform( mTransform); + + mWasAirbornTimer += dt; + if(mWasAirbornTimer > 1.0f) + { + mWasAirborn = false; + } + + if(mBottomedOutThisFrame && mWasAirborn) + { + // try this here for Esan + mWasAirborn = false; + if( mTerrainType == TT_Road || mTerrainType == TT_Metal || mTerrainType == TT_Gravel ) + { + GetSparkleManager()->AddBottomOut( GetPosition() ); + } + GetEventManager()->TriggerEvent(EVENT_VEHICLE_SUSPENSION_BOTTOMED_OUT, (void*)this); + //rDebugPrintf("vehicle bottomed out this frame \n"); + + + + } + +} + + +//============================================================================= +// Vehicle::GetGroundY +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: float +// +//============================================================================= +float Vehicle::GetGroundY() +{ + // for James, for the shadows... + + if(this->mLoco == VL_PHYSICS) + { + // need this value for either case + + float avg = 0.0f; + avg = mPhysicsLocomotion->mTerrainIntersectCache[0].planePosn.y + + mPhysicsLocomotion->mTerrainIntersectCache[1].planePosn.y + + mPhysicsLocomotion->mTerrainIntersectCache[2].planePosn.y + + mPhysicsLocomotion->mTerrainIntersectCache[3].planePosn.y; + + avg *= 0.25; + + if(mAirBorn) // all four wheels out of contact + { + // in this situation, give the average of the terrain intersect caches? + return avg; + } + else + { + // here at least one wheel is in collision + // so... give the average y of all the wheels that are in collision + float wheelavg = 0.0f; + float count = 0.0f; + + int i; + for(i = 0; i < 4; i++) + { + if(mWheels[i]->mWheelInCollision) + { + count = count + 1.0f; + + poser::Pose* pose = mPoseEngine->GetPose(); + + poser::Joint* joint = pose->GetJoint(mWheelToJointIndexMapping[i]); + rmt::Vector trans = joint->GetWorldTranslation(); + + wheelavg += ( trans.y - mWheels[ i ]->mRadius ); + } + } + + if(count > 0.0f) + { + return wheelavg / count; + } + return avg; + + } + } + else + { + float y = mTransform.m[3][1]; + float diff = GetRestHeightAboveGround(); + + y -= diff; + + return y; + } + + // should never get here + return 0.0f; +} + + +// I'm hiding. I don't have a comment header. I'm a fugitive. I am hunted. +// +// that's ok Dusit, here you go: + +//============================================================================= +// Vehicle::PostSubstepUpdate +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::PostSubstepUpdate(float dt) +{ + //------------------- + // reset input values + //------------------- +// mGas = 0.0f; // for tony, for hud +// mBrake = 0.0f; ? safe to do this - want the IsInReverse method to work afterall... + //mWheelTurnAngle = 0.0f; + mReverse = 0.0f; + //mEBrake = 0.0f; + + // skid setting + + if( mVehicleID == VehicleEnum::HUSKA ) + { + rmt::Vector vel = mVelocityCM; + if( ( rmt::Abs( vel.x ) + rmt::Abs( vel.z ) > 0.5f ) && ( mPhysicsLocomotion ) ) + { + vel.y = 0.0f; + vel.Normalize(); + // Make a sound effects call here. + for( int wi = 0; wi < 2; ++wi ) + { + eTerrainType tt = mPhysicsLocomotion->mTerrainIntersectCache[ wi ].mTerrainType; + if( tt == TT_Road || tt == TT_Metal || tt == TT_Gravel ) + { + rmt::Vector pos = mSuspensionRestPoints[ wi ]; + pos.y -= mWheels[ wi ]->mRadius; + pos.y += mWheels[ wi ]->mYOffset; + GetTransform().Transform( pos, &pos ); + GetSparkleManager()->AddSparks( pos, vel, 0.1f ); + } + } + } + } + + if(!mNoSkid) + { + //if(mSkidLevel > 0.0f)// && mVehicleType == VT_USER) + + if(mLoco == VL_PHYSICS) + { + int i; + if(mBurnoutLevel > 0.0f) + { + for(i = 0; i < 2; i++) + { + if(mWheels[i]->mWheelInCollision) // will this value still be valid here? + { // need to fetch ground plane normal also + rAssert(mPhysicsLocomotion); + rmt::Vector normal = mPhysicsLocomotion->mIntersectNormalUsed[i]; + + mGeometryVehicle->SetSkidValues(i, mSkidLevel, normal, mPhysicsLocomotion->mTerrainIntersectCache[i].mTerrainType ); + + } + } + } + else + { + int numWheels = mNoFrontSkid ? 2 : 4; // We want to determine how many wheels + // generate skids, taking advantage of the fact that wheels 0 and 1 are always the + // back wheels. + for(i = 0; i < numWheels; i++) + { + if(mWheels[i]->mWheelInCollision) // will this value still be valid here? + { + rAssert(mPhysicsLocomotion); + rmt::Vector normal = mPhysicsLocomotion->mIntersectNormalUsed[i]; + + mGeometryVehicle->SetSkidValues(i, mSkidLevel, normal, mPhysicsLocomotion->mTerrainIntersectCache[i].mTerrainType ); + } + } + } + } + + + mGeometryVehicle->UpdateSkids(); + + } + + int i; + int count = 0; + for(i = 0; i < 4; i++) + { + if(mWheels[i]->mWheelInCollision) + { + count++; + } + } + + + // only want to do the following two tests if we are the avatar's car + // + // unless, we are supersprint, then we want to do it for all cars, with no fade to black... + if(GetAvatarManager()->GetAvatarForPlayer(0)->GetVehicle() == this || + GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT) + { + + if(count == 0 && GetWorldPhysicsManager()->mWorldUp.DotProduct(mVehicleUp) < 0.0f) + { + float linear = mPastLinear.Smooth(GetSimState()->GetLinearVelocity().Magnitude()); + float angular = mPastAngular.Smooth(GetSimState()->GetAngularVelocity().Magnitude()); + + // flipped + if(rmt::Fabs(linear) < REST_LINEAR_TOL * 3.0f && rmt::Fabs(angular) < REST_ANGULAR_TOL && !mAlreadyCalledAutoResetOnSpot) + { + // put it at rest + // reset + + if(GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT) + { + this->ResetOnSpot(false); + } + else + { + GetGuiSystem()->HandleMessage( GUI_MSG_MANUAL_RESET, reinterpret_cast< unsigned int >(this) ); + } + + mAlreadyCalledAutoResetOnSpot = true; + + } + + } + + // other on side reset test: + const float lowspeed = 1.0f; + if(mSpeed < lowspeed) + { + + rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; + float tip = mVehicleUp.DotProduct(up); + + //float cos85 = 0.0872f; + float test = 0.2f; + if(tip < test) + { + + // anytime we're in here we should incrememnt the timer + mStuckOnSideTimer += dt; + if(mStuckOnSideTimer > 1.0f && !mAlreadyCalledAutoResetOnSpot) // yeah for magic numbers! + { + mStuckOnSideTimer = 0.0f; /// this is done in the reset anyway, but easier to see like this + + if(GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT) + { + this->ResetOnSpot(false); + } + else + { + GetGuiSystem()->HandleMessage( GUI_MSG_MANUAL_RESET, reinterpret_cast< unsigned int >(this) ); + } + + mAlreadyCalledAutoResetOnSpot = true; + + } + } + else + { + // just in case + mStuckOnSideTimer = 0.0f; + } + } + else + { + // not satisfied the on-side test so reset stuckonsidetimer + mStuckOnSideTimer = 0.0f; + } + + } + + + // revisit this + // + // with new husk system all you have to do here for all types of cars is play an explosion + // + // that should probably move to where the event is fired - so that we don't swap out the car and replace with husk before this? + + if(mVehicleID != VehicleEnum::HUSKA) + { + if( mVehicleDestroyed && !mAlreadyPlayedExplosion ) + { + // Lets detach any objects from this vehicle on explosion + DetachCollectible( rmt::Vector( 0, 0, 0 ), true ); + if( this->mVehicleType == VT_USER || GetGameplayManager()->mIsDemo ) + { + mDamageOutResetTimer += dt; + if(mDamageOutResetTimer > 3.0f) + { + // kaboom + PlayExplosionEffect(); + + mAlreadyPlayedExplosion = true; // this will get reset immediately in ResetOnSpot + + if ( GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + ResetOnSpot( true ); + } + else + { + // appropriate manager should catch this and swap in husk + GetEventManager()->TriggerEvent(EVENT_VEHICLE_DESTROYED, (void*)this); + } + } + + } + else + { + mDamageOutResetTimer += dt; + if( GetGameplayManager()->GetGameType() == GameplayManager::GT_NORMAL || + mDamageOutResetTimer > 3.0f ) + { + // kaboom + PlayExplosionEffect(); + mAlreadyPlayedExplosion = true; // this will get reset immediately in ResetOnSpot + + if ( GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + ResetOnSpot( true ); + } + else + { + // appropriate manager should catch this and swap in husk + GetEventManager()->TriggerEvent(EVENT_VEHICLE_DESTROYED, (void*)this); + if(mWasHitByVehicleType != VT_AI) + { + GetEventManager()->TriggerEvent(EVENT_VEHICLE_DESTROYED_BY_USER, (void*)this); + } + + } + } + } + } + + } + + /* + if(mDamageType == VDT_AI && mVehicleDestroyed && !mAlreadyPlayedExplosion) + { + // just kaboom and it sits there dead...... for now + PlayExplosionEffect(); + mAlreadyPlayedExplosion = true; + + } + + if(mDamageType == VDT_TRAFFIC && mVehicleDestroyed && !mAlreadyPlayedExplosion && this->mVehicleID != VehicleEnum::HUSKA) + { + PlayExplosionEffect(); + mAlreadyPlayedExplosion = true; + + // Dusit here... + // It seems that the way we set mDamageType to VDT_TRAFFIC doesn't necessarily + // imply that this is a traffic car. SwapInTrafficHusk is only safe to call if + // the car is actually a traffic car (owned and initialized by TrafficManager) + // So... if it's not a traffic car, we won't call SwapInTrafficHusk... It will + // just sit there after explosions... for now... just like what's done for VDT_AI + // + if( this->mVehicleType == VT_TRAFFIC ) + { + TrafficManager::GetInstance()->SwapInTrafficHusk(this); + } + } + */ + + + // hmm... this might be the place to try moving the characters? + BounceCharacters(dt); + + + +} + + +//============================================================================= +// Vehicle::BounceCharacters +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::BounceCharacters(float dt) +{ + + if(mVehicleType == VT_USER) + { + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + if(sDoBounce && (playerAvatar->GetCharacter()->GetStateManager()->GetState() == CharacterAi::INCAR) && playerAvatar->GetVehicle() == this) + { + mYAccelForSeatingOffset = (mVelocityCM.y - mVelocityCMLag.y) / dt; + // seems to hover around -1.0f to 1.0f a lot - goes up to 8 or 10 occassionally + + if(rmt::Fabs(mYAccelForSeatingOffset) > mBounceAccelThreshold) + { + // want to displace it opposite the accel dir, proportional to the amount? + //static float magicShitScale = 0.25f; + const float magicShitScale = 0.003f; + float amountToDisplace = mYAccelForSeatingOffset * magicShitScale * -1.0f; + if(rmt::Fabs(amountToDisplace) > mMaxBounceDisplacementPerSecond * dt) + { + amountToDisplace = mMaxBounceDisplacementPerSecond * dt * rmt::Sign(amountToDisplace); + } + + ApplyDisplacementToCharacters(amountToDisplace); + + } + else + { + // want to move back to the middle + MoveCharactersTowardsRestPosition(dt); + + } + + } + + } + +} + + +//============================================================================= +// Vehicle::MoveCharactersTowardsRestPosition +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::MoveCharactersTowardsRestPosition(float dt) +{ + // let's say, for first try, move at half max diplacment speed + float rate = 0.5f; + + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + if( mpDriver && mpDriver != playerAvatar->GetCharacter() ) + { + // there is both a driver and us + // + // sort of clunky to set every frame but whatever... + mNPCRestSeatingPosition = mDriverLocation; + mOurRestSeatingPosition = mPassengerLocation; + + // hackey, hackey - need to slide lisa up a little because of dress poking through car + if(mpDriver->IsLisa()) + { + mNPCRestSeatingPosition.y += 0.12f; + } + + if(playerAvatar->GetCharacter()->IsLisa()) + { + mOurRestSeatingPosition.y += 0.12f; + } + + // us + Character* us = playerAvatar->GetCharacter(); + choreo::Puppet* ourPuppet = us->GetPuppet(); + const rmt::Vector& ourPuppetPosition = ourPuppet->GetPosition(); + rmt::Vector newOurPuppetPosition = ourPuppetPosition; + + // compare rest pos + float diff = newOurPuppetPosition.y - mOurRestSeatingPosition.y; + float maxchange = rate * dt * mMaxBounceDisplacementPerSecond; + if(rmt::Fabs(diff) <= maxchange) + { + // easy - back at rest + newOurPuppetPosition.y = mOurRestSeatingPosition.y; + ourPuppet->SetPosition(newOurPuppetPosition); + } + else if(diff < 0.0f) + { + // add to it + newOurPuppetPosition.y += maxchange; + ourPuppet->SetPosition(newOurPuppetPosition); + + } + else + { + // subtract + newOurPuppetPosition.y -= maxchange; + ourPuppet->SetPosition(newOurPuppetPosition); + + } + + + // driver + choreo::Puppet* npcPuppet = mpDriver->GetPuppet(); + const rmt::Vector& npcPuppetPosition = npcPuppet->GetPosition(); + rmt::Vector newNPCPuppetPosition = npcPuppetPosition; + + + diff = newNPCPuppetPosition.y - mNPCRestSeatingPosition.y; + //maxchange = rate * dt * mMaxBounceDisplacementPerSecond; + if(rmt::Fabs(diff) <= maxchange) + { + // easy - back at rest + newNPCPuppetPosition.y = mNPCRestSeatingPosition.y; + npcPuppet->SetPosition(newNPCPuppetPosition); + } + else if(diff < 0.0f) + { + // add to it + newNPCPuppetPosition.y += maxchange; + npcPuppet->SetPosition(newNPCPuppetPosition); + + } + else + { + // subtract + newNPCPuppetPosition.y -= maxchange; + npcPuppet->SetPosition(newNPCPuppetPosition); + + } + } + else + { + // if we're the driver... or if no driver (so we must be the driver) + + mOurRestSeatingPosition = mDriverLocation; + + if(playerAvatar->GetCharacter()->IsLisa()) + { + mOurRestSeatingPosition.y += 0.12f; + } + + // just us drivign? + Character* us = playerAvatar->GetCharacter(); + choreo::Puppet* ourPuppet = us->GetPuppet(); + const rmt::Vector& ourPuppetPosition = ourPuppet->GetPosition(); + rmt::Vector newOurPuppetPosition = ourPuppetPosition; + + // compare rest pos + float diff = newOurPuppetPosition.y - mOurRestSeatingPosition.y; + float maxchange = rate * dt * mMaxBounceDisplacementPerSecond; + if(rmt::Fabs(diff) <= maxchange) + { + // easy - back at rest + newOurPuppetPosition.y = mOurRestSeatingPosition.y; + ourPuppet->SetPosition(newOurPuppetPosition); + } + else if(diff < 0.0f) + { + // add to it + newOurPuppetPosition.y += maxchange; + ourPuppet->SetPosition(newOurPuppetPosition); + + } + else + { + // subtract + newOurPuppetPosition.y -= maxchange; + ourPuppet->SetPosition(newOurPuppetPosition); + + } + } +} + + +//============================================================================= +// Vehicle::ApplyDisplacementToCharacters +//============================================================================= +// Description: Comment +// +// Parameters: (float displacement) +// +// Return: void +// +//============================================================================= +void Vehicle::ApplyDisplacementToCharacters(float displacement) +{ + // now either we're driving alone or we're a passenger and there's a driver + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + if( mpDriver && mpDriver != playerAvatar->GetCharacter() ) + { + // sort of clunky to set every frame but whatever... + mNPCRestSeatingPosition = mDriverLocation; + mOurRestSeatingPosition = mPassengerLocation; + + + // there is both a driver and us + Character* us = playerAvatar->GetCharacter(); + choreo::Puppet* ourPuppet = us->GetPuppet(); + const rmt::Vector& ourPuppetPosition = ourPuppet->GetPosition(); + rmt::Vector newOurPuppetPosition = ourPuppetPosition; + + //newOurPuppetPosition.y += 0.001f; + newOurPuppetPosition.y += displacement; + + // test against limit + if(newOurPuppetPosition.y > mOurRestSeatingPosition.y + mBounceLimit) + { + newOurPuppetPosition.y = mOurRestSeatingPosition.y + mBounceLimit; + } + if(newOurPuppetPosition.y < mOurRestSeatingPosition.y - mBounceLimit) + { + newOurPuppetPosition.y = mOurRestSeatingPosition.y - mBounceLimit; + } + + ourPuppet->SetPosition(newOurPuppetPosition); + + + choreo::Puppet* npcPuppet = mpDriver->GetPuppet(); + const rmt::Vector& npcPuppetPosition = npcPuppet->GetPosition(); + rmt::Vector newNPCPuppetPosition = npcPuppetPosition; + + newNPCPuppetPosition.y += displacement; + + // test against limit + if(newNPCPuppetPosition.y > mNPCRestSeatingPosition.y + mBounceLimit) + { + newNPCPuppetPosition.y = mNPCRestSeatingPosition.y + mBounceLimit; + } + if(newNPCPuppetPosition.y < mNPCRestSeatingPosition.y - mBounceLimit) + { + newNPCPuppetPosition.y = mNPCRestSeatingPosition.y - mBounceLimit; + } + + npcPuppet->SetPosition(newNPCPuppetPosition); + + } + else + { + // just us drivign? + mOurRestSeatingPosition = mDriverLocation; + + Character* us = playerAvatar->GetCharacter(); + choreo::Puppet* ourPuppet = us->GetPuppet(); + const rmt::Vector& ourPuppetPosition = ourPuppet->GetPosition(); + rmt::Vector newOurPuppetPosition = ourPuppetPosition; + + //newOurPuppetPosition.y += 0.001f; + newOurPuppetPosition.y += displacement; + + + if(newOurPuppetPosition.y > mOurRestSeatingPosition.y + mBounceLimit) + { + newOurPuppetPosition.y = mOurRestSeatingPosition.y + mBounceLimit; + } + if(newOurPuppetPosition.y < mOurRestSeatingPosition.y - mBounceLimit) + { + newOurPuppetPosition.y = mOurRestSeatingPosition.y - mBounceLimit; + } + ourPuppet->SetPosition(newOurPuppetPosition); + } +} + + +//============================================================================= +// Vehicle::RecordRestSeatingPositionsOnEntry +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::RecordRestSeatingPositionsOnEntry() +{ + // same logic as BounceCharacters to decide which of these we need + + return; + + if(mVehicleType == VT_USER) + { + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + if(playerAvatar->IsInCar() && playerAvatar->GetVehicle() == this) + { + // now either we're driving alone or we're a passenger and there's a driver + if(this->mpDriver) + { + // there is a driver + if(this->mpDriver == playerAvatar->GetCharacter()) + { + // don't think we should ever hit this - the mpDriver is an NPC driver + int stophere = 1; + } + else + { + // there is both a driver and us + Character* us = playerAvatar->GetCharacter(); + choreo::Puppet* ourPuppet = us->GetPuppet(); + const rmt::Vector& ourPuppetPosition = ourPuppet->GetPosition(); + mOurRestSeatingPosition = ourPuppetPosition; + + choreo::Puppet* npcPuppet = mpDriver->GetPuppet(); + const rmt::Vector& npcPuppetPosition = npcPuppet->GetPosition(); + mNPCRestSeatingPosition = npcPuppetPosition; + + + } + } + else + { + // just us drivign? + Character* us = playerAvatar->GetCharacter(); + choreo::Puppet* ourPuppet = us->GetPuppet(); + const rmt::Vector& ourPuppetPosition = ourPuppet->GetPosition(); + mOurRestSeatingPosition = ourPuppetPosition; + + + } + + } + + } +} + + +//============================================================================= +// Vehicle::RestTest +//============================================================================= +// Description: Comment +// +// Parameters: (void) +// +// Return: void +// +//============================================================================= +void Vehicle::RestTest(void) +{ + // only traffic rests + if(mVehicleType != VT_TRAFFIC) + { + return; + } + + if(!GetSimState() || GetSimState()->GetControl() == sim::simAICtrl) + { + // already at rest + return; + } + + // check if smoothed velocities are below tolerance + if((mPastLinear.Smooth(GetSimState()->GetLinearVelocity().Magnitude()) < REST_LINEAR_TOL) && + (mPastAngular.Smooth(GetSimState()->GetAngularVelocity().Magnitude()) < REST_ANGULAR_TOL)) + { + // put it at rest + // this is done inside the loco-switch + //GetSimState()->SetControl(sim::simAICtrl); + //GetSimState()->ResetVelocities(); + + } +} + + + +//============================================================================= +// Vehicle::SelfRestTest +//============================================================================= +// Description: Comment +// +// Parameters: (void) +// +// Return: void +// +//============================================================================= +bool Vehicle::SelfRestTest(void) +{ + // new version of this for user and ai cars to call on themselves + // during physicslocomotion updates so that at very low speeds they come to a complete stop + + if(!GetSimState()) + { + rAssert(0); + return false; + } + + + int i; + int count = 0; + for(i = 0; i < 4; i++) + { + if(mWheels[i]->mWheelInCollision) + { + count++; + } + } + + /* + if(count == 0 && GetWorldPhysicsManager()->mWorldUp.DotProduct(mVehicleUp) < 0.0f) + { + // flipped + if((mPastLinear.Smooth(GetSimState()->GetLinearVelocity().Magnitude()) < REST_LINEAR_TOL) && + (mPastAngular.Smooth(GetSimState()->GetAngularVelocity().Magnitude()) < REST_ANGULAR_TOL)) + { + // put it at rest + // reset + + this->ResetOnSpot(false); + + } + + } + */ + Vehicle* playerVehicle = GetAvatarManager()->GetAvatarForPlayer(0)->GetVehicle(); + //Vehicle* playerVehicle = GetGameplayManager()->GetCurrentVehicle(); + + if(mGas < 0.1f && mBrake < 0.2f && count > 2) + { + + float dot = this->mVehicleUp.DotProduct(GetWorldPhysicsManager()->mWorldUp); + + float linear = mPastLinear.Smooth(GetSimState()->GetLinearVelocity().Magnitude()); + float angular = mPastAngular.Smooth(GetSimState()->GetAngularVelocity().Magnitude()); + + float ebrakefactor = 1.0f; + if(this->mEBrake == 1.0f) + { + //ebrakefactor = 2.0f; + } + + if(mUsingInCarPhysics) + { + if(1)//dot < 0.995f) + { + // steep, so more aggresive test + //if( linear < 2.0f * REST_LINEAR_TOL * ebrakefactor && angular < 2.0f * REST_ANGULAR_TOL * ebrakefactor) + if( linear < 3.0f * REST_LINEAR_TOL * ebrakefactor && angular < 3.0f * REST_ANGULAR_TOL * ebrakefactor) + { + //GetSimState()->SetControl(sim::simAICtrl); + //GetSimState()->ResetVelocities(); + + + if(this->mVehicleType == VT_TRAFFIC && this != playerVehicle && !mCreatedByParkedCarManager) + { + GetSimState()->SetControl(sim::simAICtrl); + GetSimState()->ResetVelocities(); + } + else + { + this->ZeroOutXZVelocity(); + } + + + mAtRestAsFarAsTriggersAreConcerned = true; + return true; + } + + } + else if( linear < REST_LINEAR_TOL * ebrakefactor && angular < REST_ANGULAR_TOL * ebrakefactor) + { + // put it at rest + //GetSimState()->SetControl(sim::simAICtrl); + //GetSimState()->ResetVelocities(); + //this->ZeroOutXZVelocity(); + + + if(this->mVehicleType == VT_TRAFFIC && this != playerVehicle && !mCreatedByParkedCarManager) + { + GetSimState()->SetControl(sim::simAICtrl); + GetSimState()->ResetVelocities(); + } + else + { + this->ZeroOutXZVelocity(); + } + + + + + mAtRestAsFarAsTriggersAreConcerned = true; + return true; + } + } + else + { + // more aggressive test + if( linear < 10.0f * REST_LINEAR_TOL * ebrakefactor && angular < 10.0f * REST_ANGULAR_TOL * ebrakefactor) + { + //GetSimState()->SetControl(sim::simAICtrl); + //GetSimState()->ResetVelocities(); + + //this->ZeroOutXZVelocity(); + + + if(this->mVehicleType == VT_TRAFFIC && this != playerVehicle && !mCreatedByParkedCarManager) + { + GetSimState()->SetControl(sim::simAICtrl); + GetSimState()->ResetVelocities(); + } + else + { + this->ZeroOutXZVelocity(); + } + + + + + mAtRestAsFarAsTriggersAreConcerned = true; + return true; + } + + } + } + else + { + if(0)//this->mVehicleType == VT_TRAFFIC) + { + GetSimState()->SetControl(sim::simSimulationCtrl); + } + } + mAtRestAsFarAsTriggersAreConcerned = false; + return false; +} + + + +//============================================================================= +// Vehicle::ZeroOutXZVelocity +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::ZeroOutXZVelocity() +{ + rmt::Vector& linear = mSimStateArticulated->GetLinearVelocity(); + + rmt::Vector& angular = mSimStateArticulated->GetAngularVelocity(); + + rmt::Vector newlinear = this->mVehicleUp; + float linearProj = linear.DotProduct(newlinear); + + newlinear.Scale(linearProj); + + linear = newlinear; + + + angular.Set(0.0f, 0.0f, 0.0f); +} + + +//============================================================================= +// Vehicle::SetGeometryVehicleWheelSmokeLevel +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::SetGeometryVehicleWheelSmokeLevel() +{ + // just based on burnout level for now + + if(mBurnoutLevel > 0.0f) + { + if(this->mSpeedBurstTimer >= 2.5f) + { + // TODO + // replace this with wheel flame + // MKR - replaced. Greg, does this work ok? + // -1 indicates apply to all wheel + mGeometryVehicle->SetWheelSmoke( -1, ParticleEnum::eFireSpray, mBurnoutLevel); + } + else + { + // TODO - if anyone notices or complains, switch this to + // set on a wheel by wheel basis + + // for now, just base on James's overall value + + //TT_Road, // Default road terrain. Also used for sidewalk. This is default. If not set, it's this. + //TT_Grass, // Grass type terrain most everything else which isn't road or sidewalk. + //TT_Sand, // Sand type terrain. + //TT_Gravel, // Loose gravel type terrain. + //TT_Water, // Water on surface type terrain. + //TT_Wood, // Boardwalks, docks type terrain. + //TT_Metal, // Powerplant and other structures. + //TT_Dirt, // Dirt type terrain. + //TT_NumTerrainTypes + + for ( int i = 0 ; i < 2 ; i++) + { + + switch( mPhysicsLocomotion->mTerrainIntersectCache[i].mTerrainType ) + { + case TT_Grass: + mGeometryVehicle->SetWheelSmoke( i, ParticleEnum::eGrassSpray, mBurnoutLevel); + break; + + case TT_Gravel: + case TT_Dirt: + case TT_Sand: + mGeometryVehicle->SetWheelSmoke( i, ParticleEnum::eDirtSpray, mBurnoutLevel); + break; + + case TT_Water: + mGeometryVehicle->SetWheelSmoke( i, ParticleEnum::eWaterSpray, mBurnoutLevel); + break; + + default: + mGeometryVehicle->SetWheelSmoke( i, ParticleEnum::eSmokeSpray, mBurnoutLevel); + break; + + } + } + } + } + else + { + mGeometryVehicle->SetWheelSmoke( -1, ParticleEnum::eNull, 0.0f); + } + +} + + + +//============================================================================= +// Vehicle::DSGUpdateAndMove +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::DSGUpdateAndMove() +{ + /* + rmt::Box3D mBBox; + rmt::Sphere mSphere; + rmt::Vector mPosn; + sim::SimState* mpSimStateObj; // TODO - ever care about this? + */ + + rmt::Box3D oldBox = mBBox; + + mPosn = *((rmt::Vector*)mTransform.m[3]); + + mBBox.low = mPosn; + mBBox.high = mPosn; + + // ? + // I think the same code should work for vehicle... + mBBox.high += mSimStateArticulated->GetCollisionObject()->GetCollisionVolume()->mBoxSize; + mBBox.low -= mSimStateArticulated->GetCollisionObject()->GetCollisionVolume()->mBoxSize; + + mSphere.centre.Sub(mBBox.high,mBBox.low); + mSphere.centre *= 0.5f; + mSphere.centre.Add(mBBox.low); + mSphere.radius = mSimStateArticulated->GetCollisionObject()->GetCollisionVolume()->mSphereRadius; + + + // now move! + GetRenderManager()->pWorldScene()->Move(oldBox, (IEntityDSG*)this); + + +} + + + +//============================================================================= +// Vehicle::UpdateGearAndRPM +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::UpdateGearAndRPM() +{ + // make sure this method can work on both + // physics and traffic updated vehicles. + + + float speedAlongFacing; + + speedAlongFacing = mVehicleFacing.DotProduct(mVelocityCM); + + // just use wheel 0 + + float linearDistancePerRev = rmt::PI * mWheels[0]->mRadius * 2.0f; + + float revPerTime = speedAlongFacing / linearDistancePerRev; + revPerTime = rmt::Fabs(revPerTime); + + // now we go backwards from this to figure out what the rpm is, + // and if necessary, shift gears + + // !!! TODO - deal with reverse! + + // first see if we're in neutral and just sitting there + //static float smallRevPerTime = 0.1f; + float smallRevPerTime = 0.1f; + if(mGas == 0.0f && revPerTime < smallRevPerTime) + { + mGear = 0; + mRPM = mBaseRPM; + return; + } + + // otherwise let's move! + if(mGear == 0) + { + mGear = 1; + } + + if((mVehicleState == VS_NORMAL || mGas == 0) && mBurnoutLevel == 0.0f && !mDoSpeedBurst) + { + //-------------------------- + // determine tentative value + //-------------------------- + float targetRPM = mGearRatios[mGear - 1] * mFinalDriveRatio * revPerTime * 60.0f; // 60.0f to get to minutes instead of seconds + + + //------------------------ + // only change by set rate + //------------------------ + if(targetRPM > mRPM) + { + // shouldn't be able to make arbitrary changes in rpm + mRPM += mRPMUpRate; + if(mRPM > targetRPM) + { + mRPM = targetRPM; + } + } + if(targetRPM < mRPM) + { + // shouldn't be able to make arbitrary changes in rpm + mRPM -= mRPMDownRate; + if(mRPM < targetRPM) + { + mRPM = targetRPM; + } + } + + //-------------------------- + // change gears if necessary + //-------------------------- + if(mRPM > mShiftPointHigh) + { + // change gears and recalculate + mGear++; + if(mGear > mNumGears) + { + mGear = mNumGears; + } + + } + else if(mRPM < mShiftPointLow) + { + mGear--; + if(mGear < 1) + { + mGear = 1; // TODO - again - deal with reverse! + } + + } + + //----------------------------------------- + // recalc target due to gear change - maybe + //----------------------------------------- + targetRPM = mGearRatios[mGear - 1] * mFinalDriveRatio * revPerTime * 60.0f; // 60.0f to get to minutes instead of seconds + + + //------------------------ + // only change by set rate + //------------------------ + if(targetRPM > mRPM) + { + // shouldn't be able to make arbitrary changes in rpm + mRPM += mRPMUpRate; + if(mRPM > targetRPM) + { + mRPM = targetRPM; + } + } + if(targetRPM < mRPM) + { + // shouldn't be able to make arbitrary changes in rpm + mRPM -= mRPMDownRate; + if(mRPM < targetRPM) + { + mRPM = targetRPM; + } + } + + //------------------------ + // lock no lower than base + //------------------------ + + if(mRPM < mBaseRPM) + { + mRPM = mBaseRPM; + } + } + else // VS_SLIP + { + if(mGas > 0.0f) + { + const float hack = 0.1f; + //mRPM *= 1.0f + (hack * mGas); + + const float hack2 = 3.1f; + if(mBurnoutLevel > 0.0f) + { + mRPM += mGas * mRPMUpRate * hack2; + } + else + { + mRPM += mGas * mRPMUpRate * hack; + } + + // gear same + } + if(mRPM > mShiftPointHigh) + { + mRPM = mShiftPointHigh; + } + } + + +} + + +//============================================================================= +// Vehicle::UpdateWheelRenderingInfo +//============================================================================= +// Description: Comment +// +// Parameters: (float dt) +// +// Return: void +// +//============================================================================= +void Vehicle::UpdateWheelRenderingInfo(float dt) +{ + int i; + for(i = 0; i < 4; i++) + { + // setup this method such that it can be called to get the right info + // cahced into the Wheel after either a physics or traffic update + // + // maybe this can be the single calculation-type method in + // Wheel. + mWheels[i]->CalculateRotAngle(dt); + } +} + + +//============================================================================= +// Vehicle::GetTransform +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: const +// +//============================================================================= +const rmt::Matrix& Vehicle::GetTransform() +{ + return mTransform; +} + + + +//======================================================================== +// vehicle:: +//======================================================================== +// +// Description: +// +// Parameters: None. +// +// Return: None. +// +// Constraints: None. +// +//======================================================================== +rmt::Vector* Vehicle::pPosition() +{ + return (rmt::Vector*)mTransform.m[3]; +} +//======================================================================== +// vehicle:: +//======================================================================== +// +// Description: +// +// Parameters: None. +// +// Return: None. +// +// Constraints: None. +// +//======================================================================== +const rmt::Vector& Vehicle::rPosition() +{ +// rAssert(false); +// return NULL; + return *((rmt::Vector*)mTransform.m[3]); +} + +//------------------------------------------------------------------------ +//============================================================================= +// Vehicle::Display +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::Display() +{ + ActivateTriggers(!(IsUnstable() || IsAirborn()) || (GetLocomotionType() == VL_TRAFFIC) || mAtRestAsFarAsTriggersAreConcerned); + + if(IS_DRAW_LONG) return; + + // not sure if these belong here? + //p3d::stack->Push(); + //p3d::stack->Multiply(mTransform); + + //return; + + + //DebugDisplay(); + //return; + + if( !mOkToDrawSelf || !mDrawVehicle ) + { + return; + } + + DSG_BEGIN_PROFILE(" Vehicle::Display") + BillboardQuadManager::Enable(); + //DisplaySimpleShadow(); + // TODO - how to set this up cleaner? + + // little debug test + // + //if(!(mWheels[2]->mWheelInCollision) || !(mWheels[3]->mWheelInCollision)) + if(0)//mWeebleOn) + //if(mDrawWireFrame) + //if(this->mAirBorn) + { + p3d::pddi->SetFillMode(PDDI_FILL_WIRE); + } + + + // temp!! + + //p3d::pddi->SetFillMode(PDDI_FILL_WIRE); + + + mPoseEngine->End(); // copy over what we're gonna render + mGeometryVehicle->Display(); + + + + //p3d::stack->Pop(); + + //if(!(mWheels[2]->mWheelInCollision) || !(mWheels[3]->mWheelInCollision)) + if(0)//mWeebleOn) + //if(mDrawWireFrame) + //if(this->mAirBorn) + { + p3d::pddi->SetFillMode(PDDI_FILL_SOLID); + } + // ugly, but just for debugging shit + mDrawWireFrame = false; + this->mLosingTractionDueToAccel = false; + + + BillboardQuadManager::Disable(); + DSG_END_PROFILE(" Vehicle::Display") +} + + +//============================================================================= +// Vehicle::DebugDisplay +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::DebugDisplay() +{ + mSimStateArticulated->DebugDisplay(2); // this one draws some sort of virtual centre of mass or something + sim::DrawCollisionObject(mSimStateArticulated->GetCollisionObject()); +} + + +//============================================================================= +// Vehicle::CarDisplay +//============================================================================= +// Description: Comment +// +// Parameters: (bool doit) +// +// Return: void +// +//============================================================================= +void Vehicle::CarDisplay(bool doit) +{ + mOkToDrawSelf = doit; +} + + +//============================================================================= +// Vehicle::GetSpeedKmh +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: float +// +//============================================================================= +float Vehicle::GetSpeedKmh() +{ + //return mSpeed * 3.6f; + return mSpeedKmh; +} + + +float Vehicle::GetAccelMss() +{ + return mAccelMss; +} + +//============================================================================= +// Vehicle::GetRPM +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: float +// +//============================================================================= +float Vehicle::GetRPM() +{ + return mRPM; +} + + +//============================================================================= +// Vehicle::GetSkidLevel +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: float +// +//============================================================================= +float Vehicle::GetSkidLevel() +{ + return mSkidLevel; +} + + +//============================================================================= +// Vehicle::GetGear +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: int +// +//============================================================================= +int Vehicle::GetGear() +{ + // TODO - how to do the gearshift interface? + + return mGear; +} + + +//---------------------- +// camera inteface stuff +//---------------------- + +//============================================================================= +// Vehicle::GetPosition +//============================================================================= +// Description: Comment +// +// Parameters: ( rmt::Vector* position ) +// +// Return: void +// +//============================================================================= +void Vehicle::GetPosition( rmt::Vector* position ) +{ + *position = *((rmt::Vector*)mTransform.m[3]); +} + + +//============================================================================= +// Vehicle::GetHeading +//============================================================================= +// Description: Comment +// +// Parameters: ( rmt::Vector* heading ) +// +// Return: void +// +//============================================================================= +void Vehicle::GetHeading( rmt::Vector* heading ) +{ + *heading = mVehicleFacing; +} + + +//============================================================================= +// Vehicle::GetVUP +//============================================================================= +// Description: Comment +// +// Parameters: ( rmt::Vector* vup ) +// +// Return: void +// +//============================================================================= +void Vehicle::GetVUP( rmt::Vector* vup ) +{ + *vup = mVehicleUp; +} + + +//============================================================================= +// Vehicle::GetVelocity +//============================================================================= +// Description: Comment +// +// Parameters: ( rmt::Vector* velocity ) +// +// Return: void +// +//============================================================================= +void Vehicle::GetVelocity( rmt::Vector* velocity ) +{ + // TODO - make sure this holds the right thing when + // we are being traffic locomoted + *velocity = mVelocityCM; +} + +//============================================================================= +// Vehicle::GetID +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: unsigned +// +//============================================================================= +unsigned int Vehicle::GetID() +{ + return mVehicleID; +} + +//============================================================================= +// Vehicle::IsCar +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: bool +// +//============================================================================= +bool Vehicle::IsCar() const +{ + return true; +} + + +//============================================================================= +// Vehicle::IsAirborn +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: bool +// +//============================================================================= +bool Vehicle::IsAirborn() +{ + //return mSteeringWheelsOutOfContact; + return mAirBorn; +} + + +//============================================================================= +// Vehicle::IsUnstable +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: bool +// +//============================================================================= +bool Vehicle::IsUnstable() +{ + // see how much it's tipped... + rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; + float tip = mVehicleUp.DotProduct(up); + + //float cos10 = 0.9848f; + float cos30 = 0.866f; + if(tip < cos30) + { + return true; + } + + if(mAirBorn) + { + return true; + } + + return false; + + // quick test + //return mWeebleOn; +} + + +//============================================================================= +// Vehicle::IsSafeToUpShift +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: bool +// +//============================================================================= +bool Vehicle::IsSafeToUpShift() +{ + // let's say if at least 1 wheel in contact with ground (ie. !airborn) + // and the tip angle is less than 15 degrees, then ok. + + if(!mAirBorn) + { + rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; + float tip = mVehicleUp.DotProduct(up); + + //float cos15 = 0.9659f; + float cos10 = 0.9848f; + + if( (tip > cos10) || (mVehicleFacing.DotProduct(up) <= 0.0f) ) + { + return true; + } + + } + + return false; +} + + +//============================================================================= +// Vehicle::IsQuickTurn +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: bool +// +//============================================================================= +bool Vehicle::IsQuickTurn() +{ + // TODO + return false; +} + +//============================================================================= +// Vehicle::IsInReverse +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: bool +// +//============================================================================= +bool Vehicle::IsInReverse() +{ + //return false; + + // first heuristic + // + // if the brake button is down and we're travelling backwards + float proj = mVelocityCM.DotProduct(mVehicleFacing); + + // don't want just any slight backwards motion to trigger + const float cos120 = -0.5f; + + if(mBrake > 0.1f && proj < cos120) + { + return true; + } + + return false; +} + +//============================================================================= +// Vehicle::GetTerrainIntersect +//============================================================================= +// Description: Comment +// +// Parameters: ( rmt::Vector& pos, rmt::Vector& normal ) +// +// Return: void +// +//============================================================================= +void Vehicle::GetTerrainIntersect( rmt::Vector& pos, rmt::Vector& normal ) const +{ + //THIS IS A LIE! + pos = mTransform.Row(3); + normal.Set( 0.0f, 1.0f, 0.0f ); +} + +//============================================================================= +// Vehicle::IsMovingBackward +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: bool +// +//============================================================================= +bool Vehicle::IsMovingBackward() +{ + // this one does not require the brake input to be held down + + if(this->mPercentOfTopSpeed > 0.05f) + { + float proj = mVelocityCM.DotProduct(mVehicleFacing); + + const float cos120 = -0.5f; + + if(proj < cos120) + { + return true; + } + } + + return false; + + +} + +//============================================================================= +// Vehicle::GetName +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: const +// +//============================================================================= +const char* const Vehicle::GetName() +{ + return (const char*)mName; +} + +const rmt::Vector& Vehicle::GetPassengerLocation( void ) const +{ + return mPassengerLocation; +} + +const rmt::Vector& Vehicle::GetDriverLocation( void ) const +{ + return mDriverLocation; +} +//============================================================================= +// Vehicle::PreReactToCollision +//============================================================================= +// Description: Comment +// +// Parameters: ( sim::SimState* pCollidedObj, sim::Collision& inCollision ) +// +// Return: bool +// +//============================================================================= +sim::Solving_Answer Vehicle::PreReactToCollision( sim::SimState* pCollidedObj, sim::Collision& inCollision ) +{ + // this is called from the generic worldcollisionsolveragentmanager + + // don't abort + return sim::Solving_Continue; +} + + + +//============================================================================= +// Vehicle::SetHitJoint +//============================================================================= +// Description: Comment +// +// Parameters: (int hj) +// +// Return: void +// +//============================================================================= +/* +void Vehicle::SetHitJoint(int hj) +{ + // want to try and use the most "interesting" value + + if(IsAFlappingJoint(hj)) + { + // set for sure + mHitJoint = hj; + return; + } + + if(IsAFlappingJoint(mHitJoint)) + { + // we already have a flapping joint set and the incoming one is not + return; + } + + // ok we'll use it. + mHitJoint = hj; + + +} +*/ + + + +//============================================================================= +// Vehicle::ResetDamageState +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::ResetDamageState() +{ + mVehicleDestroyed = false; + mDontShowBrakeLights = false; + mGeometryVehicle->SetLightsOffDueToDamage(false); + mDamageOutResetTimer = 0.0f; + mHitPoints = mDesignerParams.mHitPoints; + + mDesiredDoorPosition[0] = mDesiredDoorPosition[1] = 0.0f; + mDesiredDoorAction[0] = mDesiredDoorAction[1] = DOORACTION_NONE; + + ActivateTriggers(true); + + if (mbPlayerCar ==true) + { + GetCharacterSheetManager()->UpdateCarHealth( mCharacterSheetCarIndex, 1.0f ); + } + + switch(mDamageType) + { + //case 1: + case VDT_USER: + { + // reset flapping joints + + if(mHoodJoint != -1) + { + int index = mJointIndexToInertialJointDriverMapping[mHoodJoint]; + if(index != -1) + { + mPhObj->GetJoint(mHoodJoint)->SetInvStiffness(0.0f); + mPhObj->GetJoint(mHoodJoint)->ResetDeformation(); + mInertialJointDrivers[index]->SetIsEnabled(false); + } + } + + + if(mTrunkJoint != -1) + { + int index = mJointIndexToInertialJointDriverMapping[mTrunkJoint]; + if(index != -1) + { + mPhObj->GetJoint(mTrunkJoint)->SetInvStiffness(0.0f); + mPhObj->GetJoint(mTrunkJoint)->ResetDeformation(); + mInertialJointDrivers[index]->SetIsEnabled(false); + } + } + + if(mDoorDJoint != -1) + { + int index = mJointIndexToInertialJointDriverMapping[mDoorDJoint]; + if(index != -1) + { + mPhObj->GetJoint(mDoorDJoint)->SetInvStiffness(0.0f); + mPhObj->GetJoint(mDoorDJoint)->ResetDeformation(); + mInertialJointDrivers[index]->SetIsEnabled(false); + } + } + + + if(mDoorPJoint != -1) + { + int index = mJointIndexToInertialJointDriverMapping[mDoorPJoint]; + if(index != -1) + { + mPhObj->GetJoint(mDoorPJoint)->SetInvStiffness(0.0f); + mPhObj->GetJoint(mDoorPJoint)->ResetDeformation(); + mInertialJointDrivers[index]->SetIsEnabled(false); + } + } + + mGeometryVehicle->DamageTextureHood(false); + mGeometryVehicle->DamageTextureTrunk(false); + mGeometryVehicle->DamageTextureDoorD(false); + mGeometryVehicle->DamageTextureDoorP(false); + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eNull); + + mGeometryVehicle->HideFlappingPiece(mHoodJoint, false); + + } + break; + + //case 2: + case VDT_AI: + { + mGeometryVehicle->DamageTextureHood(false); + mGeometryVehicle->DamageTextureTrunk(false); + mGeometryVehicle->DamageTextureDoorD(false); + mGeometryVehicle->DamageTextureDoorP(false); + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eNull); + } + break; + + //case 3: + case VDT_TRAFFIC: + { + //mGeometryVehicle->DamageTextureChassis(false); + //mGeometryVehicle->SetEngineSmoke(ParticleEnum::eNull); + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eNull); + } + break; + + default: + rAssertMsg(0, "what are you doing here?"); + + } + +} + +//============================================================================= +// Vehicle::DebugInflictDamageHood +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::DebugInflictDamageHood() +{ + //int stophere = 1; + + + + //float perc = GetVehicleLifePercentage(); + float perc = TriggerDamage(0.15f); + + switch(mDamageType) + { + //case 1: // user level - flaping joints etc... + case VDT_USER: + { + VisualDamageType1(1.0f - perc, dl_hood); + } + break; + + //case 2: // ai - localized damage textures but no flapping + case VDT_AI: + { + VisualDamageType2(1.0f - perc, dl_hood); + } + break; + + //case 3: // traffic - one big poof and texture + case VDT_TRAFFIC: + { + VisualDamageType3(1.0f - perc); + } + break; + + default: + rAssertMsg(0, "what are you doing here?"); + } + + +} + +//============================================================================= +// Vehicle::DebugInflictDamageBack +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::DebugInflictDamageBack() +{ + + //TriggerDamage(0.15f); + //float perc = GetVehicleLifePercentage(); + + + //float perc = GetVehicleLifePercentage(); + float perc = TriggerDamage(0.15f); + + + switch(mDamageType) + { + //case 1: // user level - flaping joints etc... + case VDT_USER: + { + VisualDamageType1(1.0f - perc, dl_trunk); + } + break; + + //case 2: // ai - localized damage textures but no flapping + case VDT_AI: + { + VisualDamageType2(1.0f - perc, dl_trunk); + } + break; + + //case 3: // traffic - one big poof and texture + case VDT_TRAFFIC: + { + VisualDamageType3(1.0f - perc); + } + break; + + default: + rAssertMsg(0, "what are you doing here?"); + } +} + +//============================================================================= +// Vehicle::DebugInflictDamageDriverSide +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::DebugInflictDamageDriverSide() +{ + + //TriggerDamage(0.15f); + //float perc = GetVehicleLifePercentage(); + + + //float perc = GetVehicleLifePercentage(); + float perc = TriggerDamage(0.15f); + + + switch(mDamageType) + { + //Case 1: // user level - flaping joints etc... + case VDT_USER: + { + VisualDamageType1(1.0f - perc, dl_driverside); + } + break; + + //case 2: // ai - localized damage textures but no flapping + case VDT_AI: + { + VisualDamageType2(1.0f - perc, dl_driverside); + } + break; + + //case 3: // traffic - one big poof and texture + case VDT_TRAFFIC: + { + VisualDamageType3(1.0f - perc); + } + break; + + default: + rAssertMsg(0, "what are you doing here?"); + } + +} + +//============================================================================= +// Vehicle::DebugInflictDamagePassengerSide +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::DebugInflictDamagePassengerSide() +{ + //TriggerDamage(0.15f); + + //float perc = GetVehicleLifePercentage(); + + + //float perc = GetVehicleLifePercentage(); + float perc = TriggerDamage(0.15f); + + + switch(mDamageType) + { + //case 1: // user level - flaping joints etc... + case VDT_USER: + { + VisualDamageType1(1.0f - perc, dl_passengerside); + } + break; + + //case 2: // ai - localized damage textures but no flapping + case VDT_AI: + { + VisualDamageType2(1.0f - perc, dl_passengerside); + } + break; + + //case 3: // traffic - one big poof and texture + case VDT_TRAFFIC: + { + VisualDamageType3(1.0f - perc); + } + break; + + default: + rAssertMsg(0, "what are you doing here?"); + } + +} + +//============================================================================= +// Vehicle::PostReactToCollision +//============================================================================= +// Description: Comment +// +// Parameters: ( sim::SimState* pCollidedObj, sim::Collision& inCollision ) +// +// Return: sim +// +//============================================================================= +sim::Solving_Answer Vehicle::PostReactToCollision(rmt::Vector& impulse, sim::Collision& inCollision) +{ + + // this is called from the generic worldcollisionsolveragentmanager + + // don't abort + + sim::CollisionObject* collObjA = inCollision.mCollisionObjectA; + sim::CollisionObject* collObjB = inCollision.mCollisionObjectB; + + sim::SimState* simStateA = collObjA->GetSimState(); + sim::SimState* simStateB = collObjB->GetSimState(); + + + float impulseMagnitude = impulse.Magnitude(); + + const float maxIntensity = 100000.0f; // empircally determined + + // Check to see if the impact was strong enough to detach any attached collectibles. + if ( impulseMagnitude > mForceToDetachCollectible ) + { + rmt::Vector velocity = GetSimState()->GetLinearVelocity(); + DetachCollectible( velocity ); + } + + float normalizedMagnitude = impulseMagnitude / maxIntensity; + + + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickVehicle && simStateB->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + // stricter test + if(normalizedMagnitude > 0.8f) + { + return sim::Solving_Aborted; + } + else if( ( ((Vehicle*)(simStateA->mAIRefPointer))->mVehicleID == VehicleEnum::BART_V && ((Vehicle*)(simStateB->mAIRefPointer))->mVehicleID == VehicleEnum::CKLIMO) || + ( ((Vehicle*)(simStateA->mAIRefPointer))->mVehicleID == VehicleEnum::CKLIMO && ((Vehicle*)(simStateB->mAIRefPointer))->mVehicleID == VehicleEnum::BART_V) ) + + { + if(normalizedMagnitude > 0.5f) + { + return sim::Solving_Aborted; + } + } + } + + if(normalizedMagnitude > 1.0f) + { + rDebugPrintf("Yikes!!! Enormous impulse!\n"); + //impulse.x /= normalizedMagnitude * 2; + //impulse.y /= normalizedMagnitude * 2; + //impulse.z /= normalizedMagnitude * 2; + if(normalizedMagnitude > 2.0f) + { + return sim::Solving_Aborted; + } + + + normalizedMagnitude = 1.0f; + } + if(normalizedMagnitude < 0.0f) + { + rAssert(0); + } + + + if(mVehicleID == VehicleEnum::HUSKA) + { + normalizedMagnitude = 0.2f; + } + + if(this->mVehicleID == VehicleEnum::DUNE_V) + { + if( simStateA->mAIRefIndex == PhysicsAIRef::redBrickPhizFence || + simStateB->mAIRefIndex == PhysicsAIRef::redBrickPhizFence) + + { + // r/c car hitting a fence + //? scale up impulse? + + impulse.Scale(2.0f); + } + } + + + //Rumble the controller + RumbleCollision rc; + rc.normalizedForce = normalizedMagnitude; + rc.vehicle = this; + rc.point = inCollision.GetPositionA(); //This could be either. + + GetEventManager()->TriggerEvent( EVENT_RUMBLE_COLLISION, &rc ); + + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickVehicle && simStateB->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + TestWhoHitWhom( simStateA, simStateB, normalizedMagnitude, inCollision ); + if(mVehicleType != VT_USER) + { + DusitsStunTest(normalizedMagnitude); + } + // Vehicle - vehicle collision + // we want to emit paint flicks of the appropriate colour + if ( mGeometryVehicle->HasVehicleColour() ) + { + tColour vehicleColour = mGeometryVehicle->GetVehicleColour(); + float strength = impulseMagnitude * ( 1.0f / 5000.0f ); + float velocityScale = strength; + const rmt::Vector& position = inCollision.GetPositionA(); + rmt::Vector velocity = mVelocityCM; + if ( velocity.MagnitudeSqr() > 0.01f ) + { + velocity.y = 0.0f; + velocity.Normalize(); + velocity.Scale( velocityScale ); + } + else + { + velocity = rmt::Vector(0,0,0); + } + + GetSparkleManager()->AddPaintChips( position, velocity, vehicleColour, strength ); + } + + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + if(playerAvatar->GetVehicle() == this) + { + CarOnCarCollisionEventData data; + + //data.vehicle shall be the other vehicle. + if ( simStateA->mAIRefPointer == this ) + { + data.vehicle = ((Vehicle*)(simStateB->mAIRefPointer)); + } + else + { + data.vehicle = ((Vehicle*)(simStateA->mAIRefPointer)); + } + data.force = normalizedMagnitude; + data.collision = &inCollision; + GetEventManager()->TriggerEvent( EVENT_VEHICLE_VEHICLE_COLLISION, &data ); + } + } + + if(mVehicleType == VT_USER) + { + SparksTest(impulseMagnitude, inCollision); // should this also be wrapped in mUserDrivingCar?? + } + + //which one are we? + bool thisIsA = true; + + if(simStateA->mAIRefPointer == this) + { + // ok + } + else + { + thisIsA = false; + } + + // try movign this down so that husks hitting cars and statics will still make sound + //if(mVehicleID == VehicleEnum::HUSKA) + //{ + // return sim::Solving_Continue; + //} + + // new debug test just for traffic collision + bool oneTapTrafficDeath = false; + + + if( GetCheatInputSystem()->IsCheatEnabled(CHEAT_ID_ONE_TAP_TRAFFIC_DEATH) ) + { + + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickVehicle && simStateB->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + if(thisIsA) + { + if( (((Vehicle*)(simStateA->mAIRefPointer))->mVehicleType == VT_TRAFFIC || ((Vehicle*)(simStateA->mAIRefPointer))->mVehicleType == VT_AI) && + ((Vehicle*)(simStateB->mAIRefPointer))->mVehicleType == VT_USER) + { + int stophere = 1; + oneTapTrafficDeath = true; + + } + } + else + { + if( (((Vehicle*)(simStateB->mAIRefPointer))->mVehicleType == VT_TRAFFIC || ((Vehicle*)(simStateB->mAIRefPointer))->mVehicleType == VT_AI) && + ((Vehicle*)(simStateA->mAIRefPointer))->mVehicleType == VT_USER) + + { + int stophere = 1; + oneTapTrafficDeath = true; + } + } + } + } + + // This is the "minimum damage threshold" for applying ANY damage + // - think of the number as a percentage, i.e., any hit less than .1 (10 &) intensity + // won't cause any damage at all + const float minDamageThreshold = 0.06f; + + + + if(!oneTapTrafficDeath) + { + if(normalizedMagnitude < minDamageThreshold) + { + //#ifdef RAD_DEBUG + //char buffy[128]; + //sprintf(buffy, "normalizedMagnitude %.4f\n", normalizedMagnitude); + //rDebugPrintf(buffy); + //#endif + + return sim::Solving_Continue; + } + } + + + + + + //Greg says to do this. Won't work in 2-player. + if (mUserDrivingCar) + { + CameraShakeTest(impulseMagnitude, inCollision); // pass struct, by reference, for convenience + // pass in impulseMagnitude because we don't want to compute it twice. + } + + + + + SoundCollisionData soundData( normalizedMagnitude, + static_cast<CollisionEntityDSG*>(simStateA->mAIRefPointer), + static_cast<CollisionEntityDSG*>(simStateB->mAIRefPointer) ); + GetEventManager()->TriggerEvent( EVENT_COLLISION, &soundData ); + + + if(mVehicleID == VehicleEnum::HUSKA) + { + return sim::Solving_Continue; + } + + + + // before further testing, see if this is a car that hit it's own ground plane + // if so, abort + + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + + if(GetGameplayManager()->GetGameType() != GameplayManager::GT_SUPERSPRINT && playerAvatar->GetVehicle() == this) + { + const float percentageOfSpeedToMaintain = 0.85f; // plum, change this number + + if(thisIsA) + { + if(simStateB->mAIRefIndex == PhysicsAIRef::redBrickPhizVehicleGroundPlane && this->mGas > 0.0f) + { + if(mBottomOutSpeedMaintenance == 0.0f) + { + // not set + mBottomOutSpeedMaintenance = this->mSpeed * percentageOfSpeedToMaintain; + } + else + { + // it is set + // + // so.... maintain it! + if(mBottomOutSpeedMaintenance > (this->mDesignerParams.mDpTopSpeedKmh / 3.6f) * 0.6f) + { + rmt::Vector& linearVel = mSimStateArticulated->GetLinearVelocity(); + + float proj = linearVel.DotProduct(this->mVehicleFacing); + if(proj < mBottomOutSpeedMaintenance) + { + float diff = mBottomOutSpeedMaintenance - proj; + rmt::Vector boost = mVehicleFacing; + boost.Scale(diff); + + linearVel.Add(boost); + + } + + + } + } + + return sim::Solving_Continue; + } + else + { + // didn't hit our ground plane this frame so make sure the maintenance speed is reset + mBottomOutSpeedMaintenance = 0.0f; + } + } + else + { + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickPhizVehicleGroundPlane && this->mGas > 0.0f) + { + + if(mBottomOutSpeedMaintenance == 0.0f) + { + // not set + mBottomOutSpeedMaintenance = this->mSpeed * percentageOfSpeedToMaintain; + } + else + { + // it is set + // + // so.... maintain it! + + + if(mBottomOutSpeedMaintenance > (this->mDesignerParams.mDpTopSpeedKmh / 3.6f) * 0.6f) + { + rmt::Vector& linearVel = mSimStateArticulated->GetLinearVelocity(); + + float proj = linearVel.DotProduct(this->mVehicleFacing); + if(proj < mBottomOutSpeedMaintenance) + { + float diff = mBottomOutSpeedMaintenance - proj; + rmt::Vector boost = mVehicleFacing; + boost.Scale(diff); + + linearVel.Add(boost); + + } + + + } + + + } + + + return sim::Solving_Continue; + } + else + { + // didn't hit our ground plane this frame so make sure the maintenance speed is reset + mBottomOutSpeedMaintenance = 0.0f; + } + + } + } + + + + + + + bool inflictDamage = true; + + + if(simStateA->mAIRefIndex == PhysicsAIRef::redBrickVehicle && simStateB->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + + // lateral resistance drop + + if(mVehicleType != VT_USER) + { + rmt::Vector otherFacing; + if(thisIsA) + { + otherFacing = ((Vehicle*)(simStateB->mAIRefPointer))->mVehicleFacing; + } + else + { + otherFacing = ((Vehicle*)(simStateA->mAIRefPointer))->mVehicleFacing; + } + + float dp = mVehicleFacing.DotProduct(otherFacing); + if(dp < 0.1f) + { + dp = 0.1f; + } + + mCollisionLateralResistanceDropFactor = dp; + + + // exaggerated hit results - note the test for ! VT_USER ain't gonna help here + /* + rmt::Vector funImpulse = impulse; + funImpulse.NormalizeSafe(); + funImpulse.y += 0.7f; + + funImpulse.Scale(impulseMagnitude); + + impulse = funImpulse; + */ + + } + + + bool carOnCarDamage = CarOnCarDamageLogic(thisIsA, simStateA, simStateB); + + inflictDamage &= carOnCarDamage; + + + } + + + + + if(GetCheatInputSystem()->IsCheatEnabled(CHEAT_ID_INVINCIBLE_CAR)) + { + if(GetAvatarManager()->GetAvatarForPlayer(0)->GetVehicle() == this) + { + return sim::Solving_Continue; + } + } + + // time for damage inc. + + // debug cheat for the testers... + + if(GetCheatInputSystem()->IsCheatEnabled(CHEAT_ID_FULL_DAMAGE_TO_CAR) && mVehicleType == VT_USER) + { + // debug cheat + + // note - don't actually decrememtn hitpoints, just draw all damaged. + + switch(mDamageType) + { + //case 1: // user level - flaping joints etc... + case VDT_USER: + { + DamageLocation dl = TranslateCollisionIntoLocation(inCollision); + VisualDamageType1(0.995f, dl); // any point in this actually taking in a parameter + + + + } + break; + + //case 2: // ai - localized damage textures but no flapping + case VDT_AI: + { + DamageLocation dl = TranslateCollisionIntoLocation(inCollision); + VisualDamageType2(0.995f, dl); // any point in this actually taking in a parameter + + } + break; + + //case 3: // traffic - one big poof and texture + case VDT_TRAFFIC: + { + VisualDamageType3(0.995f); // any point in this actually taking in a parameter + } + break; + + default: + rAssertMsg(0, "what are you doing here?"); + + } + + + } + // normal case + else if((mVehicleCanSustainDamage && inflictDamage) || oneTapTrafficDeath)// && this->mVehicleType != VT_TRAFFIC) // todo - definately need to reassess the traffic test here + { + if(oneTapTrafficDeath) + { + this->TriggerDamage(100.0f); + } + else if(mNoDamageTimer == 0.0f) + { + if( simStateA->mAIRefIndex == PhysicsAIRef::redBrickVehicle && + simStateB->mAIRefIndex == PhysicsAIRef::redBrickVehicle ) + { + // The hitter sustains less damage, the hit sustains more + if( !mWasHitByVehicle ) + { + normalizedMagnitude *= 0.5f; // I was the hitter! Yes! + } + } + SwitchOnDamageTypeAndApply(normalizedMagnitude, inCollision); + } + } + + + // test + //static float fun = 2.0f; + //impulse.Scale(fun); + + return sim::Solving_Continue; + +} + +//============================================================================= +// Vehicle::DusitsStunTest +//============================================================================= +// Description: Comment +// +// Parameters: (float normalizedMagnitude) +// +// Return: void +// +//============================================================================= +void Vehicle::DusitsStunTest(float normalizedMagnitude) +{ + mCollidedWithVehicle = true; + /* + const float AI_VEHICLE_STUNNED_IMPACT_THRESHOLD = 0.01f; + if( normalizedMagnitude > AI_VEHICLE_STUNNED_IMPACT_THRESHOLD ) + { + mCollidedWithVehicle = true; + } + */ +} +void Vehicle::TestWhoHitWhom( sim::SimState* simA, sim::SimState* simB, float normalizedMagnitude, const sim::Collision& inCollision ) +{ + rAssert( simA->mAIRefIndex == PhysicsAIRef::redBrickVehicle && + simB->mAIRefIndex == PhysicsAIRef::redBrickVehicle ); + + sim::Collision simCollision = inCollision; + rAssert( rmt::Epsilon( simCollision.mNormal.MagnitudeSqr(),1.0f,0.0005f ) ); + + // Want to determine which one I am. + // We also want collision normal to point from him to me.. + // But since collision normal for all sim::Collision always points from B to A, + // if he is simState A, then invert the collision normal. + + Vehicle* him = NULL; + if( simA->mAIRefPointer == this ) + { + him = (Vehicle*)simB->mAIRefPointer; + } + else + { + him = (Vehicle*)simA->mAIRefPointer; + simCollision.mNormal.Scale( -1.0f ); + } + + rmt::Vector myVel; + GetVelocity( &myVel ); + + bool gotHitByHim = false; + float velThreshold = 0.005f; + + // If one of the speeds is near zero, it's obvious who hit whom... + if( mSpeed < velThreshold ) + { + gotHitByHim = true; + } + else if( him->mSpeed < velThreshold ) + { + gotHitByHim = false; + } + else + { + // Deal with the ambiguous case of who hit whom, when + // both velocities are > zero. Sooo... + // The collision normal is always pointing from him to me and is + // at this point NORMALIZED. We steal this and invert the vector + // to get the heading vector from US to HIM. + rmt::Vector toHim = simCollision.mNormal * -1.0f; + rAssert( rmt::Epsilon( toHim.MagnitudeSqr(), 1.0f, 0.001f ) ); + + // Now... if my velocity vector is NOT pointing in "more or less" + // the same direction as my vector to him, then I'm not the hitter, + // so I got hit by him... + rmt::Vector myNormalizedVel = myVel / mSpeed; + rAssert( rmt::Epsilon( myNormalizedVel.MagnitudeSqr(), 1.0f, 0.001f ) ); + + const float cosAlphaTest = 0.8660254f; //approx cos30 (in either directions) + if( myNormalizedVel.Dot( toHim ) < cosAlphaTest ) // angle greater than cosalpha + { + gotHitByHim = true; // we're not the hitter, so we're the hit + } + else + { + // ok, we're the hitter... BUT there's a special case for when + // we're in a head-on collision with the other car. If we detect + // this case we don't want to set both as the hitter... we want + // each party to take full damage.... + + rmt::Vector hisVel; + him->GetVelocity( &hisVel ); + rmt::Vector hisNormalizedVel = hisVel / him->mSpeed; + rAssert( rmt::Epsilon( hisNormalizedVel.MagnitudeSqr(), 1.0f, 0.001f ) ); + + rmt::Vector toMe = toHim * -1.0f; + rAssert( rmt::Epsilon( toMe.MagnitudeSqr(), 1.0f, 0.001f ) ); + + if( hisNormalizedVel.Dot( toMe ) < cosAlphaTest ) + { + // angle greater than tolerance, so he's not headed at me + // therefore not a head-on collision. So do the setting + // normally. + gotHitByHim = false; // we're the hitter + } + else + { + // we're the hit (because he's coming straight at us with some + // tangible velocity.) + gotHitByHim = true; + } + } + + } + + // ok we figured out that ya we got hit, so store the appropriate data + if( gotHitByHim ) + { + // Do special transit to slip for all vehicles + // NOTE: If don't want player vehicle to transit to slip from impact, + // just gotta put in a check here that we're not player vehicle + if( normalizedMagnitude > 0.035f ) + { + mVehicleState = VS_SLIP; + } + mWasHitByVehicle = true; + mWasHitByVehicleType = him->mVehicleType; + mNormalizedMagnitudeOfVehicleHit = normalizedMagnitude; + mSwerveNormal = simCollision.mNormal; + } +} + + +//============================================================================= +// Vehicle::SwitchOnDamageTypeAndApply +//============================================================================= +// Description: Comment +// +// Parameters: (float normalizedMagnitude) +// +// Return: void +// +//============================================================================= +void Vehicle::SwitchOnDamageTypeAndApply(float normalizedMagnitude, sim::Collision& inCollision) +{ + + // first just decrement the hit points + //TriggerDamage(normalizedMagnitude); + + // make sure appropriate effects level is playing + + //float perc = GetVehicleLifePercentage(); + float perc = TriggerDamage(normalizedMagnitude); + + switch(mDamageType) + { + //case 1: // user level - flaping joints etc... + case VDT_USER: + { + DamageLocation dl = TranslateCollisionIntoLocation(inCollision); + VisualDamageType1(1.0f - perc, dl); // any point in this actually taking in a parameter + + + + } + break; + + //case 2: // ai - localized damage textures but no flapping + case VDT_AI: + { + DamageLocation dl = TranslateCollisionIntoLocation(inCollision); + VisualDamageType2(1.0f - perc, dl); // any point in this actually taking in a parameter + + } + break; + + case VDT_TRAFFIC: + { + VisualDamageType3(1.0f - perc); // any point in this actually taking in a parameter + } + break; + + default: + rAssertMsg(0, "what are you doing here?"); + + } + + + +} + + +//============================================================================= +// Vehicle::CarOnCarDamageLogic +//============================================================================= +// Description: Comment +// +// Parameters: ( sim::SimState* simStateA, sim::SimState* simStateB) +// +// Return: bool +// +//============================================================================= +bool Vehicle::CarOnCarDamageLogic(bool thisIsA, sim::SimState* simStateA, sim::SimState* simStateB) +{ + + if(this->mIsADestroyObjective) + { + if(thisIsA) + { + if( ((Vehicle*)(simStateB->mAIRefPointer))->mVehicleType == VT_USER) + { + // destroy objective hit by user car so it should take damage! + return true; + } + else + { + //inflictDamage = false; + return false; + } + } + else + { + if( ((Vehicle*)(simStateA->mAIRefPointer))->mVehicleType == VT_USER) + { + // destroy objective hit by user car so it should take damage + return true; + } + else + { + //inflictDamage = false; + return false; + } + } + + + } + else if(this->mVehicleType == VT_USER) + { + + // I want to know if this is a dump mission - if so, I shouldn't take damage.... + + + + if(thisIsA) + { + if( ((Vehicle*)(simStateB->mAIRefPointer))->mIsADestroyObjective) + { + // user car hitting a destroy objective so it should not take damage + //inflictDamage = false; + + //return false; + + // test - April 8, 2003 + // even during a destroy mission, the user will take damage when hitting the destory objective + // makes stronger cars more important + return true; + + } + } + else + { + if( ((Vehicle*)(simStateA->mAIRefPointer))->mIsADestroyObjective) + { + // destroy objective hit by user car so it should take damage + //inflictDamage = false; + + + //return false; + // see note above + return true; + } + } + + } + + return true; +} + + +//============================================================================= +// Vehicle::CameraShakeTest +//============================================================================= +// Description: Comment +// +// Parameters: (float impulseMagnitude, sim::Collision& inCollision) +// +// Return: void +// +//============================================================================= +void Vehicle::CameraShakeTest(float impulseMagnitude, sim::Collision& inCollision) +{ + + //TODO: Greg can you make this more reliable? Should I take into account + //the mass of the vehicle? + //Fudge some magnitude calc to send to the shaker. + + // greg responds: the mass is taken into account in the size of the impulse - I think...? + + const float cameraShakeThreshold = 19000.0f; + const float maxIntensity = 100000.0f; // copied from PostReactToCollision, but doesn't have to be the same + + if(impulseMagnitude > cameraShakeThreshold) + { + ShakeEventData shakeData; + shakeData.playerID = 0; //Hack... + shakeData.force = impulseMagnitude / maxIntensity; + + rmt::Vector pointOnOtherObject; + + sim::CollisionObject* collObjA = inCollision.mCollisionObjectA; + sim::CollisionObject* collObjB = inCollision.mCollisionObjectB; + + sim::SimState* simStateA = collObjA->GetSimState(); + sim::SimState* simStateB = collObjB->GetSimState(); + + if(simStateA == mSimStateArticulated) + { + pointOnOtherObject = inCollision.GetPositionB(); + } + else if(simStateB == mSimStateArticulated) + { + pointOnOtherObject = inCollision.GetPositionA(); + } + else + { + rAssertMsg(0, "Ask Greg what went wrong!"); + } + + rmt::Vector contactPointToCenter; + contactPointToCenter.Sub(this->rPosition(), pointOnOtherObject); + contactPointToCenter.NormalizeSafe(); + + shakeData.direction = contactPointToCenter; + + GetEventManager()->TriggerEvent( EVENT_CAMERA_SHAKE, (void*)(&shakeData) ); + } + + +} + +//============================================================================= +// Vehicle::SparksTest +//============================================================================= +// Description: Comment +// +// Parameters: (float impulseMagnitude, sim::Collision& inCollision) +// +// Return: void +// +//============================================================================= +void Vehicle::SparksTest(float impulseMagnitude, sim::Collision& inCollision) // should this also be wrapped in mUserDrivingCar?? +{ + sim::CollisionObject* collObjA = inCollision.mCollisionObjectA; + sim::CollisionObject* collObjB = inCollision.mCollisionObjectB; + + sim::SimState* simStateA = collObjA->GetSimState(); // A is the vehicle. + sim::SimState* simStateB; + if( simStateA->mAIRefPointer != this ) + { + simStateB = simStateA; + simStateA = collObjB->GetSimState(); + } + else + { + simStateB = collObjB->GetSimState(); + } + + // Lets add some vehicle specific particle effects upon collision + // If the other collision object also a vehicle, lets create some sparks! + // Better place for this constant? + const float MIN_IMPULSE_VEHICLE_VEHICLE_SPARKS = 0.01f; + float velocityScale = 0.0f; + + bool doStars = false; + switch( simStateB->mAIRefIndex ) + { + case PhysicsAIRef::redBrickPhizFence: + impulseMagnitude *= ( 1.0f / 25000.0f ); + doStars = true; + break; + case PhysicsAIRef::redBrickVehicle: + impulseMagnitude *= ( 1.0f / 5000.0f ); + velocityScale = impulseMagnitude; + break; + case PhysicsAIRef::redBrickPhizMoveable: + impulseMagnitude *= ( 1.0f / 5000.0f ); + velocityScale = impulseMagnitude * 0.5f; + break; + case PhysicsAIRef::redBrickPhizStatic: + impulseMagnitude *= ( 1.0f / 25000.0f ); + impulseMagnitude = rmt::Min( 1.0f, impulseMagnitude * 2.0f ); + doStars = true; + default: + return; + } + + if( impulseMagnitude > 10.0f ) + { + impulseMagnitude *= 0.1f; + } + + if ( impulseMagnitude > MIN_IMPULSE_VEHICLE_VEHICLE_SPARKS ) + { + const rmt::Vector& pos = inCollision.GetPositionA(); + if( doStars ) + { + GetSparkleManager()->AddStars( pos, impulseMagnitude ); + } + else + { + rmt::Vector vel = mVelocityCM; + vel.y = 0.0f; + vel.Normalize(); + vel.Scale( velocityScale ); + GetSparkleManager()->AddSparks( pos, vel, impulseMagnitude ); + } + } +} + +//============================================================================= +// Vehicle::VisualDamageType1 +//============================================================================= +// Description: Comment +// +// Parameters: (float percentageDamage) +// +// Return: void +// +//============================================================================= +void Vehicle::VisualDamageType1(float percentageDamage, DamageLocation dl) +{ +/* + // new design, new range + + 0 normal + 10 flap + 30 texture + 60 white smoke + 99 grey smoke (the one-hit indicator) - this will have been clamped to exactly this value + 100 black smoke and flame - car will blow in X seconds + + + +*/ + + // get the joint in question: + int joint = -1; + switch(dl) + { + case dl_hood: + joint = mHoodJoint; + break; + + case dl_trunk: + joint = mTrunkJoint; + break; + + case dl_driverside: + joint = mDoorDJoint; + break; + + case dl_passengerside: + joint = mDoorPJoint; + break; + + default: + rAssert(0); + } + + if(percentageDamage > 0.1f) + { + // only join that got hit should flap + if(joint != -1) + { + int index = mJointIndexToInertialJointDriverMapping[joint]; + if(index != -1) + { + mPhObj->GetJoint(joint)->SetInvStiffness(1.0f); + mInertialJointDrivers[index]->SetIsEnabled(true); + } + } + } + + + if(percentageDamage > 0.3f) + { + // only the joint that got hit should have texture on it. + + switch(dl) + { + case dl_hood: + mGeometryVehicle->DamageTextureHood(true); + break; + + case dl_trunk: + mGeometryVehicle->DamageTextureTrunk(true); + mDontShowBrakeLights = true; + break; + + case dl_driverside: + mGeometryVehicle->DamageTextureDoorD(true); + break; + + case dl_passengerside: + mGeometryVehicle->DamageTextureDoorP(true); + break; + + default: + rAssert(0); + } + + // but all should flap + // all sides flapping ? + int index; + + if(mHoodJoint != -1) + { + index = mJointIndexToInertialJointDriverMapping[mHoodJoint]; + if(index != -1) + { + mPhObj->GetJoint(mHoodJoint)->SetInvStiffness(1.0f); + mInertialJointDrivers[index]->SetIsEnabled(true); + } + } + + if(mTrunkJoint != -1) + { + index = mJointIndexToInertialJointDriverMapping[mTrunkJoint]; + if(index != -1) + { + mPhObj->GetJoint(mTrunkJoint)->SetInvStiffness(1.0f); + mInertialJointDrivers[index]->SetIsEnabled(true); + } + } + + if(mDoorDJoint != -1) + { + index = mJointIndexToInertialJointDriverMapping[mDoorDJoint]; + if(index != -1) + { + mPhObj->GetJoint(mDoorDJoint)->SetInvStiffness(1.0f); + mInertialJointDrivers[index]->SetIsEnabled(true); + } + } + + if(mDoorPJoint != -1) + { + index = mJointIndexToInertialJointDriverMapping[mDoorPJoint]; + if(index != -1) + { + mPhObj->GetJoint(mDoorPJoint)->SetInvStiffness(1.0f); + mInertialJointDrivers[index]->SetIsEnabled(true); + } + } + + + + } + + + if(percentageDamage > 0.6f) + { + // for now, just try taking off the hood before we make smoke pour out there. + //int index; + + if(mHoodJoint != -1) + { + mGeometryVehicle->HideFlappingPiece(mHoodJoint, true); + } + + // all texture + + mGeometryVehicle->DamageTextureHood(true); + mGeometryVehicle->DamageTextureTrunk(true); + mGeometryVehicle->DamageTextureDoorD(true); + mGeometryVehicle->DamageTextureDoorP(true); + + // white smoke + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeLight); + + mDontShowBrakeLights = true; + mGeometryVehicle->SetLightsOffDueToDamage(true); + + // at this point lights and brake lights should stop working + + + } + + if(percentageDamage >= 0.98f) // this is the one-more-hit warning + { + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeMedium); + + } + + if(percentageDamage > 0.99f) // about to blow + { + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeHeavy); + } + +} + + + +//============================================================================= +// Vehicle::VisualDamageType2 +//============================================================================= +// Description: Comment +// +// Parameters: (float percentageDamage, DamageLocation dl) +// +// Return: void +// +//============================================================================= +void Vehicle::VisualDamageType2(float percentageDamage, DamageLocation dl) +{ +/* + try this range + + 0 normal + 10 texture damage (localized front, back, driver side, passenger side) + + 40 smoke level 1 + 60 smoke level 2 + 80 smoke level 3 + 100 disabled + + +*/ + + + // note: + // joint might still be -1, if the is a type 1 car but it doesn't have all four + // flapping joints? + + // this is bulky, but fuck it, gotta get this shit working like yesterday + + if(percentageDamage > 0.1f) + { + // only the joint that got hit should have texture on it. + + switch(dl) + { + case dl_hood: + mGeometryVehicle->DamageTextureHood(true); + break; + + case dl_trunk: + mGeometryVehicle->DamageTextureTrunk(true); + mDontShowBrakeLights = true; + break; + + case dl_driverside: + mGeometryVehicle->DamageTextureDoorD(true); + break; + + case dl_passengerside: + mGeometryVehicle->DamageTextureDoorP(true); + break; + + default: + rAssert(0); + } + + + + } + + + if(percentageDamage > 0.4f) + { + // smoke 1 + // + // ? all sides textured? + + mGeometryVehicle->DamageTextureHood(true); + mGeometryVehicle->DamageTextureTrunk(true); + mGeometryVehicle->DamageTextureDoorD(true); + mGeometryVehicle->DamageTextureDoorP(true); + + //mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeLight); + mGeometryVehicle->SetLightsOffDueToDamage(true); + + } + + if(percentageDamage > 0.6f) + { + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeLight); + //mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeMedium); + } + + if(percentageDamage >= 0.98f) + { + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeMedium); + } + + if(percentageDamage > 0.99f) + { + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeHeavy); + } + +} + + + +//============================================================================= +// Vehicle::VisualDamageType3 +//============================================================================= +// Description: Comment +// +// Parameters: (float percentageDamage) +// +// Return: void +// +//============================================================================= +void Vehicle::VisualDamageType3(float percentageDamage) +{ + + if(percentageDamage > 0.6f) + { + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeLight); + //mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeMedium); + } + + if(percentageDamage >= 0.98f) + { + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeMedium); + } + + if(percentageDamage > 0.99f) + { + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeHeavy); + } + +} + + +//============================================================================= +// Vehicle::SyncVisualDamage +//============================================================================= +// Description: Comment +// +// Parameters: ( float Health ) +// +// Return: void +// +//============================================================================= +void Vehicle::SyncVisualDamage( float Health ) +{ + if( Health >= 1.0f ) + { + return; + } + else if( Health <= 0.0f ) + { + // Husk. + mHitPoints = 0.0f; + mVehicleDestroyed = true; + ActivateTriggers(false); + } + else + { + // this should use the same visual logic functions as the normal damage system + // just need to make up a DamageLocation + // how about the hood? :) + switch(mDamageType) + { + //case 1: // user level - flaping joints etc... + case VDT_USER: + { + VisualDamageType1(1.0f - Health, dl_hood); // any point in this actually taking in a parameter + } + break; + + //case 2: // ai - localized damage textures but no flapping + case VDT_AI: + { + VisualDamageType2(1.0f - Health, dl_hood); // any point in this actually taking in a parameter + } + break; + + case VDT_TRAFFIC: + { + VisualDamageType3(1.0f - Health); // any point in this actually taking in a parameter + } + break; + + default: + rAssertMsg(0, "what are you doing here?"); + + } + + + + + + + + /* + if( Health <= 0.8f) + { + mGeometryVehicle->DamageTextureHood(true); + mGeometryVehicle->DamageTextureTrunk(true); + mGeometryVehicle->DamageTextureDoorD(true); + mGeometryVehicle->DamageTextureDoorP(true); + } + if( Health <= 0.6f ) + { + mGeometryVehicle->SetLightsOffDueToDamage(true); + if( Health <= 0.01f ) + { + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeHeavy); + } + else if( Health <= 0.4f ) + { + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeMedium); + } + else + { + mGeometryVehicle->SetEngineSmoke(ParticleEnum::eEngineSmokeLight); + } + } + + */ + } +} + +//============================================================================= +// Vehicle::TranslateCollisionIntoLocation +//============================================================================= +// Description: Comment +// +// Parameters: (sim::Collision& inCollision) +// +// Return: +// +//============================================================================= +Vehicle::DamageLocation Vehicle::TranslateCollisionIntoLocation(sim::Collision& inCollision) +{ + + sim::CollisionObject* collObjA = inCollision.mCollisionObjectA; + sim::CollisionObject* collObjB = inCollision.mCollisionObjectB; + + sim::SimState* simStateA = collObjA->GetSimState(); + sim::SimState* simStateB = collObjB->GetSimState(); + + rmt::Vector pointOnOtherCar; + + if(simStateA == mSimStateArticulated) + { + pointOnOtherCar = inCollision.GetPositionB(); + } + else if(simStateB == mSimStateArticulated) + { + pointOnOtherCar = inCollision.GetPositionA(); + } + else + { + rAssertMsg(0, "problem in Vehicle::TranslateCollisionIntoLocation"); + } + + rmt::Vector centreToContactPoint; + centreToContactPoint.Sub(pointOnOtherCar, this->rPosition()); + centreToContactPoint.NormalizeSafe(); + + float dot = mVehicleFacing.DotProduct(centreToContactPoint); + + // first pass: + // if no more than 45 degrees from facing, use trunk or hood + // + // otherwise, project on transverse and see + + const float cos45 = 0.7071f; + + int joint = 0; + + if(dot > cos45) + { + // hood + //return mHoodJoint; + //joint = mHoodJoint; + + return dl_hood; + } + else if(dot < -cos45) + { + // trunk + //return mTrunkJoint; + //joint = mTrunkJoint; + + return dl_trunk; + } + else + { + float dot2 = mVehicleTransverse.DotProduct(centreToContactPoint); + + if(dot2 > cos45) + { + // passenger side + //return mDoorPJoint; + //joint = mDoorPJoint; + + return dl_driverside; + } + else + { + // driver side + //return mDoorDJoint; + //joint = mDoorDJoint; + + return dl_passengerside; + } + + } + + rAssert(0); // shouldn't really ever get here. + return dl_hood; +} + + + +//============================================================================= +// Vehicle::BeefUpHitPointsOnTrafficCarsWhenUserDriving +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::BeefUpHitPointsOnTrafficCarsWhenUserDriving() +{ + // can't just use VT_TRAFFIC since we want the effect if we've gone to a phone booth + if( this->mVehicleID == VehicleEnum::TAXIA || // plus all others Jeff gives me + this->mVehicleID == VehicleEnum::COMPACTA || + this->mVehicleID == VehicleEnum::MINIVANA || + this->mVehicleID == VehicleEnum::PICKUPA || + this->mVehicleID == VehicleEnum::SEDANA || + this->mVehicleID == VehicleEnum::SEDANB || + this->mVehicleID == VehicleEnum::SPORTSA || + this->mVehicleID == VehicleEnum::SPORTSB || + this->mVehicleID == VehicleEnum::SUVA || + this->mVehicleID == VehicleEnum::WAGONA || + this->mVehicleID == VehicleEnum::COFFIN || + this->mVehicleID == VehicleEnum::HALLO || + this->mVehicleID == VehicleEnum::SHIP || + this->mVehicleID == VehicleEnum::WITCHCAR || + this->mVehicleID == VehicleEnum::AMBUL || + this->mVehicleID == VehicleEnum::BURNSARM || + this->mVehicleID == VehicleEnum::FISHTRUC || + this->mVehicleID == VehicleEnum::GARBAGE || + this->mVehicleID == VehicleEnum::GLASTRUC || + this->mVehicleID == VehicleEnum::ICECREAM || + this->mVehicleID == VehicleEnum::ISTRUCK || + this->mVehicleID == VehicleEnum::NUCTRUCK || + this->mVehicleID == VehicleEnum::PIZZA || + this->mVehicleID == VehicleEnum::SCHOOLBU || + this->mVehicleID == VehicleEnum::VOTETRUC || + this->mVehicleID == VehicleEnum::CBONE ) + + { + + // only add to an undamaged car, so that you can't get in and out to repair + if(this->GetVehicleLifePercentage(this->mHitPoints) == 1.0f) + { + + if(this->mDesignerParams.mHitPoints <= 1.0f) + { + this->mDesignerParams.mHitPoints += 1.0f; + this->mHitPoints = mDesignerParams.mHitPoints; + } + } + } + +} + + +//============================================================================= +// Vehicle::GetVehicleDamagePercentage +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: float +// +//============================================================================= +float Vehicle::GetVehicleLifePercentage(float testvalue) +{ + float temp = testvalue / mDesignerParams.mHitPoints; + if(temp < 0.0f) + { + // shouldn't ever really be here + // going below 0 should be caught somewhere else + temp = 0.0f; + } + rAssert(temp <= 1.0f); + rAssert(temp >= 0.0f); + + return temp; + +} + + +//============================================================================= +// Vehicle::TriggerDamage +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +float Vehicle::TriggerDamage(float amount, bool clamp) +{ + + // TODO - disable some updating stuff - like locomotive shit, if + // we are destroyed + if(mVehicleDestroyed) + { + // already destroyed + // do nothing + + return 0.0f; + } + + // try the clamp to 99% for last hit on all car types + if(clamp)//mDamageType == VDT_USER || mDamageType == VDT_AI) + { + + float currentPerc = GetVehicleLifePercentage(mHitPoints); + + if(currentPerc > 0.02f) + { + // we want to make sure we clamp this hit at 1% life left + + float testvalue = mHitPoints; + testvalue -= amount; + + if(testvalue < 0.0f) + { + testvalue = 0.0f; + } + + float perc = GetVehicleLifePercentage(testvalue); + + if(perc < 0.02f) + { + // the clamp case: + + mHitPoints = 0.001f; // just some token amount + + //only update the charactersheet if this car belongs to the player + if (mbPlayerCar == true) + { + GetCharacterSheetManager()->UpdateCarHealth( mCharacterSheetCarIndex, perc); + } + + GetEventManager()->TriggerEvent(EVENT_VEHICLE_DAMAGED, (void*)this); + mNoDamageTimer = 1.0f; // set countdown + return 0.02f; // return fixed amount + } + else + { + // just normal hit + mHitPoints = testvalue; // fall through and continue + } + + } + else + { + // this is the hit that takes us out + mHitPoints = 0.0f; + } + + + } + else + { + mHitPoints -= amount; + } + + if(mHitPoints <= 0.0f) + { + // we've been destroyed + mHitPoints = 0.0f; + mVehicleDestroyed = true; + + ActivateTriggers(false); + //GetEventManager()->TriggerEvent(EVENT_VEHICLE_DESTROYED_BY_USER, (void*)this); + // move the triggering of this event to PostSubstepUpdate + // + // user car will pause for timer, other cars will send out immediately + mDamageOutResetTimer = 0.0f; + + /* + // if this is a traffic, then just give'r right here + if(mDamageType == VDT_TRAFFIC) + { + + ParticleAttributes pa; + pa.mType = ParticleEnum::eCarExplosion; + rmt::Vector pos = this->rPosition(); + + GetParticleManager()->Add(pa, pos); + + TrafficManager::GetInstance()->SwapInTrafficHusk(this); + + } + */ + + } + + // make sure the damaged event fired off either way... + GetEventManager()->TriggerEvent(EVENT_VEHICLE_DAMAGED, (void*)this); + + float health = GetVehicleLifePercentage(mHitPoints); + + //only update the charactersheet if this car belongs to the player + if (mbPlayerCar == true) + { + GetCharacterSheetManager()->UpdateCarHealth( mCharacterSheetCarIndex, health ); + } + return health; +} + + + +//============================================================================= +// Vehicle door handling +//============================================================================= +// +// Figure out the final position of the doors, based on current state, +// and whether someone is opening/closing them +// + +bool Vehicle::NeedToOpenDoor(Door door) +{ + if(!mHasDoors) + { + return false; + } + + if(mDesiredDoorPosition[door] < 0.8f) + { + return true; + } + + if(!HasActiveDoor(door)) + { + return false; + } + + return false; +} + +bool Vehicle::NeedToCloseDoor(Door door) +{ + // TODO : there are cases where we might want to not close it + // (physics, missing door, door left open, etc) + if(!mHasDoors) + { + return false; + } + + // check if door was left open when we exited + if(mDesiredDoorPosition[door] > 0.0f) + { + return true; + } + + if(!HasActiveDoor(door)) + { + return false; + } + + return false; +} + +bool Vehicle::HasActiveDoor(Door door) +{ + if(!mHasDoors) + { + return false; + } + + switch(door) + { + case DOOR_DRIVER: + if(mDoorDJoint != -1) + { + int inertialJointIndex = mJointIndexToInertialJointDriverMapping[ mDoorDJoint ]; + if( inertialJointIndex == -1 ) + { + rWarningMsg( false, "why is there no inertial joint? doesn't this car have a door?" ); + return false; + } + sim::PhysicsJointInertialEffector* inertialJoint = mInertialJointDrivers[ inertialJointIndex ]; + if( inertialJoint == NULL ) + { + return false; + } + if( inertialJoint->IsEnabled() ) + { + return false; + } + } + + break; + + case DOOR_PASSENGER: + if(mDoorPJoint != -1) + { + if(mInertialJointDrivers[mJointIndexToInertialJointDriverMapping[mDoorPJoint]]->IsEnabled()) + return false; + } + break; + } + return true; +} + +void Vehicle::UpdateDoorState(void) +{ + if(mDesiredDoorPosition[DOOR_DRIVER] == 0.0f) + { + if((mDoorDJoint != -1) && (mPhObj->GetJoint(mDoorDJoint))) + { + mDesiredDoorPosition[DOOR_DRIVER] = mPhObj->GetJoint(mDoorDJoint)->Deformation(); + } + } + + if(mDesiredDoorPosition[DOOR_PASSENGER] == 0.0f) + { + if((mDoorPJoint != -1) && (mPhObj->GetJoint(mDoorPJoint))) + { + mDesiredDoorPosition[DOOR_PASSENGER] = mPhObj->GetJoint(mDoorPJoint)->Deformation(); + } + } +} + +void Vehicle::ReleaseDoors(void) +{ + mDesiredDoorPosition[DOOR_DRIVER] = 0.0f; + mDesiredDoorPosition[DOOR_PASSENGER] = 0.0f; +} + +void Vehicle::PlayExplosionEffect() +{ + + //rmt::Vector explosionCenter = rPosition(); + // explosionCenter.y += EXPLOSION_Y_OFFSET; + GetWorldPhysicsManager()->ApplyForceToDynamicsSpherical( mCollisionAreaIndex, rPosition(), EXPLOSION_EFFECT_RADIUS, EXPLOSION_FORCE ); + + // Lets get the explosion position as the center of the wheels + rmt::Vector explosionCenter(0,0,0); + for ( int i = 0 ; i < 4 ; i++ ) + { + explosionCenter += mSuspensionWorldSpacePoints[i]; + } + explosionCenter *= 0.25f; + rmt::Matrix explosionTransform = GetTransform(); + explosionTransform.FillTranslate( explosionCenter ); + + GetBreakablesManager()->Play( BreakablesEnum::eCarExplosion, explosionTransform ); + + GetEventManager()->TriggerEvent( EVENT_BIG_BOOM_SOUND, this ); +} + + +void Vehicle::AddToSimulation() +{ + //DynaPhysDSG::AddToSimulation(); // greg + // jan 31, 2003 + // I don't think any vehicle should do this - just use it's own ground plane + SetLocomotion( VL_PHYSICS ); +} + +void Vehicle::ApplyForce( const rmt::Vector& direction, float force ) +{ + SetLocomotion( VL_PHYSICS ); + rmt::Vector& rVelocity = GetSimState()->GetLinearVelocity(); + float deltaV = force / GetMass(); + // Apply delta velocity + rVelocity += (direction * deltaV); + // Make it interact with the world + AddToSimulation(); + + // Damage the vehicle + if ( mUserDrivingCar ) + { + TriggerDamage( force * s_DamageFromExplosionPlayer / EXPLOSION_FORCE, false ); + } + else + { + TriggerDamage( force * s_DamageFromExplosion / EXPLOSION_FORCE, false ); + } +} + + +bool Vehicle::AttachCollectible( StatePropCollectible* drawable ) +{ + if ( drawable->GetState() != 0 ) + return false; + + bool wasAttached = mGeometryVehicle->AttachCollectible( drawable ); + if ( wasAttached ) + { + GetEventManager()->TriggerEvent( EVENT_VEHICLE_COLLECTED_PROP, this ); + } + return wasAttached; +} + +StatePropCollectible* Vehicle::GetAttachedCollectible() +{ + return mGeometryVehicle->GetAttachedCollectible(); +} + +void Vehicle::DetachCollectible( const rmt::Vector& velocity, bool explode ) +{ + mGeometryVehicle->DetachCollectible(velocity, explode); +} + + +// move the door to the specified location (called by the character AI during get in/out of car) +void Vehicle::MoveDoor(Door door, DoorAction action, float position) +{ + rAssert(door < 2); + + mDesiredDoorPosition[door] = position; + mDesiredDoorAction[door] = action; +} + +// calculate the position of a single door +void Vehicle::CalcDoor(unsigned doorIndex, unsigned joint, float doorOpen) +{ + // clamp desired door position to 0 -> 1 + if(mDesiredDoorPosition[doorIndex] > rmt::Abs(doorOpen)) + mDesiredDoorPosition[doorIndex] = rmt::Abs(doorOpen); + + if(mDesiredDoorPosition[doorIndex] < 0.0f) + mDesiredDoorPosition[doorIndex] = 0.0f; + + // grab the rest pose + poser::Pose* pose = mPoseEngine->GetPose(); + rmt::Matrix matrix = pose->GetSkeleton()->GetJoint(joint)->restPose; + rmt::Matrix tmp; + + // rotate by the scaled desired position + tmp.Identity(); + tmp.FillRotateY(mDesiredDoorPosition[doorIndex] * doorOpen); + tmp.Mult(matrix); + + // set the new joint position + pose->GetJoint(joint)->SetObjectMatrix(tmp); + + // if we just closed the door all the way, turn off future door activity + if((mDesiredDoorAction[doorIndex] == DOORACTION_CLOSE) && (mDesiredDoorPosition[doorIndex] == 0.0f)) + { + // if neccesary, reset physics state + int index = mJointIndexToInertialJointDriverMapping[joint]; + if( index != -1 ) + { + if(mInertialJointDrivers[index]->IsEnabled()) + { + mPhObj->GetJoint(joint)->ResetDeformation(); + } + } + mDesiredDoorAction[doorIndex] = DOORACTION_NONE; + } + +} + +void Vehicle::CalcDoors(void) +{ + // angle in radians that represents a fully open door + // TODO : could be tweakable + const float doorOpen = 1.0f; + + // update each door, if neccesary + if((mDesiredDoorAction[0] != DOORACTION_NONE) || (mDesiredDoorAction[1] != DOORACTION_NONE)) + { + if((mDesiredDoorAction[0] != DOORACTION_NONE) && mDoorDJoint != -1) + { + CalcDoor(0, mDoorDJoint, doorOpen); + } + + if((mDesiredDoorAction[1] != DOORACTION_NONE) && mDoorPJoint != -1) + { + // passenger door is backwards, so we do negative rotation + CalcDoor(1, mDoorPJoint, -1.0f * doorOpen); + } + } +} + +void Vehicle::SetDriverName(const char* name) +{ + if(!name) + { + mDriverName[0] = 0; + return; + } + + rAssert(strlen(name) < 32); + strcpy(mDriverName, name); +} + +const char* Vehicle::GetDriverName(void) +{ + return mDriverName; +} + + +void Vehicle::SetDriver(Character* d) +{ + tRefCounted::Assign(mpDriver, d); +} + +void Vehicle::SetShadowAdjustments( float Adjustments[ 4 ][ 2 ] ) +{ + mGeometryVehicle->SetShadowAdjustments( Adjustments ); +} + +void Vehicle::SetShininess( unsigned char EnvRef ) +{ + mGeometryVehicle->SetShininess( EnvRef ); +} + +//============================================================================= +// Vehicle::GetTopSpeedKmh +//============================================================================= +// Description: returns the top speed of which this vehicle is capable in world +// units, not KPH +// +// Parameters: NONE +// +// Return: float +// +//============================================================================= +float Vehicle::GetTopSpeed() const +{ + return mDesignerParams.mDpTopSpeedKmh * ( 1000.0f / 3600.0f ); +}
\ No newline at end of file diff --git a/game/code/worldsim/redbrick/vehicle.h b/game/code/worldsim/redbrick/vehicle.h new file mode 100644 index 0000000..928e639 --- /dev/null +++ b/game/code/worldsim/redbrick/vehicle.h @@ -0,0 +1,989 @@ +//============================================================================= +// Copyright (C) 2001 Radical Entertainment Ltd. All rights reserved. +// +// File: vehicle.h +// +// Description: the car +// +// History: Nov 16, 2001 + Created, gmayer +// +//============================================================================= + + +#ifndef _VEHICLE_H +#define _VEHICLE_H + + +//======================================== +// Nested System Includes +//======================================== + +#include <camera/isupercamtarget.h> +#include <constants/vehicleenum.h> +#include <presentation/gui/utility/hudmap.h> +#include <radmath/radmath.hpp> +#include <render/DSG/InstDynaPhysDSG.h> +#include <render/intersectmanager/intersectmanager.h> // For terrain type enumeration. + +//======================================== +// Forward References +//======================================== + +class Character; +class EventLocator; +class GeometryVehicle; +class PhysicsLocomotion; +class RectTriggerVolume; +class RootMatrixDriver; +class StatePropCollectible; +class SuspensionJointDriver; +class TrafficLocomotion; +class TrafficVehicle; +class VehicleEventListener; +class VehicleLocomotion; +class Wheel; + +namespace poser +{ + class PoseEngine; +} + +namespace sim +{ + class PhysicsJointInertialEffector; + class PhysicsJointMatrixModifier; + class SimStateArticulated; + class ArticulatedPhysicsObject; + class PhysicsProperties; +} + +enum VehicleLocomotionType {VL_PHYSICS, VL_TRAFFIC}; +enum VehicleState {VS_NORMAL, VS_SLIP, VS_EBRAKE_SLIP}; + +enum VehicleType {VT_USER, VT_AI, VT_TRAFFIC, VT_LAST}; + +//============================================================================= +// +// The Vehicle. +// +//============================================================================= +class Vehicle : public DynaPhysDSG, public ISuperCamTarget, public IHudMapIconLocator +{ +public: + + //----- + // core + //----- + + Vehicle(); + virtual ~Vehicle(); + + /* + void AddRef(); + void Release(); + */ + + bool Init( const char* name, sim::SimEnvironment* se, VehicleLocomotionType loco, VehicleType vt = VT_USER, bool startoutofcar = true); + void Reset( bool ResetDamage = true, bool clearTraffic = false ); + void ResetOnSpot( bool resetDamage=true, bool moveCarOntoRoad = true ); + + // make sure, for gui reasons, we don't call repeatedly + bool mAlreadyCalledAutoResetOnSpot; + + void SetPosition(rmt::Vector* newPos); + + void TransitToAI(); + + // utility + float FacingIntoRad(rmt::Vector facing); + + // reset has come to be the one that uses the initial position & facing + // make a different reset that will just reset the flags and state - set transform can be called in + // conjunction with this + void ResetFlagsOnly(bool resetDamage); + + VehicleType mVehicleType; + + int mVehicleCentralIndex; + + // + // Dlong: Proposed new methods (works better!) + // + void SetInitialPositionGroundOffsetAutoAdjust( rmt::Vector* newPos ); + void SetInitialPosition( rmt::Vector* newPos ); + void SetResetFacingInRadians( float rotation ); + float GetFacingInRadians(); + + void SetTransform( rmt::Matrix &m ); + void TrafficSetTransform(rmt::Matrix &m); + + // for debugging and a cheat + void JumpOnHorn(float test); + + void TurboOnHorn(); + float mSecondsTillNextTurbo; + int mNumTurbos; + + // Implements CollisionEntityDSG + // + virtual sim::Solving_Answer PreReactToCollision( sim::SimState* pCollidedObj, sim::Collision& inCollision ); + virtual sim::Solving_Answer PostReactToCollision(rmt::Vector& impulse, sim::Collision& inCollision); + + // drawable vehicle encapsulation: + GeometryVehicle* mGeometryVehicle; + + rmt::Matrix mTransform; + + // TODO - this is temporary? + const rmt::Vector& GetPosition() { return *((rmt::Vector*)mTransform.m[3]); } + const rmt::Vector& GetFacing() { return mVehicleFacing; } + const rmt::Matrix& GetTransform(); + const rmt::Vector& GetExtents() { return mExtents; } + + char* mName; + rmt::Vector mInitialPosition; + float mResetFacingRadians; + + //This is which type of vehicle it is. + VehicleEnum::VehicleID mVehicleID; + void AssignEnumBasedOnName(); + + + // vehicle event stuff + VehicleEventListener* mVehicleEventListener; + + void EnteringJumpBoostVolume(); + void ExitingJumpBoostVolume(); + + bool mDoingJumpBoost; + + + void ActivateTriggers( bool activate ); + /* + + in pure3d lhc, top-down view of vehicle: + + + + ^ + | + | + | + +Z + | + | + front + _____________ + wheel 2 | | wheel 3 + | | + | | + | | + | | + | | + | | ---+X-----> + | | + | | + | | + | | + | | + wheel 1 ------------- wheel 0 + rear + + + + */ + + rmt::Vector mVehicleFacing; + rmt::Vector mVehicleUp; + rmt::Vector mVehicleTransverse; + + + rmt::Vector mVelocityCM; + float mSpeed; + float mPercentOfTopSpeed; + float mSpeedKmh; + float mLastSpeedKmh; + float mAccelMss; + + rmt::Vector mOriginalCMOffset; + rmt::Vector mCMOffset; // this one modified by designer input + + //---------------- + // Shadow stuff + //---------------- + + virtual int CastsShadow(); + bool IsSimpleShadow( void ) { return m_IsSimpleShadow; } + void SetSimpleShadow( bool IsSimpleShadow ) { m_IsSimpleShadow = IsSimpleShadow; } + void SetShadow( tShadowMesh* pShadowMesh ){}; + void DisplayShadow(); + void DisplaySimpleShadow( void ); + + //---------------- + // Terrain type stuff + //---------------- + + // This is a 'rollup' from the four wheels. + eTerrainType mTerrainType; + bool mInterior; + + + float GetGroundY(); + + + //---------------- + // locomotion shit + //---------------- + + VehicleState mVehicleState; //{VS_NORMAL, VS_SLIP}; + + VehicleLocomotionType GetLocomotionType() {return mLoco;} + + VehicleLocomotion* mVehicleLocomotion; + VehicleLocomotionType mLoco; + + + PhysicsLocomotion* mPhysicsLocomotion; + + + // *** For Traffic *** // + TrafficVehicle* mTrafficVehicle; + + TrafficLocomotion* mTrafficLocomotion; + + void CreateLocomotions(); + void SetLocomotion( VehicleLocomotionType loco ); + + + friend class PhysicsLocomotion; + friend class TrafficLocomotion; + + bool mLocoSwitchedToPhysicsThisFrame; + + //--------------------------- + // world simulation interface + //--------------------------- + + void PreSubstepUpdate(float dt); // only thing this does right now is reset a flag + void PostSubstepUpdate(float dt); + void PreCollisionPrep(float dt, bool firstSubstep); + void Update(float dt); + + void SetVehicleSimEnvironment(sim::SimEnvironment* se); // don't actually need this anymore + + void GetCollisionAreaIndexAndAddSelf(); + void RemoveSelfAndFreeCollisionAreaIndex(); + + void AddSelfToCollisionManager(); + void AddToOtherCollisionArea(int index); + void RemoveSelfFromCollisionManager(); + + int mCollisionAreaIndex; // index into the collision area this instance will use. + RenderEnums::LayerEnum mRenderLayerEnum; + void SetRenderLayerEnum(RenderEnums::LayerEnum renderLayerEnum) {mRenderLayerEnum = renderLayerEnum;} + RenderEnums::LayerEnum GetRenderLayerEnum() {return mRenderLayerEnum;} + + void DebugDisplay(); + void CarDisplay(bool doit); + bool mOkToDrawSelf; + + void DrawVehicle( bool draw ) { mDrawVehicle = draw; }; + bool mDrawVehicle; //This is the same thing as above, but Greg uses the above for debugging. + + //--------------------- + // controller interface + //--------------------- + + void SetGas(float gas); + void SetBrake(float brake); + void SetWheelTurnAngle(float wheelTurnAngle, bool doNotModifyInputValue, float dt); + void SetReverse(float reverse); + void SetEBrake(float ebrake, float dt); // new timing thing for plum + + void SetWheelTurnAngleDirectlyInRadiansForDusitOnly(float rad); + + // Vehicle's cached values + float mGas; + float mLastGas; + float mDeltaGas; + float mBrake; + float mWheelTurnAngle; + float mWheelTurnAngleInputValue; // for Plum's low speed lag + float mReverse; + float mEBrake; + float mEBrakeTimer; + bool mBrakeLightsOn; + bool mReverseLightsOn; + + float mBrakeTimer; + bool mBrakeActingAsReverse; + + + float mSteeringInputThreshold; // my own values to modify the h/w input value + float mSteeringPreSlope; + + // the designers will have their own value to max the dropoff + + float mUnmodifiedInputWheelTurnAngle; // just for ease of looking at the pure input + + + bool mDoingRockford; + + float mSpeedBurstTimer; + bool mBuildingUpSpeedBurst; + bool mDoSpeedBurst; + float mFOVToRestore; + float mSpeedBurstTimerHalf; + + //to disable player control of vehicle + void SetDisableGasAndBrake(bool tf) { mGasBrakeDisabled = tf; } + bool mGasBrakeDisabled; + + //---------------------------------- + // RenderManager/EntityDSG Interface + //---------------------------------- + void Display(); + rmt::Vector* pPosition(); + const rmt::Vector& rPosition(); + + sim::SimState* GetSimState() const {return (sim::SimState*)mSimStateArticulated;} + sim::SimState* mpSimState() const { return GetSimState(); } + + void DSGUpdateAndMove(); + + // just in case + virtual void OnSetSimState( sim::SimState* ipSimState ) {}; + + // stub out? + //virtual int FetchGroundPlane(); + //virtual void FreeGroundPlane(); + + //virtual bool IsAtRest(); + + //----------------- + // camera interface + //----------------- + + virtual void GetPosition( rmt::Vector* position ); + virtual void GetHeading( rmt::Vector* heading ); + virtual void GetVUP( rmt::Vector* vup ); + virtual void GetVelocity( rmt::Vector* velocity ); + virtual unsigned int GetID(); + virtual bool IsCar() const; + virtual bool IsAirborn(); + virtual bool IsUnstable(); + virtual bool IsQuickTurn(); + virtual bool IsInReverse(); + virtual void GetFirstPersonPosition( rmt::Vector* position ) {}; + virtual void GetTerrainIntersect( rmt::Vector& pos, rmt::Vector& normal ) const; + + + void CameraShakeTest(float impulseMagnitude, sim::Collision& inCollision); + + bool IsMovingBackward(); // for Darren - don't have to have 'brake' input held down + + bool mSteeringWheelsOutOfContact; // note: + // use for adjusting locomotive force appliction points when cresting hills, so turn this on + // when wheels 2,3 leave ground + + bool mAirBorn; + + bool mWeebleOn; + bool mCMOffsetSetToOriginal; + + bool mInstabilityOffsetOn; + + // ------------ + // ai interface + // ------------ + void SetDriverName(const char*); + Character* GetDriver()const{ return mpDriver; } + const char* GetDriverName(void); + + bool HasDriver( void ) const // note! - this only determines if the car has a character model in the driver seat + { // nothing to do with whether or not there is a 'user' driving the car + return (mpDriver != NULL) || mPhantomDriver; + } + + void SetDriver(Character*); + + void SetPhantomDriver(bool b) { mPhantomDriver = b;} + + char mDriverName[32]; + Character* mpDriver; + bool mPhantomDriver; + + void BounceCharacters(float dt); + void RecordRestSeatingPositionsOnEntry(); + rmt::Vector mOurRestSeatingPosition; + rmt::Vector mNPCRestSeatingPosition; + static bool sDoBounce; + + float mYAccelForSeatingOffset; + + float mMaxBounceDisplacementPerSecond; + float mBounceAccelThreshold; // below this value just try and move back to rest + // if accel is above this value then we move opposite accel direction + + rmt::Vector mVelocityCMLag; + rmt::Vector mPositionCMLag; + + float mBounceLimit; + void ApplyDisplacementToCharacters(float displacement); + void MoveCharactersTowardsRestPosition(float dt); + + virtual const char* const GetName(); + + + bool mUserDrivingCar; + bool IsUserDrivingCar()const { return mUserDrivingCar; } // Sorry, I just really like to use accessor functions. + void SetUserDrivingCar(bool b); + + + virtual int GetAIRef() {return PhysicsAIRef::redBrickVehicle;} + /* + static int GetAIRef( void ) + { + return PhysicsAIRef::redBrickVehicle; + } + */ + + virtual int GetGroundPlaneAIRef() {return PhysicsAIRef::redBrickPhizVehicleGroundPlane;} + + /* + static int GetGroundPlaneAIRef( void ) + { + return PhysicsAIRef::redBrickPhizVehicleGroundPlane; + } + */ + + const rmt::Vector& GetPassengerLocation( void ) const; + const rmt::Vector& GetDriverLocation( void ) const; + + //-------------- + // sfx interface + //-------------- + + float GetSpeedKmh(); + float GetAccelMss(); + float GetRPM(); + float GetGas()const { return mGas; } + float GetDeltaGas()const { return mDeltaGas; } + float GetBrake()const { return mBrake; } + float GetSkidLevel(); // overall amount amongst the (currently) 4 wheels + // both the skidding - tire locked + // and slip - tire rotating really fast. + int GetGear(); // -1 is reverse, 0 neutral + // TODO - how to do the gearshift interface? + + bool IsSafeToUpShift(); + + // gearbox stuff + float mRPM; + float mRPMUpRate; + float mRPMDownRate; + + float mBaseRPM; + float mMaxRpm; + float mShiftPointHigh; + float mShiftPointLow; + int mGear; // -1 for reverse, 0 neutral + + float mSkidLevel; + float mBurnoutLevel; // use for sound and smoke level? + bool mDoingBurnout; + void SetGeometryVehicleWheelSmokeLevel(); + + bool mNoSkid; // just to flag frinks hovering vehilces... + bool mNoFrontSkid; // For the rocket car and other vehicles that can only skid using + // the back two wheels + + bool mDoingWheelie; //need this? + + int mNumGears; + float* mGearRatios; + float mFinalDriveRatio; + void InitGears(); + + void UpdateGearAndRPM(); + + void SparksTest(float impulseMagnitude, sim::Collision& inCollision); // should this also be wrapped in mUserDrivingCar?? + + //-------------------- + // the designer params + //-------------------- + + struct DesignerParams + { + // from physicsvehicle + + float mDpGasScale; // proportional to mass + float mDpSlipGasScale; + float mDpHighSpeedGasScale; + float mDpGasScaleSpeedThreshold; + float mDpBrakeScale; // proportional to mass + float mDpTopSpeedKmh; + + float mDpMass; // think of it as kg + + float mDpMaxWheelTurnAngle; // in degrees + float mDpHighSpeedSteeringDrop; // 0.0 to 1.0 + + float mDpTireLateralStaticGrip; + float mDpTireLateralResistanceNormal; + float mDpTireLateralResistanceSlip; + + float mDpTireLateralResistanceSlipNoEBrake; // this one's for more out of control driving + float mDpSlipEffectNoEBrake; + + float mDpEBrakeEffect; + + float mDpSuspensionLimit; + float mDpSpringk; + float mDpDamperc; + + float mDpSuspensionYOffset; + + float mHitPoints; + + float mDpBurnoutRange; + + + float mDpWheelieRange; + float mDpWheelieYOffset; + float mDpWheelieZOffset; + + float mDpMaxSpeedBurstTime; + + float mDpDonutTorque; + + // new tunable value for plum + float mDpWeebleOffset; + + float mDpGamblingOdds; + + rmt::Vector mDpCMOffset; + + }; + + DesignerParams mDesignerParams; + + // simple accessors that script functions call + // + // note - you have to call CalculateValuesBasedOnDesignerParams after using these. + // + // make sure to name script methods based on watcher names + // these names can match the data. + + //Chuck: Added these so cars now have odds for gambling races. + void SetGamblingOdds(float odds) {mDesignerParams.mDpGamblingOdds = odds;} + float GetGamblingOdds() {return mDesignerParams.mDpGamblingOdds;} + + //Chuck:using this flag for forced car mission, since forced cars dont belong to the player,but the NPC + //Forced cars should respawn and do not check the check to or update the charactersheet. If Flag is true then this + //car is owned by the player, if false then its owned by the NPC + bool mbPlayerCar; + + void SetGasScale(float gs) {mDesignerParams.mDpGasScale = gs;} + void SetSlipGasScale(float value) {mDesignerParams.mDpSlipGasScale = value;} + + void SetHighSpeedGasScale(float gs) {mDesignerParams.mDpHighSpeedGasScale = gs;} + void SetGasScaleSpeedThreshold(float t) {mDesignerParams.mDpGasScaleSpeedThreshold = t;} + + void SetBrakeScale(float bs) {mDesignerParams.mDpBrakeScale = bs;} + void SetTopSpeedKmh(float ts) {mDesignerParams.mDpTopSpeedKmh = ts;} + float GetTopSpeed() const; + void SetMass(float m) {mDesignerParams.mDpMass = m;} + void SetMaxWheelTurnAngle(float mwta) {mDesignerParams.mDpMaxWheelTurnAngle = mwta;} + void SetHighSpeedSteeringDrop(float value) {mDesignerParams.mDpHighSpeedSteeringDrop = value;} + void SetTireLateralStaticGrip(float g) {mDesignerParams.mDpTireLateralStaticGrip = g;} + void SetTireLateralResistanceNormal(float n) {mDesignerParams.mDpTireLateralResistanceNormal = n;} + void SetTireLateralResistanceSlip(float s) {mDesignerParams.mDpTireLateralResistanceSlip = s;} + void SetEBrakeEffect(float s) {mDesignerParams.mDpEBrakeEffect = s;} + + void SetTireLateralResistanceSlipWithoutEBrake(float s) {mDesignerParams.mDpTireLateralResistanceSlipNoEBrake = s;} + void SetSlipEffectWithoutEBrake(float s) {mDesignerParams.mDpSlipEffectNoEBrake = s;} + + void SetCMOffsetX(float s) {mDesignerParams.mDpCMOffset.x = s;} + void SetCMOffsetY(float s) {mDesignerParams.mDpCMOffset.y = s;} + void SetCMOffsetZ(float s) {mDesignerParams.mDpCMOffset.z = s;} + + void SetSuspensionLimit(float s) {mDesignerParams.mDpSuspensionLimit = s;} + void SetSuspensionSpringK(float s) {mDesignerParams.mDpSpringk = s;} + void SetSuspensionDamperC(float s) {mDesignerParams.mDpDamperc = s;} + + void SetSuspensionYOffset(float s) {mDesignerParams.mDpSuspensionYOffset = s;} + + void SetHitPoints(float h) {mDesignerParams.mHitPoints = h;} + void SetBurnoutRange(float h) {mDesignerParams.mDpBurnoutRange = h;} + void SetMaxSpeedBurstTime(float h) {mDesignerParams.mDpMaxSpeedBurstTime = h;} + void SetDonutTorque(float h) {mDesignerParams.mDpDonutTorque = h;} + + void SetWeebleOffset(float w) {mDesignerParams.mDpWeebleOffset = w;} + + void SetWheelieRange(float w) {mDesignerParams.mDpWheelieRange = w;} + void SetWheelieYOffset(float y) {mDesignerParams.mDpWheelieYOffset = y;} + void SetWheelieZOffset(float z) {mDesignerParams.mDpWheelieZOffset = z;} + + void SetShadowAdjustments( float Adjustments[ 4 ][ 2 ] ); + void SetShininess( unsigned char EnvRef ); + + void CalculateValuesBasedOnDesignerParams(); + + + //------------ + // wheel stuff + //------------ + + void UpdateWheelRenderingInfo(float dt); + bool IsJointAWheel(int jointIndex); + //bool SetWheelCorrectionOffset(int jointNum, float objectSpaceYOffsetFromCurrentPosition); + bool SetWheelCorrectionOffset(int jointNum, float objectSpaceYOffsetFromCurrentPosition, rmt::Vector& normalPointingAtCar, rmt::Vector& groundContactPoint); + + void SetNoDamperDownFlagOnWheel(int jointIndex); // joint corresponds to a wheel + bool mDamperShouldNotPullDown[4]; + + SuspensionJointDriver* mSuspensionJointDrivers[4]; + + int mWheelToJointIndexMapping[4]; + int* mJointIndexToWheelMapping; + + rmt::Vector mSuspensionRestPointsFromFile[4]; + rmt::Vector mSuspensionRestPoints[4]; + float mSuspensionRestValue; + float mSuspensionMaxValue; + + void CalculateSuspensionLocationAndVelocity(); + rmt::Vector mSuspensionWorldSpacePoints[4]; + rmt::Vector mSuspensionPointVelocities[4]; + + float mWheelBase; + float GetWheelBase() {return mWheelBase;} + + float GetMass()const { return mDesignerParams.mDpMass; } + + float GetRestHeightAboveGround(); + + void FetchWheelMapping(); + + Wheel* mWheels[4]; + void InitWheelsAndLinkSuspensionJointDrivers(); + + friend class Wheel; + + + //-------------- + // damage states + //-------------- + +/* + + here is what damage states have evolved too: + + there will be 3 types of damage states on vehicles: + + 1. + On "user" level vehicles there will be: + + normal + texture damage (localized front, back, driver side, passenger side) + flapping joints + [flapping joints breaking off] <--- this might not be done for this milestone (or ever); I see it as very low priority right now + smoke level 1 + smoke level 2 + smoke level 3 + disabled + + + 2. + On "ai" level vehicles there will be + + normal + texture damage (localized front, back, driver side, passenger side) + smoke level 1 + smoke level 2 + smoke level 3 + disabled + + + 3. + On "traffic" there will be + + normal + simultaneous BRIEF paricle system Poof! and overall damage texture - disabled. + + + + + + +*/ + + enum VehicleDamageType { VDT_UNSET, VDT_USER, VDT_AI, VDT_TRAFFIC }; + //int mDamageType; // 1, 2, or 3 - see above. + VehicleDamageType mDamageType; + + + bool IsAFlappingJoint(int index); + bool InitFlappingJoints(); // returns true if least 1 flapping joint + bool AddFlappingJoint(const char* transJointName, const char* rotJointName, rmt::Vector& axis, rmt::Vector& rotAxis, int count, int* collisionJointIndex); + + sim::PhysicsJointInertialEffector* mInertialJointDrivers[4]; + int* mJointIndexToInertialJointDriverMapping; + // just for easier delete + sim::PhysicsJointMatrixModifier* mPhysicsJointMatrixModifiers[4]; + + int mDoorDJoint; // recall set to -1 if not present + int mDoorPJoint; + int mHoodJoint; + int mTrunkJoint; + + // logical state of pieces: + // 0 undamaged + // 1 textured + // 2 flapping + // 3 gone + + int mDoorDDamageLevel; + int mDoorPDamageLevel; + int mHoodDamageLevel; + int mTrunkDamageLevel; + + + + enum DamageLocation{ dl_hood, dl_trunk, dl_driverside, dl_passengerside }; + + DamageLocation TranslateCollisionIntoLocation(sim::Collision& inCollision); + + //void DamageJoint(int joint, int damlevel); + //void DamageAllJoints(); + + //void BreakOffFlappingPiece(int index); + + bool CarOnCarDamageLogic(bool thisIsA, sim::SimState* simStateA, sim::SimState* simStateB); + void SwitchOnDamageTypeAndApply(float normalizedMagnitude, sim::Collision& inCollision); + + // ========================================================= + bool mCollidedWithVehicle; // when we are involved in collision with a vehicle + void DusitsStunTest( float normalizedMagnitude ); + + // store data about the last thing that hit me + bool mOutOfControl; + float mNormalizedMagnitudeOfVehicleHit; + bool mWasHitByVehicle; // when we try to distinguish who hit who, if somebody hit us, this is true + VehicleType mWasHitByVehicleType; + rmt::Vector mSwerveNormal; // if somebody hit us, this tells us direction of swerve + void TestWhoHitWhom( sim::SimState* simA, sim::SimState* simB, float normalizedMagnitude, const sim::Collision & inCollision ); + // ========================================================= + + + void VisualDamageType1(float percentageDamage, DamageLocation dl); + void VisualDamageType2(float percentageDamage, DamageLocation dl); + void VisualDamageType3(float percentageDamage); + void SyncVisualDamage( float Health ); // Note that this is the health of the car 0.0f -husk, 1.0f -nice and new. + + rmt::Vector mSmokeOffset; + rmt::Vector& GetSmokeOffset() {return mSmokeOffset;} + rmt::Vector GetWheel0Offset(); + rmt::Vector GetWheel1Offset(); + + void DebugInflictDamageHood(); + void DebugInflictDamageBack(); + void DebugInflictDamageDriverSide(); + void DebugInflictDamagePassengerSide(); + + + //void TriggerDamage(float amount, int hitJoint); + float TriggerDamage(float amount, bool clamp = true); // returns new percentage + bool IsVehicleDestroyed()const { return mVehicleDestroyed; } + + bool mVehicleDestroyed; + bool mDontShowBrakeLights; + + bool mAlreadyPlayedExplosion; + + float mDamageOutResetTimer; + + float mNoDamageTimer; // to make a clean distinction between the second last and the last hit + + + float mHitPoints; + bool mVehicleCanSustainDamage; // defaults to false + void SetVehicleCanSustainDamage(bool trueOrFalse) {mVehicleCanSustainDamage = trueOrFalse;} + float GetVehicleLifePercentage(float testvalue); + + bool mIsADestroyObjective; + void VehicleIsADestroyObjective(bool b) {mIsADestroyObjective = b;} + + + void ResetDamageState(); + + void BeefUpHitPointsOnTrafficCarsWhenUserDriving(); + + //---------- + // sim model + //---------- + + sim::SimStateArticulated* mSimStateArticulated; + sim::ArticulatedPhysicsObject* mPhObj; + sim::PhysicsProperties* mPhysicsProperties; + + // new - swap in model for character to jump on top of + sim::SimStateArticulated* mSimStateArticulatedOutOfCar; + + sim::SimStateArticulated* mSimStateArticulatedInCar; + + bool mUsingInCarPhysics; + void SetInCarSimState(); + void SetOutOfCarSimState(); + + //bool mWaitingToSwitchToOutOfCar; + //float mOutOfCarSwitchTimer; // brutal fucking hack + + void InitSimState(sim::SimEnvironment* se); + void SetupPhysicsProperties(); + + void InitGroundPlane(); + sim::ManualSimState* mGroundPlaneSimState; + sim::WallVolume* mGroundPlaneWallVolume; + sim::PhysicsProperties* mGroundPlanePhysicsProperties; + + + void RestTest(void); + bool SelfRestTest(void); + bool mAtRestAsFarAsTriggersAreConcerned; + void ZeroOutXZVelocity(); + + bool mCreatedByParkedCarManager; + + void CreatePoseEngine(); + poser::PoseEngine* mPoseEngine; + RootMatrixDriver* mRootMatrixDriver; + + //void CreatePoseEngineOutOfCar(); + //poser::PoseEngine* mPoseEngineOutOfCar; + //RootMatrixDriver* mRootMatrixDriverOutOfCar; + + + float mDragCoeff; // TODO - do we want regular, quadratic drag? + void CalculateDragCoeffBasedOnTopSpeed(); + + float mRollingFrictionForce; + float mTireLateralResistance; + float mSlipGasModifier; + + float mCollisionLateralResistanceDropFactor; + + //bool mOkToCrashLand; + //----------- + // other crap + //----------- + + bool mBottomedOutThisFrame; // for debug rendering + bool mWasAirborn; // used to help decide when to fire a bottomed out event for esan + float mWasAirbornTimer; + + float mBottomOutSpeedMaintenance; + + float mStuckOnSideTimer; + + bool mDrawWireFrame; // also debug rendering + bool mLosingTractionDueToAccel; // for plum + + void SetupRadDebugWatchStuff(); + + // unfortunately vehicle will now have a dependency on this shyte, but + // I still think this is the cleanest way to do it. + RectTriggerVolume* mpTriggerVolume; + EventLocator* mpEventLocator; + bool mTriggerActive; + void InitEventLocator(); + + int mDriverInit; + + bool mHijackedByUser; + + // Attach a tDrawable collectible to the vehicle + // returns true if attached or false if not ( false happens + // when a collectible is already attached to the vehicle) + bool AttachCollectible( StatePropCollectible* ); + StatePropCollectible* GetAttachedCollectible(); + // The collectible is detached and free to move around the world under physics control + void DetachCollectible( const rmt::Vector& velocity, bool explode = true ); + float mForceToDetachCollectible; + + int mCharacterSheetCarIndex; + + //---------------------- + // door opening/closing + //---------------------- +public: + enum Door + { + DOOR_DRIVER, + DOOR_PASSENGER + }; + + enum DoorAction + { + DOORACTION_NONE, + DOORACTION_OPEN, + DOORACTION_CLOSE + }; + + // move the door to the given "position' + // positions are in 0 to 1, DoorAction should be open or close, not none + void MoveDoor(Door, DoorAction, float position); + + // do we actually need to open or close the door (test's physics state, current position, etc) + bool NeedToOpenDoor(Door); // returns false if door is already open + bool NeedToCloseDoor(Door); // returns false if door is already closed + bool HasActiveDoor(Door); // is there and active door (i.e. exists, and isn't flapping) + + void UpdateDoorState(void); + void ReleaseDoors(void); + + void PlayExplosionEffect(); + + virtual void AddToSimulation(); + virtual void ApplyForce( const rmt::Vector& direction, float force ); + + bool mHasDoors : 1; + bool mVisibleCharacters : 1; + bool mIrisTransition : 1; + bool mAllowSlide : 1; + bool mHighRoof : 1; + float mCharacterScale; + + // Calculates the s_ForceToDamage variable when given + // the number of hitpoints that should be removed when a vehicle explodes + static void SetPercentDamageFromExplosion( float percent ); + // Calculates the s_ForceToDamagePlayer variable when given + // the number of hitpoints that should be removed when a vehicle explodes + static void SetPercentDamageFromExplosionPlayer( float percent ); + +private: + + float mDesiredDoorPosition[2]; + DoorAction mDesiredDoorAction[2]; + + void CalcDoor(unsigned index, unsigned joint, float scale); + void CalcDoors(void); + + //---------------------- + +private: + rmt::Vector mPassengerLocation; + rmt::Vector mDriverLocation; + rmt::Vector mExtents; + bool m_IsSimpleShadow; + + + // The number of hitpoints that are removed (via TriggerDamage) + // + static float s_DamageFromExplosion; + static float s_DamageFromExplosionPlayer; +}; + + +#endif // _VEHICLE_H diff --git a/game/code/worldsim/redbrick/vehiclecontroller/aivehiclecontroller.cpp b/game/code/worldsim/redbrick/vehiclecontroller/aivehiclecontroller.cpp new file mode 100644 index 0000000..d452954 --- /dev/null +++ b/game/code/worldsim/redbrick/vehiclecontroller/aivehiclecontroller.cpp @@ -0,0 +1,167 @@ +#include <worldsim/redbrick/vehiclecontroller/aivehiclecontroller.h> +#include <main/game.h> +#include <memory/classsizetracker.h> +#include <radmath/radmath.hpp> + +class Behaviour +{ +public: + virtual rmt::Vector Tick( Vehicle* pVehicle ) = 0; +}; + +class VehicleWander +: +public Behaviour +{ +public: + + VehicleWander( void ); + virtual ~VehicleWander( void ); + + rmt::Vector& GetTargetPoint( void ); + void SetTargetPoint( rmt::Vector& targetPoint ); + + rmt::Vector& GetPosition( void ); + void SetPosition( rmt::Vector& position ); + + float GetTargetRadius( void ) const; + void SetTargetRadius( float radius ); + + float GetSteeringRadius( void ) const; + void SetSteeringRadius( float radius ); + + rmt::Vector Tick( Vehicle* pVehicle ); +private: + rmt::Vector mTargetPoint; + rmt::Vector mPosition; + float mfTargetCircleRadius; + float mfSteeringCircleRadius; +}; + +VehicleWander::VehicleWander( void ) +: +mTargetPoint( 0.0f, 0.0f, 1.0f ), +mPosition( 0.0f, 0.0f, 0.1f ), +mfTargetCircleRadius( 1.0f ), +mfSteeringCircleRadius( 0.003f ) +{ + mTargetPoint.z = mfTargetCircleRadius; +} + +VehicleWander::~VehicleWander( void ) +{ +} + +rmt::Vector VehicleWander::Tick( Vehicle* pVehicle ) +{ + static rmt::Randomizer r( Game::GetRandomSeed () ); + static rmt::Vector point( 0.0f, 0.0f, 0.0f ); + + point.x = r.FloatSigned( ); + point.y = 0.0f; + point.z = r.FloatSigned( ); + + // Make a point on the new circle. + // + point.Normalize( ); + point.Scale( mfSteeringCircleRadius ); + + // Add to point on circle. + // + mTargetPoint.Add( point ); + // Project back to unit circle. + // + mTargetPoint.Normalize( ); + // Scale to actual Target Circle. + // + mTargetPoint.Scale( mfTargetCircleRadius ); + + rmt::Vector retVector; + retVector.Add( mPosition, mTargetPoint ); + retVector.Normalize( ); + + return retVector; +} + +AiVehicleController::AiVehicleController( Vehicle* pVehicle ) +{ + //CLASSTRACKER_CREATE( AiVehicleController ); + SetVehicle( pVehicle ); +} + +AiVehicleController::~AiVehicleController( void ) +{ + //CLASSTRACKER_DESTROY( AiVehicleController ); +} + +void AiVehicleController::Update( float timeins ) +{ + /* + static Behaviour* spBehaviour = 0; + if ( !spBehaviour ) + { + spBehaviour = new VehicleWander; + } + rmt::Vector direction = spBehaviour->Tick( GetVehicle( ) ); + mSteering.SetValue( direction.x ); + mGas.SetValue( direction.z ); + */ + mHandBrake.SetValue( 1.0f ); + + mGas.SetValue( 0.0f ); + mSteering.SetValue( 0.0f ); + mBrake.SetValue( 0.0f ); + mReverse.SetValue( 0.0f ); + mHorn.SetValue( 0.0f ); + +} + +float AiVehicleController::GetGas( void ) const +{ + // ai shoudln't really use this? + return mGas.GetValue(); +} + +float AiVehicleController::GetThrottle( void ) const +{ + return mGas.GetValue(); +} + +float AiVehicleController::GetBrake( void ) const +{ + return mBrake.GetValue(); +} + +float AiVehicleController::GetSteering( bool& isWheel ) const +{ + isWheel = false; + return mSteering.GetValue(); +} + + +// AI shouldn't really call these +float AiVehicleController::GetSteerLeft( void ) const +{ + return 0.0f; +} + + +float AiVehicleController::GetSteerRight( void ) const +{ + return 0.0f; +} + +float AiVehicleController::GetHandBrake( void ) const +{ + return mHandBrake.GetValue(); +} + +float AiVehicleController::GetReverse( void ) const +{ + return mReverse.GetValue(); +} + +float AiVehicleController::GetHorn( void ) const +{ + return mHorn.GetValue(); +}
\ No newline at end of file diff --git a/game/code/worldsim/redbrick/vehiclecontroller/aivehiclecontroller.h b/game/code/worldsim/redbrick/vehiclecontroller/aivehiclecontroller.h new file mode 100644 index 0000000..2f2a463 --- /dev/null +++ b/game/code/worldsim/redbrick/vehiclecontroller/aivehiclecontroller.h @@ -0,0 +1,40 @@ +#ifndef AIVEHICLECONTROLLER_H_ +#define AIVEHICLECONTROLLER_H_ + +#include <worldsim/redbrick/vehiclecontroller/vehiclecontroller.h> +#include <input/button.h> + +class Vehicle; + +class AiVehicleController +: +public VehicleController +{ +public: + AiVehicleController( Vehicle* pVehicle ); + virtual ~AiVehicleController( void ); + + virtual void Update( float timeins ); + + virtual float GetGas( void ) const; + virtual float GetThrottle( void ) const; + virtual float GetBrake( void ) const; + virtual float GetSteering( bool& isWheel ) const; + + virtual float GetSteerLeft( void ) const; + virtual float GetSteerRight( void ) const; + + virtual float GetHandBrake( void ) const; + virtual float GetReverse( void ) const; + virtual float GetHorn( void ) const; +protected: + Button mGas; + Button mBrake; + Button mSteering; + Button mHandBrake; + Button mReverse; + Button mHorn; +private: +}; + +#endif //AIVEHICLECONTROLLER_H_
\ No newline at end of file diff --git a/game/code/worldsim/redbrick/vehiclecontroller/allvehiclecontroller.cpp b/game/code/worldsim/redbrick/vehiclecontroller/allvehiclecontroller.cpp new file mode 100644 index 0000000..517da35 --- /dev/null +++ b/game/code/worldsim/redbrick/vehiclecontroller/allvehiclecontroller.cpp @@ -0,0 +1,4 @@ +#include <worldsim/redbrick/vehiclecontroller/aivehiclecontroller.cpp> +#include <worldsim/redbrick/vehiclecontroller/humanvehiclecontroller.cpp> +#include <worldsim/redbrick/vehiclecontroller/vehiclecontroller.cpp> +#include <worldsim/redbrick/vehiclecontroller/vehiclemappable.cpp> diff --git a/game/code/worldsim/redbrick/vehiclecontroller/humanvehiclecontroller.cpp b/game/code/worldsim/redbrick/vehiclecontroller/humanvehiclecontroller.cpp new file mode 100644 index 0000000..3e9ae01 --- /dev/null +++ b/game/code/worldsim/redbrick/vehiclecontroller/humanvehiclecontroller.cpp @@ -0,0 +1,807 @@ +#include <worldsim/redbrick/vehiclecontroller/humanvehiclecontroller.h> +#include <worldsim/redbrick/vehiclecontroller/vehiclemappable.h> +#include <worldsim/redbrick/vehiclecontroller/../vehicle.h> +#include <presentation/gui/guisystem.h> +#include <raddebugwatch.hpp> + +#include <input/inputmanager.h> +#include <events/eventmanager.h> +#include <events/eventdata.h> + +#include <main/commandlineoptions.h> + +#if defined(RAD_GAMECUBE) || defined(RAD_PS2) || defined(RAD_WIN32) +#include <input/basedamper.h> +#include <input/steeringspring.h> +#include <input/constanteffect.h> +#include <input/wheelrumble.h> +#endif + +#ifdef RAD_WIN32 +const short DEFAULT_SPRING_COEFF = 3000; +#else +const short DEFAULT_SPRING_COEFF = 50; +#endif +const char DEFAULT_SPRING_OFFSET = 0; +const unsigned char DEFAULT_SPRING_DEADBAND = 0; +const unsigned char DEFAULT_SPRING_SAT = 0; + +const char DEFAULT_DAMPER_OFFSET = 0; +const unsigned char DEFAULT_DAMPER_DEADBAND = 5; +const unsigned char DEFAULT_DAMPER_SAT = 0; +const short DEFAULT_DAMPER_COEFF = 0; + +const short DEFAULT_CONSTANT_MAGNITUDE = 0; +const unsigned short DEFAULT_CONSTANT_DIRECTION = 0; + +#ifdef DEBUGWATCH +short gDamperSlip = -100; +short gDamperMax = 100; +float gDamperSpeed = 200.0f; + +unsigned char gSpringSlip = 20; +unsigned char gSpringMax = 200; +float gSpringSpeed = 80.0f; +#else +const float gDamperSpeed = 200.0f; + +#ifdef RAD_WIN32 +const u16 gSpringMax = 1000; +const unsigned char gSpringSlip = 20; +const short gDamperMax = 100; +const short gDamperSlip = -600; +#else +const unsigned char gSpringMax = 200; +const unsigned char gSpringSlip = 20; +const short gDamperMax = 100; +const short gDamperSlip = -100; +#endif + +const float gSpringSpeed = 80.0f; + +const short gConstantMag = 0; +const unsigned short gConstantDirection = 0; + +#endif + +#ifdef RAD_GAMECUBE +const float GC_STEERING_FUDGE = 1.25f; +#endif + +HumanVehicleController::HumanVehicleController( void ) +: +mpMappable( 0 ), +mControllerId( -1 ), +#if defined(RAD_GAMECUBE) || defined(RAD_PS2) || defined(RAD_WIN32) +mSpring( NULL ), +mDamper( NULL ), +mConstantEffect( NULL ), +mWheelRumble( NULL ), +mHeavyWheelRumble( NULL ), +#endif +mDisableReset( false ) +{ +#ifdef RAD_PS2 + unsigned int i; + for ( i = 0; i < Input::MaxUSB; ++i ) + { + mpWheel[ i ] = NULL; + } +#endif +} + +void HumanVehicleController::Create( Vehicle* pVehicle, VehicleMappable* pMappable, int controllerId ) +{ + SetVehicle( pVehicle ); + tRefCounted::Assign( mpMappable, pMappable ); + mControllerId = controllerId; +} + +#ifdef RAD_PS2 +void HumanVehicleController::SetWheel( VehicleMappable* pMappable, unsigned int wheelNum ) +{ + rAssert( wheelNum < Input::MaxUSB ); + + tRefCounted::Assign( mpWheel[ wheelNum ], pMappable ); +} +#endif + +HumanVehicleController::~HumanVehicleController( void ) +{ + ReleaseVehicleMappable(); + +#ifdef RAD_PS2 + unsigned int i; + for ( i = 0; i < Input::MaxUSB; ++i ) + { + if ( mpWheel[ i ] ) + { + mpWheel[ i ]->Release(); + mpWheel[ i ] = NULL; + } + } +#endif +} + +void HumanVehicleController::ReleaseVehicleMappable( void ) +{ + if ( mpMappable ) + { + mpMappable->Release(); + mpMappable = 0; + } +} + + +float HumanVehicleController::GetGas( void ) const +{ +#ifdef RAD_PS2 + float value = mpMappable->GetButton( VehicleMappable::Gas )->GetValue(); + + if ( mControllerId >= Input::USB0 ) + { + return value; + } + + unsigned int i; + for ( i = 0; i < Input::MaxUSB; ++i ) + { + if ( mpWheel[ i ] ) + { + float tempVal = mpWheel[ i ]->GetButton( VehicleMappable::Gas )->GetValue(); + if ( rmt::Fabs( tempVal ) > rmt::Fabs( value ) ) + { + value = tempVal; + break; + } + } + } + + return value; +#else + return mpMappable->GetButton( VehicleMappable::Gas )->GetValue(); +#endif +} + +float HumanVehicleController::GetThrottle( void ) const +{ + //No throttle on the wheels. + return mpMappable->GetButton( VehicleMappable::Throttle )->GetValue(); +} + + +float HumanVehicleController::GetBrake( void ) const +{ +#ifdef RAD_PS2 + float value = mpMappable->GetButton( VehicleMappable::Brake )->GetValue(); + + if ( mControllerId >= Input::USB0 ) + { + return value; + } + + unsigned int i; + for ( i = 0; i < Input::MaxUSB; ++i ) + { + if ( mpWheel[ i ] ) + { + float tempVal = mpWheel[ i ]->GetButton( VehicleMappable::Brake )->GetValue(); + if ( rmt::Fabs( tempVal ) > rmt::Fabs( value ) ) + { + value = tempVal; + break; + } + } + } + + return value; +#else + return mpMappable->GetButton( VehicleMappable::Brake )->GetValue(); +#endif +} + +float HumanVehicleController::GetSteering( bool& isWheel ) const +{ +#ifdef RAD_PS2 + isWheel = false; + + float value = mpMappable->GetButton( VehicleMappable::Steer )->GetValue(); + + if ( mControllerId >= Input::USB0 ) + { + isWheel = true; + return value; + } + + unsigned int i; + for ( i = 0; i < Input::MaxUSB; ++i ) + { + if ( mpWheel[ i ] ) + { + float tempVal = mpWheel[ i ]->GetButton( VehicleMappable::Steer )->GetValue(); + if ( rmt::Fabs( tempVal ) > rmt::Fabs( value ) ) + { + value = tempVal; + isWheel = true; + break; + } + } + } + + return value; +#else + //How do I tell if this is from a wheel or a stick? +#ifdef RAD_GAMECUBE + //This is cheating. + isWheel = mpMappable->IsWheel(); + return rmt::Clamp( mpMappable->GetButton( VehicleMappable::Steer )->GetValue() * GC_STEERING_FUDGE, -1.0f, 1.0f ); +#else +#ifdef RAD_WIN32 + isWheel = GetInputManager()->GetController(mControllerId)->IsWheel(); +#endif + return mpMappable->GetButton( VehicleMappable::Steer )->GetValue(); +#endif + +#endif +} + +float HumanVehicleController::GetSteerLeft( void ) const +{ + //No steerleft on the wheels + return mpMappable->GetButton( VehicleMappable::SteerLeft )->GetValue(); +} + +float HumanVehicleController::GetSteerRight( void ) const +{ + //No steer right on the wheels + return mpMappable->GetButton( VehicleMappable::SteerRight )->GetValue(); +} + +float HumanVehicleController::GetReverse( void ) const +{ + //No reverse mapped. + return 0.0f; +} + + +float HumanVehicleController::GetHandBrake( void ) const +{ +#ifdef RAD_PS2 + float value = mpMappable->GetButton( VehicleMappable::HandBrake )->GetValue(); + + if ( mControllerId >= Input::USB0 ) + { + return value; + } + + unsigned int i; + for ( i = 0; i < Input::MaxUSB; ++i ) + { + if ( mpWheel[ i ] ) + { + float tempVal = mpWheel[ i ]->GetButton( VehicleMappable::HandBrake )->GetValue(); + if ( rmt::Fabs( tempVal ) > rmt::Fabs( value ) ) + { + value = tempVal; + break; + } + } + } + + return value; +#else + return mpMappable->GetButton( VehicleMappable::HandBrake )->GetValue(); +#endif +} + +float HumanVehicleController::GetHorn( void ) const +{ +#ifdef RAD_PS2 + float value = mpMappable->GetButton( VehicleMappable::Horn )->GetValue(); + + if ( mControllerId >= Input::USB0 ) + { + return value; + } + + unsigned int i; + for ( i = 0; i < Input::MaxUSB; ++i ) + { + if ( mpWheel[ i ] ) + { + float tempVal = mpWheel[ i ]->GetButton( VehicleMappable::Horn )->GetValue(); + if ( rmt::Fabs( tempVal ) > rmt::Fabs( value ) ) + { + value = tempVal; + break; + } + } + } + + return value; +#else + return mpMappable->GetButton( VehicleMappable::Horn )->GetValue(); +#endif +} + +void HumanVehicleController::Reset( void ) +{ + if( !mDisableReset ) + { + Vehicle* vehicle = GetVehicle(); + GetGuiSystem()->HandleMessage( GUI_MSG_MANUAL_RESET, reinterpret_cast< unsigned int >( vehicle ) ); + } +} + +/* +============================================================================== +HumanVehicleController::GetMappable +============================================================================== +Description: Comment + +Parameters: ( void ) + +Return: VehicleMappable + +============================================================================= +*/ +VehicleMappable *HumanVehicleController::GetMappable( void ) const +{ + return mpMappable; +} + +//============================================================================= +// HumanVehicleController::Update +//============================================================================= +// Description: Comment +// +// Parameters: ( float timeins ) +// +// Return: void +// +//============================================================================= +void HumanVehicleController::Update( float timeins ) +{ + //This is where the output points will be set. + + Vehicle* vehicle = GetVehicle(); + float speed = vehicle->GetSpeedKmh(); + +#if defined(RAD_GAMECUBE) || defined(RAD_PS2) || defined(RAD_WIN32) + UserController* uc = NULL; + + //Set up the output points to default settings. +#ifdef RAD_PS2 + //TODO: Make this only set up the active wheel. + if ( mpWheel[ 0 ] != NULL ) + { + uc = GetInputManager()->GetController( Input::USB0 ); + } + else if ( mpWheel[ 1 ] != NULL ) + { + uc = GetInputManager()->GetController( Input::USB1 ); + } + else + { + uc = GetInputManager()->GetController( mControllerId ); + } +#elif defined RAD_GAMECUBE || defined(RAD_WIN32) + uc = GetInputManager()->GetController( mControllerId ); +#endif + + SetupRumbleFeatures( uc ); + + if ( mSpring || mDamper || mConstantEffect ) + { + rAssert( mSpring && mDamper ); + //Update these effects. + + if ( vehicle->IsAirborn() || + vehicle->mVehicleState == VS_SLIP || + vehicle->mVehicleState == VS_EBRAKE_SLIP ) + { +#ifdef RAD_WIN32 + if( vehicle->IsAirborn() ) + { + mSpring->SetSpringStrength( 0 ); + mSpring->SetSpringCoefficient( 0 ); + + mDamper->SetDamperCoefficient( 0 ); + mDamper->SetDamperStrength( 0 ); + } + else + { + mDamper->SetDamperCoefficient( gDamperSlip ); + mDamper->SetDamperStrength( 255 ); + + mSpring->SetSpringStrength( static_cast<u16>(50*speed) ); + mSpring->SetSpringCoefficient( gDamperSlip*20 ); + + mConstantEffect->SetDirection( static_cast<u16>(90*speed) ); + mConstantEffect->SetMagnitude( static_cast<s16>(-200*speed) ); + } +#else + //Kill the damper + mDamper->SetDamperCoefficient( gDamperSlip ); + mDamper->SetDamperStrength( 0 ); + + + mSpring->SetSpringStrength( gSpringSlip ); + mSpring->SetSpringCoefficient( gDamperSlip ); +#endif + } + else + { + + if ( speed > gSpringSpeed ) + { + speed = gSpringSpeed; + } + +#ifdef RAD_WIN32 + u16 springSat = static_cast<u16>(rmt::FtoL(gSpringMax * ( speed / gSpringSpeed ))); + if( speed < 5.0f ) + { + if( rmt::Epsilon( 0.0f, speed, 0.05f ) ) springSat = gSpringMax; + else springSat = static_cast<u16>((gSpringMax)/(speed)); + } + springSat*=50; + +#else + unsigned char springSat = static_cast<unsigned char>(rmt::FtoL(gSpringMax * ( speed / gSpringSpeed ))); +#endif + mSpring->SetSpringStrength( springSat ); + mSpring->SetSpringCoefficient( DEFAULT_SPRING_COEFF ); + + if ( speed > gDamperSpeed ) + { + speed = gDamperSpeed; + } + + short dampercoeff = gDamperMax - static_cast<short>(rmt::FtoL(rmt::LtoF(gDamperMax) * ( speed / gDamperSpeed ))); + mDamper->SetDamperCoefficient( dampercoeff ); + mDamper->SetDamperStrength( DEFAULT_DAMPER_SAT ); + } + } +#endif + //Update the rumble effect. + switch ( vehicle->mTerrainType ) + { + case TT_Grass: // Grass type terrain most everything else which isn't road or sidewalk. + case TT_Dirt: // Dirt type terrain. + { + if ( speed > 40.0f ) //Hmmmm... TODO: allow this to be modified + { + GetInputManager()->GetController( mControllerId )->ApplyEffect( RumbleEffect::GROUND2, 250 ); + +#if defined(RAD_GAMECUBE) || defined(RAD_PS2) || defined(RAD_WIN32) + if ( mWheelRumble ) + { +// mWheelRumble->SetMagDir( 200, 90 ); +// mWheelRumble->SetPPO( 120 - (80 * (speed / 80.0f)), 0, 0 ); + } +#endif + } + } + case TT_Water: // Water on surface type terrain. + case TT_Gravel: // Loose gravel type terrain. + { + if ( speed > 40.0f ) //Hmmmm... TODO: allow this to be modified + { + GetInputManager()->GetController( mControllerId )->ApplyEffect( RumbleEffect::GROUND4, 250 ); + +#if defined(RAD_GAMECUBE) || defined(RAD_PS2) || defined(RAD_WIN32) + if ( mWheelRumble ) + { +// mWheelRumble->SetMagDir( 200, 90 ); +// mWheelRumble->SetPPO( 120 - (60 * (speed / 80.0f)), 0, 0 ); + } +#endif + } + } + case TT_Sand: // Sand type terrain. + { + if ( speed > 40.0f ) //Hmmmm... TODO: allow this to be modified + { + GetInputManager()->GetController( mControllerId )->ApplyEffect( RumbleEffect::GROUND3, 250 ); + } + } + case TT_Metal: // Powerplant and other structurs. + { + if ( speed > 40.0f ) //Hmmmm... TODO: allow this to be modified + { + GetInputManager()->GetController( mControllerId )->ApplyEffect( RumbleEffect::GROUND2, 250 ); +#if defined(RAD_GAMECUBE) || defined(RAD_PS2) || defined(RAD_WIN32) + if ( mWheelRumble ) + { +// mWheelRumble->SetMagDir( 255, 90 ); +// mWheelRumble->SetPPO( 120 - (60 * (speed / 80.0f)), 0, 0 ); + } +#endif + } + } + case TT_Road: + case TT_Wood: + default: + { +#if defined(RAD_GAMECUBE) || defined(RAD_PS2) //|| defined(RAD_WIN32) + if ( mWheelRumble ) + { + mWheelRumble->SetMagDir( 0, 0 ); + mWheelRumble->SetPPO( 0, 0, 0 ); + } +#endif + break; + } + } + +// if ( vehicle->mSkidLevel > 0.0f ) +// { +// GetInputManager()->GetController( mControllerId )->ApplyEffect( RumbleEffect::PEELOUT, 250 ); +// } +} + +//============================================================================= +// HumanVehicleController::Init +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void HumanVehicleController::Init() +{ + if ( mControllerId == -1 ) + { + return; + } + + UserController* uc = NULL; + +#if defined(RAD_GAMECUBE) || defined(RAD_PS2) || defined(RAD_WIN32) + //Set up the output points to default settings. +#ifdef RAD_PS2 + //TODO: Make this only set up the active wheel. + if ( mpWheel[ 0 ] != NULL ) + { + uc = GetInputManager()->GetController( Input::USB0 ); + } + else if ( mpWheel[ 1 ] != NULL ) + { + uc = GetInputManager()->GetController( Input::USB1 ); + } + else + { + uc = GetInputManager()->GetController( mControllerId ); + } +#elif defined RAD_GAMECUBE || defined(RAD_WIN32) + uc = GetInputManager()->GetController( mControllerId ); +#endif + + if ( uc ) + { + SetupRumbleFeatures( uc ); + } +#endif + + GetEventManager()->AddListener( this, EVENT_BIG_CRASH ); + GetEventManager()->AddListener( this, EVENT_BIG_VEHICLE_CRASH ); + GetEventManager()->AddListener( this, EVENT_MINOR_CRASH ); + GetEventManager()->AddListener( this, EVENT_MINOR_VEHICLE_CRASH ); + GetEventManager()->AddListener( this, EVENT_RUMBLE_COLLISION ); +} + +//============================================================================= +// HumanVehicleController::Shutdown +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void HumanVehicleController::Shutdown() +{ +#if defined(RAD_GAMECUBE) || defined(RAD_PS2) || defined(RAD_WIN32) + //Stop the vehicle output point settings + + if ( mSpring ) + { + mSpring = NULL; + } + + if ( mDamper ) + { + mDamper = NULL; + } + + if ( mConstantEffect ) + { + mConstantEffect = NULL; + } + +#endif + + GetEventManager()->RemoveAll( this ); +} + +//============================================================================= +// HumanVehicleController::HandleEvent +//============================================================================= +// Description: Comment +// +// Parameters: ( EventEnum id, void* pEventData ) +// +// Return: void +// +//============================================================================= +void HumanVehicleController::HandleEvent( EventEnum id, void* pEventData ) +{ + Vehicle* vehicle = GetVehicle(); + float speed = vehicle->GetSpeedKmh(); + switch ( id ) + { + case EVENT_BIG_CRASH: + case EVENT_BIG_VEHICLE_CRASH: + { +#if defined(RAD_PS2) || defined(RAD_GAMECUBE) || defined(RAD_WIN32) + if ( mHeavyWheelRumble ) + { +#ifdef RAD_WIN32 + mHeavyWheelRumble->SetMagDir( static_cast<u16>(speed*20), 90 ); + mHeavyWheelRumble->SetPPO( 20, 0, 0 ); + if( mWheelRumble ) + { + mWheelRumble->SetMagDir( static_cast<u16>(speed*20), 90 ); + mWheelRumble->SetPPO( 20, 0, 0 ); + } +#else + mHeavyWheelRumble->SetMagDir( 180, 90 ); + mHeavyWheelRumble->SetPPO( 20, 0, 0 ); +#endif + } +#endif +// GetInputManager()->GetController( mControllerId )->ApplyEffect( RumbleEffect::HARD1, 250 ); +// GetInputManager()->GetController( mControllerId )->ApplyEffect( RumbleEffect::HARD2, 250 ); + break; + } + case EVENT_MINOR_CRASH: + case EVENT_MINOR_VEHICLE_CRASH: + { +#ifdef RAD_WIN32 + if ( mWheelRumble ) + { + mWheelRumble->SetMagDir( 100, 90 ); + mWheelRumble->SetPPO( 20, 0, 0 ); + } +#endif +// GetInputManager()->GetController( mControllerId )->ApplyEffect( RumbleEffect::LIGHT, 250 ); +// GetInputManager()->GetController( mControllerId )->ApplyEffect( RumbleEffect::HARD2, 250 ); + break; + } + case EVENT_RUMBLE_COLLISION: + { + RumbleCollision* rc = static_cast<RumbleCollision*>(pEventData); + if ( rc->vehicle == GetVehicle() && mControllerId != -1 && rc->vehicle->mSpeedKmh > 1.0f ) + { + GetInputManager()->GetController( mControllerId )->ApplyDynaEffect( RumbleEffect::COLLISION1, 333, rc->normalizedForce ); + GetInputManager()->GetController( mControllerId )->ApplyDynaEffect( RumbleEffect::COLLISION2, 333, rc->normalizedForce ); + +#if defined(RAD_PS2) || defined(RAD_GAMECUBE) || defined(RAD_WIN32) + if ( mConstantEffect ) + { + if ( mWheelRumble && rc->normalizedForce > 0.02f ) + { + rmt::Vector thisPosition; + GetVehicle()->GetPosition( &thisPosition ); + + rmt::Vector X( 1.0f, 0.0f, 0.0f ); + rmt::Matrix mat = GetVehicle()->GetTransform(); + mat.IdentityTranslation(); + + X.Transform( mat ); + + rmt::Vector carToPoint; + carToPoint.Sub( rc->point, thisPosition ); + carToPoint.NormalizeSafe(); + + float dot = carToPoint.DotProduct( X ); + bool left = ( dot < 0.0f ); + u16 direction = rmt::FtoL( rmt::Fabs(dot) * 90.0f ); + if ( left ) + { + direction = (90 + 180) + ( 90 - (direction) ); + } + + mConstantEffect->SetDirection( direction ); + mConstantEffect->SetMagnitude( 200 ); + } + } + + if ( mWheelRumble && rc->normalizedForce > 0.008f ) + { + mWheelRumble->SetMagDir( 180, 90 ); + mWheelRumble->SetPPO( static_cast<u16>( rmt::FtoL(rmt::Clamp( rc->normalizedForce, 0.5f, 1.0f ) * 200.0f ) ), + 0, 0 ); + } +#endif + } + } + default: + break; + } +} + + +#if defined(RAD_GAMECUBE) || defined(RAD_PS2) || defined(RAD_WIN32) +//============================================================================= +// HumanVehicleController::SetupRumbleFeatures +//============================================================================= +// Description: Comment +// +// Parameters: ( UserController* uc ) +// +// Return: void +// +//============================================================================= +void HumanVehicleController::SetupRumbleFeatures( UserController* uc ) +{ + if ( !mSpring ) + { + mSpring = uc->GetSpring(); + if ( mSpring ) + { + //Set default settings for the spring + mSpring->SetCenterPoint( DEFAULT_SPRING_OFFSET, DEFAULT_SPRING_DEADBAND ); + mSpring->SetSpringCoefficient( DEFAULT_SPRING_COEFF ); + mSpring->SetSpringStrength( DEFAULT_SPRING_SAT ); + } + } + + if ( !mDamper ) + { + mDamper = uc->GetDamper(); + if ( mDamper ) + { + //Set default settings for the damper + mDamper->SetCenterPoint( DEFAULT_SPRING_OFFSET, DEFAULT_SPRING_DEADBAND ); + mDamper->SetDamperCoefficient( DEFAULT_SPRING_COEFF ); + mDamper->SetDamperStrength( DEFAULT_SPRING_SAT ); + } + } + + if ( !mConstantEffect ) + { + mConstantEffect = uc->GetConstantEffect(); + if ( mConstantEffect ) + { + //Default + mConstantEffect->SetMagnitude( DEFAULT_CONSTANT_MAGNITUDE ); + mConstantEffect->SetDirection( DEFAULT_CONSTANT_DIRECTION ); + } + } + + if ( !mWheelRumble ) + { + mWheelRumble = uc->GetWheelRumble(); + if ( mWheelRumble ) + { + mWheelRumble->SetMagDir( 0, 0 ); + mWheelRumble->SetPPO( 0, 0, 0 ); + } + } + + if ( !mHeavyWheelRumble ) + { + mHeavyWheelRumble = uc->GetHeavyWheelRumble(); + if ( mHeavyWheelRumble ) + { + mHeavyWheelRumble->SetMagDir( 0, 0 ); + mHeavyWheelRumble->SetPPO( 0, 0, 0 ); + } + } +} +#endif diff --git a/game/code/worldsim/redbrick/vehiclecontroller/humanvehiclecontroller.h b/game/code/worldsim/redbrick/vehiclecontroller/humanvehiclecontroller.h new file mode 100644 index 0000000..8e7feb8 --- /dev/null +++ b/game/code/worldsim/redbrick/vehiclecontroller/humanvehiclecontroller.h @@ -0,0 +1,78 @@ +#ifndef HUMANVEHICLECONTROLLER_H_ +#define HUMANVEHICLECONTROLLER_H_ + +#include <worldsim/redbrick/vehiclecontroller/vehiclecontroller.h> + +#include <input/controller.h> +#include <events/eventlistener.h> + +class VehicleMappable; +class BaseDamper; +class SteeringSpring; +class ConstantEffect; +class WheelRumble; +class UserController; + +class HumanVehicleController +: +public VehicleController, +public EventListener +{ +public: + HumanVehicleController( void ); + virtual ~HumanVehicleController( void ); + + void ReleaseVehicleMappable( void ); + + void Create( Vehicle* pVehicle, VehicleMappable* pMappable, int controllerId ); +#ifdef RAD_PS2 + void SetWheel( VehicleMappable* pMappable, unsigned int wheelNum ); +#endif + virtual float GetGas( void ) const; + virtual float GetThrottle( void ) const; + virtual float GetBrake( void ) const; + virtual float GetSteering( bool& isWheel ) const; + + // for dpad + virtual float GetSteerLeft( void ) const; + virtual float GetSteerRight( void ) const; + + virtual float GetHandBrake( void ) const; + virtual float GetReverse( void ) const; + virtual float GetHorn( void ) const; + + void Reset( void ); + void SetDisableReset( bool tf ) { mDisableReset = tf; } + + VehicleMappable* GetMappable( void ) const; + + virtual void Update( float timeins ); + virtual void Init(); + virtual void Shutdown(); + + void HandleEvent( EventEnum id, void* pEventData ); + +protected: +private: + + VehicleMappable* mpMappable; + +#ifdef RAD_PS2 + VehicleMappable* mpWheel[ Input::MaxUSB ]; +#endif + + int mControllerId; + +#if defined(RAD_GAMECUBE) || defined(RAD_PS2) || defined(RAD_WIN32) + SteeringSpring* mSpring; + BaseDamper* mDamper; + ConstantEffect* mConstantEffect; + WheelRumble* mWheelRumble; + WheelRumble* mHeavyWheelRumble; + + void SetupRumbleFeatures( UserController* uc ); +#endif + bool mDisableReset; +}; + +#endif //HUMANVEHICLECONTROLLER_H_
\ No newline at end of file diff --git a/game/code/worldsim/redbrick/vehiclecontroller/vehiclecontroller.cpp b/game/code/worldsim/redbrick/vehiclecontroller/vehiclecontroller.cpp new file mode 100644 index 0000000..87942e2 --- /dev/null +++ b/game/code/worldsim/redbrick/vehiclecontroller/vehiclecontroller.cpp @@ -0,0 +1,16 @@ +#include <worldsim/redbrick/vehiclecontroller/vehiclecontroller.h> + +VehicleController::VehicleController( void ) +: +mpVehicle( 0 ) +{ +} + +VehicleController::~VehicleController( void ) +{ +} + +Vehicle* VehicleController::GetVehicle( void ) const +{ + return mpVehicle; +} diff --git a/game/code/worldsim/redbrick/vehiclecontroller/vehiclecontroller.h b/game/code/worldsim/redbrick/vehiclecontroller/vehiclecontroller.h new file mode 100644 index 0000000..9a7ce5f --- /dev/null +++ b/game/code/worldsim/redbrick/vehiclecontroller/vehiclecontroller.h @@ -0,0 +1,49 @@ +#ifndef VEHICLECONTROLER_H_ +#define VEHICLECONTROLER_H_ + +#include <p3d/refcounted.hpp> +#include <input/button.h> + +class Vehicle; + +/* +// Skeleton class for example. +// +struct IButton +{ + virtual float GetValue( void ) const = 0; + virtual float GetHeldTime( void ) const = 0; +}; +*/ +class VehicleController +: +public tRefCounted +{ +public: + VehicleController( void ); + virtual ~VehicleController( void ); + + virtual void Update( float timeins ) {}; + virtual void Init() {}; + virtual void Shutdown() {}; + + void SetVehicle( Vehicle* pVehicle ) { mpVehicle = pVehicle; } + + virtual float GetGas( void ) const = 0; + virtual float GetThrottle( void ) const = 0; + + virtual float GetBrake( void ) const = 0; + virtual float GetSteering( bool& isWheel ) const = 0; + virtual float GetSteerLeft( void ) const = 0; + virtual float GetSteerRight( void ) const = 0; + + virtual float GetHandBrake( void ) const = 0; + virtual float GetReverse( void ) const = 0; + virtual float GetHorn( void ) const = 0; + + Vehicle* GetVehicle( void ) const; +private: + Vehicle* mpVehicle; +}; + +#endif // VEHICLECONTROLER_H_
\ No newline at end of file diff --git a/game/code/worldsim/redbrick/vehiclecontroller/vehiclemappable.cpp b/game/code/worldsim/redbrick/vehiclecontroller/vehiclemappable.cpp new file mode 100644 index 0000000..94c7d87 --- /dev/null +++ b/game/code/worldsim/redbrick/vehiclecontroller/vehiclemappable.cpp @@ -0,0 +1,263 @@ +#include <worldsim/redbrick/vehiclecontroller/vehiclemappable.h> +#include <worldsim/redbrick/vehiclecontroller/humanvehiclecontroller.h> + +#include <worldsim/worldphysicsmanager.h> + +// Temp. +// +#include <worldsim/avatarmanager.h> + +#ifdef RAD_WIN32 +#include <gameflow/gameflow.h> +#include <contexts/context.h> +#endif + +// Constructor. +// +VehicleMappable::VehicleMappable( void ) +: +Mappable(Input::ACTIVE_GAMEPLAY), +mpHumanVehicleController( 0 ), +mIsWheel( false ), +mIsWheelA( false ) +{ +} + +// Destructor +// +VehicleMappable::~VehicleMappable( void ) +{ + ReleaseController(); +} + +void VehicleMappable::ReleaseController( void ) +{ + if ( mpHumanVehicleController ) + { + mpHumanVehicleController->Release(); + mpHumanVehicleController = 0; + } +} + + + +// This method is called when ever a button state changes. +//void +void VehicleMappable::OnButton( int controllerId, int id, const IButton* pButton ) +{ +} + +// This method is called when a button changes state from "Pressed" to "Released". +// +void VehicleMappable::OnButtonUp( int controllerId, int buttonId, const IButton* pButton ) +{ +} + +// This method is called when a button changes state from "Released" to "Pressed". +// +void VehicleMappable::OnButtonDown( int controllerId, int buttonId, const IButton* pButton ) +{ + switch ( buttonId ) + { + case Reset: + { +#ifdef RAD_GAMECUBE + if ( pButton->GetValue() > 0.5f ) + { + GetController( )->Reset(); + } +#else + GetController( )->Reset(); +#endif + break; + } + case Horn: + { + GetWorldPhysicsManager()->ToggleTimerState(); + } + default: + { + // Do nothing. That's cool. + // + break; + } + } +} + +// This is how we create our controller device mappings to logical game mappings. +// The mappings set up in this method are platform specific. +// +// The basic format of the calls is to "Map" a input, to a enumerated output id. +// The output of the specified input will be contained in the Button[] array. +// This id will also be sent as a the second parameter in the OnButton... messages. +// +void VehicleMappable::LoadControllerMappings( unsigned int controllerId ) +{ + // The names used in parameter 1 of the Map method are + // Platform Specific. + // + + // Configuration 1. + // + + // MS7 + // #ifdef this for different platforms + #ifdef RAD_XBOX + + ClearMap(0); + Map( "LeftStickX", Steer, 0, controllerId ); + Map( "DPadLeft", SteerLeft, 0, controllerId ); + Map( "DPadRight", SteerRight, 0, controllerId ); + + // The misnamed "Throttle" controls gas (up) and brake (down). + // + //Map( "RightStickY", Throttle, 0, controllerId ); Not on XBOX, please + + Map( "RightTrigger", Gas, 0, controllerId ); + Map( "A", Gas, 0, controllerId ); + + Map( "B", Brake, 0, controllerId ); + Map( "LeftTrigger", Brake, 0, controllerId ); + + Map( "X", HandBrake, 0, controllerId ); + + Map( "White", Horn, 0, controllerId ); + Map( "LeftThumb", Horn, 0, controllerId ); + + Map( "Back", Reset, 0, controllerId ); + #endif + + + #ifdef RAD_PS2 + + ClearMap(0); + + if ( controllerId >= Input::MaxControllers - Input::MaxUSB ) + { + mIsWheel = true; + + //This is a wheel! + //And for the wheels! + Map( "Wheel", Steer, 0, controllerId ); + Map( "Gas", Gas, 0, controllerId ); + Map( "Brake", Brake, 0, controllerId ); + + if ( !Map( "R2", Gas, 0, controllerId ) ) + { + mIsWheelA = true; + + //This is the GT wheel. + Map( "LGR1", Gas, 0, controllerId ); + Map( "LGA", Brake, 0, controllerId ); + Map( "LGL1", HandBrake, 0, controllerId ); + Map( "LGX", Horn, 0, controllerId ); + Map( "LGB", Reset, 0, controllerId ); + } + else + { + //This is the DPad Wheel + Map( "X", Gas, 0, controllerId ); + Map( "R1", Brake, 0, controllerId ); + Map( "Circle", Brake, 0, controllerId ); + Map( "Square", HandBrake, 0, controllerId ); + Map( "L1", HandBrake, 0, controllerId ); + Map( "DPadUp", Horn, 0, controllerId ); + Map( "DPadDown", Horn, 0, controllerId ); + Map( "DPadLeft", Horn, 0, controllerId ); + Map( "DPadRight", Horn, 0, controllerId ); + Map( "Select", Reset, 0, controllerId ); + } + } + else + { + //This is a DUALSHOCK! + Map( "LeftStickX", Steer, 0, controllerId ); + Map( "DPadLeft", SteerLeft, 0, controllerId ); + Map( "DPadRight", SteerRight, 0, controllerId ); + + // The misnamed "Throttle" controls gas (up) and brake (down). + // + Map( "RightStickY", Throttle, 0, controllerId ); + + Map( "X", Gas, 0, controllerId ); + + Map( "Circle", Brake, 0, controllerId ); + + Map( "R1", HandBrake, 0, controllerId ); + Map( "Square", HandBrake, 0, controllerId ); + + Map( "L3", Horn, 0, controllerId ); + + Map( "Select", Reset, 0, controllerId ); + } + + #endif + + #ifdef RAD_GAMECUBE + + ClearMap(0); + Map( "LeftStickX", Steer, 0, controllerId ); +// Map( "DPadLeft", SteerLeft, 0, controllerId ); +// Map( "DPadRight", SteerRight, 0, controllerId ); + + Map( "AnalogTriggerR", Gas, 0, controllerId ); + Map( "A", Gas, 0, controllerId ); + + Map( "X", Brake, 0, controllerId ); + Map( "AnalogTriggerL", Brake, 0, controllerId ); + + Map( "B", HandBrake, 0, controllerId ); + + Map( "TriggerZ", Horn, 0, controllerId ); + Map( "DPadUp", Reset, 0, controllerId ); + Map( "DPadDown", Reset, 0, controllerId ); + Map( "DPadLeft", Reset, 0, controllerId ); + Map( "DPadRight", Reset, 0, controllerId ); + + if ( Map( "Wheel", Steer, 0, controllerId ) ) + { + mIsWheel = true; + //This is a wheel! + Map( "Gas", Gas, 0, controllerId ); + Map( "Brake", Brake, 0, controllerId ); + } + else + { +// + } + + #endif + + #ifdef RAD_WIN32 + + ClearMap(0); + if ( GetGameFlow()->GetCurrentContext() == CONTEXT_LOADING_SUPERSPRINT && + controllerId == 3 ) + { + Map( "P1_KBD_Left", SteerLeft, 0, controllerId ); + Map( "P1_KBD_Right", SteerRight, 0, controllerId ); + Map( "P1_KBD_Gas", Gas, 0, controllerId ); + Map( "P1_KBD_Brake", Brake, 0, controllerId ); + Map( "P1_KBD_EBrake", HandBrake, 0, controllerId ); + Map( "P1_KBD_Nitro", Horn, 0, controllerId ); + } + + Map( "SteerLeft", SteerLeft, 0, controllerId ); + Map( "SteerRight", SteerRight, 0, controllerId ); + + Map( "Accelerate", Gas, 0, controllerId ); + Map( "Reverse", Brake, 0, controllerId ); + + Map( "HandBrake", HandBrake, 0, controllerId ); + Map( "Horn", Horn, 0, controllerId ); + + Map( "ResetCar", Reset, 0, controllerId ); + + #endif + +} + +void VehicleMappable::SetController( HumanVehicleController* pHumanVehicleController ) +{ + tRefCounted::Assign( mpHumanVehicleController, pHumanVehicleController ); +} diff --git a/game/code/worldsim/redbrick/vehiclecontroller/vehiclemappable.h b/game/code/worldsim/redbrick/vehiclecontroller/vehiclemappable.h new file mode 100644 index 0000000..e63d6e0 --- /dev/null +++ b/game/code/worldsim/redbrick/vehiclecontroller/vehiclemappable.h @@ -0,0 +1,60 @@ +#ifndef VEHICLEMAPPABLE_H_ +#define VEHICLEMAPPABLE_H_ + +#include <input/mappable.h> + +class HumanVehicleController; + +class VehicleMappable +: +public Mappable +{ +public: + VehicleMappable( void ); + ~VehicleMappable( void ); + + void ReleaseController( void ); + + void OnButton( int controllerId, int id, const IButton* pButton ); + void OnButtonUp( int controllerId, int buttonId, const IButton* pButton ); + void OnButtonDown( int controllerId, int buttonId, const IButton* pButton ); + void LoadControllerMappings( unsigned int controllerId ); + + void SetController( HumanVehicleController* pHumanVehicleController ); + + bool IsWheel() const { return mIsWheel; }; + bool IsWheelA() const { return mIsWheelA; }; + + enum + { + Steer = 0, + SteerLeft, + SteerRight, + Throttle, + Gas, + Brake, + Horn, + Reset, + Pause, + Reverse, + HandBrake, + Burnout, + GetOutofCar, + LookBack, + Spring, + Damper, + Rumble, + RightStickX, + RightStickY + }; +protected: + HumanVehicleController* GetController( void ) const + { return mpHumanVehicleController; } +private: + HumanVehicleController* mpHumanVehicleController; + + bool mIsWheel; + bool mIsWheelA; +}; + +#endif //VEHICLEMAPPABLE_H_
\ No newline at end of file diff --git a/game/code/worldsim/redbrick/vehicleeventlistener.cpp b/game/code/worldsim/redbrick/vehicleeventlistener.cpp new file mode 100644 index 0000000..34838aa --- /dev/null +++ b/game/code/worldsim/redbrick/vehicleeventlistener.cpp @@ -0,0 +1,304 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: vehicleeventlistener.cpp +// +// Description: +// +// History: created, Nov 29, 2002, gmayer +// +//============================================================================= + + +#include <worldsim/redbrick/vehicleeventlistener.h> + +#include <events/eventmanager.h> +#include <events/eventenum.h> + +#include <meta/locatorevents.h> +#include <meta/eventlocator.h> +#include <meta/triggervolumetracker.h> + +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/vehiclecentral.h> +#include <worldsim/traffic/trafficmanager.h> +#include <worldsim/parkedcars/parkedcarmanager.h> +#include <worldsim/avatarmanager.h> +#include <worldsim/avatar.h> + +#include <gameflow/gameflow.h> + +#include <mission/objectives/missionobjective.h> +#include <mission/objectives/destroyobjective.h> + +#include <mission/gameplaymanager.h> +#include <supersprint/supersprintmanager.h> + +//----------------------------------------------------------------------------- +VehicleEventListener::VehicleEventListener() +{ + mVehicleOwner = 0; +} + + +//----------------------------------------------------------------------------- +VehicleEventListener::VehicleEventListener(Vehicle* owner) +{ + mVehicleOwner = owner; + GetEventManager()->AddListener( this, EVENT_DEATH_VOLUME_SCREEN_BLANK ); + GetEventManager()->AddListener( this, (EventEnum)(EVENT_LOCATOR + LocatorEvent::WHACKY_GRAVITY)); + GetEventManager()->AddListener( this, (EventEnum)(EVENT_LOCATOR + LocatorEvent::DEATH) ); +} + + +//----------------------------------------------------------------------------- +VehicleEventListener::~VehicleEventListener() +{ + GetEventManager()->RemoveAll( this ); +} + + +//----------------------------------------------------------------------------- +void VehicleEventListener::HandleEvent( EventEnum id, void* pEventData ) +{ + //////////////////////////////////////////////////////////////// + // WHACKY GRAVITY (FOR JUMP VOLUMES) + + if( id == EVENT_LOCATOR + LocatorEvent::WHACKY_GRAVITY ) + { + EventLocator* pLocator = static_cast<EventLocator*>( pEventData ); + rAssert( pLocator ); + + Vehicle* testVehicle = NULL; + if ( pLocator->GetPlayerID() < static_cast<unsigned int>(MAX_PLAYERS) ) + { + //This is a player vehicle + testVehicle = GetAvatarManager()->GetAvatarForPlayer( pLocator->GetPlayerID() )->GetVehicle(); + } + else + { + testVehicle = GetTriggerVolumeTracker()->GetAI( pLocator->GetPlayerID() ); + } + + if ( testVehicle != mVehicleOwner ) + { + //If this isn't for me, ignore it. + return; + } + + // if in supersprint, ignore jump boost for player + if( GetGameFlow()->GetCurrentContext() == CONTEXT_SUPERSPRINT && + mVehicleOwner->mVehicleType == VT_USER ) + { + return; + } + + if ( pLocator->GetPlayerEntered() ) + { + int stophere = 1; + // only apply this if we are moderately aligned to it + + rmt::Matrix mat = pLocator->GetMatrix(); + rmt::Vector facing = mat.Row(2); + + if(mVehicleOwner->mVehicleFacing.DotProduct(facing) > 0.0f) + { + mVehicleOwner->EnteringJumpBoostVolume(); + } + } + else + { + // should be leaving + int stophere = 1; + mVehicleOwner->ExitingJumpBoostVolume(); + } + } + + + //////////////////////////////////////////////////////////////// + // DEATH VOLUMES + + else if( id == EVENT_LOCATOR + LocatorEvent::DEATH ) + { + EventLocator* pLocator = static_cast<EventLocator*>( pEventData ); + rAssert( pLocator ); + if( pLocator == NULL ) + { + return; + } + + Vehicle* testVehicle = NULL; + if ( pLocator->GetPlayerID() < static_cast<unsigned int>(MAX_PLAYERS) ) + { + //This is a player vehicle + testVehicle = GetAvatarManager()->GetAvatarForPlayer( pLocator->GetPlayerID() )->GetVehicle(); + } + else + { + testVehicle = GetTriggerVolumeTracker()->GetAI( pLocator->GetPlayerID() ); + } + + if ( testVehicle != mVehicleOwner ) + { + //If this isn't for me, ignore it. + return; + } + + // Ok, it is for me... + + GameplayManager::GameTypeEnum gameType = GetGameplayManager()->GetGameType(); + + // + // In supersprint, we want to handle DEATH event for AI & player vehicles, + // but if in other contexts, we handle DEATH event only for AI (player vehicles + // will need to listen to the fade to DEATH_VOLUME_SCREEN_BLANK event because + // we don't want death to be instantaneous) + // + VehicleType type = mVehicleOwner->mVehicleType; + if( ( gameType != GameplayManager::GT_SUPERSPRINT ) && + ( type != VT_AI ) ) + { + // there's some stuff that we want to do right away + if ( pLocator->GetPlayerEntered() ) + { + GetEventManager()->TriggerEvent( EVENT_DEATH_VOLUME_SOUND, mVehicleOwner ); + } + return; + } + + /* + // + // If this is a destroy objective, we want to kill the target AI vehicle + // (thus triggering the destroyed condition, completing the mission... + // + if( GetGameFlow()->GetCurrentContext() != CONTEXT_SUPERSPRINT ) + { + MissionStage* ms = GetGameplayManager()->GetCurrentMission()->GetCurrentStage(); + if( ms ) + { + MissionObjective* mobj = ms->GetObjective(); + if( mobj->GetObjectiveType() == MissionObjective::OBJ_DESTROY ) + { + DestroyObjective* dobj = (DestroyObjective*) mobj; + if( dobj->GetTargetVehicle() == mVehicleOwner ) + { + mVehicleOwner->mHitPoints = 0.0f; + mVehicleOwner->mVehicleDestroyed = true; + return; + } + } + } + } + */ + + rAssert( mVehicleOwner->mVehicleCentralIndex != -1 ); + + // Only do something upon entry + if ( pLocator->GetPlayerEntered() ) + { + // We're here if we're a car that the player is driving + // or if we're an AI car... + + rmt::Matrix mat = pLocator->GetMatrix(); + + //Let's turn the car around in specific cases. + if ( GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + if ( GetSSM()->IsTrackReversed() ) + { + rmt::Vector heading; + mat.GetHeading( heading ); + heading *= -1.0f; + mat.FillHeadingXZ( heading ); //Turn, turn around + } + + // + // Dusit said I should do this here + // + GetEventManager()->TriggerEvent( EVENT_DEATH_VOLUME_SOUND, mVehicleOwner ); + } + + // John McLean here, + // locator is snapped to ground, so we need to spawn at some rest + // height above ground. Yippe ka yay! + rmt::Vector loc; + pLocator->GetLocation(&loc); + float yAug = mVehicleOwner->GetRestHeightAboveGround(); + loc.y += yAug; + mat.Row(3).y = loc.y; + + mVehicleOwner->SetTransform(mat); + + // do reset flags, do reset AI info (if an AI car) + // don't reset pos (already done manually be setting transform), + // don't reset damage states + mVehicleOwner->ResetFlagsOnly(false); + } + } + else if( id == EVENT_DEATH_VOLUME_SCREEN_BLANK ) + { + // Only handle the player vehicle triggering this. This includes: + // - player default vehicle (could be under AI control in Demo) in Demo or Normal modes + // - any traffic car or parked car or husk that the player is driving + + EventLocator* pLocator = static_cast<EventLocator*>( pEventData ); + rAssert( pLocator ); + if( pLocator == NULL ) + { + return; + } + + rAssert( pLocator->GetPlayerID() < static_cast<unsigned int>(MAX_PLAYERS) ); + + //This is a player vehicle + Vehicle* testVehicle = GetAvatarManager()->GetAvatarForPlayer( + pLocator->GetPlayerID() )->GetVehicle(); + + if( testVehicle != mVehicleOwner ) + { + //If this isn't for me, ignore it. + return; + } + + // Ok, it is for me... + rAssert( mVehicleOwner->mVehicleCentralIndex != -1 ); + rAssert( GetGameplayManager()->GetGameType() != GameplayManager::GT_SUPERSPRINT ); + + // Only do something upon entry + if ( pLocator->GetPlayerEntered() ) + { + // We're here if we're a car that the player is driving + // or if we're an AI-controller player car (in Demo mode) + + rmt::Matrix mat = pLocator->GetMatrix(); + + // John McLean here, + // locator is snapped to ground, so we need to spawn at some rest + // height above ground. Yippe ka yay! + rmt::Vector loc; + pLocator->GetLocation(&loc); + float yAug = mVehicleOwner->GetRestHeightAboveGround(); + loc.y += yAug; + mat.Row(3).y = loc.y; + + // + // what if there is already a car at the location we want to put htis car + // + const rmt::Vector& position = mat.Row( 3 ); + rmt::Sphere s; + s.centre = position; + s.radius = 4.0; + TrafficManager::GetInstance()->ClearTrafficInSphere( s ); + GetGameplayManager()->DumpCurrentCarIfInSphere( s ); + GetPCM().RemoveFreeCarIfClose( position ); + + mVehicleOwner->SetTransform(mat); + + // do reset flags, do reset AI info (if an AI car) + // don't reset pos (already done manually be setting transform), + // don't reset damage states + mVehicleOwner->ResetFlagsOnly(false); + } + } +} + diff --git a/game/code/worldsim/redbrick/vehicleeventlistener.h b/game/code/worldsim/redbrick/vehicleeventlistener.h new file mode 100644 index 0000000..30f0668 --- /dev/null +++ b/game/code/worldsim/redbrick/vehicleeventlistener.h @@ -0,0 +1,45 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: vehicleeventlistener.h +// +// Description: +// +// History: created, Nov 29, 2002, gmayer +// +//============================================================================= + + + +#ifndef VEHICLEEVENTLISTENER_H +#define VEHICLEEVENTLISTENER_H + +#include <events/eventlistener.h> + +class Vehicle; + +class VehicleEventListener : public EventListener +{ +public: + + VehicleEventListener(); + VehicleEventListener(Vehicle* owner); + + virtual ~VehicleEventListener(); + + + virtual void HandleEvent( EventEnum id, void* pEventData ); + +private: + + // Declared but not defined to prevent copying and assignment. + VehicleEventListener( const VehicleEventListener& ); + VehicleEventListener& operator=( const VehicleEventListener& ); + + Vehicle* mVehicleOwner; +}; + + + + +#endif // VEHICLEEVENTLISTENER_H diff --git a/game/code/worldsim/redbrick/vehicleinit.cpp b/game/code/worldsim/redbrick/vehicleinit.cpp new file mode 100644 index 0000000..79ee9fe --- /dev/null +++ b/game/code/worldsim/redbrick/vehicleinit.cpp @@ -0,0 +1,2309 @@ +//============================================================================= +// Copyright (C) 2001 Radical Entertainment Ltd. All rights reserved. +// +// File: vehicleinit.cpp +// +// Description: init stuff for the vehicle, that is only called once! +// +// History: Aug 2, 2002 + Created -- gmayer +// +//============================================================================= + + +//======================================== +// System Includes +//======================================== + +#include <simcommon/skeletoninfo.hpp> +#include <simcommon/simstate.hpp> +#include <simcommon/simstatearticulated.hpp> +#include <simcommon/simulatedobject.hpp> +#include <simphysics/articulatedphysicsobject.hpp> +#include <simphysics/physicsjoint.hpp> +#include <simphysics/physicsobject.hpp> +#include <simcommon/simenvironment.hpp> +#include <simcollision/collisionobject.hpp> +#include <simcollision/collisionvolume.hpp> +#include <mission/charactersheet/charactersheetmanager.h> +#include <simcollision/collisiondisplay.hpp> + +#include <raddebug.hpp> +#include <raddebugwatch.hpp> + +#include <p3d/anim/drawablepose.hpp> +#include <p3d/shadow.hpp> + +#include <string.h> + +//======================================== +// Project Includes +//======================================== + +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/worldobject.h> + +#include <worldsim/worldphysicsmanager.h> +#include <worldsim/redbrick/geometryvehicle.h> +#include <worldsim/redbrick/rootmatrixdriver.h> +#include <worldsim/redbrick/suspensionjointdriver.h> +#include <worldsim/redbrick/vehicleeventlistener.h> +#include <worldsim/redbrick/wheel.h> +#include <worldsim/redbrick/redbrickcollisionsolveragent.h> + +#include <worldsim/redbrick/physicslocomotion.h> +#include <worldsim/redbrick/trafficlocomotion.h> + +#include <worldsim/character/charactermanager.h> + + +#include <memory/srrmemory.h> +#include <mission/gameplaymanager.h> + +#include <meta/carstartlocator.h> + +#include <debug/debuginfo.h> + + +#include <ai/actionbuttonhandler.h> +#include <ai/actionbuttonmanager.h> + +#include <meta/eventlocator.h> +#include <meta/spheretriggervolume.h> +#include <meta/recttriggervolume.h> + +#include <worldsim/character/character.h> + +#include <gameflow/gameflow.h> + +using namespace sim; + +// The force required to knock a collectible off +const float MIN_FORCE_DETACH_COLLECTIBLES = 30000.0f; + + +//============================================================================= +// Vehicle::Vehicle +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: Vehicle +// +//============================================================================= +Vehicle::Vehicle() : + mGeometryVehicle( NULL ), + mName( NULL ), + mVehicleID( VehicleEnum::INVALID ), + mTerrainType( TT_Road), + mInterior( false ), + mpEventLocator( NULL ), + mCharacterSheetCarIndex( -1 ), + m_IsSimpleShadow( true ) +{ + mTranslucent = true; + mDrawVehicle = true; + + mTransform.Identity(); + + mVehicleCentralIndex = -1; + + // wtf? + mInitialPosition.Set(0.0f, 8.0f, 20.0f); + mResetFacingRadians = 0.0f; + + mVehicleFacing.Set(0.0f, 0.0f, 1.0f); + mVehicleUp.Set(0.0f, 1.0f, 0.0f); + +#ifdef RAD_GAMECUBE + HeapMgr()->PushHeap( GMA_GC_VMM ); +#else + GameMemoryAllocator allocator = GetGameplayManager()->GetCurrentMissionHeap(); + HeapMgr()->PushHeap( allocator ); +#endif + mGeometryVehicle = new GeometryVehicle; +#ifdef RAD_GAMECUBE + HeapMgr()->PopHeap( GMA_GC_VMM ); +#else + HeapMgr()->PopHeap( allocator ); +#endif + + + //mPhysicsVehicle = new PhysicsVehicle; + //mPhysicsVehicle = 0; test this motherfucker + + // should this be here or in Init? + // shouldn't really be in this class at all + // TODO - take this shit out + //mVehicleRender = new VehicleRender(this); + + + mSimStateArticulated = 0; + mPoseEngine = 0; + mRootMatrixDriver = 0; + + //mPoseEngineOutOfCar = 0; + //mRootMatrixDriverOutOfCar = 0; + + + mSimStateArticulatedOutOfCar = 0; + mSimStateArticulatedInCar = 0; + + mGas = 0.0f; + mLastGas = 0.0f; + mDeltaGas = 0.0f; + mBrake = 0.0f; + mWheelTurnAngle = 0.0f; + mReverse = 0.0f; + mEBrake = 0.0f; + + mBrakeTimer = 0.0f; + mBrakeActingAsReverse = false; + + mGasBrakeDisabled = false; + + mUnmodifiedInputWheelTurnAngle = 0.0f; + + mVelocityCM.Set(0.0f, 0.0f, 0.0f); + mSpeed = 0.0f; + mSpeedKmh = 0.0f; + mLastSpeedKmh = 0.0f; + mAccelMss = 0.0f; + mPercentOfTopSpeed = 0.0f; + + mRollingFrictionForce = 2.0f; // need to tune? I don't think so + //mRollingFrictionForce = 1.5f; // need to tune? I don't think so + + mRPM = 0.0f; + mBaseRPM = 500.0f; + mMaxRpm = 6500.0f; + + mRPMUpRate = 10.0f; + mRPMDownRate = 50.0f; + //mShiftPointHigh = 3500.0f; + //mShiftPointLow = 1000.0f; + + //mShiftPointHigh = 4000.0f; + mShiftPointHigh = 4500.0f; + //mShiftPointLow = 1500.0f; + mShiftPointLow = 2000.0f; + + + mGear = 0; // neutral? + + mNumGears = -1; // unset + mGearRatios = 0; + mFinalDriveRatio = -1.0f; // unset + + mSkidLevel = 0.0f; + mBurnoutLevel = 0.0f; + mSlipGasModifier = 1.0f; + + int i; + for(i = 0; i < 4; i++) + { + mInertialJointDrivers[i] = 0; + mPhysicsJointMatrixModifiers[i] = 0; + } + mJointIndexToInertialJointDriverMapping = 0; + + mCollisionAreaIndex = -1; + + mDoorDJoint = -1; + mDoorPJoint = -1; + mHoodJoint = -1; + mTrunkJoint = -1; + + mDoorDDamageLevel = 0; + mDoorPDamageLevel = 0; + mHoodDamageLevel = 0; + mTrunkDamageLevel = 0; + + mOkToDrawSelf = true; + mSteeringWheelsOutOfContact = false; + mAirBorn = false; + mWasAirborn = false; + mWasAirbornTimer = 0.0f; + mWeebleOn = false; + mInstabilityOffsetOn = false; + + mSpeedBurstTimer = 0.0f; + mBuildingUpSpeedBurst = false; + mDoSpeedBurst = false; + mFOVToRestore = 1.57f; + mSpeedBurstTimerHalf = 0.0f; + + //mDamageType = 0; // 0 is unset - normally 1, 2, or 3 + mDamageType = VDT_UNSET; + + mVehicleDestroyed = false; + mVehicleCanSustainDamage = true; + //mVehicleCanSustainDamage = false; + + mIsADestroyObjective = false; + + mHitPoints = 2.0f; + + mDamageOutResetTimer = 0.0f; + + mSmokeOffset.Set(0.0f, -0.5f, 1.5f); + +// mHitJoint = 0; + + mTrafficVehicle = NULL; + + mCollisionLateralResistanceDropFactor = 1.0f; + + mUserDrivingCar = false; + + mDesiredDoorPosition[0] = mDesiredDoorPosition[1] = 0.0f; + mDesiredDoorAction[0] = mDesiredDoorAction[1] = DOORACTION_NONE; + + mpTriggerVolume = NULL; + + mpDriver = NULL; + + mDoingRockford = false; + mDoingWheelie = false; + + + strcpy(mDriverName,"phantom"); +// mDriverName[0] = 0; + mPhantomDriver = false; + + + // vehicle event stuff + mVehicleEventListener = 0; + + mDoingJumpBoost = false; + + mNoSkid = false; // only true for frink's hovering vehicles (also the ghost ship) + mNoFrontSkid = false; // only true for the rocket car + + mBrakeLightsOn = false; + mReverseLightsOn = false; + mDontShowBrakeLights = false; + + mCMOffsetSetToOriginal = false; + + mOutOfControl = false; + mCollidedWithVehicle = false; + + mNormalizedMagnitudeOfVehicleHit = 0.0f; + mWasHitByVehicle = false; + mWasHitByVehicleType = VT_LAST; + + mDamperShouldNotPullDown[0] = false; + mDamperShouldNotPullDown[1] = false; + mDamperShouldNotPullDown[2] = false; + mDamperShouldNotPullDown[3] = false; + + + mNumTurbos = 3; // default to 3 for now + mSecondsTillNextTurbo = 0.0f; + + mUsingInCarPhysics = true; + + //mWaitingToSwitchToOutOfCar = false; + //mOutOfCarSwitchTimer = 0.0f; // brutal fucking hack + + mNoDamageTimer = 0.0f; + + mAlreadyPlayedExplosion = false; + + mEBrakeTimer = 0.0f; + + mWheelTurnAngleInputValue = 0.0f; + + mDrawWireFrame = false; + mLosingTractionDueToAccel = false; // for plum - just debug output + + mHijackedByUser = false; + + + + mDesignerParams.mDpGamblingOdds= 1.0f; //Chuck: set the Gambling Odds to 1.0 or 1:1 by default. + mbPlayerCar = false; + + mOurRestSeatingPosition.Set(0.0f, 0.0f, 0.0f); + mNPCRestSeatingPosition.Set(0.0f, 0.0f, 0.0f); + mYAccelForSeatingOffset = 0.0f; + + mVelocityCMLag.Set(0.0f, 0.0f, 0.0f); + mPositionCMLag.Set(0.0f, 0.0f, 0.0f); + + // value... fresh from my ass: + //mBounceLimit = 0.5f; + //mBounceLimit = 0.25f; + mBounceLimit = 0.11f; + + + //mMaxBounceDisplacementPerSecond = 1.0f; + mMaxBounceDisplacementPerSecond = 2.0f; + + mBounceAccelThreshold = 1.0f; // below this value just try and move back to rest + //mBounceAccelThreshold = 2.0f; // below this value just try and move back to rest + + + mForceToDetachCollectible = MIN_FORCE_DETACH_COLLECTIBLES; + + mHasDoors = true; + mVisibleCharacters = true; + mIrisTransition = false; + mAllowSlide = true; + mHighRoof = false; + mCharacterScale = 1.0f; + + + // Lets set the default to inflict half damage on this vehicle when they explode + s_DamageFromExplosion = 0.5f; + // And no damage on player vehicles + s_DamageFromExplosionPlayer = 0; + + mStuckOnSideTimer = 0.0f; + mAlreadyCalledAutoResetOnSpot = false; + + + mOriginalCMOffset.x = 0.0f; + mOriginalCMOffset.y = 0.0f; + mOriginalCMOffset.z = 0.0f; + + mBottomOutSpeedMaintenance = 0.0f; + + mTriggerActive = false; + + mAtRestAsFarAsTriggersAreConcerned = true; + + mLocoSwitchedToPhysicsThisFrame = false; + + mCreatedByParkedCarManager = false; +} + + +//============================================================================= +// Vehicle::Init +//============================================================================= +// Description: Comment +// +// Parameters: (char* name, int num, SimEnvironment* se, VehicleLocomotionType loco) +// +// Return: bool +// +//============================================================================= +bool Vehicle::Init( const char* name, SimEnvironment* se, VehicleLocomotionType loco, VehicleType vt, bool startoutofcar) +{ + if(mbPlayerCar ==true) + { + mCharacterSheetCarIndex = GetCharacterSheetManager()->GetCarIndex( name ); + } + else + { + mCharacterSheetCarIndex = -1; + } + + mDrawVehicle = true; + + mVehicleType = vt; + + rAssert( mName == NULL ); + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + mName = new(gma)char[strlen(name)+1]; + strcpy( mName, name ); + + CollisionEntityDSG::SetName(mName); + + mLoco = loco; + + AssignEnumBasedOnName(); + + //++++++++++++++++++++ + // the designer params + //++++++++++++++++++++ + + + //mDesignerParams.mDpGasScale = 8.0f; // proportional to mass + //mDesignerParams.mDpGasScale = 6.0f; // proportional to mass + //mDesignerParams.mDpGasScale = 3.0f; // proportional to mass + mDesignerParams.mDpGasScale = 4.0f; // proportional to mass + + mDesignerParams.mDpSlipGasScale = 4.0f; + + mDesignerParams.mDpHighSpeedGasScale = 2.0f; + //mDesignerParams.mDpGasScaleSpeedThreshold = 0.5f; + mDesignerParams.mDpGasScaleSpeedThreshold = 1.0f; // make this the default so it is not used for the time being + + + mDesignerParams.mDpBrakeScale = 3.0f; // proportional to mass + + //mDesignerParams.mDpTopSpeedKmh = 300.0f; + //mDesignerParams.mDpTopSpeedKmh = 250.0f; + mDesignerParams.mDpTopSpeedKmh = 220.0f; + + mDesignerParams.mDpMass = 1000.0f; // think of it as kg + + mDesignerParams.mDpMaxWheelTurnAngle = 35.0f; // in degrees + //mDesignerParams.mDpMaxWheelTurnAngle = 30.0f; // in degrees + //mDesignerParams.mDpMaxWheelTurnAngle = 24.0f; // in degrees + //mDesignerParams.mDpHighSpeedSteeringDrop = 0.25f; // 0.0 to 1.0 + mDesignerParams.mDpHighSpeedSteeringDrop = 0.0f; // 0.0 to 1.0 + + // for wheel + //mDesignerParams.mDpTireLateralStaticGrip = 2.5f; + //mDesignerParams.mDpTireLateralStaticGrip = 5.0f; + mDesignerParams.mDpTireLateralStaticGrip = 4.0f; + + + mDesignerParams.mDpTireLateralResistanceNormal = 110.0f; + mDesignerParams.mDpTireLateralResistanceSlip = 35.0f; + + mDesignerParams.mDpEBrakeEffect = 0.5f; + + //mDesignerParams.mDpTireLateralResistanceSlipNoEBrake = 20.0f; // this one's for more out of control driving + mDesignerParams.mDpTireLateralResistanceSlipNoEBrake = 50.0f; // this one's for more out of control driving + mDesignerParams.mDpSlipEffectNoEBrake = 0.1f; + + mDesignerParams.mDpCMOffset.x = 0.0f; + mDesignerParams.mDpCMOffset.y = 0.0f; + mDesignerParams.mDpCMOffset.z = 0.0f; + + // don't think they need to touch these for now + mDesignerParams.mDpSuspensionLimit = 0.4f; + mDesignerParams.mDpSpringk = 0.5f; + mDesignerParams.mDpDamperc = 0.2f; + + mDesignerParams.mDpSuspensionYOffset = 0.0f; + + mDesignerParams.mHitPoints = 2.0f; + //mDesignerParams.mHitPoints = 0.5f; + + //mDesignerParams.mDpBurnoutRange = 0.2f; + mDesignerParams.mDpBurnoutRange = 0.3f; + + mDesignerParams.mDpWheelieRange = 0.0f; + mDesignerParams.mDpWheelieYOffset = 0.0f; + mDesignerParams.mDpWheelieZOffset = 0.0f; + + mDesignerParams.mDpMaxSpeedBurstTime = 1.0f; + mDesignerParams.mDpDonutTorque = 10.0f; + + mDesignerParams.mDpWeebleOffset = -1.0f; + + + mVehicleState = VS_NORMAL; + mTireLateralResistance = mDesignerParams.mDpTireLateralResistanceNormal; + + mSteeringInputThreshold = 0.7f; + mSteeringPreSlope = 0.5f; + + + bool localizedDamage; + //localizedDamage = mGeometryVehicle->Init(name, this, num); + localizedDamage = mGeometryVehicle->Init(name, this, 0); + + // change the logic so that localizedDamage is true if there as at + // least one localized damage texture + + + if(!localizedDamage) + { + //mDamageType = 3; // traffic + mDamageType = VDT_TRAFFIC; + } + + InitSimState(se); + + InitGroundPlane(); + + FetchWheelMapping(); + InitWheelsAndLinkSuspensionJointDrivers(); + + + mGeometryVehicle->InitParticles(); + //if(mVehicleType == VT_USER) + if(1)// mVehicleType == VT_USER || mVehicleType == VT_AI) change this to init for all, only draw for USER - that way it is totally safe to switch vehicle type on the fly + { + mGeometryVehicle->InitSkidMarks(); + } + + + + bool flappingJointPresent = InitFlappingJoints(); + if(flappingJointPresent) + { + //if(mDamageType == 3) + if(mDamageType == VDT_TRAFFIC) + { + //rAssertMsg(0, "Fink, what are you doing?"); + + } + + // else + + //mDamageType = 1; + mDamageType = VDT_USER; + + } + else if(mDamageType == VDT_UNSET) // still unset + { + mDamageType = VDT_AI; + } + + // should be set for sure at this point + rAssertMsg(mDamageType != VDT_UNSET, "damage type not set?"); + + InitGears(); + + //CalculateDragCoeffBasedOnTopSpeed(); + + CalculateValuesBasedOnDesignerParams(); + // does this: + // SetupPhysicsProperties(); + // CalculatedDragCoeffBasedOnTopSpeed + // SetDesignerParams on Wheels + + + CreateLocomotions(); + mLocoSwitchedToPhysicsThisFrame = false; + + + + // need this call here? + Reset( false ); + + SetupRadDebugWatchStuff(); + + // moved here from VehicleCentral + InitEventLocator(); + + + // vehicle event stuff + mVehicleEventListener = new VehicleEventListener(this); + + + + + //mUsingInCarPhysics + + // default at this point in creation is to be using the incar physics. + // + // perhaps there should be a paramter to say which, passed into + + + if(startoutofcar) + { + SetOutOfCarSimState(); + } + + /* + if( GetGameFlow()->GetCurrentContext() == CONTEXT_DEMO ) + { + mWaitingToSwitchToOutOfCar = true; + } + else + { + mWaitingToSwitchToOutOfCar = false; + } + */ + + // actually any use in this result? + // might as well just assert. + //return result; + return true; +} + + + +//============================================================================= +// Vehicle::InitEventLocator +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::InitEventLocator() +{ + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + + mpEventLocator = new(gma)EventLocator(); + mpEventLocator->SetName( mName ); + mpEventLocator->AddRef(); + mpEventLocator->SetEventType( LocatorEvent::CAR_DOOR ); + int id = -1; + + ActionButton::GetInCar* pABHandler = static_cast<ActionButton::GetInCar*>( ActionButton::GetInCar::NewAction( mpEventLocator ) ); + rAssert( dynamic_cast<ActionButton::GetInCar*>( pABHandler ) != NULL ); + rAssert( pABHandler ); + bool bResult = GetActionButtonManager()->AddAction( pABHandler, id ); + rAssert( bResult ); + + // this part is done in VehicleCentral::AddVehicleToActiveList + //pABHandler->SetVehicleId( mNumActiveVehicles ); + + mpEventLocator->SetData( id ); + + // grab the car bounding box + mExtents = GetSimState()->GetCollisionObject()->GetCollisionVolume()->mBoxSize; + + // slap down a trigger volume for getting intop car + const float edge = 1.0f; // how big? TODO : tunable? + mpTriggerVolume = new(gma) RectTriggerVolume( mTransform.Row(3), + mTransform.Row(0), + mTransform.Row(1), + mTransform.Row(2), + mExtents.x + edge, + mExtents.y + edge, + mExtents.z + edge); + + // add volume to event trigger + mpEventLocator->SetNumTriggers( 1, gma ); + mpEventLocator->AddTriggerVolume( mpTriggerVolume ); + mpTriggerVolume->SetLocator( mpEventLocator ); + mpTriggerVolume->SetName( "get_in" ); + +} + +//============================================================================= +// Vehicle::AssignEnumBasedOnName +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::AssignEnumBasedOnName() +{ + /* + enum VehicleID + { + BART_V = 0, // bart_v 0 + APU_V, // apu_v 1 + SNAKE_V, // snake_v 2 + HOMER_V, // homer_v 3 + FAMIL_V, // famil_v 4 + GRAMP_V, // gramp_v 5 + CLETU_V, // cletu_v 6 + WIGGU_V, // wiggu_v 7 + KRUSTYKAR_NOTYETIN, // + MARGE_V, // marge_v 9 + OTTOBUS_NOTYETIN, + MOESDUFFTRUCK_NOTYETIN, + SMITH_V, // smith_v 12 + FLANDERSMOTORHOME_NOTYETIN, + MCBAINHUMMER_NOTYETIN, + ALIEN_NOTYETIN, + ZOMBI_V, // zombi_v 16 + MUNSTERS_NOTYETIN, + HUMMER2_NOTYETIN, + + CVAN, // cVan 19 + COMPACTA, // compactA 20 + + COMIC_V, // comic_v 21 + SKINN_V, // skinn_v 22 + + + + // some more new ai cars + CCOLA, // 23 + CSEDAN, + CPOLICE, + CCELLA, + CCELLB, + CCELLC, + CCELLD, + + // some more new traffic cars + MINIVANA, // 30 + PICKUPA, + TAXIA, + SPORTSA, + SPORTSB, + SUVA, + WAGONA, // 36 + + // some more new cars: nov 12, 2002 + HBIKE_V, // 37 + BURNS_V, // 38 + HONOR_V, // 39 + + CARMOR, // 40 + CCURATOR, // 41 + CHEARS, // 42 + CKLIMO, // 43 + CLIMO, // 44 + CNERD, // 45 + + FRINK_V, // 46 + + // some more new cars: dec 18, 2002 + CMILK, // 47 + CDONUT, // 48 + BBMAN_V, // 49 + BOOKB_V, // 50 + CARHOM_V, // 51 + ELECT_V, // 52 + FONE_V, // 53 + GRAMR_V, // 54 + MOE_V, // 55 + MRPLO_V, // 56 + OTTO_V, // 57 + PLOWK_V, // 58 + SCORP_V, // 59 + WILLI_V, // 60 + + // new traffic cars: Jan 11, 2003 + SEDANA, // 61 + SEDANB, // 62 + + // new Chase cars: Jan 11, 2003 + CBLBART, // 63 + CCUBE, // 64 + CDUFF, // 65 + CNONUP, // 66 + + // new Driver cars: Jan 11, 2003 + LISA_V, // 67 + KRUST_V, // 68 + + // new Traffic cars: Jan 13, 2003 + COFFIN, // 69 + HALLO, // 70 + SHIP, // 71 + WITCHCAR, // 72 + + // new Traffic husk: Feb 10, 2003 + HUSKA, // 73 + + // new Driver cars: Feb 11, 2003 + ATV_V, // 74 + DUNE_V, // 75 + HYPE_V, // 76 + KNIGH_V, // 77 + MONO_V, // 78 + OBLIT_V, // 79 + ROCKE_V, // 80 + + // new Traffic cars: Feb 24, 2003 + AMBUL, // 81 + BURNSARM, // 82 + FISHTRUC, // 83 + GARBAGE, // 84 + ICECREAM, // 85 + ISTRUCK, // 86 + NUCTRUCK, // 87 + PIZZA, // 88 + SCHOOLBU, // 89 + VOTETRUC, // 90 + + // new traffic cars: April 2, 2003 + GLASTRUC, // 91 + CFIRE_V, // 92 + + CBONE, + REDBRICK, // 94 + + NUM_VEHICLES, + INVALID + + }; + + */ + + if(strcmp(mName, "bart_v") == 0) + { + mVehicleID = VehicleEnum::BART_V; + } + + if(strcmp(mName, "apu_v") == 0) + { + mVehicleID = VehicleEnum::APU_V; + } + + if(strcmp(mName, "snake_v") == 0) + { + mVehicleID = VehicleEnum::SNAKE_V; + } + + if(strcmp(mName, "homer_v") == 0) + { + mVehicleID = VehicleEnum::HOMER_V; + } + + if(strcmp(mName, "famil_v") == 0) + { + mVehicleID = VehicleEnum::FAMIL_V; + } + + if(strcmp(mName, "gramp_v") == 0) + { + mVehicleID = VehicleEnum::GRAMP_V; + } + + if(strcmp(mName, "cletu_v") == 0) + { + mVehicleID = VehicleEnum::CLETU_V; + } + + if(strcmp(mName, "wiggu_v") == 0) + { + mVehicleID = VehicleEnum::WIGGU_V; + } + + if(strcmp(mName, "marge_v") == 0) + { + mVehicleID = VehicleEnum::MARGE_V; + } + + if(strcmp(mName, "smith_v") == 0) + { + mVehicleID = VehicleEnum::SMITH_V; + } + + if(strcmp(mName, "zombi_v") == 0) + { + mVehicleID = VehicleEnum::ZOMBI_V; + } + + if(strcmp(mName, "cVan") == 0) + { + mVehicleID = VehicleEnum::CVAN; + } + + if(strcmp(mName, "compactA") == 0) + { + mVehicleID = VehicleEnum::COMPACTA; + } + + if(strcmp(mName, "comic_v") == 0) + { + mVehicleID = VehicleEnum::COMIC_V; + } + + if(strcmp(mName, "skinn_v") == 0) + { + mVehicleID = VehicleEnum::SKINN_V; + } + + if(strcmp(mName, "cCola") == 0) + { + mVehicleID = VehicleEnum::CCOLA; + } + + if(strcmp(mName, "cSedan") == 0) + { + mVehicleID = VehicleEnum::CSEDAN; + } + + if(strcmp(mName, "cPolice") == 0) + { + mVehicleID = VehicleEnum::CPOLICE; + } + + if(strcmp(mName, "cCellA") == 0) + { + mVehicleID = VehicleEnum::CCELLA; + } + + if(strcmp(mName, "cCellB") == 0) + { + mVehicleID = VehicleEnum::CCELLB; + } + + if(strcmp(mName, "cCellC") == 0) + { + mVehicleID = VehicleEnum::CCELLC; + } + + if(strcmp(mName, "cCellD") == 0) + { + mVehicleID = VehicleEnum::CCELLD; + } + + if(strcmp(mName, "minivanA") == 0) + { + mVehicleID = VehicleEnum::MINIVANA; + } + + if(strcmp(mName, "pickupA") == 0) + { + mVehicleID = VehicleEnum::PICKUPA; + } + + if(strcmp(mName, "taxiA") == 0) + { + mVehicleID = VehicleEnum::TAXIA; + } + + if(strcmp(mName, "sportsA") == 0) + { + mVehicleID = VehicleEnum::SPORTSA; + } + + if(strcmp(mName, "sportsB") == 0) + { + mVehicleID = VehicleEnum::SPORTSB; + } + + if(strcmp(mName, "SUVA") == 0) + { + mVehicleID = VehicleEnum::SUVA; + } + + if(strcmp(mName, "wagonA") == 0) + { + mVehicleID = VehicleEnum::WAGONA; + } + + if(strcmp(mName, "hbike_v") == 0) + { + mVehicleID = VehicleEnum::HBIKE_V; + mNoSkid = true; + } + + + if(strcmp(mName, "burns_v") == 0) + { + mVehicleID = VehicleEnum::BURNS_V; + } + + if(strcmp(mName, "honor_v") == 0) + { + mVehicleID = VehicleEnum::HONOR_V; + mNoFrontSkid = true; + } + + if(strcmp(mName, "cArmor") == 0) + { + mVehicleID = VehicleEnum::CARMOR; + } + + if(strcmp(mName, "cCurator") == 0) + { + mVehicleID = VehicleEnum::CCURATOR; + } + + if(strcmp(mName, "cHears") == 0) + { + mVehicleID = VehicleEnum::CHEARS; + } + + if(strcmp(mName, "cKlimo") == 0) + { + mVehicleID = VehicleEnum::CKLIMO; + } + + if(strcmp(mName, "cLimo") == 0) + { + mVehicleID = VehicleEnum::CLIMO; + } + + if(strcmp(mName, "cNerd") == 0) + { + mVehicleID = VehicleEnum::CNERD; + } + + if(strcmp(mName, "frink_v") == 0) + { + mVehicleID = VehicleEnum::FRINK_V; + mNoSkid = true; + } + + if(strcmp(mName, "cMilk") == 0) + { + mVehicleID = VehicleEnum::CMILK; + } + + if(strcmp(mName, "cDonut") == 0) + { + mVehicleID = VehicleEnum::CDONUT; + } + + if(strcmp(mName, "bbman_v") == 0) + { + mVehicleID = VehicleEnum::BBMAN_V; + } + + if(strcmp(mName, "bookb_v") == 0) + { + mVehicleID = VehicleEnum::BOOKB_V; + } + + if(strcmp(mName, "carhom_v") == 0) + { + mVehicleID = VehicleEnum::CARHOM_V; + } + + if(strcmp(mName, "elect_v") == 0) + { + mVehicleID = VehicleEnum::ELECT_V; + } + + if(strcmp(mName, "fone_v") == 0) + { + mVehicleID = VehicleEnum::FONE_V; + } + + if(strcmp(mName, "gramR_v") == 0) + { + mVehicleID = VehicleEnum::GRAMR_V; + } + + if(strcmp(mName, "moe_v") == 0) + { + mVehicleID = VehicleEnum::MOE_V; + } + + if(strcmp(mName, "mrplo_v") == 0) + { + mVehicleID = VehicleEnum::MRPLO_V; + } + + if(strcmp(mName, "otto_v") == 0) + { + mVehicleID = VehicleEnum::OTTO_V; + } + + if(strcmp(mName, "plowk_v") == 0) + { + mVehicleID = VehicleEnum::PLOWK_V; + } + + if(strcmp(mName, "scorp_v") == 0) + { + mVehicleID = VehicleEnum::SCORP_V; + } + + if(strcmp(mName, "willi_v") == 0) + { + mVehicleID = VehicleEnum::WILLI_V; + } + + if(strcmp(mName, "sedanA") == 0) + { + mVehicleID = VehicleEnum::SEDANA; + } + + if(strcmp(mName, "sedanB") == 0) + { + mVehicleID = VehicleEnum::SEDANB; + } + + if(strcmp(mName, "cBlbart") == 0) + { + mVehicleID = VehicleEnum::CBLBART; + } + + if(strcmp(mName, "cCube") == 0) + { + mVehicleID = VehicleEnum::CCUBE; + } + + if(strcmp(mName, "cDuff") == 0) + { + mVehicleID = VehicleEnum::CDUFF; + } + + if(strcmp(mName, "cNonup") == 0) + { + mVehicleID = VehicleEnum::CNONUP; + } + + if(strcmp(mName, "lisa_v") == 0) + { + mVehicleID = VehicleEnum::LISA_V; + } + + if(strcmp(mName, "krust_v") == 0) + { + mVehicleID = VehicleEnum::KRUST_V; + } + + if(strcmp(mName, "coffin") == 0) + { + mVehicleID = VehicleEnum::COFFIN; + } + + if(strcmp(mName, "hallo") == 0) + { + mVehicleID = VehicleEnum::HALLO; + } + + if(strcmp(mName, "ship") == 0) + { + mVehicleID = VehicleEnum::SHIP; + m_IsSimpleShadow = false; + mNoSkid = true; + } + + if(strcmp(mName, "witchcar") == 0) + { + mVehicleID = VehicleEnum::WITCHCAR; + mNoSkid = true; + } + + if(strcmp(mName, "huskA") == 0) + { + mVehicleID = VehicleEnum::HUSKA; + } + + if(strcmp(mName, "atv_v") == 0) + { + mVehicleID = VehicleEnum::ATV_V; + } + + if(strcmp(mName, "dune_v") == 0) + { + mVehicleID = VehicleEnum::DUNE_V; + } + + if(strcmp(mName, "hype_v") == 0) + { + mVehicleID = VehicleEnum::HYPE_V; + } + + if(strcmp(mName, "knigh_v") == 0) + { + mVehicleID = VehicleEnum::KNIGH_V; + } + + if(strcmp(mName, "mono_v") == 0) + { + mVehicleID = VehicleEnum::MONO_V; + mNoSkid = true; + } + + if(strcmp(mName, "oblit_v") == 0) + { + mVehicleID = VehicleEnum::OBLIT_V; + } + + if(strcmp(mName, "rocke_v") == 0) + { + mVehicleID = VehicleEnum::ROCKE_V; + mNoFrontSkid = true; + } + + if(strcmp(mName, "ambul") == 0) + { + mVehicleID = VehicleEnum::AMBUL; + } + + if(strcmp(mName, "burnsarm") == 0) + { + mVehicleID = VehicleEnum::BURNSARM; + } + + if(strcmp(mName, "fishtruc") == 0) + { + mVehicleID = VehicleEnum::FISHTRUC; + } + + if(strcmp(mName, "garbage") == 0) + { + mVehicleID = VehicleEnum::GARBAGE; + } + + if(strcmp(mName, "icecream") == 0) + { + mVehicleID = VehicleEnum::ICECREAM; + } + + if(strcmp(mName, "IStruck") == 0) + { + mVehicleID = VehicleEnum::ISTRUCK; + } + + if(strcmp(mName, "nuctruck") == 0) + { + mVehicleID = VehicleEnum::NUCTRUCK; + } + + if(strcmp(mName, "pizza") == 0) + { + mVehicleID = VehicleEnum::PIZZA; + } + + if(strcmp(mName, "schoolbu") == 0) + { + mVehicleID = VehicleEnum::SCHOOLBU; + } + + if(strcmp(mName, "votetruc") == 0) + { + mVehicleID = VehicleEnum::VOTETRUC; + } + + if(strcmp(mName, "glastruc") == 0) + { + mVehicleID = VehicleEnum::GLASTRUC; + } + + if(strcmp(mName, "cFire_v") == 0) + { + mVehicleID = VehicleEnum::CFIRE_V; + } + + if(strcmp(mName, "cBone") == 0) + { + mVehicleID = VehicleEnum::CBONE; + } + + + // the best for last! + if(strcmp(mName, "redbrick") == 0) + { + mVehicleID = VehicleEnum::REDBRICK; + } + + +} + + + +// call back for debug watcher: +//typedef void (*RADDEBUGWATCH_CALLBACK)( void* userData ); +void DebugWatchVehicleTuningCallback(void* userData) +{ + ((Vehicle*)userData)->CalculateValuesBasedOnDesignerParams(); +} + + +void InflictDamageHoodCallback(void* userData) +{ + ((Vehicle*)userData)->DebugInflictDamageHood(); +} + +void InflictDamageBackCallback(void* userData) +{ + ((Vehicle*)userData)->DebugInflictDamageBack(); +} + +void InflictDamageDriverSideCallback(void* userData) +{ + ((Vehicle*)userData)->DebugInflictDamageDriverSide(); +} + +void InflictDamagePassengerSideCallback(void* userData) +{ + ((Vehicle*)userData)->DebugInflictDamagePassengerSide(); +} + + +//============================================================================= +// Vehicle::SetupRadDebugWatchStuff +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::SetupRadDebugWatchStuff() +{ + radDbgWatchAddFloat(&mSpeedKmh, "speed kmh", mName, NULL, (void*)this, 0.0f, 1000.0f); // just want to observe this + radDbgWatchAddFloat(&mForceToDetachCollectible, "Detach Collectible Force", mName, DebugWatchVehicleTuningCallback, (void*)this, 5000.0f, 50000.0f ); + + // Add the static explosion damage variables to the watcher. Make sure to only add them once + // under group "Vehicle" + static bool staticVariablesInitialized = false; + if ( !staticVariablesInitialized) + { + radDbgWatchAddFloat(&Vehicle::s_DamageFromExplosion, "Damage from Explosion (other)", "Vehicle", NULL, NULL, 0, 10.0f ); + radDbgWatchAddFloat(&Vehicle::s_DamageFromExplosionPlayer, "Damage from Explosion (player)", "Vehicle", NULL, NULL, 0, 10.0f ); + staticVariablesInitialized = true; + } + + radDbgWatchAddFloat(&(mDesignerParams.mDpMass), "mass", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 10000.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpGasScale), "gas scale", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 30.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpSlipGasScale), "slip gas scale", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 30.0f ); + + radDbgWatchAddFloat(&(mDesignerParams.mDpHighSpeedGasScale), "high speed gas scale", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 30.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpGasScaleSpeedThreshold), "gas scale threshold", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 1.0f ); + + radDbgWatchAddFloat(&(mDesignerParams.mDpBrakeScale), "brake scale", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 30.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpTopSpeedKmh), "top speed kmh", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 300.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpMaxWheelTurnAngle), "max wheel turn angle", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 55.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpHighSpeedSteeringDrop), "high speed steering drop", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 1.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpTireLateralStaticGrip), "tire grip", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 15.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpTireLateralResistanceNormal), "normal steering", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 500.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpTireLateralResistanceSlip), "slip steering", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 500.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpEBrakeEffect), "ebrake effect", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 1.0f ); + + radDbgWatchAddFloat(&(mDesignerParams.mDpTireLateralResistanceSlipNoEBrake), "slip steering without ebrake", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 500.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpSlipEffectNoEBrake), "slip effect without ebrake", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 1.0f ); + + radDbgWatchAddFloat(&(mDesignerParams.mDpCMOffset.x), "cmoffset x", mName, DebugWatchVehicleTuningCallback, (void*)this, -1.0f, 1.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpCMOffset.y), "cmoffset y", mName, DebugWatchVehicleTuningCallback, (void*)this, -1.0f, 1.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpCMOffset.z), "cmoffset z", mName, DebugWatchVehicleTuningCallback, (void*)this, -1.0f, 1.0f ); + + radDbgWatchAddFloat(&(mDesignerParams.mDpSuspensionLimit), "suspension limit", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 1.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpSpringk), "spring k", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 1.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpDamperc), "damper c", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 1.0f ); + + radDbgWatchAddFloat(&(mDesignerParams.mDpSuspensionYOffset), "suspension Y Offset", mName, DebugWatchVehicleTuningCallback, (void*)this, -1.0f, 1.0f ); + + radDbgWatchAddFloat(&(mDesignerParams.mDpBurnoutRange), "burnout range", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 1.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpMaxSpeedBurstTime), "max speed burst time", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 10.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpDonutTorque), "donut torque", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 20.0f ); + + radDbgWatchAddFloat(&(mDesignerParams.mDpWeebleOffset), "weeble offset", mName, DebugWatchVehicleTuningCallback, (void*)this, -3.0f, 3.0f ); + + radDbgWatchAddFloat(&(mDesignerParams.mDpWheelieRange), "wheelie range", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 1.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpWheelieYOffset), "wheelie Y offset", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, 2.0f ); + radDbgWatchAddFloat(&(mDesignerParams.mDpWheelieZOffset), "wheelie Z offset", mName, DebugWatchVehicleTuningCallback, (void*)this, 0.0f, -2.0f ); + + radDbgWatchAddFunction( "Inflict Damage Hood", &InflictDamageHoodCallback, (void*)this, mName ); + radDbgWatchAddFunction( "Inflict Damage Back", &InflictDamageBackCallback, (void*)this, mName ); + radDbgWatchAddFunction( "Inflict Damage Driver Side", &InflictDamageDriverSideCallback, (void*)this, mName ); + radDbgWatchAddFunction( "Inflict Damage Passenger Side", &InflictDamagePassengerSideCallback, (void*)this, mName ); + +} + +//============================================================================= +// Vehicle::~Vehicle +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: Vehicle +// +//============================================================================= +Vehicle::~Vehicle() +{ + if(GetGameplayManager()->GetCurrentVehicle() == this) + { + GetGameplayManager()->UnregisterVehicleHUDIcon(); + } + + delete mGeometryVehicle; + + delete mPhysicsLocomotion; + delete mTrafficLocomotion; + + int i; + for(i = 0; i < 4; i++) + { + delete mWheels[i]; + mSuspensionJointDrivers[i]->Release(); + + // TODO - need to wrap these somehow in case they don't exist? + if(mInertialJointDrivers[i]) + { + mInertialJointDrivers[i]->Release(); + } + if(mPhysicsJointMatrixModifiers[i]) + { + mPhysicsJointMatrixModifiers[i]->Release(); + } + + } + + delete[] mGearRatios; + delete[] mJointIndexToWheelMapping; + if(mJointIndexToInertialJointDriverMapping) + { + delete[] mJointIndexToInertialJointDriverMapping; + } + + // these moved to a method that can be called from VehicleCentral::RemoveVehicleFromActiveList + //RemoveSelfFromCollisionManager(); + //GetWorldPhysicsManager()->FreeCollisionAreaIndex(mCollisionAreaIndex); + //mCollisionAreaIndex = -1; + + mGroundPlaneWallVolume->Release(); + mGroundPlanePhysicsProperties->Release(); + mPhysicsProperties->Release(); + + if(mRootMatrixDriver) + { + mRootMatrixDriver->Release(); + } + + mPoseEngine->Release(); + + mGroundPlaneSimState->Release(); + + //p3d::inventory->SelectSection("Level"); + //p3d::inventory->Remove(mSimStateArticulated->GetCollisionObject()); + //p3d::inventory->Remove(mSimStateArticulated->GetSimulatedObject()); + + + tRefCounted::Release(mSimStateArticulatedInCar); + tRefCounted::Release(mSimStateArticulatedOutOfCar); + + + int id = mpEventLocator->GetData(); + GetActionButtonManager()->RemoveActionByIndex( id ); + + + mpEventLocator->Release(); + + if(mpDriver) + { + // DO NOT remove a pedestrian manually! + // We need them for recycling within the level. + if( mpDriver->GetRole() != Character::ROLE_PEDESTRIAN ) + { + GetCharacterManager()->RemoveCharacter(mpDriver); + } + tRefCounted::Release(mpDriver); + } + + GetCharacterManager()->ClearTargetVehicle(this); + + rAssert( mName != NULL ); + delete[] mName; + + if(mVehicleEventListener) + { + delete mVehicleEventListener; + } +} + + + +//============================================================================= +// Vehicle::CreateLocomotions +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::CreateLocomotions() +{ +MEMTRACK_PUSH_GROUP( "Vehicle" ); + // is this vehicle being setup for A or B? + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + mPhysicsLocomotion = new(gma)PhysicsLocomotion(this); + mTrafficLocomotion = new(gma)TrafficLocomotion(this); + + //? + SetLocomotion( mLoco ); +MEMTRACK_POP_GROUP( "Vehicle" ); +} + + + + +//============================================================================= +// Vehicle::InitWheelsAndLinkSuspensionJointDrivers +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::InitWheelsAndLinkSuspensionJointDrivers() +{ +MEMTRACK_PUSH_GROUP( "Vehicle" ); + CollisionObject* collObj = mSimStateArticulated->GetCollisionObject(); + CollisionVolume* collVol = collObj->GetCollisionVolume(); + rAssert(collVol); + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + + int i; + for(i = 0; i < 4; i++) + { + CollisionVolume* sub = collVol->GetSubCollisionVolume(mWheelToJointIndexMapping[i], true); // true because we only care about the local sub volume + + // sub should be of type SphereVolumeType + rAssert(sub->Type() == SphereVolumeType); + + float radius = ((SphereVolume*)sub)->GetRadius(); + + mWheels[i] = new(gma)Wheel; + + // now we can init the goddamn wheel + if(i < 2) // todo - way to NOT hardcode this for 4 wheels???? + { + mWheels[i]->Init(this, i, radius, false, true); + } + else + { + mWheels[i]->Init(this, i, radius, true, false); + // try all wheel drive + //mWheels[i]->Init(this, i, radius, true, true); + } + + mSuspensionJointDrivers[i] = new(gma)SuspensionJointDriver(mWheels[i], mWheelToJointIndexMapping[i]); + + mSuspensionJointDrivers[i]->AddRef(); + mPoseEngine->AddPoseDriver(1, mSuspensionJointDrivers[i]); + } +MEMTRACK_POP_GROUP("Vehicle"); +} + + +//============================================================================= +// Vehicle::InitFlappingJoints +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +bool Vehicle::InitFlappingJoints() +{ + + tPose* p3dPose = mGeometryVehicle->GetP3DPose(); + rAssert(p3dPose); + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + mJointIndexToInertialJointDriverMapping = new(gma) int[p3dPose->GetNumJoint()]; + + // TODO ? + // ok to use memset? + memset(mJointIndexToInertialJointDriverMapping, -1, (sizeof(int) * p3dPose->GetNumJoint()) ); + + + int count = 0; + + rmt::Vector axis; + rmt::Vector rotAxis; + + bool atLeastOneFlappingJointPresent = false; + + + //----------------- + // driver-side door + //----------------- + axis.Set(0.0f, 0.0f, -1.0f); + rotAxis.Set(0.0f, 1.0f, 0.0f); + if(AddFlappingJoint("DoorDTrans", "DoorDRot", axis, rotAxis, count, &mDoorDJoint)) + { + // override default settings + + mInertialJointDrivers[count]->SetSpeedRate(15.0f); + mInertialJointDrivers[count]->SetAccelRate(180.0f); + mInertialJointDrivers[count]->SetGravityRate(0.5f); + mInertialJointDrivers[count]->SetCentrifugalRate(2.0f); + + atLeastOneFlappingJointPresent = true; + } + + //-------------------- + // passenger-side door + //-------------------- + count++; + rotAxis.Set(0.0f, -1.0f, 0.0f); + if(AddFlappingJoint("DoorPTrans", "DoorPRot", axis, rotAxis, count, &mDoorPJoint)) + { + mInertialJointDrivers[count]->SetSpeedRate(15.0f); + mInertialJointDrivers[count]->SetAccelRate(180.0f); + mInertialJointDrivers[count]->SetGravityRate(0.5f); + mInertialJointDrivers[count]->SetCentrifugalRate(2.0f); + + atLeastOneFlappingJointPresent = true; + } + + //----- + // hood + //----- + count++; + axis.Set(0.0f, 0.0f, 1.0f); + rotAxis.Set(-1.0f, 0.0f, 0.0f); + if(AddFlappingJoint("HoodTrans", "HoodRot", axis, rotAxis, count, &mHoodJoint)) + { + mInertialJointDrivers[count]->SetSpeedRate(3.0f); + mInertialJointDrivers[count]->SetAccelRate(300.0f); + mInertialJointDrivers[count]->SetGravityRate(1.0f); + mInertialJointDrivers[count]->SetCentrifugalRate(1.0f); + + atLeastOneFlappingJointPresent = true; + } + + //------ + // trunk + //------ + count++; + axis.Set(0.0f, 0.0f, -1.0f); + rotAxis.Set(1.0f, 0.0f, 0.0f); + if(AddFlappingJoint("TrunkTrans", "TrunkRot", axis, rotAxis, count, &mTrunkJoint)) + { + mInertialJointDrivers[count]->SetSpeedRate(12.0f); + mInertialJointDrivers[count]->SetAccelRate(180.0f); + mInertialJointDrivers[count]->SetGravityRate(1.0f); + mInertialJointDrivers[count]->SetCentrifugalRate(1.0f); + + atLeastOneFlappingJointPresent = true; + } + + return atLeastOneFlappingJointPresent; + +} + + +//============================================================================= +// Vehicle::AddFlappingJoint +//============================================================================= +// Description: Comment +// +// Parameters: (const char* transJointName, const char* rotJointName, rmt::Vector& axis, rmt::Vector& rotAxis, int count) +// +// Return: void +// +//============================================================================= +bool Vehicle::AddFlappingJoint(const char* transJointName, const char* rotJointName, rmt::Vector& axis, rmt::Vector& rotAxis, int count, int* collisionJointIndex) +{ +MEMTRACK_PUSH_GROUP( "Vehicle" ); + + //SkeletonInfo* skelInfo = p3d::find<SkeletonInfo>(mName); + SkeletonInfo* skelInfo = mPhObj->GetSkeletonInfo (); + rAssert(skelInfo); + + // release debugging: + if(skelInfo == 0) + { + char buffy[64]; + sprintf(buffy, "can't find skelInfo for %s\n", mName); + rReleaseString(buffy); + } + + + tPose* p3dPose = mGeometryVehicle->GetP3DPose(); + rAssert(p3dPose); + + + int indexTrans; + int indexRot; + + indexTrans = p3dPose->FindJointIndex(transJointName); + indexRot = p3dPose->FindJointIndex(rotJointName); + + *collisionJointIndex = indexRot; + + if(indexTrans == -1 || indexRot == -1) + { + MEMTRACK_POP_GROUP( "Vehicle" ); + return false; + } + + #ifdef RAD_DEBUG // wrap in define? + tPose::Joint* transJoint = p3dPose->GetJoint(indexTrans); + tPose::Joint* rotJoint = p3dPose->GetJoint(indexRot); + rAssert(rotJoint->parent == transJoint); + #endif + + skelInfo->SetJointAxis(indexTrans, axis, 1.0f); + skelInfo->SetJointAxis(indexRot, axis, 1.0f); + + skelInfo->SetJointRotAxis(indexRot, rotAxis); + + // add new driver... + mJointIndexToInertialJointDriverMapping[indexRot] = count; + + PhysicsJoint* phizJoint = mPhObj->GetJoint(indexRot); + rAssert(phizJoint); + + // test + //phizJoint->SetInvStiffness(0.5f); + + + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + mInertialJointDrivers[count] = new(gma) PhysicsJointInertialEffector(phizJoint); + + mInertialJointDrivers[count]->AddRef(); + + // defaults + // override shortly after this... + + mInertialJointDrivers[count]->SetSpeedRate(15.0f); + //mInertialJointDrivers[count]->SetAccelRate(20.0f); + mInertialJointDrivers[count]->SetAccelRate(180.0f); + mInertialJointDrivers[count]->SetGravityRate(1.0f); + mInertialJointDrivers[count]->SetCentrifugalRate(2.0f); + + // new + // start out disabled + + mPhObj->GetJoint(indexRot)->SetInvStiffness(0.0f); + mPhObj->GetJoint(indexRot)->ResetDeformation(); + + mInertialJointDrivers[count]->SetIsEnabled(false); + + // debug + //mInertialJointDrivers[count]->SetIsEnabled(true); + + + /* + mInertialJointDrivers[count]->SetSpeedRate(0.0f); + //mInertialJointDrivers[count]->SetAccelRate(20.0f); + mInertialJointDrivers[count]->SetAccelRate(0.0f); + mInertialJointDrivers[count]->SetGravityRate(0.0f); + mInertialJointDrivers[count]->SetCentrifugalRate(2.0f); + */ + + + // TODO - which pose engine pass? + mPoseEngine->AddPoseDriver(2, mInertialJointDrivers[count]); + + // also need this other jointmatrixmodifier thing... + mPhysicsJointMatrixModifiers[count] = new(gma) PhysicsJointMatrixModifier(phizJoint); + + mPhysicsJointMatrixModifiers[count]->AddRef(); + + mPoseEngine->AddPoseDriver(2, mPhysicsJointMatrixModifiers[count]); + + MEMTRACK_POP_GROUP("Vehicle"); + return true; +} + + + +//============================================================================= +// Vehicle::InitGears +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::InitGears() +{ + mNumGears = 6; // TODO - not sure we actually want designers to worry about this? + // maybe, they can just choose number of gears and there will be pre-defined ratios?? + // + // recall: all this gear shit is just for sound and does not affect performance + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + mGearRatios = new(gma) float[mNumGears]; + + mFinalDriveRatio = 3.42f; + + mGearRatios[0] = 2.97f; // 1st + //mGearRatios[1] = 2.07f; // 2nd + mGearRatios[1] = 1.8f; // 2nd + mGearRatios[2] = 1.43f; // 3rd + mGearRatios[3] = 1.00f; // 4th + mGearRatios[4] = 0.84f; // 5th + mGearRatios[5] = 0.56f; // 6th + + + +/* some stuff fromt trav for a corvette + + Transmission..........6-speed manual + Final-drive ratio..........3.42:1, limited slip + Gear..........Ratio..........Mph/1000 rpm..........Max. test speed + I..........2.97..........7.4..........48 mph (6500 rpm) + II..........2.07..........10.6..........69 mph (6500 rpm) + III..........1.43..........15.3..........100 mph (6500 rpm) + IV..........1.00..........21.9..........143 mph (6500 rpm) + V..........0.84..........26.1..........168 mph (6450 rpm) + VI..........0.56..........39.2..........155 mph (4000 rpm) + +*/ + + +} + + +//============================================================================= +// Vehicle::SetupPhysicsProperties +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::SetupPhysicsProperties() +{ + // called when we first create the sim state, as well as when + // we set new designer paramters. + + //float volume = mPhObj->GetVolume(); + + float volume = ((ArticulatedPhysicsObject*)(mSimStateArticulatedInCar->GetSimulatedObject(-1)))->GetVolume(); + + // TODO - verify this is coming back in the correct units + + // can't set mass directly + float density = mDesignerParams.mDpMass / volume; + + // TODO - which of these do we actually want to allow designers to modify? + //mPhysicsProperties->SetFrictCoeffCGS(0.8f); + //mPhysicsProperties->SetRestCoeffCGS(1.05f); + //mPhysicsProperties->SetTangRestCoeffCGS(0.0f); + + // these values slide you along walls a little nicer + mPhysicsProperties->SetFrictCoeffCGS(0.3f); + mPhysicsProperties->SetRestCoeffCGS(1.15f); + mPhysicsProperties->SetTangRestCoeffCGS(-0.6f); + + + //mPhysicsProperties->SetDensityCGS(density / 1000000.0f); // our density is in g / m^3 + mPhysicsProperties->SetDensityCGS(density); // our density is in g / m^3 + + //mSimStateArticulated->SetPhysicsProperties(mPhysicsProperties); + + mSimStateArticulatedInCar->SetPhysicsProperties(mPhysicsProperties); + if(mSimStateArticulatedOutOfCar) + { + mSimStateArticulatedOutOfCar->SetPhysicsProperties(mPhysicsProperties); + } + + mPhObj->SetInvTWDissip(0); + mPhObj->SetDissipationInternalRate(0); + mPhObj->SetDissipationDeformationRate(0); //no good Martin + + + // TODO - where to put this! + // should i even use it? + + // TODO + // temp hack to fix lack of phizsim + + const rmt::Vector& gravity = GetWorldPhysicsManager()->mSimEnvironment->Gravity();// this or CGS ? + + //float gravity_y = 9.81f; + float gravity_y = -1.0f * gravity.y; + + //rmt::Vector gravity = SG::phizSim.mSimEnvironment->Gravity(); + //mSuspensionRestValue = mDesignerParams.mDpMass * rmt::Fabs(gravity.y) * 0.25f; + mSuspensionRestValue = mDesignerParams.mDpMass * gravity_y * 0.25f; + + // new thing aimed at minimizing strage whipping effects on the car when the wheels bottom out + // and you get insane values calculated for suspension force + mSuspensionMaxValue = mDesignerParams.mDpMass * gravity_y * 2.5f; // TODO - find the right value + + + mCMOffset = mOriginalCMOffset; + + mCMOffset.Add(mDesignerParams.mDpCMOffset); + + ((ArticulatedPhysicsObject*)(mSimStateArticulatedInCar->GetSimulatedObject(-1)))->SetExternalCMOffset(mCMOffset); + + //mPhObj->SetExternalCMOffset(mCMOffset); + + +} + + +//============================================================================= +// Vehicle::InitGroundPlane +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::InitGroundPlane() +{ +MEMTRACK_PUSH_GROUP( "Vehicle" ); + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + + rmt::Vector p(0.0f, 0.0f, 0.0f); + rmt::Vector n(0.0f, 1.0f, 0.0f); + + HeapMgr()->PushHeap (gma); + + mGroundPlaneWallVolume = new WallVolume(p, n); + mGroundPlaneWallVolume->AddRef(); + + mGroundPlaneSimState = (sim::ManualSimState*)(SimState::CreateManualSimState(mGroundPlaneWallVolume)); + mGroundPlaneSimState->AddRef(); + + mGroundPlaneSimState->GetCollisionObject()->SetManualUpdate(true); + mGroundPlaneSimState->GetCollisionObject()->SetAutoPair(false); + mGroundPlaneSimState->GetCollisionObject()->SetIsStatic(true); + + char buffy[128]; + sprintf(buffy, "%s_groundplane", mName); + + mGroundPlaneSimState->GetCollisionObject()->SetName(buffy); + + mGroundPlaneSimState->mAIRefIndex = this->GetGroundPlaneAIRef(); + mGroundPlaneSimState->mAIRefPointer = (void*)this; + + mGroundPlanePhysicsProperties = new PhysicsProperties; + mGroundPlanePhysicsProperties->AddRef(); + + mGroundPlanePhysicsProperties->SetFrictCoeffCGS(0.8f); + mGroundPlanePhysicsProperties->SetRestCoeffCGS(1.15f); + mGroundPlanePhysicsProperties->SetTangRestCoeffCGS(0.0f); + + mGroundPlaneSimState->SetPhysicsProperties(mGroundPlanePhysicsProperties); + + HeapMgr()->PopHeap (gma); + +/* + + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + + rmt::Vector p, n; + p.Set(0.0f, 0.0f, 0.0f); + //n.Set(0.0f, 1.0f, 0.0f); + n.Set(0.0f, 0.0f, 1.0f); + + mGroundPlaneWallVolume = new(gma) WallVolume(p, n); + + mGroundPlaneWallVolume->AddRef(); + + mGroundPlaneSimState = SimState::CreateSimState(mGroundPlaneWallVolume); + mGroundPlaneSimState->AddRef(); + //mGroundPlaneSimState->GetCollisionObject()->SetIsStatic(true); + mGroundPlaneSimState->GetCollisionObject()->SetAutoPair(false); // TODO - does not work yet + // TODO - name? + + char buffy[128]; + sprintf(buffy, "%s_groundplane", mName); + + mGroundPlaneSimState->GetCollisionObject()->SetName(buffy); + + mGroundPlaneSimState->mAIRefIndex = this->GetGroundPlaneAIRef(); + mGroundPlaneSimState->mAIRefPointer = (void*)this; + + mGroundPlanePhysicsProperties = new(gma) PhysicsProperties; + mGroundPlanePhysicsProperties->AddRef(); + + mGroundPlanePhysicsProperties->SetFrictCoeffCGS(0.8f); + mGroundPlanePhysicsProperties->SetRestCoeffCGS(1.05f); + mGroundPlanePhysicsProperties->SetTangRestCoeffCGS(0.0f); + + mGroundPlaneSimState->SetPhysicsProperties(mGroundPlanePhysicsProperties); + + +*/ + + + +MEMTRACK_POP_GROUP("Vehicle"); +} + + + + +//============================================================================= +// Vehicle::FetchWheelMapping +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::FetchWheelMapping() +{ + // should have already done these asserts, but just in case things + // get moved around a bit... + + rAssert(mGeometryVehicle); + + tPose* p3dPose = mGeometryVehicle->GetP3DPose(); + rAssert(p3dPose); + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + mJointIndexToWheelMapping = new(gma) int[p3dPose->GetNumJoint()]; + + memset(mJointIndexToWheelMapping, -1, (sizeof(int) * p3dPose->GetNumJoint()) ); + + // + // naming convention so far: + // + // should have joints named: + // + // w0, w1, w2, w3 + + char buffy[8]; + memset(buffy, 0, sizeof(buffy)); + + + int i; + for(i = 0; i < 4; i++) + { + // TODO! + // safe to use sprintf ?? + sprintf(buffy, "w%d", i); + mWheelToJointIndexMapping[i] = p3dPose->FindJointIndex(buffy); + + // for convenience we can look up either way.... + mJointIndexToWheelMapping[mWheelToJointIndexMapping[i]] = i; + + tPose::Joint* joint = p3dPose->GetJoint(mWheelToJointIndexMapping[i]); + + // fill mSuspensionRestPoints[4] with the transform row of the joint object space + // matrix + + mSuspensionRestPointsFromFile[i] = joint->objectMatrix.Row(3); + + mSuspensionRestPoints[i] = mSuspensionRestPointsFromFile[i]; + + mSuspensionRestPoints[i].y += mDesignerParams.mDpSuspensionYOffset; + + // used to be other stuff in here for radius, and Wheels, and suspensionjointdrivers + + } + + // passenger/driver locations + + int passengerIndex = p3dPose->FindJointIndex( "pl" ); + int driverIndex = p3dPose->FindJointIndex( "dl" ); + + if(passengerIndex != -1) + { + tPose::Joint* joint = p3dPose->GetJoint( passengerIndex ); + mPassengerLocation = joint->objectMatrix.Row(3); + } + else + { + mPassengerLocation.Set( 0.5f, -0.6f, 0.01f ); + } + + if(driverIndex != -1) + { + tPose::Joint* joint = p3dPose->GetJoint( driverIndex ); + mDriverLocation = joint->objectMatrix.Row(3); + } + else + { + mDriverLocation.Set( -0.5f, -0.6f, 0.01f ); + } + + + mWheelBase = mSuspensionRestPoints[3].z - mSuspensionRestPoints[1].z; + + // smoke location + + //int smokeIndex = p3dPose->FindJointIndex("smoke"); + int smokeIndex = p3dPose->FindJointIndex("sl"); + tPose::Joint* joint = p3dPose->GetJoint(smokeIndex); + if (joint) + { + mSmokeOffset = joint->objectMatrix.Row(3); + } + +} + + +//============================================================================= +// Vehicle::GetWheel0Offset +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: rmt +// +//============================================================================= +rmt::Vector Vehicle::GetWheel0Offset() +{ + // this is not accurate as wheel is lurching around but should be close enough for smoke effects? + rmt::Vector temp = mSuspensionRestPoints[0]; + temp.y -= mWheels[0]->mRadius; + return temp; +} + + +//============================================================================= +// Vehicle::GetWheel1Offset +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: rmt +// +//============================================================================= +rmt::Vector Vehicle::GetWheel1Offset() +{ + rmt::Vector temp = mSuspensionRestPoints[1]; + temp.y -= mWheels[1]->mRadius; + return temp; +} + +//============================================================================= +// Vehicle::InitSimState +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::InitSimState(SimEnvironment* se) +{ +MEMTRACK_PUSH_GROUP( "Vehicle" ); + CreatePoseEngine(); + rAssert(mPoseEngine); + + // The section stuff here is a hack. + // Tracked to ATG as bug 1259. + // + p3d::inventory->PushSection (); + p3d::inventory->AddSection (SKELCACHE); + p3d::inventory->SelectSection (SKELCACHE); + //mSimStateArticulated = SimStateArticulated::CreateSimStateArticulated(mName, mPoseEngine->GetPose(), SimStateAttribute_Default, NULL); + + SimStateArticulated* newSimState = SimStateArticulated::CreateSimStateArticulated(mName, mPoseEngine->GetPose(), SimStateAttribute_Default, NULL); + rAssert( mSimStateArticulatedInCar == NULL ); + tRefCounted::Assign( mSimStateArticulatedInCar, newSimState ); + rAssert( mSimStateArticulatedInCar != NULL ); + + + mSimStateArticulatedInCar->mAIRefIndex = PhysicsAIRef::redBrickVehicle; + + mSimStateArticulatedInCar->mAIRefPointer = (void*)this; + + + ((ArticulatedPhysicsObject*)(mSimStateArticulatedInCar->GetSimulatedObject(-1)))->SetSimEnvironment(se); + + + + + // new test: + char buffy[128]; + sprintf(buffy, "%sBV", mName); + rAssert( mSimStateArticulatedOutOfCar == NULL ); + mSimStateArticulatedOutOfCar = SimStateArticulated::CreateSimStateArticulated(buffy, mPoseEngine->GetPose(), SimStateAttribute_Default, NULL); + + if(mSimStateArticulatedOutOfCar) + { + + mSimStateArticulatedOutOfCar->AddRef(); + mSimStateArticulatedOutOfCar->mAIRefIndex = PhysicsAIRef::redBrickVehicle; + + mSimStateArticulatedOutOfCar->mAIRefPointer = (void*)this; + ((ArticulatedPhysicsObject*)(mSimStateArticulatedOutOfCar->GetSimulatedObject(-1)))->SetSimEnvironment(se); + + } + + + p3d::inventory->PopSection (); + + // finish initialization with this... then switch back + mSimStateArticulated = mSimStateArticulatedInCar; + + + + + + + mPhObj = (ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)); + + // fetch once only ever + // seven fucking days + //mOriginalCMOffset = mPhObj->GetExternalCMOffset(); + + mOriginalCMOffset.x = 0.0f; + mOriginalCMOffset.y = 0.0f; + mOriginalCMOffset.z = 0.0f; + + + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + mPhysicsProperties = new(gma) PhysicsProperties; // TODO - who owns - ie. who is responsible to delete + + mPhysicsProperties->AddRef(); + +/* + for (int i=0; i<mPhObj->NumSimJoints(); i++) + { + PhysicsJoint* joint = mPhObj->GetSimJoint(i); + P3DASSERT(joint); + mPoseEngine->AddPoseDriver(2, new PhysicsJointMatrixModifier(joint)); + } + + sim::PhysicsJointMatrixModifier mPhysicsJointMatrixModifiers[4]; +*/ +MEMTRACK_POP_GROUP( "Vehicle" ); +} + + +//============================================================================= +// Vehicle::CreatePoseEngineOutOfCar +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +/* +void Vehicle::CreatePoseEngineOutOfCar() +{ +MEMTRACK_PUSH_GROUP( "Vehicle" ); + // TODO - wtf? + //const int PoseEngineSimPass = 1; + const int PoseEngineSimPass = 2; + + rAssert(mGeometryVehicle); + + + + tPose* p3dPose = mGeometryVehicle->GetP3DPose(); + rAssert(p3dPose); + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + mPoseEngine = new(gma) poser::PoseEngine(p3dPose, PoseEngineSimPass+1, p3dPose->GetNumJoint()); + mRootMatrixDriver = new(gma) RootMatrixDriver(&mTransform); + + mPoseEngine->AddRef(); + mRootMatrixDriver->AddRef(); + mPoseEngine->AddPoseDriver(1, mRootMatrixDriver); // 0 is the pass + +MEMTRACK_POP_GROUP(); + +} +*/ +//============================================================================= +// Vehicle::CreatePoseEngine +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void Vehicle::CreatePoseEngine() +{ +MEMTRACK_PUSH_GROUP( "Vehicle" ); + // TODO - wtf? + //const int PoseEngineSimPass = 1; + const int PoseEngineSimPass = 2; + + rAssert(mGeometryVehicle); + + + + tPose* p3dPose = mGeometryVehicle->GetP3DPose(); + rAssert(p3dPose); + + GameMemoryAllocator gma = GetGameplayManager()->GetCurrentMissionHeap(); + mPoseEngine = new(gma) poser::PoseEngine(p3dPose, PoseEngineSimPass+1, p3dPose->GetNumJoint()); + mRootMatrixDriver = new(gma) RootMatrixDriver(&mTransform); + + mPoseEngine->AddRef(); + mRootMatrixDriver->AddRef(); + //mPoseEngine->AddPoseDriver(1, mRootMatrixDriver); // 0 is the pass + mPoseEngine->AddPoseDriver(0, mRootMatrixDriver); // 0 is the pass + +MEMTRACK_POP_GROUP("Vehicle"); +} diff --git a/game/code/worldsim/redbrick/vehiclelocomotion.cpp b/game/code/worldsim/redbrick/vehiclelocomotion.cpp new file mode 100644 index 0000000..4cf7f2b --- /dev/null +++ b/game/code/worldsim/redbrick/vehiclelocomotion.cpp @@ -0,0 +1,46 @@ +/*=========================================================================== + vehiclelocomotion.cpp + + created April 24, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ + + +#include <worldsim/redbrick/vehiclelocomotion.h> + + + +/* +//------------------------------------------------------------------------ +VehicleLocomotion::VehicleLocomotion() +{ + // +} + + + +//------------------------------------------------------------------------ +VehicleLocomotion::~VehicleLocomotion() +{ + // +} + + +//------------------------------------------------------------------------ +void VehicleLocomotion::PreCollisionPrep(Vehicle* vehicle) +{ + +} + + +//------------------------------------------------------------------------ +void VehicleLocomotion::Update(Vehicle* vehicle, float dt) +{ + +} +*/
\ No newline at end of file diff --git a/game/code/worldsim/redbrick/vehiclelocomotion.h b/game/code/worldsim/redbrick/vehiclelocomotion.h new file mode 100644 index 0000000..a700dc6 --- /dev/null +++ b/game/code/worldsim/redbrick/vehiclelocomotion.h @@ -0,0 +1,41 @@ +/*=========================================================================== + vehiclelocomotion.h + + created April 24, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ + +#ifndef _VEHICLELOCOMOTION_H +#define _VEHICLELOCOMOTION_H + +#include <radmath/radmath.hpp> + +class Vehicle; + +class VehicleLocomotion +{ +public: + + VehicleLocomotion(Vehicle* vehicle) {}; + virtual ~VehicleLocomotion() {}; + + virtual void PreSubstepUpdate() = 0; + + virtual void PreCollisionPrep(bool firstSubstep) = 0; + virtual void UpdateVehicleGroundPlane() = 0; + + virtual void PreUpdate() = 0; + virtual void Update(float dt) = 0; + virtual void PostUpdate() = 0; + + virtual void CompareNormalAndHeight(int index, rmt::Vector& normalPointingAtCar, rmt::Vector& groundContactPoint) = 0; + +}; + + +#endif // _VEHICLELOCOMOTION_H diff --git a/game/code/worldsim/redbrick/wheel.cpp b/game/code/worldsim/redbrick/wheel.cpp new file mode 100644 index 0000000..f889909 --- /dev/null +++ b/game/code/worldsim/redbrick/wheel.cpp @@ -0,0 +1,533 @@ +/*=========================================================================== + wheel.cpp + + created Jan 29, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ + +#include <worldsim/redbrick/wheel.h> +#include <radmath/radmath.hpp> +#include <worldsim/worldphysicsmanager.h> + + +//------------------------------------------------------------------------ +Wheel::Wheel() +{ + mRadius = 0.0f; + mObjectSpaceYOffsetFromCurrentPosition = 0.0f; + + mWheelNum = -1; // unset flag + mYOffset = 0.0f; + + mLimit = 0.0f; + mk = 0.0f; + mc = 0.0f; + + mWheelInCollision = false; // TODO - is this method adequate!? + mWheelBottomedOutThisFrame = false; + + mSpringRelaxRate = 0.5f; // second to move from limit to 0 when not in collision + + mWheelTurnAngle = 0.0f; + + mRotAngle = 0.0f; + mCumulativeRot = 0.0f; + mTotalTime = 0.0f; + + mWheelRenderSpeedRadPerSecond = 0.0f; + //mSpinUpRate = 10.0f; //? - I think this is in (rad/s)/s + + //mRenderingSpinUpRateBase = 0.1f; // this is actually in units of rad/s - + // it's a direct setting of the increment amount, for now + + mRenderingSpinUpRateBase = 100.0f; // back to rad/s + + mRenderingSpinUpRate = 0.0f; // this one is scaled by the gas input + + // % of top speed at which mode goes back to NORMAL, if we were slipping 'cause we floored it initially + mDpRenderingSpinUpSpeedThresholdPercentage = 0.3f; + //mDpRenderingSpinUpSpeedThresholdPercentage = 0.2f; + + + mSteerWheel = false; + mDriveWheel = false; + + + mVehicle = 0; + + //---------------- + // designer values + //---------------- + + mWheelState = WS_NORMAL; + + + //--------- + // redbrick normal + //--------- + + // NOTE: these are all overwritten shortly after init + // these are just default values that are ok for the ferrari + + mDpSuspensionLimit = 0.4f; // try this for ferrari + + mDpSpringk = 0.5f; // prett good value for redbrick + mDpDamperc = 0.2f; // scale on critical damping temp for debugging + + +} + + +//------------------------------------------------------------------------ +Wheel::~Wheel() +{ + // +} + + +//------------------------------------------------------------------------ +void Wheel::Init(Vehicle* vehicle, int wheelNum, float radius, bool steer, bool drive) +{ + mVehicle = vehicle; + + mWheelNum = wheelNum; + mRadius = radius; + + mSteerWheel = steer; + mDriveWheel = drive; + + // + // calculate 'real' values from designer params + // + + mLimit = mDpSuspensionLimit * mRadius; + + // TODO + // temp hack for lack of phizsim + //float gravity_y = 9.81f; + + const rmt::Vector& gravity = GetWorldPhysicsManager()->mSimEnvironment->Gravity();// this or CGS ? + + //float gravity_y = 9.81f; + float gravity_y = -1.0f * gravity.y; + + //rmt::Vector gravity = SG::phizSim.mSimEnvironment->Gravity(); + //float force = mVehicle->mDesignerParams.mDpMass * rmt::Fabs(gravity.y); + float force = mVehicle->mDesignerParams.mDpMass * gravity_y; + mk = force / mLimit; + mk *= mDpSpringk; + + //mqk = rmt::Sqrt(mk) * 2.0f; // first guess at quad spring + mqk = mk * 5.0f; // first guess at quad spring + //mqk = mk * 10.0f; // first guess at quad spring + + float criticalDamping = 2.0f * (float)rmt::Sqrt(mk * mVehicle->mDesignerParams.mDpMass); + mc = criticalDamping * mDpDamperc; + + + // these will get recalculated wheh SetDesignParams is called + +} + +//------------------------------------------------------------------------ +void Wheel::SetDesignerParams(Vehicle::DesignerParams* dp) +{ + + mDpSuspensionLimit = dp->mDpSuspensionLimit; + + mDpSpringk = dp->mDpSpringk; + mDpDamperc = dp->mDpDamperc; + + + //---------- + + mLimit = mDpSuspensionLimit * mRadius; + + // TODO + // temp hack for lack of phizsim + //float gravity_y = 9.81f; + + const rmt::Vector& gravity = GetWorldPhysicsManager()->mSimEnvironment->Gravity();// this or CGS ? + + //float gravity_y = 9.81f; + float gravity_y = -1.0f * gravity.y; + + //rmt::Vector gravity = SG::phizSim.mSimEnvironment->Gravity(); + //float force = mPhysicsVehicleOwner->mDpMass * rmt::Fabs(gravity.y); + float force = mVehicle->mDesignerParams.mDpMass * gravity_y; + mk = force / mLimit; + mk *= mDpSpringk; + + //mqk = rmt::Sqrt(mk) * 2.0f; // first guess at quad spring + mqk = mk * 5.0f; // first guess at quad spring + //mqk = mk * 10.0f; // first guess at quad spring + + + float criticalDamping = 2.0f * (float)rmt::Sqrt(mk * mVehicle->mDesignerParams.mDpMass); + mc = criticalDamping * mDpDamperc; + + + CalculateRenderingSpinUpRateBase(dp->mDpTopSpeedKmh); + +} + +//------------------------------------------------------------------------ +float Wheel::CalculateSuspensionForce(float suspensionPointYVelocity, float dt) +{ + + rAssert(0); + + float force = 0.0f; + + if(mWheelInCollision) + { + + float springForce = mk * mYOffset; + + // apply extra force if we're close to bottoming out + // + // TODO + // hmmmm..... revisit this... + // replace with quadratic + + // + // or... topping out! + // + if((mLimit - rmt::Fabs(mYOffset)) < mLimit * 0.25f) + { + springForce *= 3.0f; + } + + // for now, only add damping if chassis is trying to compress suspension - ie. y velocity is down, -ve + float damperForce = 0.0f; + + if(suspensionPointYVelocity < 0.0f) + { + damperForce = mc * -1.0f * suspensionPointYVelocity; + } + else + { + // like the havok guys, only let damper pull down at about 1/3 of push up + damperForce = mc * -1.0f * suspensionPointYVelocity * 0.3f; + + } + + force = springForce + damperForce; + } + else + { + // need to relax spring somehow + float relaxDistance = mLimit / mSpringRelaxRate * dt; + if(mYOffset >= relaxDistance) + { + mYOffset -= relaxDistance; + } + else if(mYOffset <= -relaxDistance) + { + mYOffset += relaxDistance; + } + + } + return force; + + +} + + + +//------------------------------------------------------------------------------------------------ +void Wheel::CalculateRenderingSpinUpRateBase(float topSpeedKmh) +{ + // mDpRenderingSpinUpSpeedThreshold; + + // at what rad/s is the wheel going % of top speed + + float topspeedmps = topSpeedKmh / 3.6f; + + float thresholdmps = topspeedmps * mDpRenderingSpinUpSpeedThresholdPercentage; + + float linearDistancePerRev = rmt::PI * mRadius * 2.0f; + + // rev/s = rev/dist * dist/s + + // ! fuck you idiot + // we need rads not revs + float linearDistancePerRad = linearDistancePerRev / (2.0f * rmt::PI); + + + mRenderingSpinUpRateBase = (1.0f / linearDistancePerRad) * thresholdmps; + + + +} + + +//------------------------------------------------------------------------ +void Wheel::CalculateRotAngle(float dt) +{ + if(mVehicle->mEBrake > 0.0f && mWheelInCollision) + { + // now, if no gas, don't rotate + if(mVehicle->mGas == 0.0f && this->mDriveWheel == true) + { + mRotAngle = 0.0f; + return; + } + + } + + + float speedAlongFacing; + + rmt::Vector wheelDirection = mVehicle->mVehicleFacing; + + if(mSteerWheel) + { + rmt::Matrix steeringMatrix; // TODO - make this a class member? + steeringMatrix.Identity(); + steeringMatrix.FillRotateY(mWheelTurnAngle); + + wheelDirection.Transform(steeringMatrix); + } + + speedAlongFacing = wheelDirection.DotProduct(mVehicle->mSuspensionPointVelocities[mWheelNum]); + + //float linearDistance = speedAlongFacing * dt; + + float linearDistancePerRev = rmt::PI * mRadius * 2.0f; + + float revPerTime = speedAlongFacing / linearDistancePerRev; + + float radiansPerTime = revPerTime * 2.0f * rmt::PI; + + // ? + // see if vehicle's doin' a burnout + if(mVehicle->mBurnoutLevel > 0.0f && mDriveWheel) + { + if(mVehicle->mGas > mVehicle->mBrake) + { + radiansPerTime = mRenderingSpinUpRateBase; + } + else + { + radiansPerTime = -1.0f * mRenderingSpinUpRateBase; + } + } + + + /* + if(mWheelState == WS_LOCOMOTIVE_SLIDE || mWheelState == WS_LOCOMOTIVE_FREE_SPIN) + { + if(radiansPerTime >= mRenderingSpinUpRate) + { + // kick out of thid mode and use the calculated value + // + // TODO - how to then easily tune the mRenderingSpinUpRateBase? + // + // should be able to calculate based on some % of top speed + // yeah - no fucking timer needed! + + mWheelState = WS_NORMAL; + + } + else + { + //mRotAngle *= 1.5f; + //otAngle = mRenderingSpinUpRate; + radiansPerTime = mRenderingSpinUpRate; + } + + } + */ + + mRotAngle = radiansPerTime * dt; + + //if(rmt::Fabs(radiansPerTime - (2.0f * rmt::PI)) < 0.1f) + //{ + // int stophere = 1; + // } + + + // fucking hack to (temporarily?) fix wheel rot on ps2 + /* + mTotalTime += dt; + + if(mTotalTime > 30.0f) // value is in seconds + { + mTotalTime = 0.0f; + mCumulativeRot = 0.0f; + } + */ + + mCumulativeRot += mRotAngle; + + if(mCumulativeRot > rmt::PI_2) + { + mCumulativeRot -= rmt::PI_2; + + //safeguard - higly unlikely we'd get here... + if(mCumulativeRot > rmt::PI_2) + { + // still? + // something strange must have happened + mCumulativeRot = 0.0f; + } + + } + + + + + // TODO + // what we might want to try here is always calculate the wheel rotation to render based on desired forces + // not the actual forces we apply. + // + // add booleans to the Cap methods? + // + // also need to throw in some consideration for braking - draw wheels locked. + // + // furthermore, this should be done before the vehicle PostUpdate where the pose drivers actually change the joint matrices + + +} + +//============================================================================= +// Wheel::SetYOffsetFromCurrentPosition +//============================================================================= +// Description: +// +// Parameters: (float yoffset) +// +// Return: float +// +// note - if we've bottomed out, this will return the overflow +// amount that the suspension could not absorb +// if we didn't bottom out it will return 0.0f; +// +// +//============================================================================= +float Wheel::SetYOffsetFromCurrentPosition(float yoffset) +{ + + // the parameter mObjectSpaceYOffsetFromCurrentPosition is a bit verbose at this + // point since before doing wheel collision we set the wheels to the bottom of the + // suspension limit, so at this poitn the currentposition is always the same + + if(yoffset > mObjectSpaceYOffsetFromCurrentPosition) + { + mObjectSpaceYOffsetFromCurrentPosition = yoffset; + } + // TODO - make sure this gets reset to 0.0 at the right place + + + // this call got propagated to us from the collision solver, so I think this is the place to set the flag + mWheelInCollision = true; + + // temp + // TODO - remove + //return 0.0f; + + + if(mObjectSpaceYOffsetFromCurrentPosition > 2.0f * mLimit) + { + // suspension has bottomed out + // + // TODO - rename this method? + // + // TODO - double check this + + float overflow = mObjectSpaceYOffsetFromCurrentPosition - (2.0f * mLimit); + mObjectSpaceYOffsetFromCurrentPosition = 2.0f * mLimit; + + return overflow; + //return true; + } + + // ?? + // TODO + // is this ok? + // should be if we always started out at the bottom of the suspension limit + if(mObjectSpaceYOffsetFromCurrentPosition < 0.0f) + { + rAssert(0); + mObjectSpaceYOffsetFromCurrentPosition = 0.0f; + } + + return 0.0f; + +} + + +//------------------------------------------------------------------------ +void Wheel::ResolveOffset() +{ + // we assume at this point anyone who wants to has called SetYOffsetFromCurrentPosition + mYOffset += mObjectSpaceYOffsetFromCurrentPosition; + + + // temp + // TODO - remove + //mObjectSpaceYOffsetFromCurrentPosition = 0.0f; + //return; + + if(mYOffset > mLimit) + { + mYOffset = mLimit; + } + if(mYOffset < -mLimit) + { + mYOffset = -mLimit; + } + + // ! + // + // mYOffset is only of use internally to this class + // + // this is NOT the value other shit should use to move suspension points around and what have you. + + + + mObjectSpaceYOffsetFromCurrentPosition = 0.0f; + //mWheelInCollision = false; // TODO - is this method adequate!? + +} + + +//------------------------------------------------------------------------ +float Wheel::GetYCorrectionValue() +{ + return mObjectSpaceYOffsetFromCurrentPosition; +} + + + + +//------------------------------------------------------------------------ +void Wheel::Reset() +{ + mYOffset = 0.0f; + mObjectSpaceYOffsetFromCurrentPosition = 0.0f; + + mRotAngle = 0.0f; + + mCumulativeRot = 0.0f; + mTotalTime = 0.0f; + + mWheelInCollision = false; + mWheelBottomedOutThisFrame = false; + + mWheelState = WS_NORMAL; + + mWheelRenderSpeedRadPerSecond = 0.0f; + +} + + + + + diff --git a/game/code/worldsim/redbrick/wheel.h b/game/code/worldsim/redbrick/wheel.h new file mode 100644 index 0000000..832a052 --- /dev/null +++ b/game/code/worldsim/redbrick/wheel.h @@ -0,0 +1,107 @@ +/*=========================================================================== + wheel.h + + created Jan 29, 2002 + by Greg Mayer + + Copyright (c) 2002 Radical Entertainment, Inc. + All rights reserved. + + +===========================================================================*/ + +#ifndef _WHEEL_H +#define _WHEEL_H + +#include <radmath/radmath.hpp> + +#include <worldsim/redbrick/vehicle.h> + +class SuspensionJointDriver; + +enum WheelState{ WS_NORMAL, WS_SLIP, WS_LOCOMOTIVE_SLIDE, WS_LOCOMOTIVE_FREE_SPIN }; + +class Wheel +{ +private: + + friend class SuspensionJointDriver; + friend class Vehicle; + friend class PhysicsLocomotion; + friend class GeometryVehicle; // everyone's friends with the Wheel :) + friend class RedBrickCollisionSolverAgent; + + + Wheel(); + ~Wheel(); + + Vehicle* mVehicle; + + // TODO - initialize k and c from vehicle too...easier to tune? + void Init(Vehicle* vehicle, int wheelNum, float radius, bool steer, bool drive); + void Reset(); + + void SetDesignerParams(Vehicle::DesignerParams* dp); + + // this will check the one we're passing in against the one stored + float SetYOffsetFromCurrentPosition(float yoffset); + void ResolveOffset(); // modify mYOffset by the final thing left in mObjectSpaceYOffsetFromCurrentPosition + + float GetYCorrectionValue(); + + float CalculateSuspensionForce(float suspensionPointYVelocity, float dt); + void CalculateRenderingSpinUpRateBase(float topSpeedKmh); + + bool mSteerWheel; + bool mDriveWheel; + + int mWheelNum; + float mRadius; + float mObjectSpaceYOffsetFromCurrentPosition; // set frame to frame by collision agent + + + float mYOffset; // object space offset from suspension rest point + + void CalculateRotAngle(float dt); + float mRotAngle; + float mCumulativeRot; + float mTotalTime; + + float mLimit; + float mk; // spring constant + float mc; // damper constant + + float mqk; // new test - quadratic spring + + bool mWheelInCollision; + bool mWheelBottomedOutThisFrame; + + float mSpringRelaxRate; + + float mWheelTurnAngle; + + + + WheelState mWheelState; + + float mWheelRenderSpeedRadPerSecond; + + float mRenderingSpinUpRate; + float mRenderingSpinUpRateBase; + + + //--------------------- + // designer parameters: + //--------------------- + + float mDpSuspensionLimit; // multiplier on radius + float mDpSpringk; + float mDpDamperc; // normalized values + + float mDpRenderingSpinUpSpeedThresholdPercentage; + + +}; + + +#endif // _WHEEL_H
\ No newline at end of file diff --git a/game/code/worldsim/skidmarks/SkidMarkGenerator.cpp b/game/code/worldsim/skidmarks/SkidMarkGenerator.cpp new file mode 100644 index 0000000..df7d860 --- /dev/null +++ b/game/code/worldsim/skidmarks/SkidMarkGenerator.cpp @@ -0,0 +1,272 @@ +//=========================================================================== +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// Component: SkidMarkGenerator +// +// Description: A per-vehicle class that generates skidmarks +// +// Authors: Michael Riegger +// +//=========================================================================== + +//--------------------------------------------------------------------------- +// Includes +//=========================================================================== +#include <stdlib.h> + +#include <worldsim/SkidMarks/SkidMarkGenerator.h> +#include <worldsim/SkidMarks/skidmarkmanager.h> +#include <worldsim/SkidMarks/skidmark.h> + +#include <radmath/util.hpp> +#include <float.h> +#include <debug/profiler.h> +#include <raddebug.hpp> +#include <raddebugwatch.hpp> + +#ifdef RAD_RELEASE +const float SKID_INTENSITY_DAMPENING = 0.5f; +#else +static float SKID_INTENSITY_DAMPENING = 0.5f; +#endif + + +SkidMarkGenerator::SkidMarkGenerator() +{ +#ifndef RAD_RELEASE + static bool s_AddedDebugging = false; + if ( s_AddedDebugging == false ) + { + radDbgWatchAddFloat( &SKID_INTENSITY_DAMPENING, "Skid mark dampening", "Skidmarks" ); + s_AddedDebugging = true; + } +#endif +} + +SkidMarkGenerator::~SkidMarkGenerator() +{ + for(int i = 0; i < 4; i++) + { + if(m_CurrentSkidData[i].currentSkid) + { + GetSkidmarkManager()->ReturnUsedSkidmark( m_CurrentSkidData[i].currentSkid ); + } + m_CurrentSkidData[i].currentSkid = NULL; + } +} + +// Height in meters to raise the skids above the ground to avoid z fighting + +// Keep pointers to the tShaders +static tShader* spPavementShader = NULL; +static tShader* spDirtShader = NULL; +static tShader* spGrassShader = NULL; + +// The shader names for use in searching for skid mark shaders in the inventory +const char* PAVEMENT_SKIDMARK_SHADERNAME = "skid_m"; +const char* DIRT_SKIDMARK_SHADERNAME = "dirt_skidmark_shader"; +const char* GRASS_SKIDMARK_SHADERNAME = "grass_skidmark_shader"; + +// enables z test, disables z test, required for correct +// skid mark appearance +static void SetShaderSkidSettings( tShader* pShader ) +{ + pShader->SetInt(PDDI_SP_ALPHAOP1, PDDI_BLEND_ALPHA); + pShader->SetInt(PDDI_SP_TEXTUREOP1, PDDI_TEXBLEND_MODULATE); + pShader->SetInt(PDDI_SP_COLOUROP1, PDDI_BLEND_SUBTRACT); +} + +// Find the shaders in the inventory, set zwrite/ztest (since the artists can't +// export these shader settings directly and addref them +// SkidMarkGenerator::ReleaseShaders() must be called afterwards +void SkidMarkGenerator::InitShaders() +{ + + // Just so that we never do an accidental double addref + ReleaseShaders(); + + spPavementShader = p3d::find< tShader >( PAVEMENT_SKIDMARK_SHADERNAME ); + if ( spPavementShader != NULL ) + { + spPavementShader->AddRef(); + SetShaderSkidSettings( spPavementShader ); + } + rTuneWarningMsg( spPavementShader != NULL, "Pavement skid shader not found in inventory! Some skids will not appear!"); + + spDirtShader = p3d::find< tShader >( DIRT_SKIDMARK_SHADERNAME ); + if ( spDirtShader != NULL ) + { + spDirtShader->AddRef(); + SetShaderSkidSettings( spDirtShader ); + } + rTuneWarningMsg( spDirtShader != NULL, "Dirt skid shader not found in inventory! Some skids will not appear!"); + + spGrassShader = p3d::find< tShader >( GRASS_SKIDMARK_SHADERNAME ); + if ( spGrassShader != NULL ) + { + spGrassShader->AddRef(); + SetShaderSkidSettings( spGrassShader ); + } + + rTuneWarningMsg( spGrassShader != NULL, "Grass skid shader not found in inventory! Some skids will not appear!"); + + +} +// Fetch the appropriate shader for the terrain type +tShader* SkidMarkGenerator::GetShader( eTerrainType terrainType ) +{ + tShader* shader = NULL; + + switch (terrainType) + { + case TT_Dirt: + shader = spDirtShader; + break; + case TT_Grass: + shader = spGrassShader; + break; + case TT_Road: + default: + shader = spPavementShader; + break; + }; + return shader; +} + + +// Releases all the shaders that were addrefed in InitShaders() +void SkidMarkGenerator::ReleaseShaders() +{ + if ( spPavementShader != NULL ) + { + spPavementShader->Release(); + spPavementShader = NULL; + } + if ( spDirtShader != NULL ) + { + spDirtShader->Release(); + spDirtShader = NULL; + } + if ( spGrassShader != NULL ) + { + spGrassShader->Release(); + spGrassShader = NULL; + } +} + + + + +void SkidMarkGenerator::GenerateSkid( int wheel, const SkidMarkGenerator::SkidData& skidData ) +{ + if ( skidData.velocityDirection.MagnitudeSqr() < 0.01f || + skidData.intensity < 0.01f ) + return; + + + float dampenedIntensity = skidData.intensity * SKID_INTENSITY_DAMPENING; + + + rAssert( wheel >=0 && wheel < 4 ); + TireGeneratorData& generatorData = m_CurrentSkidData[ wheel ]; + // Is the tire currently skidding? + // if so, continue it + // otherwise start a new skid + + rmt::Vector alignedOffset; + skidData.transform.RotateVector( generatorData.offset, &alignedOffset ); + rmt::Vector skidCenter = skidData.transform.Row(3) + alignedOffset; + + rmt::Vector skidDirection = skidData.velocityDirection; + skidDirection.Normalize(); + + // Check to see if the terrain type changed + if ( generatorData.currentSkid != NULL ) + { + const float ANGLE_TOO_SHARP = 0.7f; + bool angleTooSharp = generatorData.currentSkid->GetCurrentDirection().Dot( skidDirection ) < ANGLE_TOO_SHARP; + bool terrainTypeChanged = generatorData.terrainType != skidData.terrainType; + + if ( angleTooSharp || terrainTypeChanged ) + { + // Finish off the current skid and start a new one + generatorData.currentSkid->FadeOutTrailingVertices(); + GetSkidmarkManager()->ReturnUsedSkidmark( generatorData.currentSkid ); + generatorData.currentSkid = NULL; + GenerateSkid( wheel, skidData ); + } + } + // Check to see if + + if ( generatorData.currentSkid != NULL ) + { + // Is there space left for more vertices? + if ( generatorData.currentSkid->SpaceLeft() ) + { + generatorData.currentSkid->Extend( skidCenter, skidDirection, skidData.groundPlaneNormal, generatorData.halfWidth, dampenedIntensity ); + generatorData.skidExtendedThisFrame = true; + } + else + { + // No space left. We must continue this skid with a new skidmark + Skidmark* skidMark = GetSkidmarkManager()->GetUnusedSkidmark(); + if ( skidMark != NULL ) + { + generatorData.currentSkid->ContinueSkidmark( skidMark ); + GetSkidmarkManager()->ReturnUsedSkidmark( generatorData.currentSkid ); + generatorData.currentSkid = skidMark; + generatorData.currentSkid->Extend( skidCenter, skidDirection, skidData.groundPlaneNormal, generatorData.halfWidth, dampenedIntensity ); + generatorData.skidExtendedThisFrame = true; + } + else + { + // No space available! + // Tell the current skid to fade out, we are done. Pretend the skid is finished + generatorData.currentSkid->FadeOutTrailingVertices(); + GetSkidmarkManager()->ReturnUsedSkidmark( generatorData.currentSkid ); + generatorData.currentSkid = NULL; + generatorData.skidExtendedThisFrame = false; + } + } + } + else + { + // Fetch a skidmark thats not being used right now + generatorData.currentSkid = GetSkidmarkManager()->GetUnusedSkidmark(); + if ( generatorData.currentSkid != NULL ) + { + generatorData.currentSkid->Extend( skidCenter, skidDirection, skidData.groundPlaneNormal, generatorData.halfWidth, dampenedIntensity ); + generatorData.skidExtendedThisFrame = true; + generatorData.terrainType = skidData.terrainType; + generatorData.currentSkid->SetShader( GetShader( skidData.terrainType ) ); + generatorData.currentSkid->FadeInVertices(); + } + } +} + + + +void +SkidMarkGenerator::Update( ) +{ + for ( int i = 0 ; i < 4 ; i++ ) + { + if ( m_CurrentSkidData[i].skidExtendedThisFrame == false) + { + if ( m_CurrentSkidData[i].currentSkid != NULL ) + { + m_CurrentSkidData[i].currentSkid->FadeOutTrailingVertices(); + GetSkidmarkManager()->ReturnUsedSkidmark( m_CurrentSkidData[i].currentSkid ); + m_CurrentSkidData[i].currentSkid = NULL; + } + } + else + { + m_CurrentSkidData[i].skidExtendedThisFrame = false; + } + } +} + + + + diff --git a/game/code/worldsim/skidmarks/SkidMarkGenerator.h b/game/code/worldsim/skidmarks/SkidMarkGenerator.h new file mode 100644 index 0000000..ebea931 --- /dev/null +++ b/game/code/worldsim/skidmarks/SkidMarkGenerator.h @@ -0,0 +1,187 @@ +//=========================================================================== +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// Component: SkidMarkGenerator +// +// Description: A per-vehicle class that generates skidmarks +// +// Authors: Michael Riegger +// +//=========================================================================== + +// Recompilation protection flag. +#ifndef SKIDMARKGENERATOR_H +#define SKIDMARKGENERATOR_H + +//=========================================================================== +// Nested Includes +//=========================================================================== + +#include <render/IntersectManager/IntersectManager.h> + + + +//=========================================================================== +// Forward References +//=========================================================================== + +class Skidmark; +class tShader; + +//=========================================================================== +// Constants, Typedefs, and Macro Definitions (needed by external clients) +//=========================================================================== + +//=========================================================================== +// Interface Definitions +//=========================================================================== + +//=========================================================================== +// +// Description: +// +// +// +// +// +// Constraints: +// +//=========================================================================== + + + +class SkidMarkGenerator +{ + public: + // Tries and finds the shaders the skidmarks in the inventory, retrieves + // them so that skidmarkgenerator doesn't need to call p3d::find<> + // and also sets the zwrite/ztest properties + static void InitShaders(); + // Releases the shaders that were addrefed in initshaders + static void ReleaseShaders(); + + public: + SkidMarkGenerator(); + ~SkidMarkGenerator(); + + enum Constants + { + MAX_NUM_WHEELS = 4 + }; + + // Data for a single tire + struct TireData + { + // Tire dimensions as they would look when pressed evenly flat against the road + // A tank tread, or a snowboard, for instance would be much longer than it is wide + float width; + float length; + // offset of the tire, relative to the vehicle center + rmt::Vector offset; + }; + + struct SkidData + { + SkidData() : groundPlaneNormal( 0 , 1 ,0 ) { } + + // Direction that the vehicle is moving + rmt::Vector velocityDirection; + // Orientation and position of the vehicle + rmt::Matrix transform; + float intensity; // 1 most intense, 0 nonexistent skid + // Ground plane normal, used to raise the skid mark over the ground + // to avoid Z figthing. + // Constraint : this variable must be normalized before use + rmt::Vector groundPlaneNormal; + + // Terrain type underneath the tire. Used to determine which texture to use. + eTerrainType terrainType; + }; + + // Generate a skidmark, starts a new strip if necessary + + + /* + ^ + | + | + | + +Z + | + | + front + _____________ + wheel 2 | | wheel 3 + | | + | | + | | + | | + | | + | | ---+X-----> + | | + | | + | | + | | + | | + wheel 1 ------------- wheel 0 + rear + + */ + + // Sets the translation offset for the specified wheel relative to the + // vehicle's origin + void SetWheelOffset( int wheel, const rmt::Vector& offset ) + { + m_CurrentSkidData[ wheel ].offset = offset; + } + + // Sets the length and width of the wheel as it presses against the ground + // Determines the dimensions of the skid + void SetWheelDimensions( int wheel, float width, float length ) + { + m_CurrentSkidData[ wheel ].halfWidth = width / 2.0f; + } + void GenerateSkid( int wheel, const SkidData& ); + + void Update( ); + + protected: + + private: + + tShader* GetShader( eTerrainType terrainType ); + void LayDownVertices( int wheel, const SkidMarkGenerator::SkidData& skidData ); + + struct TireGeneratorData + { + TireGeneratorData(): + halfWidth( 0.25f ), + currentSkid( NULL ), + skidExtendedThisFrame( false ), + offset( 0,0,0 ), + wasLastVertexLaidTemporary( false ), + terrainType( TT_NumTerrainTypes ) + { + + } + float halfWidth; + Skidmark* currentSkid; + bool skidExtendedThisFrame; + rmt::Vector offset; + bool wasLastVertexLaidTemporary; + eTerrainType terrainType; + }; + + TireGeneratorData m_CurrentSkidData[4]; + + + + // These methods defined as private and not implemented ensure that + // clients will not be able to use them. For example, we will + // disallow SkidMarkGenerator from being copied and assigned. + SkidMarkGenerator( const SkidMarkGenerator& ); + SkidMarkGenerator& operator=( const SkidMarkGenerator& ); + +}; + +#endif
\ No newline at end of file diff --git a/game/code/worldsim/skidmarks/allskidmarks.cpp b/game/code/worldsim/skidmarks/allskidmarks.cpp new file mode 100644 index 0000000..e178a4a --- /dev/null +++ b/game/code/worldsim/skidmarks/allskidmarks.cpp @@ -0,0 +1,3 @@ +#include <worldsim/SkidMarks/SkidMarkGenerator.cpp> +#include <worldsim/SkidMarks/SkidMarkmanager.cpp> +#include <worldsim/skidmarks/skidmark.cpp>
\ No newline at end of file diff --git a/game/code/worldsim/skidmarks/skidmark.cpp b/game/code/worldsim/skidmarks/skidmark.cpp new file mode 100644 index 0000000..8cfd239 --- /dev/null +++ b/game/code/worldsim/skidmarks/skidmark.cpp @@ -0,0 +1,442 @@ +#include <worldsim/SkidMarks/skidmark.h> +#include <camera/supercammanager.h> +#include <debug/profiler.h> + +const float METERS_PER_TEX_UNIT = 0.5f; +const float TEX_UNITS_PER_METER = 1.0f / METERS_PER_TEX_UNIT; + + + +Skidmark::Skidmark(): +m_NumSegments( 0 ), +mpShader( NULL ), +m_IsInDSG( false ), +m_LastU( 0.0f ), +m_TextureDirection( 1.0f ), +m_FadedOut( false ), +m_FadingIn( false ) +{ + // Skidmarks are always translucent + mTranslucent = true; + SetName( "Skidmark" ); +} + +Skidmark::~Skidmark() +{ + if ( mpShader != NULL ) + { + mpShader->Release(); + mpShader = NULL; + } + if ( m_IsInDSG ) + { + RemoveFromDSG(); + m_IsInDSG = false; + } +} + +bool Skidmark::IsVisible()const +{ + if ( m_NumSegments < 2 || m_FadedOut ) + return false; + + rmt::Sphere boundingSphere = m_BoundingBox.GetBoundingSphere(); + + if (GetSuperCamManager()->GetSCC( 0 )->GetCamera()->SphereVisible( boundingSphere.centre, boundingSphere.radius ) ) + { + return true; + } + else + { + return false; + } +} + + +void Skidmark::Display() +{ + if ( m_NumSegments < 2 || mpShader == NULL ) + return; + + BEGIN_PROFILE("Skidmark::Display") + bool oldZWrite = p3d::pddi->GetZWrite(); + pddiCompareMode oldZComp = p3d::pddi->GetZCompare(); + if( oldZWrite ) + { + p3d::pddi->SetZWrite( false ); + } + + pddiPrimStream* pStream = p3d::pddi->BeginPrims( mpShader->GetShader(), PDDI_PRIM_TRISTRIP, PDDI_V_CT, m_NumSegments * 2 ); + for ( int i = 0 ; i < m_NumSegments ; i++ ) + { + tColour colour( m_Segments[i].intensity, m_Segments[i].intensity, m_Segments[i].intensity, m_Segments[i].intensity ); + pStream->Vertex( &m_Segments[ i ].left.position, colour, &m_Segments[ i ].left.uv ); + pStream->Vertex( &m_Segments[ i ].right.position, colour, &m_Segments[ i ].right.uv ); + } + p3d::pddi->EndPrims( pStream ); + + if( oldZWrite ) + { + p3d::pddi->SetZWrite( true ); + } + END_PROFILE("Skidmark::Display") +} + +void Skidmark::Extend( const rmt::Vector& position, const rmt::Vector& forward, const rmt::Vector groundNormal, float halfWidth, float intensity ) +{ + const float Z_FIGHTING_OFFSET = 0.1f; + + rAssert( SpaceLeft () ); + rmt::Vector elevatedPosition = position + (groundNormal * Z_FIGHTING_OFFSET); + + + + rmt::Box3D oldBoundingBox; + GetBoundingBox( &oldBoundingBox ); + + m_CurrentDirection = forward; + + unsigned char ucIntensity = static_cast< unsigned char >( intensity * 255.0f ); + + // if vertices have been laid already + // find texture break position + // it is on the straight line vector from the last position to the current one + // extend the last two vertices laid to the texture break position + // the texture break position is recorded as the last position written + // lay down two more at the current (given position) + if ( m_NumSegments > 0 ) + { + // Check to see that we have travelled the minimum distance from the last + // point that we laid down a segment + if ( ( elevatedPosition - m_LastPositionWritten ).MagnitudeSqr() < 0.05f ) + return; + + rmt::Vector toPosition = elevatedPosition - m_LastPositionWritten; + toPosition.Normalize(); + rmt::Vector right; + right.CrossProduct( toPosition, groundNormal ); + right.Normalize(); + + rmt::Vector textureBreakPosition; + float breakPointU; + float distanceToTextureBreakPoint; + float distanceToGivenPoint; + if ( FindTextureBreakPosition( elevatedPosition, &textureBreakPosition, &breakPointU, &distanceToTextureBreakPoint, &distanceToGivenPoint ) ) + { + float distanceFromStart = distanceToTextureBreakPoint + m_Segments[ m_NumSegments - 1].distanceFromStart; + // ExtendVertices( textureBreakPosition, right, halfWidth, ucIntensity, breakPointU, distanceFromStart ); + m_LastPositionWritten = textureBreakPosition; + // Reverse texture direction + m_TextureDirection *= -1.0f; + // Calculate the texture coordinate of the elevated position + // u = distance( tbp, ep ) * TEX_UNITS_PER_METER * texturedirection + //float distance = (textureBreakPosition - elevatedPosition).Magnitude(); + float currentU = breakPointU + distanceToTextureBreakPoint * TEX_UNITS_PER_METER * m_TextureDirection; + if ( currentU > 1.0f ) + currentU = 1.0f; + else if ( currentU < 0 ) + currentU = 0; + m_LastU = currentU; + + distanceFromStart = distanceToGivenPoint + m_Segments[ m_NumSegments - 1].distanceFromStart; + WriteVertices( elevatedPosition, right, halfWidth, ucIntensity, currentU, distanceFromStart ); + WriteVertices( elevatedPosition, right, halfWidth, ucIntensity, currentU, distanceFromStart ); + m_LastPositionWritten = elevatedPosition; + } + else + { + // We haven't crossed a texture break point boundary + // extend the last two vertices written to the current position + // then write two more vertices, also at the current position for use in + // the next extension + float distance = (m_LastPositionWritten - elevatedPosition).Magnitude(); + + float currentU = m_LastU + distance * TEX_UNITS_PER_METER * m_TextureDirection; + + float distanceFromStart = m_Segments[ m_NumSegments - 1 ].distanceFromStart + distance; + + ExtendVertices( elevatedPosition, right, halfWidth, ucIntensity, currentU, distanceFromStart ); + WriteVertices( elevatedPosition, right, halfWidth, ucIntensity, currentU, distanceFromStart ); + m_LastPositionWritten = elevatedPosition; + m_LastU = currentU; + } + + } + else + { + rmt::Vector right; + right.CrossProduct( forward, groundNormal ); + right.Normalize(); + + // Brand new strip + // write 4 vertices, first two are fixed, 2nd two are temporaries + WriteVertices( elevatedPosition, right, halfWidth, ucIntensity, 0, 0 ); + WriteVertices( elevatedPosition, right, halfWidth, ucIntensity, 0, 0 ); + m_TextureDirection = 1.0f; + m_LastU = 0; + m_LastPositionWritten = elevatedPosition; + } + // Toss it in the dsg if its not there already + if ( m_IsInDSG == false ) + { + AddToDSG( RenderEnums::LevelSlot ); + } + else + { + MoveInDSG( oldBoundingBox ); + } + +} + +void Skidmark::ClearVertices() +{ + // Reset the bounding box + + // Which direction are we laying our coordinates, either 1.0 or -1.0 + m_LastU = 0.0f; + + RemoveFromDSG(); + m_NumSegments = 0; + m_FadedOut = false; + m_FadingIn = false; + + m_BoundingBox.low = rmt::Vector(FLT_MAX,FLT_MAX,FLT_MAX); + m_BoundingBox.high = rmt::Vector(-FLT_MAX,-FLT_MAX,-FLT_MAX); + +} + +void Skidmark::ContinueSkidmark( Skidmark* newSkidmark ) +{ + tRefCounted::Assign( newSkidmark->mpShader, mpShader ); + newSkidmark->ClearVertices(); + newSkidmark->m_LastPositionWritten = m_LastPositionWritten; + newSkidmark->m_Segments[ 0 ] = m_Segments[ m_NumSegments - 1 ]; + newSkidmark->m_Segments[ 1 ] = m_Segments[ m_NumSegments - 2 ]; + newSkidmark->m_NumSegments = 2; + newSkidmark->m_LastU = m_LastU; + newSkidmark->m_TextureDirection = m_TextureDirection; + newSkidmark->m_CurrentDirection = m_CurrentDirection; + rmt::Box3D oldBox; + GetBoundingBox( &oldBox ); + + newSkidmark->m_BoundingBox.Expand( newSkidmark->m_Segments[ 0 ].left.position ); + newSkidmark->m_BoundingBox.Expand( newSkidmark->m_Segments[ 0 ].right.position ); + newSkidmark->m_BoundingBox.Expand( newSkidmark->m_Segments[ 1 ].left.position ); + newSkidmark->m_BoundingBox.Expand( newSkidmark->m_Segments[ 1 ].right.position ); + + m_NumSegments--; + +} + +void Skidmark::FadeInVertices() +{ + const float DISTANCE_TO_FADE_IN = 1.0f; + + // We want to fade in the last few vertices, but we don't want to make the fade greater in intensity than + // existing ones, so check before writing new values + + if ( m_NumSegments < 1 ) + return; + + for ( int i = 0 ; i < m_NumSegments ; i++ ) + { + int intensity = static_cast< int >( m_Segments[ i ].distanceFromStart / DISTANCE_TO_FADE_IN * 255.0f ); + if ( m_Segments[ i ].intensity > intensity ) + m_Segments[ i ].intensity = intensity; + } + +} + +void Skidmark::FadeOutTrailingVertices() +{ + const float DISTANCE_TO_FADE_OUT = 1.0f; + + rAssert(m_NumSegments != 0); // happened to me once, probably bad, you can click + // through okay now, but we might want to fix it at some point + // nbrooke, apr 11, 2003 + if(m_NumSegments == 0) + return; + + // We want to fade out the last few vertices, but we don't want to make the fade greater in intensity than + // existing ones, so check before writing new values + + // Last segment is zero + // next to last is -1.dist - this.dist + + float distFadedOutSoFar = 0; + m_Segments[ m_NumSegments - 1 ].intensity = 0; + int intensity = 0; + + for ( int i = m_NumSegments - 2 ; i >= 0 ; i-- ) + { + distFadedOutSoFar += m_Segments[ i + 1 ].distanceFromStart - m_Segments[ i ].distanceFromStart; + intensity = static_cast< int >( distFadedOutSoFar / DISTANCE_TO_FADE_OUT * 255.0f ); + if ( m_Segments[ i ].intensity > intensity ) + m_Segments[ i ].intensity = intensity; + } + +} + +void Skidmark::FadeOut( float deltaAlpha ) +{ + int iDeltaAlpha = static_cast< unsigned char > ( deltaAlpha * 255.0f ); + + bool stillVisible = false; + for ( int i = 0 ; i < m_NumSegments ; i++ ) + { + int newAlpha = m_Segments[i].intensity - iDeltaAlpha; + if ( newAlpha <= 0 ) + { + newAlpha = 0; + } + else + { + stillVisible = true; + } + m_Segments[ i ].intensity = newAlpha; + } + if ( stillVisible == false ) + m_FadedOut = true; +} + +void Skidmark::AddToDSG( RenderEnums::LayerEnum renderLayer ) +{ + if ( m_IsInDSG ) + RemoveFromDSG(); + + m_RenderLayer = renderLayer; + WorldRenderLayer* pWorldRenderLayer = static_cast< WorldRenderLayer* > (GetRenderManager()->mpLayer( m_RenderLayer )); + rAssert( dynamic_cast<WorldRenderLayer*>(pWorldRenderLayer) != NULL ); + pWorldRenderLayer->pWorldScene()->Add( this ); + m_IsInDSG = true; + +} + +void Skidmark::RemoveFromDSG() +{ + + if ( m_IsInDSG ) + { + WorldRenderLayer* pWorldRenderLayer = static_cast< WorldRenderLayer* > (GetRenderManager()->mpLayer( m_RenderLayer )); + rAssert( dynamic_cast<WorldRenderLayer*>(pWorldRenderLayer) != NULL ); + pWorldRenderLayer->pWorldScene()->Remove( this ); + m_IsInDSG = false; + } +} + +void Skidmark::SetShader( tShader* pShader ) +{ + tRefCounted::Assign( mpShader, pShader ); +} + +void Skidmark::WriteVertices( const rmt::Vector& elevatedPosition, + const rmt::Vector& right, + float halfWidth, + unsigned char intensity, + float u, + float distanceFromStart ) +{ + + + rmt::Vector leftVertex = elevatedPosition - (right * halfWidth); + rmt::Vector rightVertex = elevatedPosition + (right * halfWidth); + + // Increase bounding box + m_BoundingBox.Expand( rightVertex ); + m_BoundingBox.Expand( leftVertex ); + + + // Set position data + m_Segments[ m_NumSegments ].left.position = leftVertex; + m_Segments[ m_NumSegments ].right.position = rightVertex; + + + // Intensity + m_Segments[ m_NumSegments ].intensity = intensity; + + // Tex coordinates + m_Segments[ m_NumSegments ].left.uv = pddiVector2( 0.0f, u ); + m_Segments[ m_NumSegments ].right.uv = pddiVector2( 1.0f, u ); + + m_Segments[ m_NumSegments ].distanceFromStart = distanceFromStart; + + m_NumSegments++; +} + +void Skidmark::ExtendVertices( const rmt::Vector& elevatedPosition, + const rmt::Vector& right, + float halfWidth, + unsigned char intensity, + float u, + float distance ) +{ + + rmt::Vector leftVertex = elevatedPosition - (right * halfWidth); + rmt::Vector rightVertex = elevatedPosition + (right * halfWidth); + + // Increase bounding box + m_BoundingBox.Expand( rightVertex ); + m_BoundingBox.Expand( leftVertex ); + + // Set position data + m_Segments[ m_NumSegments - 1 ].left.position = leftVertex; + m_Segments[ m_NumSegments - 1 ].right.position = rightVertex; + + // Intensity + m_Segments[ m_NumSegments - 1 ].intensity = intensity; + + // Tex coordinates + m_Segments[ m_NumSegments -1 ].left.uv = pddiVector2( 0.0f, u ); + m_Segments[ m_NumSegments -1 ].right.uv = pddiVector2( 1.0f, u ); + + m_Segments[ m_NumSegments - 1].distanceFromStart = distance; +} + +bool Skidmark::FindTextureBreakPosition( const rmt::Vector& position, rmt::Vector* texBreakPosition, float* u, float* distanceToTextureBreakPoint, float* distanceToGivenPoint ) +{ + // Calculate the point on the vector from the last vertex written + rmt::Vector toPosition = position - m_LastPositionWritten; + toPosition.Normalize(); + float deltaU; + float breakTexCoordinate; + if ( m_TextureDirection > 0 ) + { + deltaU = 1.0f - m_LastU; + breakTexCoordinate = 1.0f; + } + else + { + deltaU = m_LastU - 0.0f; + breakTexCoordinate = 0.0f; + } + + float distance = METERS_PER_TEX_UNIT * deltaU; + // Check to see that the break position is actually between the last position and + // the given position + *texBreakPosition = m_LastPositionWritten + toPosition * distance; + *u = breakTexCoordinate; + + float distanceToBreakPointSqr = ( *texBreakPosition - m_LastPositionWritten ).MagnitudeSqr(); + float distanceToGivenPointSqr = ( position - m_LastPositionWritten ).MagnitudeSqr(); + if ( distanceToGivenPointSqr > distanceToBreakPointSqr ) + { + *distanceToTextureBreakPoint = rmt::Sqrt ( distanceToBreakPointSqr ); + *distanceToGivenPoint = rmt::Sqrt ( distanceToGivenPointSqr ); + return true; + } + else + return false; + +} + +void Skidmark::MoveInDSG( rmt::Box3D& oldBox ) +{ + if ( m_IsInDSG ) + { + WorldRenderLayer* pWorldRenderLayer = static_cast< WorldRenderLayer* > (GetRenderManager()->mpLayer( m_RenderLayer )); + rAssert( dynamic_cast<WorldRenderLayer*>(pWorldRenderLayer) != NULL ); + pWorldRenderLayer->pWorldScene()->Move( oldBox, this ); + } +} + + diff --git a/game/code/worldsim/skidmarks/skidmark.h b/game/code/worldsim/skidmarks/skidmark.h new file mode 100644 index 0000000..5803913 --- /dev/null +++ b/game/code/worldsim/skidmarks/skidmark.h @@ -0,0 +1,85 @@ +#ifndef SKIDMARK_H +#define SKIDMARK_H + +#include <render/DSG/StaticEntityDSG.h> +#include <render/RenderManager/WorldRenderLayer.h> +#include <render/RenderManager/RenderManager.h> + + +// A contigous skidmark +class Skidmark : public StaticEntityDSG +{ +public: + Skidmark(); + ~Skidmark(); + virtual void Display(); + virtual void GetBoundingBox( rmt::Box3D* box ){ *box = m_BoundingBox; } + virtual void GetBoundingSphere( rmt::Sphere* sphere ) { *sphere = m_BoundingBox.GetBoundingSphere(); } + bool SpaceLeft()const { return (NUM_SEGMENTS - m_NumSegments) >= 3; } + bool IsVisible()const; + void Extend( const rmt::Vector& position, const rmt::Vector& forward, const rmt::Vector groundNormal, float halfWidth, float intensity ); + void ClearVertices(); + // Continue a skidmark by copying the last two vertices + texture information to the new + // skidmark + void ContinueSkidmark( Skidmark* newSkidmark ); + void FadeInVertices(); + void FadeOutTrailingVertices(); + void FadeOut( float deltaAlpha ); + void AddToDSG( RenderEnums::LayerEnum renderLayer ); + void RemoveFromDSG(); + bool IsInDSG()const{ return m_IsInDSG; } + void SetShader( tShader* pShader ); + void SetFadeIn( bool useFadeIn ){ m_FadingIn = useFadeIn; } + const rmt::Vector& GetCurrentDirection()const { return m_CurrentDirection; } + +private: + + void WriteVertices( const rmt::Vector& elevatedPosition, + const rmt::Vector& rightVector, + float halfWidth, + unsigned char intensity, + float u, + float distance ); + + void ExtendVertices( const rmt::Vector& elevatedPosition, + const rmt::Vector& rightVector, + float halfWidth, + unsigned char intensity, + float u, + float distance ); + void MoveInDSG( rmt::Box3D& oldBox ); + + + bool FindTextureBreakPosition( const rmt::Vector& position, rmt::Vector* texBreakPosition, float* u, float* distanceToTBP, float* distanceToGivenPoint ); + + struct Vertex + { + pddiVector position; + pddiVector2 uv; + }; + + struct Segment + { + Vertex left,right; + unsigned char intensity; + float distanceFromStart; + }; + enum { NUM_SEGMENTS = 33 }; + int m_NumSegments; + Segment m_Segments[ NUM_SEGMENTS ]; + + tShader* mpShader; + rmt::Box3D m_BoundingBox; + bool m_IsInDSG; + RenderEnums::LayerEnum m_RenderLayer; + // The midpoint of the last position written, needed for texture coordinate generation + rmt::Vector m_LastPositionWritten; + float m_LastU; + float m_TextureDirection; + bool m_FadedOut; + // Is this the start of the skid? If so, fade it in + bool m_FadingIn; + rmt::Vector m_CurrentDirection; +}; + +#endif diff --git a/game/code/worldsim/skidmarks/skidmarkmanager.cpp b/game/code/worldsim/skidmarks/skidmarkmanager.cpp new file mode 100644 index 0000000..d1e5391 --- /dev/null +++ b/game/code/worldsim/skidmarks/skidmarkmanager.cpp @@ -0,0 +1,178 @@ +//=========================================================================== +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// Component: SkidmarkManager +// +// Description: +// +// Authors: Michael Riegger +// +//=========================================================================== + +//--------------------------------------------------------------------------- +// Includes +//=========================================================================== + +#include <worldsim/SkidMarks/skidmarkmanager.h> +#include <memory/srrmemory.h> +#include <worldsim/SkidMarks/skidmark.h> +#include <radtime.hpp> + +//=========================================================================== +// Local Constants, Typedefs, and Macros +//=========================================================================== + +const float ALPHA_FADE_PER_MILLISECOND = 0.001f; + +//=========================================================================== +// Global Data, Local Data, Local Classes +//=========================================================================== + +SkidmarkManager* SkidmarkManager::spInstance = NULL; + +//=========================================================================== +// Member Functions +//=========================================================================== + +SkidmarkManager::ManagedSkidmark::ManagedSkidmark(): +age( 0 ), +inUse( false ) +{ + skidmark = new Skidmark(); + skidmark->AddRef(); +} + +SkidmarkManager::ManagedSkidmark::~ManagedSkidmark() +{ + skidmark->RemoveFromDSG(); + skidmark->Release(); +} + + +SkidmarkManager::SkidmarkManager(): +m_SkidMarks( NULL ), +m_UseTimedFadeouts( false ) +{ + +} + +SkidmarkManager::~SkidmarkManager() +{ + Destroy(); +} + + + +SkidmarkManager* SkidmarkManager::CreateInstance() +{ + HeapMgr()->PushHeap( GMA_LEVEL_OTHER ); + rAssert( spInstance == NULL ); + spInstance = new SkidmarkManager(); + HeapMgr()->PopHeap( GMA_LEVEL_OTHER ); + + return spInstance; +} + +SkidmarkManager* SkidmarkManager::GetInstance() +{ + return spInstance; +} + +void SkidmarkManager::DestroyInstance() +{ + delete spInstance; +} + +void SkidmarkManager::Update( unsigned int timeInMS ) +{ + + const unsigned int TIME_TO_WAIT_BEFORE_FADE = 5000; + unsigned int currentTime = radTimeGetMilliseconds(); + if ( m_UseTimedFadeouts ) + { + + float deltaAlpha = static_cast< float >( timeInMS ) * ALPHA_FADE_PER_MILLISECOND; + // Iterate through all skidmarks, fading out the old ones that aren't in use + for ( int i = 0 ; i < m_NumSkidMarks ; i++ ) + { + if ( m_SkidMarks[i]->age + TIME_TO_WAIT_BEFORE_FADE < currentTime ) + { + if ( m_SkidMarks[i]->skidmark->IsInDSG() && m_SkidMarks[i]->inUse == false ) + { + m_SkidMarks[i]->skidmark->FadeOut( deltaAlpha ); + } + } + } + } +} + +Skidmark* SkidmarkManager::GetUnusedSkidmark() +{ + + Skidmark* availSkidmark = NULL; + for ( int i = 0 ; i < m_NumSkidMarks ; i++ ) + { + if ( m_SkidMarks[ i ]->inUse == false && m_SkidMarks[ i ]->skidmark->IsVisible() == false ) + { + availSkidmark = m_SkidMarks[i]->skidmark; + availSkidmark->ClearVertices(); + m_SkidMarks[ i ]->inUse = true; + + break; + } + } + return availSkidmark; +} + +void SkidmarkManager::ReturnUsedSkidmark( Skidmark* skidmark ) +{ + rAssert(m_SkidMarks); + if(!m_SkidMarks) + { + return; + } + + for ( int i = 0 ; i < m_NumSkidMarks ; i++ ) + { + if ( m_SkidMarks[ i ]->skidmark == skidmark ) + { + rAssert( m_SkidMarks[i]->inUse == true ); + m_SkidMarks[ i ]->inUse = false; + m_SkidMarks[ i ]->age = radTimeGetMilliseconds(); + break; + } + } +} + +void SkidmarkManager::Init( int numSkids ) +{ + + HeapMgr()->PushHeap( GMA_LEVEL_OTHER ); + rAssert( m_SkidMarks == NULL ); + m_NumSkidMarks = numSkids; + rAssert( m_SkidMarks == NULL ); + m_SkidMarks = new ManagedSkidmark*[ numSkids ]; + + for ( int i = 0 ; i < m_NumSkidMarks ; i++) + { + m_SkidMarks[i] = new ManagedSkidmark(); + } + HeapMgr()->PopHeap( GMA_LEVEL_OTHER ); +} + +void SkidmarkManager::Destroy() +{ + + if ( m_SkidMarks != NULL ) + { + for ( int i = 0 ; i < m_NumSkidMarks ; i++) + { + delete m_SkidMarks[i]; + m_SkidMarks[i] = NULL; + } + delete [] m_SkidMarks; + m_SkidMarks = NULL; + } + +} + diff --git a/game/code/worldsim/skidmarks/skidmarkmanager.h b/game/code/worldsim/skidmarks/skidmarkmanager.h new file mode 100644 index 0000000..057be54 --- /dev/null +++ b/game/code/worldsim/skidmarks/skidmarkmanager.h @@ -0,0 +1,94 @@ +//=========================================================================== +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// Component: SkidmarkManager +// +// Description: +// +// Authors: Michael Riegger +// +//=========================================================================== + +// Recompilation protection flag. +#ifndef SKIDMARKMANAGER_H +#define SKIDMARKMANAGER_H + + +//=========================================================================== +// Nested Includes +//=========================================================================== + +//=========================================================================== +// Forward References +//=========================================================================== + +class Skidmark; + +//=========================================================================== +// Constants, Typedefs, and Macro Definitions (needed by external clients) +//=========================================================================== + +const int DEFAULT_NUM_SKID_MARKS = 30; + +//=========================================================================== +// Interface Definitions +//=========================================================================== + +//=========================================================================== +// +// Description: +// Generally, describe what behaviour this class possesses that +// clients can rely on, and the actions that this service +// guarantees to clients. +// +// Constraints: +// +//=========================================================================== +class SkidmarkManager +{ + public: + + // Static Methods (for creating, destroying and acquiring an instance + // of the TriStripDSGManager) + static SkidmarkManager* CreateInstance(); + static SkidmarkManager* GetInstance(); + static void DestroyInstance(); + static SkidmarkManager* spInstance; + + void Update( unsigned int timeInMS ); + Skidmark* GetUnusedSkidmark(); + void ReturnUsedSkidmark( Skidmark* ); + + void SetTimedFadeouts( bool useFadeouts ) { m_UseTimedFadeouts = useFadeouts; } + void Init( int numSkids = DEFAULT_NUM_SKID_MARKS ); + void Destroy(); + + private: + SkidmarkManager(); + ~SkidmarkManager(); + SkidmarkManager( const SkidmarkManager& ); + SkidmarkManager& operator=( const SkidmarkManager& ); + + struct ManagedSkidmark + { + ManagedSkidmark(); + ~ManagedSkidmark(); + + Skidmark* skidmark; + unsigned int age; + bool inUse; + }; + + ManagedSkidmark** m_SkidMarks; + // Returns a preallocated skidmark thats not currently in use at the moment + bool m_UseTimedFadeouts; + int m_NumSkidMarks; + +}; +// A little syntactic sugar for getting at this singleton. +inline SkidmarkManager* GetSkidmarkManager() +{ + return( SkidmarkManager::GetInstance() ); +} + +#endif
\ No newline at end of file diff --git a/game/code/worldsim/spawn/allspawn.cpp b/game/code/worldsim/spawn/allspawn.cpp new file mode 100644 index 0000000..443b1d7 --- /dev/null +++ b/game/code/worldsim/spawn/allspawn.cpp @@ -0,0 +1 @@ +#include <worldsim/spawn/spawnmanager.cpp> diff --git a/game/code/worldsim/spawn/spawnmanager.cpp b/game/code/worldsim/spawn/spawnmanager.cpp new file mode 100644 index 0000000..357a791 --- /dev/null +++ b/game/code/worldsim/spawn/spawnmanager.cpp @@ -0,0 +1,107 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: spawnmanager.cpp +// +// Description: SpawnManager Class Implementation +// +// History: 11/5/2002 + Created -- Dusit Eakkachaichanvet +// +//============================================================================= + +#include <worldsim/spawn/spawnmanager.h> + +void SpawnManager::Init() +{ + ////////////////////////////////////////////////////////////////// + // Sanity check subclass implementations + rAssert( GetAbsoluteMaxObjects() >= 0 ); + rAssert( GetMaxModels() >= 0 ); + rAssert( GetSecondsBetwAdds() >= 0.0f ); + rAssert( GetSecondsBetwRemoves() >= 0.0f ); + rAssert( GetSecondsBetwUpdates() >= 0.0f ); + + mSecondsSinceLastAdd = 0.0f; + mSecondsSinceLastRemove = 0.0f; + mSecondsSinceLastUpdate = 0.0f; + + mAddEnabled = true; + mRemoveEnabled = true; + mUpdateEnabled = true; +} + + +void SpawnManager::Update( float seconds ) +{ + rAssert( seconds >= 0.0f ); + + if( !IsActive() ) + { + return; + } + + /////////////////////////////////////////////////////// + // Do some local sanity checks + rAssert( mSpawnRadius >= 0.0f ); + rAssert( mRemoveRadius >= 0.0f ); + + /////////////////////////////////////////////////////// + // Do some enforcement of subclass implementations + rAssert( 0 <= mNumObjects && mNumObjects <= GetAbsoluteMaxObjects() ); + rAssert( 0 <= GetMaxObjects() && GetMaxObjects() <= GetAbsoluteMaxObjects() ); + rAssert( 0 < GetNumRegisteredModels() && GetNumRegisteredModels() <= GetMaxModels() ); + + /////////////////////////////////////////////////////// + // Do things in this order: + // - Remove everything that's outside the radius + // - Add at the fringes of the radius + // - Update as necessary. + + ///////////////// + // REMOVE PHASE + float secondsBetwRemoves = GetSecondsBetwRemoves(); + rAssert( secondsBetwRemoves >= 0.0f ); + if( mRemoveEnabled && mSecondsSinceLastRemove >= secondsBetwRemoves ) + { + mSecondsSinceLastRemove = 0.0f; + RemoveObjects( seconds ); + } + else + { + mSecondsSinceLastRemove += seconds; + } + rAssert( 0 <= mNumObjects && mNumObjects <= GetAbsoluteMaxObjects() ); + ///////////////// + + //////////////// + // ADD PHASE + float secondsBetwAdds = GetSecondsBetwAdds(); + rAssert( secondsBetwAdds >= 0.0f ); + if( mAddEnabled && mSecondsSinceLastAdd >= secondsBetwAdds ) + { + mSecondsSinceLastAdd = 0.0f; + AddObjects( seconds ); + } + else + { + mSecondsSinceLastAdd += seconds; + } + rAssert( 0 <= mNumObjects && mNumObjects <= GetAbsoluteMaxObjects() ); + //////////////// + + //////////////// + // UPDATE PHASE + float secondsBetwUpdates = GetSecondsBetwUpdates(); + rAssert( secondsBetwUpdates >= 0.0f ); + if( mUpdateEnabled && mSecondsSinceLastUpdate >= secondsBetwUpdates ) + { + mSecondsSinceLastUpdate = 0.0f; + UpdateObjects( seconds ); + } + else + { + mSecondsSinceLastUpdate += seconds; + } + rAssert( (0 <= mNumObjects) && (mNumObjects <= GetAbsoluteMaxObjects() ) ); + //////////////// +} diff --git a/game/code/worldsim/spawn/spawnmanager.h b/game/code/worldsim/spawn/spawnmanager.h new file mode 100644 index 0000000..4a9ccc4 --- /dev/null +++ b/game/code/worldsim/spawn/spawnmanager.h @@ -0,0 +1,154 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: spawnmanager.h +// +// Description: SpawnManager Class Declaration +// +// History: 11/5/2002 + Created -- Dusit Eakkachaichanvet +// +//============================================================================= + +#ifndef SPAWNMANAGER_H +#define SPAWNMANAGER_H + +#include <radmath/radmath.hpp> +#include <raddebug.hpp> + +class SpawnManager +{ +public: + + SpawnManager(); + virtual ~SpawnManager(); + void Update( float seconds ); // don't allow overwriting of Update, which enforces structure + + // PURE VIRTUALS + virtual void Init(); + virtual void ClearAllObjects() = 0; + virtual void ClearObjectsInsideRadius( rmt::Vector center, float radius ) = 0; + virtual void ClearObjectsOutsideRadius( rmt::Vector center, float radius ) = 0; + virtual int GetAbsoluteMaxObjects() const = 0; + virtual int GetMaxObjects() const = 0; + virtual void SetMaxObjects( int maxObjects ) = 0; + virtual int GetMaxModels() const = 0; + virtual int GetNumRegisteredModels() const = 0; + virtual bool RegisterModel( const char* name, int spawnFreq ) = 0; + virtual bool UnregisterModel( const char* name ) = 0; + + // ACCESSORS + bool IsActive() const; + + // turning on will make use of current enable flags (all default to true) + // turning off will not doing anything (no adding, removing, updating whatsoever) + void SetActive( bool activeFlag ); + + void EnableAdd( bool enable ); + void EnableRemove( bool enable ); + void EnableUpdate( bool enable ); + + float GetSpawnRadius() const; + void SetSpawnRadius( float radius ); + float GetRemoveRadius() const; + void SetRemoveRadius( float radius ); + + int GetNumObjects() const; + + +protected: + + // PURE VIRTUALS + // return # objects added/removed + virtual void AddObjects( float seconds ) = 0; + virtual void RemoveObjects( float seconds ) = 0; + virtual void UpdateObjects( float seconds ) = 0; + + // subclass accessors + virtual float GetSecondsBetwAdds() const = 0; + virtual float GetSecondsBetwRemoves() const = 0; + virtual float GetSecondsBetwUpdates() const = 0; + + + bool mIsActive; + float mSpawnRadius; // in meters + float mRemoveRadius; // in meters + // Betw 0 and AbsoluteMax (defined by subclass)... + // NOTE: Not betw 0 and mMaxObjects because mMaxObjects can be changed + // to a lower number while there are mNumObjects > mMaxObjects + // still within radius (they don't get removed till an explicit + // call to ClearObjectsOutsideRadius takes place) + int mNumObjects; + + float mSecondsSinceLastAdd; + float mSecondsSinceLastRemove; + float mSecondsSinceLastUpdate; + + bool mAddEnabled; + bool mRemoveEnabled; + bool mUpdateEnabled; +private: + +}; + +inline SpawnManager::SpawnManager() : +mIsActive( true ), +mSpawnRadius( -1.0f ), +mRemoveRadius( -1.0f ), +mNumObjects( 0 ), +mSecondsSinceLastAdd( 0.0f ), +mSecondsSinceLastRemove( 0.0f ), +mSecondsSinceLastUpdate( 0.0f ), +mAddEnabled( true ), +mRemoveEnabled( true ), +mUpdateEnabled( true ) +{ +} + +inline SpawnManager::~SpawnManager() +{ +} + +inline bool SpawnManager::IsActive() const +{ + return mIsActive; +} +inline void SpawnManager::SetActive( bool activeFlag ) +{ + mIsActive = activeFlag; +} +inline float SpawnManager::GetSpawnRadius() const +{ + return mSpawnRadius; +} +inline void SpawnManager::SetSpawnRadius( float radius ) +{ + rAssert( radius >= 0.0f ); + mSpawnRadius = radius; +} +inline float SpawnManager::GetRemoveRadius() const +{ + return mRemoveRadius; +} +inline void SpawnManager::SetRemoveRadius( float radius ) +{ + rAssert( radius >= 0.0f ); + mRemoveRadius = radius; +} +inline int SpawnManager::GetNumObjects() const +{ + return mNumObjects; +} +inline void SpawnManager::EnableAdd( bool enable ) +{ + mAddEnabled = enable; +} +inline void SpawnManager::EnableRemove( bool enable ) +{ + mRemoveEnabled = enable; +} +inline void SpawnManager::EnableUpdate( bool enable ) +{ + mUpdateEnabled = enable; +} + +#endif diff --git a/game/code/worldsim/traffic/alltraffic.cpp b/game/code/worldsim/traffic/alltraffic.cpp new file mode 100644 index 0000000..f75d366 --- /dev/null +++ b/game/code/worldsim/traffic/alltraffic.cpp @@ -0,0 +1 @@ +#include <worldsim/traffic/trafficmanager.cpp> diff --git a/game/code/worldsim/traffic/trafficmanager.cpp b/game/code/worldsim/traffic/trafficmanager.cpp new file mode 100644 index 0000000..45ae929 --- /dev/null +++ b/game/code/worldsim/traffic/trafficmanager.cpp @@ -0,0 +1,2169 @@ +//============================================================================= +// 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; +} + + diff --git a/game/code/worldsim/traffic/trafficmanager.h b/game/code/worldsim/traffic/trafficmanager.h new file mode 100644 index 0000000..0979e87 --- /dev/null +++ b/game/code/worldsim/traffic/trafficmanager.h @@ -0,0 +1,312 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: trafficmanager.h +// +// Description: Blahblahblah +// +// History: 09/09/2002 + Spawning/Removing vehicles -- Dusit Eakkachaichanvet +// 04/07/2002 + Created -- Cary Brisebois +// +//============================================================================= + +#ifndef TRAFFICMANAGER_H +#define TRAFFICMANAGER_H + +//======================================== +// Nested Includes +//======================================== +#include <worldsim/traffic/trafficvehicle.h> +#include <worldsim/traffic/trafficmodelgroup.h> +#include <ai/vehicle/trafficai.h> +#include <events/eventlistener.h> +#include <roads/intersection.h> + +#include <render/culling/swaparray.h> + +//======================================== +// Forward References +//======================================== +class Vehicle; +class Lane; +class Character; + + +//******************************************** +// USED TO CONTROL WHETHER OR NOT TRAFFIC CARS GET ADDED TO THE WORLD +// FOR THE PURPOSES OF TESTING. +// +const bool DISABLETRAFFIC = false; +// +//******************************************** + + +struct ITrafficSpawnController +{ + virtual void SetMaxTraffic( int n ) = 0; + virtual void EnableTraffic() = 0; + virtual void DisableTraffic() = 0; + virtual void ClearTrafficInSphere( const rmt::Sphere& s ) = 0; +}; + +/* +These are the constraints Traffic lives by. + +Road Segment +============ +- Try not to make angles of the edge normals deviate too greatly from + the segment's "direction". i.e Avoid this: + + | + / | + / | + / | + / | + / | + | | + |__________| + +- a single road segment is a flat polygon (of coplanar vertices). +- road world builder data must conform to the art/physics. +- road segments should end BEFORE they cross over the (art-side) + crosswalks. Otherwise traffic cars will run over people crossing the street. +- no road segment is overlapping another +- the road segments that belong to the same road must lie within half a meter + apart from one another to be considered attached to that road. + + +Intersections +========= +- plane of the intersection (formed by in & out road segments) must be + FLAT FLAT FLAT... This means completely X-Z horizontal. + +- For each intersection "x", for every IN road "y" belonging to "x", if "y" + has more than 1 OUT road ("z"?) other than a U-turn OUT road going back + the way it came ("w"?), then it needs to be type N_WAY (type value of 1 + for the world builders). Otherwise its type is NO_STOP (type value 0). This + goes for all intersections, including the "fake" ones we use to join + Zones/Rails. + +- The road data for IN & OUT segments of "fake" intersections (used to + join zones/rails) need to leave a small (1 meter or less) "gap" between + one another. Otherwise the traffic cars will appear to "flip". More + generally: no 2 roads going in or out of any intersection are + touching/overlapping one another. + +- the centre of any intersection (hand-placed) is more or less at the + physical centre of the intersection. + +- There is an appreciable/reasonable distance between one intersection and + another. This translates to something like 10 meters or more. + +General +====== +- The world is not round or overlapping. In other words, if I were to extend + an infinite line, parallel to y-axis down through the world, it will only + cut through only one single road segment or intersection plane. This is a + precautionary step only. Not sure what drastic effects failing to conform + to this constraint will produce, but I imagine that most of the code + assumes we are in 2.5D, not 3D. + + + +Might have missed some.. but there you are. +*/ + + +//============================================================================= +// +// Synopsis: Blahblahblah +// +//============================================================================= + +class TrafficManager : + ITrafficSpawnController, + public EventListener +{ +public: + + static TrafficManager* mInstance; + + static const float FADE_RADIUS; + static const float ADD_RADIUS; + static const float CENTER_OFFSET; + static const float INITIAL_ADD_RADIUS; + static const float REMOVE_RADIUS; + static const unsigned int MILLISECONDS_BETWEEN_REMOVE; + static const unsigned int MILLISECONDS_BETWEEN_ADD; + static const unsigned int MILLISECONDS_POPULATE_WORLD; + + enum { + MAX_CHARS_TO_STOP_FOR = 10 // Max # characters traffic will brake for + }; + + static ITrafficSpawnController* GetSpawnController(); + static TrafficManager* GetInstance(); + static void DestroyInstance(); + + /////////////////////////////////// + // EVENTLISTENTER STUFF + virtual void HandleEvent( EventEnum id, void* pEventData ); + /////////////////////////////////// + + void Init(); + void InitDefaultModelGroups(); + void Update( unsigned int milliseconds ); + + void Cleanup(); + + void AddCharacterToStopFor( Character* character ); + void RemoveCharacterToStopFor( Character* character ); + int GetNumCharsToStopFor(); + Character* GetCharacterToStopFor( int i ) const; + + // Given its pointer, find it in our static list of TrafficVehicles and + // take it out of lane traffic and out of active list. Also invalidate + // TrafficVehicle-specific fields. + void RemoveTraffic( Vehicle* vehicle ); //Take this out of traffic. + void Deactivate( Vehicle* v ); + + void SetMaxTraffic( int n ); + int GetMaxTraffic(); + + void EnableTraffic(); + void DisableTraffic(); + void ClearTrafficInSphere( const rmt::Sphere& s ); + void ClearTrafficOutsideSphere( const rmt::Sphere& s ); + + void ClearOutOfSightTraffic(); + + TrafficModelGroup* GetTrafficModelGroup( int i ); + void SetCurrTrafficModelGroup( int i ); + + float GetDesiredTrafficSpeed(); + void GenerateRandomColour( pddiColour& colour ); + void SwapInTrafficHusk( Vehicle* vehicle ); // Remove traffic vehicle, swap in a husk... + + bool IsVehicleTrafficVehicle( Vehicle* vehicle ); + +//MEMBERS +private: + float mDesiredTrafficSpeedKph; + + enum { + MAX_TRAFFIC_MODEL_GROUPS = 10, // Max # different groups of traffic models + MAX_INTERSECTIONS = 5, // Max # intersections we'll be keeping track of (calling Update to) + NUM_SWATCH_COLOURS = 25, // # artist-defined swatch colours + MAX_QUEUED_TRAFFIC_HORNS = 3 + }; + int mMaxTraffic; // value is betw 0 and MAX_TRAFFIC + int mNumTraffic; // value changes between 0 and MAX_TRAFFIC as vehicles are added/removed + + // keep repository of all the vehicles we'll ever need. + TrafficVehicle* mVehicles; + + Character* mCharactersToStopFor[ MAX_CHARS_TO_STOP_FOR ]; + int mNumCharsToStopFor; + + // keep list of intersections that will need to be updated in this loop + Intersection* mpIntersections[ MAX_INTERSECTIONS ]; + + unsigned int mMillisecondsBetweenRemove; + unsigned int mMillisecondsBetweenAdd; + unsigned int mMillisecondsPopulateWorld; + + bool mTrafficEnabled; + + TrafficModelGroup mTrafficModelGroups[ MAX_TRAFFIC_MODEL_GROUPS ]; + int mCurrTrafficModelGroup; + + struct SwatchColour + { + int red; + int green; + int blue; + }; + static SwatchColour sSwatchColours[ NUM_SWATCH_COLOURS ]; + + struct TrafficHornQueue + { + Vehicle* vehicle; + unsigned int delayInMilliseconds; + }; + SwapArray<TrafficHornQueue> mQueuedTrafficHorns; + int mNumQueuedTrafficHorns; + +//METHODS +private: + + float DetermineDesiredSpeedKph(); + + // Add a traffic vehicle to the lane and to active list and Initialize + // some TrafficVehicle-specific fields + bool AddTraffic( Lane* lane, TrafficVehicle* tv ); + + // Given its index in our static list of TrafficVehicles, take vehicle out + // of lane traffic and out of active list. Also invalidate TrafficVehicle- + // specific fields. + void RemoveTraffic( int vIndex ); + + void RemoveTrafficVehicle( TrafficVehicle* tv ); + + TrafficVehicle* FindTrafficVehicle( Vehicle* vehicle ); + TrafficVehicle* GetFreeTrafficVehicle(); + Vehicle* InitRandomVehicle(); + Vehicle* InitRandomHusk( Vehicle* v ); + + + // update the intersections to let cars go in turn (if NWAY) + void UpdateIntersection( + unsigned int milliseconds, + Vehicle* v, + int& nIntersectionsUpdated ); + + void SetVehicleFadeAlpha( Vehicle* v, const rmt::Vector& pPos ); + + bool AttemptResurrection( TrafficVehicle* tv ); + + void UpdateQueuedTrafficHorns( unsigned int milliseconds ); + + // constructors/destructors we wish to hide so we can implement singleton + TrafficManager(); + virtual ~TrafficManager(); + + //Prevent wasteful constructor creation. + TrafficManager( const TrafficManager& trafficmanager ); + TrafficManager& operator=( const TrafficManager& trafficmanager ); +}; + + + + +// ************************************ INLINES ******************************************* + + +inline int TrafficManager::GetNumCharsToStopFor() +{ + return mNumCharsToStopFor; +} +inline Character* TrafficManager::GetCharacterToStopFor( int i ) const +{ + rAssert( 0 <= i && i < MAX_CHARS_TO_STOP_FOR ); + return mCharactersToStopFor[i]; +} +inline TrafficModelGroup* TrafficManager::GetTrafficModelGroup( int i ) +{ + rTuneAssert( 0 <= i && i < MAX_TRAFFIC_MODEL_GROUPS ); + return &mTrafficModelGroups[i]; +} +inline void TrafficManager::SetCurrTrafficModelGroup( int i ) +{ + rTuneAssert( 0 <= i && i < MAX_TRAFFIC_MODEL_GROUPS ); + mCurrTrafficModelGroup = i; +} +inline float TrafficManager::GetDesiredTrafficSpeed() +{ + return mDesiredTrafficSpeedKph * KPH_2_MPS; +} +#endif //TRAFFICMANAGER_H + + + + diff --git a/game/code/worldsim/traffic/trafficmodelgroup.h b/game/code/worldsim/traffic/trafficmodelgroup.h new file mode 100644 index 0000000..01c1559 --- /dev/null +++ b/game/code/worldsim/traffic/trafficmodelgroup.h @@ -0,0 +1,145 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: trafficvehicle.h +// +// Description: Blahblahblah +// +// History: 02/01/2003 + Created -- Dusit Eakkachaichanvet +// +//============================================================================= + +#ifndef TRAFFICMODELGROUP_H +#define TRAFFICMODELGROUP_H + +#include <string.h> +#include <raddebug.hpp> + +//****************************************************************************** +// +// TRAFFIC MODEL +// +//****************************************************************************** + +class TrafficModel +{ +public: + enum + { + MAX_STRING_LEN = 64 + }; + + // TRACKING + int mNumInstances; + + // DATA + char mModelName[MAX_STRING_LEN+1]; + int mMaxInstances; + +public: + TrafficModel(); + void ClearData(); + void Init( const char* name, int num ); + +private: + +}; + +inline void TrafficModel::ClearData() +{ + mModelName[0] = '\0'; + mMaxInstances = 0; + mNumInstances = 0; + // TODO: + // We may not need to clear mNumInstances if we're going to initialize traffic vehicles + // on the fly. Imagine loading a new model group that shares this same model as + // the previous group. We don't want to lose the count. +} + +inline TrafficModel::TrafficModel() +{ + ClearData(); +} + +inline void TrafficModel::Init( const char* name, int num ) +{ + rTuneAssert( name != NULL ); + rTuneAssert( num > 0 ); + + int nameLen = strlen( name ); + + rTuneAssert( nameLen <= TrafficModel::MAX_STRING_LEN ); + + strncpy( mModelName, name, TrafficModel::MAX_STRING_LEN ); + mModelName[ nameLen ] = '\0'; + mMaxInstances = num; +} + +//****************************************************************************** +// +// TRAFFIC MODEL GROUP +// +//****************************************************************************** + +class TrafficModelGroup +{ +public: + TrafficModelGroup(); + void ClearGroup(); + void AddTrafficModel( const char* name, int num ); + TrafficModel* GetTrafficModel( int i ); + int GetNumModels(); + +private: + enum + { + MAX_TRAFFIC_MODELS = 5 + }; + TrafficModel mTrafficModels[MAX_TRAFFIC_MODELS]; + int mNumModels; +}; + +inline TrafficModelGroup::TrafficModelGroup() +{ + mNumModels = 0; +} +inline void TrafficModelGroup::ClearGroup() +{ + for( int i=0; i<mNumModels; i++ ) + { + mTrafficModels[i].ClearData(); + } + mNumModels = 0; +} +inline void TrafficModelGroup::AddTrafficModel( const char* name, int num ) +{ + rTuneAssert( name != NULL ); + rTuneAssert( strlen(name) <= TrafficModel::MAX_STRING_LEN ); + rTuneAssert( num > 0 ); + + bool found = false; + for( int i=0; i<mNumModels; i++ ) + { + if( strcmp( mTrafficModels[i].mModelName, name )==0 ) + { + found = true; + mTrafficModels[i].mNumInstances = num; + } + } + + if( !found ) + { + mTrafficModels[mNumModels].Init( name, num ); + mNumModels++; + } +} +inline TrafficModel* TrafficModelGroup::GetTrafficModel( int i ) +{ + rTuneAssert( 0 <= i && i < TrafficModelGroup::MAX_TRAFFIC_MODELS ); + return &mTrafficModels[i]; +} +inline int TrafficModelGroup::GetNumModels() +{ + return mNumModels; +} +#endif
\ No newline at end of file diff --git a/game/code/worldsim/traffic/trafficvehicle.h b/game/code/worldsim/traffic/trafficvehicle.h new file mode 100644 index 0000000..e7259d4 --- /dev/null +++ b/game/code/worldsim/traffic/trafficvehicle.h @@ -0,0 +1,135 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: trafficvehicle.h +// +// Description: Blahblahblah +// +// History: 09/09/2002 + Added members that aid Traffic management -- Dusit Eakkachaichanvet +// 04/07/2002 + Created -- NAME +// +//============================================================================= + +#ifndef TRAFFICVEHICLE_H +#define TRAFFICVEHICLE_H + +//======================================== +// Nested Includes +//======================================== + +//======================================== +// Forward References +//======================================== +class Vehicle; +class Lane; + + +//============================================================================= +// +// TrafficVehicle +// +//============================================================================= + +class TrafficVehicle +{ +public: + TrafficVehicle(); + virtual ~TrafficVehicle(); + + Vehicle* GetVehicle() const; + void SetVehicle( Vehicle* vehicle ); + + Lane* GetLane() const; + void SetLane( Lane* lane ); + + bool GetIsActive() const; + void SetIsActive( bool active ); + + bool HasHusk() const; + void SetHasHusk( bool yes ); + + Vehicle* GetHusk(); + void SetHusk( Vehicle* husk ); + + //int mActiveListIndex; + unsigned int mMillisecondsDeactivated; + bool mCanBeResurrected; + + unsigned int mMillisecondsOutOfSight; + bool mOutOfSight; + + + +private: + + Vehicle* mVehicle; + Vehicle* mHusk; + + bool mIsActive; // Active = being used as traffic car + Lane* mLane; // Pointer to Lane to whose list of vehicles this + // traffic vehicle has been added. + bool mHasHusk; + +private: + //Prevent wasteful constructor creation. + TrafficVehicle( const TrafficVehicle& trafficvehicle ); + TrafficVehicle& operator=( const TrafficVehicle& trafficvehicle ); +}; +inline TrafficVehicle::TrafficVehicle() : + //mActiveListIndex( -1 ), + mMillisecondsDeactivated( 0 ), + mCanBeResurrected( true ), + mMillisecondsOutOfSight( 0 ), + mOutOfSight( true ), + mVehicle( NULL ), + mHusk( NULL ), + mIsActive( false ), + mLane( NULL ), + mHasHusk( false ) +{ +} +inline TrafficVehicle::~TrafficVehicle() +{ +} +inline Vehicle* TrafficVehicle::GetVehicle() const +{ + return mVehicle; +} +inline void TrafficVehicle::SetVehicle( Vehicle* vehicle ) +{ + mVehicle = vehicle; +} +inline Lane* TrafficVehicle::GetLane() const +{ + return mLane; +} +inline void TrafficVehicle::SetLane( Lane* lane ) +{ + mLane = lane; +} +inline bool TrafficVehicle::GetIsActive() const +{ + return mIsActive; +} +inline void TrafficVehicle::SetIsActive( bool active ) +{ + mIsActive = active; +} +inline bool TrafficVehicle::HasHusk() const +{ + return mHasHusk; +} +inline void TrafficVehicle::SetHasHusk( bool yes ) +{ + mHasHusk = yes; +} +inline Vehicle* TrafficVehicle::GetHusk() +{ + return mHusk; +} +inline void TrafficVehicle::SetHusk( Vehicle* husk ) +{ + mHusk = husk; +} + +#endif //TRAFFICVEHICLE_H diff --git a/game/code/worldsim/vehiclecentral.cpp b/game/code/worldsim/vehiclecentral.cpp new file mode 100644 index 0000000..f9dc05f --- /dev/null +++ b/game/code/worldsim/vehiclecentral.cpp @@ -0,0 +1,2278 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: vehiclecentral.cpp +// +// Description: bleek +// +// History: May 1, 2002 - created, gmayer +// +//============================================================================= + +//======================================== +// System Includes +//======================================== + +#include <raddebug.hpp> + +#include <stdlib.h> // for atof + +//======================================== +// Project Includes +//======================================== + +#include <worldsim/worldphysicsmanager.h> +#include <worldsim/vehiclecentral.h> + +#include <worldsim/redbrick/vehiclecontroller/vehiclecontroller.h> + +#include <worldsim/character/character.h> +#include <worldsim/character/charactermanager.h> +#include <worldsim/avatarmanager.h> + +#include <ai/actionbuttonhandler.h> +#include <ai/actionbuttonmanager.h> + +#include <memory/srrmemory.h> +#include <mission/gameplaymanager.h> + +#include <meta/eventlocator.h> +#include <meta/spheretriggervolume.h> +#include <meta/triggervolumetracker.h> + +#include <debug/debuginfo.h> + +#include <mission/gameplaymanager.h> +#include <mission/missionscriptloader.h> + +#include <render/RenderManager/RenderManager.h> +#include <render/Culling/WorldScene.h> + +#include <gameflow/gameflow.h> +#include <console/console.h> +#include <cheats/cheatinputsystem.h> + +#include <p3d/billboardobject.hpp> + +//****************************************************************************** +// +// Global Data, Local Data, Local Classes +// +//****************************************************************************** + +VehicleCentral* VehicleCentral::spInstance = 0; +AiVehicleController* VehicleCentral::spGenericAI = NULL; + +// +// Dusit here: +// First, keep MAX_HUSKS defined in the .cpp so we can tweak without +// recompiling the whole game... +// Now, how many husks can we possibly need in the WORST case?? +// traffic 5 +// parked cars 5 +// harass 5 +// player 1 +// mission ai 4 +// main ai 1 +// free (moment) cars 1-2 (?) +// This is too many (23). At 17KB per Vehicle instance + 35KB Husk model, +// it would total between 420 and 500 KB... Unacceptable. +// So we have forced the other systems to deal safely with not having +// husks available when they need it (RequestHusk may return NULL) +// +const int MAX_HUSKS = 5; + + +//============================================================================= +// VehicleCentral::VehicleCentral +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: VehicleCentral +// +//============================================================================= +VehicleCentral::VehicleCentral() : + mCurrentVehicleUnderContructionHead( 0 ), + mCurrentVehicleUnderConstructionTail( 0 ), + mbVehicleTriggersActive(true), + mSuppressedDriverCount(0) +{ + spGenericAI = new AiVehicleController( NULL ); + spGenericAI->AddRef(); + + int i; + for(i = 0; i < MAX_ACTIVE_VEHICLES; i++) + { + mActiveVehicleList[i] = 0; + + //mActiveVehicleControllerList[ i ] = NULL; + mActiveVehicleControllerList[ i ] = spGenericAI; + mActiveVehicleControllerList[ i ]->AddRef(); + + mVehicleUnderConstruction[ i ] = NULL; + //mDoorTriggerList[ i ] = 0; + } + + mNumActiveVehicles = 0; + + // perhaps this would be a good place to setup the console functions for the vehicle + SetupConsoleFunctionsForVehicleTuning(); + + for( i=0; i<NUM_HEADLIGHT_BBQGS; i++ ) + { + mHeadLights[i] = NULL; + } +} + +//============== +// console hooks +//============== + +static void ConsoleHookSetGasScale(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetGasScale(value); +} + +static void ConsoleHookSetSlipGasScale(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetSlipGasScale(value); +} + +static void ConsoleHookSetHighSpeedGasScale(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetHighSpeedGasScale(value); +} + + +static void ConsoleHookSetGasScaleSpeedThreshold(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetGasScaleSpeedThreshold(value); +} + + +static void ConsoleHookSetBrakeScale(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetBrakeScale(value); +} + +static void ConsoleHookSetTopSpeedKmh(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetTopSpeedKmh(value); +} + +static void ConsoleHookSetMass(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetMass(value); +} + +static void ConsoleHookSetMaxWheelTurnAngle(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetMaxWheelTurnAngle(value); +} + + +static void ConsoleHookSetHighSpeedSteeringDrop(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetHighSpeedSteeringDrop(value); +} + + +static void ConsoleHookSetTireLateralStaticGrip(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetTireLateralStaticGrip(value); +} + + +static void ConsoleHookSetTireLateralResistanceNormal(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetTireLateralResistanceNormal(value); +} + + +static void ConsoleHookSetTireLateralResistanceSlip(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetTireLateralResistanceSlip(value); +} + + +static void ConsoleHookSetEBrakeEffect(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetEBrakeEffect(value); +} + + +static void ConsoleHookSetCMOffsetX(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetCMOffsetX(value); +} + + +static void ConsoleHookSetCMOffsetY(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetCMOffsetY(value); +} + + +static void ConsoleHookSetCMOffsetZ(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetCMOffsetZ(value); +} + + +static void ConsoleHookSetSuspensionLimit(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetSuspensionLimit(value); +} + + +static void ConsoleHookSetSpringK(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetSuspensionSpringK(value); +} + + +static void ConsoleHookSetDamperC(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetSuspensionDamperC(value); +} + + +static void ConsoleHookSetHitPoints(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetHitPoints(value); +} + + +static void ConsoleHookSetSuspensionYOffset(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetSuspensionYOffset(value); +} + +static void ConsoleHookSetBurnoutRange(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetBurnoutRange(value); +} + + +static void ConsoleHookSetMaxSpeedBurstTime(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetMaxSpeedBurstTime(value); +} + + +static void ConsoleHookSetDonutTorque(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetDonutTorque(value); +} + + +static void ConsoleHookSetSlipSteeringNoEBrake(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetTireLateralResistanceSlipWithoutEBrake(value); +} + +static void ConsoleHookSetSlipEffectNoEBrake(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetSlipEffectWithoutEBrake(value); +} + + +static void ConsoleHookSetWeebleOffset(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetWeebleOffset(value); +} + + +static void ConsoleHookSetWheelieRange(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetWheelieRange(value); +} + + +static void ConsoleHookSetWheelieOffsetY(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetWheelieYOffset(value); +} + +static void ConsoleHookSetWheelieOffsetZ(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetWheelieZOffset(value); +} + +static void ConsoleHookSetShininess( int argc, char** argv ) +{ + if( argc != 2 ) + { + return; + } + float shininess = static_cast<float>( atof( argv[ 1 ] ) ); + unsigned char ref = rmt::Clamp( int( 0xff * shininess ), 0, 0xff ); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetShininess( ref ); +} + +static void ConsoleHookSetShadowAdjustments( int argc, char** argv ) +{ + if( argc != 9 ) + { + return; + } + float Adjustments[ 4 ][ 2 ]; + for( int i = 0; i < 4; ++i ) + { + Adjustments[ i ][ 0 ] = static_cast<float>( atof( argv[ 1 + ( i * 2 ) ] ) ); + Adjustments[ i ][ 1 ] = static_cast<float>( atof( argv[ 2 + ( i * 2 ) ] ) ); + } + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetShadowAdjustments( Adjustments ); +} + +static void ConsoleHookSetDriverName(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetDriverName(argv[1]); +} + +static void ConsoleHookSetHasDoors(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->mHasDoors = (atoi(argv[1]) != 0); +} + +static void ConsoleHookSetCharactersVisible(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->mVisibleCharacters = (atoi(argv[1]) != 0); +} + +static void ConsoleHookSetIrisTransition(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->mIrisTransition = (atoi(argv[1]) != 0); +} + +static void ConsoleHookSetAllowSlide(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->mAllowSlide = (atoi(argv[1]) != 0); +} + +static void ConsoleHookSetHighRoof(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->mHighRoof = (atoi(argv[1]) != 0); +} + +static void ConsoleHookSetCharacterScale(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->mCharacterScale = static_cast< float >( atof(argv[1]) ); +} + +//Chuck: added this for Gambling Races. +static void ConsoleHookSetGamblingOdds(int argc, char** argv) +{ + if(argc != 2) + { + return; + } + float value = static_cast<float>(atof(argv[1])); + GetVehicleCentral()->GetCurrentVehicleUnderConstruction()->SetGamblingOdds(value); +} + +static void ConsoleHookSuppressDriver(int argc, char** argv) +{ + GetVehicleCentral()->AddSuppressedDriver(argv[1]); +} + + +//============================================================================= +// VehicleCentral::SetupConsoleFunctionsForVehicleTuning +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::SetupConsoleFunctionsForVehicleTuning() +{ + + GetConsole()->AddFunction("SetMass", ConsoleHookSetMass, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetGasScale", ConsoleHookSetGasScale, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SetHighSpeedGasScale", ConsoleHookSetHighSpeedGasScale, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetGasScaleSpeedThreshold", ConsoleHookSetGasScaleSpeedThreshold, "help your goddamn self", 1, 1); + + + GetConsole()->AddFunction("SetSlipGasScale", ConsoleHookSetSlipGasScale, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetBrakeScale", ConsoleHookSetBrakeScale, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetTopSpeedKmh", ConsoleHookSetTopSpeedKmh, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetMaxWheelTurnAngle", ConsoleHookSetMaxWheelTurnAngle, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetHighSpeedSteeringDrop", ConsoleHookSetHighSpeedSteeringDrop, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetTireGrip", ConsoleHookSetTireLateralStaticGrip, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetNormalSteering", ConsoleHookSetTireLateralResistanceNormal, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetSlipSteering", ConsoleHookSetTireLateralResistanceSlip, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetEBrakeEffect", ConsoleHookSetEBrakeEffect, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SetCMOffsetX", ConsoleHookSetCMOffsetX, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetCMOffsetY", ConsoleHookSetCMOffsetY, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetCMOffsetZ", ConsoleHookSetCMOffsetZ, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SetSuspensionLimit", ConsoleHookSetSuspensionLimit, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetSpringK", ConsoleHookSetSpringK, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetDamperC", ConsoleHookSetDamperC, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SetSuspensionYOffset", ConsoleHookSetSuspensionYOffset, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SetHitPoints", ConsoleHookSetHitPoints, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SetBurnoutRange", ConsoleHookSetBurnoutRange, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SetMaxSpeedBurstTime", ConsoleHookSetMaxSpeedBurstTime, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SetDonutTorque", ConsoleHookSetDonutTorque, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SetSlipSteeringNoEBrake", ConsoleHookSetSlipSteeringNoEBrake, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetSlipEffectNoEBrake", ConsoleHookSetSlipEffectNoEBrake, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SetWeebleOffset", ConsoleHookSetWeebleOffset, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SetWheelieRange", ConsoleHookSetWheelieRange, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetWheelieOffsetY", ConsoleHookSetWheelieOffsetY, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetWheelieOffsetZ", ConsoleHookSetWheelieOffsetZ, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction( "SetShadowAdjustments", ConsoleHookSetShadowAdjustments, "Move the shadow points around", 8, 8 ); + GetConsole()->AddFunction( "SetShininess", ConsoleHookSetShininess, "Set the environmental reflection 0 to 1", 1, 1 ); + + GetConsole()->AddFunction("SetDriver", ConsoleHookSetDriverName, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetGamblingOdds",ConsoleHookSetGamblingOdds,"Set Gambling Odds",1,1); + + GetConsole()->AddFunction("SetHasDoors", ConsoleHookSetHasDoors, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetCharactersVisible", ConsoleHookSetCharactersVisible, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetIrisTransition", ConsoleHookSetIrisTransition, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetAllowSeatSlide", ConsoleHookSetAllowSlide, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetHighRoof", ConsoleHookSetHighRoof, "help your goddamn self", 1, 1); + GetConsole()->AddFunction("SetCharacterScale", ConsoleHookSetCharacterScale, "help your goddamn self", 1, 1); + + GetConsole()->AddFunction("SuppressDriver", ConsoleHookSuppressDriver, "help your goddamn self", 1, 1); +} + +//============================================================================= +// VehicleCentral::~VehicleCentral +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: VehicleCentral +// +//============================================================================= +VehicleCentral::~VehicleCentral() +{ + ClearSuppressedDrivers(); + + + int i; + for(i = 0; i < MAX_ACTIVE_VEHICLES; i++) + { + if(mActiveVehicleList[i]) + { + // TODO - will the mission manager delete these instead?? + // + // for now, only delete the ones that were left in there... + delete mActiveVehicleList[i]; + } + + if ( this->mActiveVehicleControllerList[ i ] ) + { + mActiveVehicleControllerList[ i ]->Release(); + mActiveVehicleControllerList[ i ] = NULL; + } +/* + if ( mDoorTriggerList[ i ] ) + { + mDoorTriggerList[ i ]->Release( ); + mDoorTriggerList[ i ] = 0; + } +*/ + } + + // create a static vehicle AI and put it in limbo... + if( spGenericAI ) + { + spGenericAI->ReleaseVerified(); + } + + for( i=0; i<NUM_HEADLIGHT_BBQGS; i++ ) + { + if( mHeadLights[i] ) + { + mHeadLights[i]->Release(); + mHeadLights[i] = NULL; + } + } + +} + + +//============================================================================= +// VehicleCentral::GetInstance +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: VehicleCentral +// +//============================================================================= +VehicleCentral* VehicleCentral::GetInstance() +{ + rAssert(spInstance); + return spInstance; +} + + +//============================================================================= +// VehicleCentral::CreateInstance +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: VehicleCentral +// +//============================================================================= +VehicleCentral* VehicleCentral::CreateInstance() +{ + rAssert(spInstance == 0); + + spInstance = new(GMA_PERSISTENT) VehicleCentral; + rAssert(spInstance); + + return spInstance; + +} + + +//============================================================================= +// VehicleCentral::DestroyInstance +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::DestroyInstance() +{ + rAssert(spInstance); + + delete(GMA_PERSISTENT, spInstance); + spInstance = NULL; + + +} + + +//============================================================================= +// VehicleCentral::InitHuskPool +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::InitHuskPool() +{ +} + + +//============================================================================= +// VehicleCentral::FreeHuskPool +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::FreeHuskPool() +{ + + +} + + +//============================================================================= +// VehicleCentral::PreLoad +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::PreLoad() +{ + // bunch of actual loading moved to frontend startup to reduce load times - nbrooke 3/7/2003 + + this->mHuskPool.Init(MAX_HUSKS); + + rAssert( mHeadLights[0] == NULL && mHeadLights[1] == NULL && mHeadLights[2] == NULL ); + + // also might as well load the vehicle commons here.. + p3d::inventory->PushSection(); + p3d::inventory->SelectSection( "Global" ); + bool oldCurrSectionOnly = p3d::inventory->GetCurrentSectionOnly(); + p3d::inventory->SetCurrentSectionOnly( true ); + + tRefCounted::Assign( mHeadLights[0], p3d::find<tBillboardQuadGroup>(tEntity::MakeUID("headlightShape8")) ); + tRefCounted::Assign( mHeadLights[1], p3d::find<tBillboardQuadGroup>(tEntity::MakeUID("headlight2Shape")) ); + tRefCounted::Assign( mHeadLights[2], p3d::find<tBillboardQuadGroup>(tEntity::MakeUID("glowGroupShape2")) ); + + p3d::inventory->SetCurrentSectionOnly( oldCurrSectionOnly ); + p3d::inventory->PopSection(); + + // grab the colours from the bbqgs + int count = 0; + for( int i=0; i<NUM_HEADLIGHT_BBQGS; i++ ) + { + rAssert( mHeadLights[i] ); + for( int j=0; j<mHeadLights[i]->GetNumQuads(); j++ ) + { + tBillboardQuad* quad = mHeadLights[i]->GetQuad( j ); + rAssert( quad ); + mOriginalHeadLightColours[count] = quad->GetColour(); + count++; + } + } + rAssert( mHeadLights[0] && mHeadLights[1] && mHeadLights[2] ); + +} + + + +//============================================================================= +// VehicleCentral::Unload +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::Unload() +{ + // empty out husk manager here also + this->mHuskPool.Empty(); + + ClearSuppressedDrivers(); + + int count = 0; + + // restore prev states and release all preloaded headlights bbqgs + for( int i=0; i<NUM_HEADLIGHT_BBQGS; i++ ) + { + rAssert( mHeadLights[i] ); + for( int j=0; j<mHeadLights[i]->GetNumQuads(); j++ ) + { + tBillboardQuad* quad = mHeadLights[i]->GetQuad( j ); + rAssert( quad ); + quad->SetColour( mOriginalHeadLightColours[count] ); + count++; + } + // ProcessShaders screwed us by causing us to lose the additive blend + // We set it back. + mHeadLights[i]->GetShader()->SetInt( PDDI_SP_BLENDMODE, PDDI_BLEND_ADD ); + + mHeadLights[i]->Release(); + mHeadLights[i] = NULL; + } + + rAssert(mNumActiveVehicles == 0); + +} + +//============================================================================= +// VehicleCentral::InitVehicle +//============================================================================= +// Description: Comment +// +// Parameters: ( const char* name ) +// +// Return: void +// +//============================================================================= +Vehicle* VehicleCentral::InitVehicle( const char* name, bool addToActiveVehicleList, char* confile, VehicleType vt, DriverInit driver, bool playercar, bool startoutofcar) +{ + HeapMgr()->PushHeap( GetGameplayManager()->GetCurrentMissionHeap() ); + + MEMTRACK_PUSH_GROUP( "VehicleCentral" ); +//#ifdef RAD_GAMECUBE +// Vehicle* vehicle = new( GMA_GC_VMM ) Vehicle; +//#else + Vehicle* vehicle = new(GetGameplayManager()->GetCurrentMissionHeap()) Vehicle; +//#endif + + //if this car is owned by the player then set the flag. + if(playercar ==true) + { + vehicle->mbPlayerCar=true; + } + + // note: moved the car open-door locator to the vehicle init + bool ok = vehicle->Init( name, GetWorldPhysicsManager()->mSimEnvironment, VL_PHYSICS, vt, startoutofcar); + rAssert(ok); + + // so the script hooks can get at it + rAssert( mVehicleUnderConstruction[mCurrentVehicleUnderConstructionTail] == NULL ); + mVehicleUnderConstruction[mCurrentVehicleUnderConstructionTail] = vehicle; + mCurrentVehicleUnderConstructionTail = (mCurrentVehicleUnderConstructionTail + 1) % MAX_ACTIVE_VEHICLES; + + //If this happens, we're in big shit. We'll need to up the number of active vehicles or look closer at the loading. + rAssert( mCurrentVehicleUnderContructionHead != mCurrentVehicleUnderConstructionTail ); + + // see TODO in comment blocks above + // DL: now loads the car based on name + + char scriptname[64]; + strcpy( scriptname, "scripts/cars/" ); + + if(confile != NULL && confile[0] != '\0') + { + strcat( scriptname, confile ); + + } + else + { + strcat( scriptname, name ); + strcat( scriptname, ".con" ); + } + + vehicle->mDriverInit = driver; + GetMissionScriptLoader()->LoadScriptAsync( scriptname, this ); + + //vehicle->CalculateValuesBasedOnDesignerParams(); + + if(addToActiveVehicleList) + { + // return slot ignored here... + int dummy = AddVehicleToActiveList(vehicle); + } + + MEMTRACK_POP_GROUP( "VehicleCentral" ); + + HeapMgr()->PopHeap( GetGameplayManager()->GetCurrentMissionHeap() ); + + return vehicle; +} + + +//============================================================================= +// VehicleCentral::AddVehicleToActiveList +//============================================================================= +// Description: Comment +// +// returns index if successful, otherwise -1 +// +// Parameters: VehicleCentral::AddVehicleToActiveList +// +// Return: int +// +//============================================================================= +int VehicleCentral::AddVehicleToActiveList(Vehicle* vehicle) +{ + + + //============================================================================== + // + // let's be clear here: + // + // an 'active' vehicle is one that will be added to the dsg + // it will also request a collision area index and put itself in there, + // but it won't do it's own query to fill that area unless it's under VL_PHYSICS + // + //============================================================================== + + // find this vehicle a slot + int slot = -1; + + int i; + for(i = 0; i < MAX_ACTIVE_VEHICLES; i++) + { + // debug test + if(mActiveVehicleList[i]) + { + if(mActiveVehicleList[i] == vehicle) + { + // this is already in our list!!!! + //rAssertMsg( false, "This vehicle is already in the active list\n"); + return i; + + //chuck: draw the driver + if (vehicle->GetDriver() !=NULL) + { + vehicle->GetDriver()->AddToWorldScene(); + } + + } + } + + + } + + for(i = 0; i < MAX_ACTIVE_VEHICLES; i++) + { + if(mActiveVehicleList[i] == 0) + { + // got it. + slot = i; + break; + } + } + + + if(slot == -1) + { + // couldn't find room for it. + //? + rAssertMsg(0,"Trying to add too many active vehicles!"); + return -1; + } + + //---------- + // add it in + //---------- + + mActiveVehicleList[slot] = vehicle; + + if (vehicle->GetDriver() !=NULL) + { + vehicle->GetDriver()->AddToWorldScene(); + } + + + + // this is perhaps totally redundant right now + mNumActiveVehicles++; + + if(mNumActiveVehicles > MAX_ACTIVE_VEHICLES) + { + rAssertMsg(0,"Too many active vehicles!"); + return -1; + } + + + //-------------------- + // trigger volume shit + //-------------------- + + if( vehicle->mVehicleType == VT_AI && + ::GetGameFlow()->GetCurrentContext() != CONTEXT_SUPERSPRINT && + !GetGameplayManager()->mIsDemo ) + { + GetTriggerVolumeTracker()->RegisterAI( vehicle ); + } + + + + int id = vehicle->mpEventLocator->GetData(); + ActionButton::GetInCar* pABHandler = static_cast<ActionButton::GetInCar*>( GetActionButtonManager()->GetActionByIndex(id) ); + rAssert( dynamic_cast<ActionButton::GetInCar*>( pABHandler ) != NULL ); + rAssert( pABHandler ); + pABHandler->SetVehicleId(slot); + + + // leave AI untouched! Their triggers remain OFF! + vehicle->ActivateTriggers(true); + + //----------- + // add to dsg + //----------- + GetRenderManager()->pWorldScene()->Add((DynaPhysDSG*)vehicle); + + vehicle->GetCollisionAreaIndexAndAddSelf(); + + int curWorldRenderLayer = GetRenderManager()->rCurWorldRenderLayer(); + vehicle->SetRenderLayerEnum((RenderEnums::LayerEnum)curWorldRenderLayer); + + + // temp - for car on car collisions + // TODO - remove + + // until the vehicles are moving around in the scenegraph, we need to make sure the cars have a collision pair for each other + + /* + for(i = 0; i < MAX_ACTIVE_VEHICLES; i++) + { + if(mActiveVehicleList[i] != 0 && i != slot) + { + vehicle->AddToOtherCollisionArea(mActiveVehicleList[i]->mCollisionAreaIndex); + } + } + */ + + // + // Notify the world about new user vehicles + // + if( vehicle->mVehicleType == VT_USER ) + { + GetEventManager()->TriggerEvent( EVENT_USER_VEHICLE_ADDED_TO_WORLD, vehicle ); + } + + // april 22, 2003 + // new + // vehicle will hold it's slot. + + vehicle->mVehicleCentralIndex = slot; + + vehicle->AddRef(); + + return slot; + +} + + + +//============================================================================= +// VehicleCentral::RemoveVehicleFromActiveList +//============================================================================= +// Description: Comment +// +// returns whether or not it was even there in the first place +// +// +// Parameters: (Vehicle* vehicle) +// +// Return: bool +// +//============================================================================= +bool VehicleCentral::RemoveVehicleFromActiveList(Vehicle* vehicle) +{ + int i; + for(i = 0; i < MAX_ACTIVE_VEHICLES; i++) + { + if(mActiveVehicleList[i] == vehicle) + { + + //Chuck: If car getting dumped was the last car used by the player + //set the mCurrentVehicle to NULL + + + // greg + // jan 4, 2003 + // + // TODO + // not sure if we want this code + // mCurrentVehicle should be updated elsewhere + + // PHONEBOOTH CAR SHOULD REPLACE DEFAULT VEHICLE. + // this change is going in rsn + + //if (GetGameplayManager()->GetCurrentVehicle() == vehicle) + //{ + // Vehicle* p_vehicle = GetGameplayManager()->GetVehicle(GetGameplayManager()->mDefaultVehicle); + // GetGameplayManager()->SetCurrentVehicle(p_vehicle); + //} + + // later in the same day + // + // GameplayManager::DumpCurrentCar () + // now sets mCurrentVehicle to NULL as the name would imply + + + mActiveVehicleList[i] = 0; + mNumActiveVehicles--; + + // make sure controller slot doesn't have junk left lying around in it. + if( mActiveVehicleControllerList[i] ) + { + mActiveVehicleControllerList[i]->Release(); + } + //mActiveVehicleControllerList[i] = NULL; + mActiveVehicleControllerList[i] = spGenericAI; + mActiveVehicleControllerList[i]->AddRef(); + + // !! need to remove this from any other vehicles mCurrentDynamics list + // + // what an ugly pain in the ass: + GetWorldPhysicsManager()->RemoveFromAnyOtherCurrentDynamicsListAndCollisionArea(vehicle); + + vehicle->RemoveSelfAndFreeCollisionAreaIndex(); + + + //---------------- + // remove from DSG + //---------------- + + int& curRenderLayer = GetRenderManager()->rCurWorldRenderLayer(); + int orgRenderLayer = curRenderLayer; + + // swtich to the vehicle's render layer + curRenderLayer = vehicle->GetRenderLayerEnum(); + + //Chuck: if we're removing the car lets check if there is a AI NPC driver in that car + if(vehicle->GetDriver() != NULL) + { + vehicle->GetDriver()->RemoveFromWorldScene(); + } + GetRenderManager()->pWorldScene()->Remove(/*(DynaPhysDSG*)*/ vehicle); + + + // restore the orignal render layer + curRenderLayer = orgRenderLayer; + + // hack + //vehicle->Release(); + + // leave AI untouched! Their triggers remain OFF! + vehicle->ActivateTriggers(false); + + // + // Notify the world about deleted user vehicles + // + if( vehicle->mVehicleType == VT_USER ) + { + GetEventManager()->TriggerEvent( EVENT_USER_VEHICLE_REMOVED_FROM_WORLD, vehicle ); + } + + // stop tracking AI... + if( GetGameFlow()->GetCurrentContext() != CONTEXT_SUPERSPRINT ) + { + GetTriggerVolumeTracker()->UnregisterAI( vehicle ); + } + + vehicle->mVehicleCentralIndex = -1; + + vehicle->Release(); + + return true; + } + /* + else if(mActiveVehicleList[i]) + { + // this isn't super-efficient but it's only temporary and should stop Darryl's whining + + // try and remove from all other collision areas just in case it's in there... + GetWorldPhysicsManager()->mCollisionManager-> + RemoveCollisionObject(vehicle->mSimStateArticulated->GetCollisionObject(), mActiveVehicleList[i]->mCollisionAreaIndex); + + + } + */ + + } + + + return false; +} + + +//============================================================================= +// VehicleCentral::KillEmAll +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::KillEmAll() +{ + // not sure if I should keep this method around, because whoever creates the + // vehicle should destroy it + // + // but the method name is so fucking cool.... + + +} + +/* +//============================================================================= +// VehicleCentral::Suspend +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::Suspend() +{ + mSuspended = true; +} + + +//============================================================================= +// VehicleCentral::Resume +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::Resume() +{ + mSuspended = false; +} +*/ + +//============================================================================= +// VehicleCentral::GetVehicle +//============================================================================= +// Description: Comment +// +// Parameters: ( int id ) +// +// Return: Vehicle +// +//============================================================================= +Vehicle* VehicleCentral::GetVehicle( int id ) const +{ + //rAssert( id < this->mNumActiveVehicles ); + return this->mActiveVehicleList[ id ]; +} + + +//============================================================================= +// VehicleCentral::SetVehicleController +//============================================================================= +// Description: Comment +// +// Parameters: ( int id, VehicleController* pVehicleController ) +// +// Return: void +// +//============================================================================= +void VehicleCentral::SetVehicleController( int id, VehicleController* pVehicleController ) +{ + + if ( id >= 0 && id < MAX_ACTIVE_VEHICLES ) + { + //rAssert( id < this->mNumActiveVehicles ); // don't think this is necessary anymore + if ( pVehicleController ) + { + pVehicleController->Init(); + } + else + { + if ( mActiveVehicleControllerList[ id ] ) + { + mActiveVehicleControllerList[ id ]->Shutdown(); + } + pVehicleController = spGenericAI; + } + + tRefCounted::Assign( mActiveVehicleControllerList[ id ], pVehicleController ); + } + else + { + rAssertMsg( 0, "SetVehicleController - id out of range" ); + } +} + +//Triage hack, only for demo mode, or until Greg actually +//addrefs and releases --dm 12/01/02 +VehicleController* VehicleCentral::RemoveVehicleController( int mAIIndex ) +{ + if ( mActiveVehicleControllerList[ mAIIndex ] ) + { + VehicleController* FoundVehicleController = mActiveVehicleControllerList[ mAIIndex ]; + mActiveVehicleControllerList[ mAIIndex ] = 0; + return FoundVehicleController; + } + return NULL; +} +//============================================================================= +// VehicleCentral::GetVehicleController +//============================================================================= +// Description: Comment +// +// Parameters: ( int id ) +// +// Return: VehicleController +// +//============================================================================= +VehicleController* VehicleCentral::GetVehicleController( int id ) const +{ + if( 0 <= id && id < MAX_ACTIVE_VEHICLES ) + { + if( mActiveVehicleControllerList[ id ] == spGenericAI ) + { + return NULL; + } + return mActiveVehicleControllerList[ id ]; + } + return NULL; +} + + +//============================================================================= +// VehicleCentral::GetVehicleId +//============================================================================= +// Description: Comment +// +// Parameters: ( Vehicle* pVehicle ) +// +// Return: int +// +//============================================================================= +int VehicleCentral::GetVehicleId( Vehicle* pVehicle, bool checkStrict ) const +{ + int i; + for ( i = 0; i < MAX_ACTIVE_VEHICLES; i++ ) + { + if(pVehicle == mActiveVehicleList[i]) + { + return i; + } + } + + if(checkStrict) + { + rAssertMsg( false, "Vehicle not found in Vehicle Database!\n" ); + } + + return (int)-1; +} + +//============================================================================= +// VehicleCentral::PreSubstepUpdate +//============================================================================= +// Description: Comment +// +// Parameters: ( ) +// +// Return: void +// +//============================================================================= +void VehicleCentral::PreSubstepUpdate( float dt ) +{ + int i; + for ( i = 0; i < MAX_ACTIVE_VEHICLES; i++ ) + { + Vehicle* vehicle = mActiveVehicleList[i]; + if( vehicle ) // TODO - wrap with render layer test + { + vehicle->PreSubstepUpdate(dt); + + // Update controller + VehicleController* vController = mActiveVehicleControllerList[i]; + if( vController != 0 ) + { + vController->Update( dt ); + + float test = vController->GetGas(); + //? + // safe to just apply these as-is to vehicle? + vehicle->SetGas(test); + + + test = vController->GetBrake( ); + vehicle->SetBrake( test ); + + + test = vController->GetThrottle(); + if(test > 0.1f) + { + vehicle->SetGas( test ); + } + if(test < -0.1f) + { + vehicle->SetBrake( -test ); + } + + + //This sucks. + bool isWheel = false; + test = vController->GetSteering( isWheel ); + + + // test stick and dpad and set highest fabs + float left = 0.0f; + if( vController->GetSteerLeft() ) + { + left = vController->GetSteerLeft( ); + } + + float right = 0.0f; + if( vController->GetSteerRight() ) + { + right = vController->GetSteerRight( ); + } + + if(rmt::Fabs(test) > left && rmt::Fabs(test) > right) + { + // use stick value + vehicle->SetWheelTurnAngle(test, isWheel, dt); + + } + else if(right > left) + { + vehicle->SetWheelTurnAngle(right, false, dt); + } + else + { + vehicle->SetWheelTurnAngle(-left, false, dt); + } + + + + /* + // only human controlled vehicles will return a pointer here + if(vController->GetSteerLeft()) + { + float left = vController->GetSteerLeft( ); + if(test <= 0.0f && left > rmt::Fabs(test)) + { + vehicle->SetWheelTurnAngle(-left, false, dt); + } + } + if(vController->GetSteerRight()) + { + float right = vController->GetSteerRight( ); + if(test >= 0.0f && right > test) + { + vehicle->SetWheelTurnAngle(right, false, dt); + } + } + */ + + test = vController->GetReverse( ); + vehicle->SetReverse(test); + + test = vController->GetHandBrake(); + vehicle->SetEBrake(test, dt); + + if( GetCheatInputSystem()->IsCheatEnabled(CHEAT_ID_CAR_JUMP_ON_HORN) && + vehicle == GetAvatarManager()->GetAvatarForPlayer(0)->GetVehicle() ) // vehicle->mVehicleType == VT_USER ) + { + test = vController->GetHorn( ); + if(test > 0.1f) + { + vehicle->JumpOnHorn(test); + } + } + + if( vController->GetHorn() > 0.0f ) + { + // Only the player vehicle's controller uses Set/GetHorn. + // AI cars never at any time set the Horn value. + if( vehicle == GetAvatarManager()->GetAvatarForPlayer(0)->GetVehicle() || + ::GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + // if in a certain game type, use the horn button for speedboost + if( ::GetGameplayManager()->GetGameType() == GameplayManager::GT_SUPERSPRINT ) + { + // may or may not do turbo depending on if turbo is available.. + vehicle->TurboOnHorn(); + } + else + { + // outside of Supersprint context, we want to fire off an event + // so that traffic can honk back and peds can panick. + GetEventManager()->TriggerEvent( EVENT_PLAYER_VEHICLE_HORN, vehicle ); + } + } + } + + + } + else + { + vehicle->SetGas(0.0f); + vehicle->SetBrake(0.0f); + vehicle->SetWheelTurnAngle(0.0f, false, dt); + vehicle->SetReverse(0.0f); + vehicle->SetEBrake(0.0f, dt); + } + + // + // [Dusit Matthew Eakkachaichanvet: Dec 10, 2002] + // ok, the controller has been updated and knows of + // last frame's collision with a vehicle, so + // we can clear this value now. + // + vehicle->mCollidedWithVehicle = false; + vehicle->mNormalizedMagnitudeOfVehicleHit = 0.0f; + vehicle->mWasHitByVehicle = false; + + } + } +} + +//============================================================================= +// VehicleCentral::Update +//============================================================================= +// Description: Comment +// +// Parameters: (float dt) +// +// Return: void +// +//============================================================================= +void VehicleCentral::Update(float dt) +{ + + int i; + for ( i = 0; i < MAX_ACTIVE_VEHICLES; i++ ) + { + //rAssert( mActiveVehicleList[ i ] ); + if( mActiveVehicleList[i] && + (GetRenderManager()->rCurWorldRenderLayer() == mActiveVehicleList[i]->GetRenderLayerEnum())) + { + BEGIN_PROFILE("ActiveVehicle->Update") + mActiveVehicleList[i]->Update( dt ); + END_PROFILE("ActiveVehicle->Update") + + //rAssertMsg( mActiveVehicleList[i] != NULL, "VehicleCentral::Update - trying to add a null vehicle* pointer!" ); + + if ( mActiveVehicleList[i] != NULL ) + { + if(mActiveVehicleList[i]->GetLocomotionType() == VL_PHYSICS) + { + BEGIN_PROFILE("WorldPhysManager()->UpdateDyna") + GetWorldPhysicsManager()->UpdateDynamicObjects(dt, mActiveVehicleList[i]->mCollisionAreaIndex); + GetWorldPhysicsManager()->UpdateAnimCollisions(dt, mActiveVehicleList[i]->mCollisionAreaIndex); + END_PROFILE("WorldPhysManager()->UpdateDyna") + // Temp removed this because objects were being updated twice. + // more important (for me) to have the character update anim objects. + // + // TBJ [8/27/2002] + // + //GetWorldPhysicsManager()->UpdateAnimCollisions(dt, mActiveVehicleList[i]->mCollisionAreaIndex); + + } + } + } + } +} + + +//============================================================================= +// VehicleCentral::ClearSpot +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector& point, float radius) +// +// Return: void +// +//============================================================================= +void VehicleCentral::ClearSpot(rmt::Vector& point, float radius, Vehicle* skipCar) +{ + rAssert(0); // don't call this + int i; + for ( i = 0; i < MAX_ACTIVE_VEHICLES; i++ ) + { + if( mActiveVehicleList[i] && + GetRenderManager()->rCurWorldRenderLayer() == mActiveVehicleList[i]->GetRenderLayerEnum() && + mActiveVehicleList[i] != skipCar) + { + + rmt::Vector carPosition = mActiveVehicleList[i]->rPosition(); + + rmt::Vector vectorToCar = carPosition; + vectorToCar.Sub(point); + + float carsWheelBase = mActiveVehicleList[i]->GetWheelBase(); + + float dist = vectorToCar.Magnitude(); + if(dist - carsWheelBase < radius) + { + // this car has to move back + + float amount = radius - (dist - carsWheelBase); + + // first test + // blast it back by some ratio of amount? + + rmt::Vector fix = vectorToCar; + fix.NormalizeSafe(); + fix.Scale(amount); + + carPosition.Add(fix); + + // quick test for fun and safety + carPosition.y += 2.0f; + + float ang = mActiveVehicleList[i]->GetFacingInRadians(); + + rmt::Matrix m; + m.Identity(); + m.FillRotateXYZ( 0.0f, ang, 0.0f ); + m.FillTranslate(carPosition); + + mActiveVehicleList[i]->SetTransform(m); + + + /* + rmt::Vector& linearVelocity = mActiveVehicleList[i]->mSimStateArticulated->GetLinearVelocity(); + + vectorToCar.NormalizeSafe(); + + static float magicshit = 1.0f; + + vectorToCar.Scale(amount * magicshit); + + linearVelocity = vectorToCar; + */ + + } + + + + } + } + + +} + + + +//============================================================================= +// VehicleCentral::PostSubstepUpdate +//============================================================================= +// Description: Comment +// +// Parameters: (float dt) +// +// Return: void +// +//============================================================================= +void VehicleCentral::PostSubstepUpdate(float dt) +{ + + int i; + for ( i = 0; i < MAX_ACTIVE_VEHICLES; i++ ) + { + //rAssert( mActiveVehicleList[ i ] ); + if( mActiveVehicleList[i] && + GetRenderManager()->rCurWorldRenderLayer() == mActiveVehicleList[i]->GetRenderLayerEnum()) + { + mActiveVehicleList[i]->PostSubstepUpdate(dt); + } + } +} + + +//============================================================================= +// VehicleCentral::PreCollisionPrep +//============================================================================= +// Description: Comment +// +// Parameters: ( ) +// +// Return: void +// +//============================================================================= +void VehicleCentral::PreCollisionPrep(float dt, bool firstSubstep) +{ + int i; + for ( i = 0; i < MAX_ACTIVE_VEHICLES; i++ ) + { + if( mActiveVehicleList[i] && + GetRenderManager()->rCurWorldRenderLayer() == mActiveVehicleList[i]->GetRenderLayerEnum()) + { + mActiveVehicleList[ i ]->PreCollisionPrep(dt, firstSubstep); + } + } +} + + +//============================================================================= +// VehicleCentral::SubmitStatics +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::SubmitStatics() +{ + int i; + for ( i = 0; i < MAX_ACTIVE_VEHICLES; i++ ) + { + if( mActiveVehicleList[i] && + GetRenderManager()->rCurWorldRenderLayer() == mActiveVehicleList[i]->GetRenderLayerEnum()) + { + if(1) + //if(mActiveVehicleList[i]->GetLocomotionType() == VL_PHYSICS) + { + rmt::Vector position = mActiveVehicleList[i]->GetPosition(); + + // at 200kmh, we cover 55.5 m/s + // a long frame would be 50ms... + // + // at that rate we'd cover 2.775 m + + // hmm.... ferrini wheel base is approx 3m + + //float radius = mActiveVehicleList[i]->GetWheelBase() * 3.0f; // TODO - what magic number? + float radius = mActiveVehicleList[i]->GetWheelBase() * 2.0f; // TODO - what magic number? + + int collisionAreaIndex = mActiveVehicleList[i]->mCollisionAreaIndex; + rAssert(collisionAreaIndex != -1); + + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + Vehicle* playerVehicle = 0; + if(playerAvatar) + { + playerVehicle = playerAvatar->GetVehicle(); + } + + if(playerVehicle && mActiveVehicleList[i] == playerVehicle) + { + GetWorldPhysicsManager()->SubmitStaticsPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState(), true); + GetWorldPhysicsManager()->SubmitFencePiecesPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState(), true); + } + else + { + GetWorldPhysicsManager()->SubmitStaticsPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState(), false); + GetWorldPhysicsManager()->SubmitFencePiecesPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState()); + } + + // TODO - should probably only do this one if it's a human driver or AI ... ie. not traffic bouncing around + //GetWorldPhysicsManager()->SubmitFencePiecesPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState()); + } + else + { + /* + // an active vehicle, but not VL_PHYSICS + // so we want to do a DSG query and cleanup of our collision area if necessary + + rmt::Vector position = mActiveVehicleList[i]->GetPosition(); + + float radius = mActiveVehicleList[i]->GetWheelBase() * 2.0f; // TODO - what magic number? + + int collisionAreaIndex = mActiveVehicleList[i]->mCollisionAreaIndex; + rAssert(collisionAreaIndex != -1); + + GetWorldPhysicsManager()->CleanOnlyStaticsPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState()); + + // TODO - should probably only do this one if it's a human driver or AI ... ie. not traffic bouncing around + GetWorldPhysicsManager()->SubmitFencePiecesPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState()); + */ + + } + + } + } + +} + + + +//============================================================================= +// VehicleCentral::SubmitDynamics +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::SubmitDynamics() +{ + int i; + for ( i = 0; i < MAX_ACTIVE_VEHICLES; i++ ) + { + if( mActiveVehicleList[i] && + GetRenderManager()->rCurWorldRenderLayer() == mActiveVehicleList[i]->GetRenderLayerEnum()) + { + // new + // call this for all (one) VT_USER cars first + //if(mActiveVehicleList[i]->GetLocomotionType() == VL_PHYSICS && mActiveVehicleList[i]->mVehicleType == VT_USER) + if(mActiveVehicleList[i]->mVehicleType == VT_USER) + { + rmt::Vector position = mActiveVehicleList[i]->GetPosition(); + + //float radius = mActiveVehicleList[i]->GetWheelBase() * 3.0f; // TODO - what magic number? + float radius = mActiveVehicleList[i]->GetWheelBase() * 2.0f; // TODO - what magic number? + + int collisionAreaIndex = mActiveVehicleList[i]->mCollisionAreaIndex; + rAssert(collisionAreaIndex != -1); + + + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + Vehicle* playerVehicle = 0; + if(playerAvatar) + { + playerVehicle = playerAvatar->GetVehicle(); + } + + if(playerVehicle && mActiveVehicleList[i] == playerVehicle) + { + GetWorldPhysicsManager()->SubmitDynamicsPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState(), true); + } + else + { + GetWorldPhysicsManager()->SubmitDynamicsPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState(), false); + } + } + } + } + + for ( i = 0; i < MAX_ACTIVE_VEHICLES; i++ ) + { + if( mActiveVehicleList[i] && + GetRenderManager()->rCurWorldRenderLayer() == mActiveVehicleList[i]->GetRenderLayerEnum()) + { + //if(mActiveVehicleList[i]->GetLocomotionType() == VL_PHYSICS && mActiveVehicleList[i]->mVehicleType != VT_USER) + if(mActiveVehicleList[i]->mVehicleType != VT_USER) + { + rmt::Vector position = mActiveVehicleList[i]->GetPosition(); + + //float radius = mActiveVehicleList[i]->GetWheelBase() * 3.0f; // TODO - what magic number? + float radius = mActiveVehicleList[i]->GetWheelBase() * 2.0f; // TODO - what magic number? + + int collisionAreaIndex = mActiveVehicleList[i]->mCollisionAreaIndex; + rAssert(collisionAreaIndex != -1); + + + + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + Vehicle* playerVehicle = 0; + if(playerAvatar) + { + playerVehicle = playerAvatar->GetVehicle(); + } + + if(playerVehicle && mActiveVehicleList[i] == playerVehicle) + { + GetWorldPhysicsManager()->SubmitDynamicsPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState(), true); + } + else + { + GetWorldPhysicsManager()->SubmitDynamicsPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState(), false); + } + + //GetWorldPhysicsManager()->SubmitDynamicsPseudoCallback(position, radius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState(), false); + } + } + } + +} + + +//============================================================================= +// VehicleCentral::SubmitAnimCollisions +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void VehicleCentral::SubmitAnimCollisions() +{ + int i; + for ( i = 0; i < MAX_ACTIVE_VEHICLES; i++ ) + { + if( mActiveVehicleList[i] && + GetRenderManager()->rCurWorldRenderLayer() == mActiveVehicleList[i]->GetRenderLayerEnum()) + { + if(1) + //if(mActiveVehicleList[i]->GetLocomotionType() == VL_PHYSICS) + { + rmt::Vector position = mActiveVehicleList[i]->GetPosition(); + + //float collradius = mActiveVehicleList[i]->GetWheelBase() * 3.0f; // TODO - what magic number? + float collradius = mActiveVehicleList[i]->GetWheelBase() * 2.0f; // TODO - what magic number? + + int collisionAreaIndex = mActiveVehicleList[i]->mCollisionAreaIndex; + rAssert(collisionAreaIndex != -1); + + GetWorldPhysicsManager()->SubmitAnimCollisionsPseudoCallback(position, collradius, collisionAreaIndex, mActiveVehicleList[i]->GetSimState()); + + float updateradius = collradius * 1.0f; //? TODO - what magic number + GetWorldPhysicsManager()->SubmitAnimCollisionsForUpdateOnly(position, updateradius, collisionAreaIndex); + } + } + } + + + +} + +//============================================================================= +// VehicleCentral::GetVehicleByName +//============================================================================= +// Description: Comment +// +// Parameters: ( const char* name ) +// +// Return: Vehicle +// +//============================================================================= +Vehicle* VehicleCentral::GetVehicleByName( const char* name ) const +{ + return GetVehicleByUID( tEntity::MakeUID( name ) ); +} + +/* +============================================================================== +VehicleCentral::GetVehicleByUID +============================================================================== +Description: Comment + +Parameters: ( tUID uid ) + +Return: Vehicle + +============================================================================= +*/ +Vehicle* VehicleCentral::GetVehicleByUID( tUID uid ) const +{ + Vehicle* vehicle = 0; + for( int i = 0; i < MAX_ACTIVE_VEHICLES; i++ ) + { + if ( mActiveVehicleList[ i ] != 0 ) + { + if( uid == tEntity::MakeUID( mActiveVehicleList[ i ]->GetName() ) ) + { + vehicle = mActiveVehicleList[ i ]; + break; + } + } + } + return vehicle; +} + + +//============================================================================= +// VehicleCentral::OnProcessRequestsComplete +//============================================================================= +// Description: Comment +// +// Parameters: ( void* pUserData ) +// +// Return: void +// +//============================================================================= +void VehicleCentral::OnProcessRequestsComplete( void* pUserData ) +{ + Character* theDriver; + + Vehicle* vehicle = GetCurrentVehicleUnderConstruction(); + rAssert( vehicle ); + + vehicle->CalculateValuesBasedOnDesignerParams(); + + SetupDriver(vehicle); + + theDriver = vehicle->GetDriver(); + if( theDriver != NULL ) + { + // + // Despite the name, send this for every vehicle load. The dialogue + // system keeps a flag to determine which ones come from phone booths, + // and throws the rest of 'em out. Not pretty, but we're going final. + // -- Esan + // + GetEventManager()->TriggerEvent( EVENT_PHONE_BOOTH_NEW_VEHICLE_SELECTED, theDriver ); + } + else + { + GetEventManager()->TriggerEvent( EVENT_PHONE_BOOTH_CANCEL_RIDEREPLY_LINE ); + } + + //Clear the head. + mVehicleUnderConstruction[mCurrentVehicleUnderContructionHead] = NULL; + mCurrentVehicleUnderContructionHead = (mCurrentVehicleUnderContructionHead + 1) % MAX_ACTIVE_VEHICLES; + + if ( mCurrentVehicleUnderContructionHead == mCurrentVehicleUnderConstructionTail ) + { + rAssert( mVehicleUnderConstruction[mCurrentVehicleUnderContructionHead] == NULL ); + } +} + +void VehicleCentral::ActivateVehicleTriggers(bool active) +{ + if(active == mbVehicleTriggersActive) + { + return; + } + + mbVehicleTriggersActive = active; + + Vehicle* v = NULL; + + if( mbVehicleTriggersActive ) + { + for(int i = 0; i < MAX_ACTIVE_VEHICLES; i++) + { + v = mActiveVehicleList[i]; + if(v) + { + v->ActivateTriggers(true); + } + } + } + else + { + for(int i = 0; i < MAX_ACTIVE_VEHICLES; i++) + { + v = mActiveVehicleList[i]; + if( v ) + { + v->ActivateTriggers(false); + } + } + } + +} + +void VehicleCentral::ClearSuppressedDrivers(void) +{ + for(unsigned i = 0; i < mSuppressedDriverCount; i++) + { + mSuppressedDrivers[i].SetText(NULL); + } + + mSuppressedDriverCount = 0; +} + +void VehicleCentral::AddSuppressedDriver(const char* name) +{ + rAssert(mSuppressedDriverCount < MAX_SuppressED_DRIVERS); + mSuppressedDrivers[mSuppressedDriverCount++].SetText(name); +} + +void VehicleCentral::RemoveSuppressedDriver(const char* name) +{ + tUID uid = tEntity::MakeUID(name); + for(unsigned i = 0; i < mSuppressedDriverCount; i++) + { + if(mSuppressedDrivers[i].GetUID() == uid) + { + mSuppressedDrivers[i].SetUID((tUID)0); + return; + } + } +} + +bool VehicleCentral::IsDriverSuppressed(const char* name) +{ + tUID uid = tEntity::MakeUID(name); + for(unsigned i = 0; i < mSuppressedDriverCount; i++) + { + if(mSuppressedDrivers[i].GetUID() == uid) + return true; + } + return false; +} + +void VehicleCentral::SetupDriver(Vehicle* vehicle) +{ + if(vehicle->GetDriver()) + { + if((vehicle->GetDriver() != GetCharacterManager()->GetCharacter(0)) && (vehicle->GetDriver()->GetRole() != Character::ROLE_PEDESTRIAN)) + { + GetCharacterManager()->RemoveCharacter(vehicle->GetDriver()); + vehicle->SetDriver(NULL); + } + } + + if(vehicle->mDriverInit != FORCE_NO_DRIVER) + { + if((vehicle->GetDriverName()[0] != 0)) + { + if(strcmp(vehicle->GetDriverName(),"phantom") == 0) + { + vehicle->SetPhantomDriver(true); + } + else + { + if((strcmp(vehicle->GetDriverName(),"none") != 0) && + ((vehicle->mDriverInit == FORCE_DRIVER) || !IsDriverSuppressed(vehicle->GetDriverName()))) + { + char uniqueName[16]; + sprintf(uniqueName, "d_%s", vehicle->GetDriverName()); + + Character* character = NULL; + character = GetCharacterManager()->GetCharacterByName(uniqueName); + if(character && ((character->GetTargetVehicle() == vehicle) || (character->GetTargetVehicle() == NULL))) + { + GetCharacterManager()->RemoveCharacter(character); + } + + character = GetCharacterManager()->AddCharacter(CharacterManager::NPC, uniqueName, vehicle->GetDriverName(), "npd", ""); + + static_cast<NPCController*>(character->GetController())->TransitToState(NPCController::NONE); + character->SetTargetVehicle( vehicle ); + character->AddToWorldScene(); + character->GetStateManager()->SetState<CharacterAi::InCar>(); + + vehicle->SetDriver(character); + + character->SetRole(Character::ROLE_DRIVER); + } + } + } + } +} + + +//chuck adding this method so we can determine if car is still underconstruction. + +bool VehicleCentral::IsCarUnderConstruction(const char* name) +{ + //unsigned int pListPtr = mCurrentVehicleUnderContructionHead; + + for(int i=0;i<MAX_ACTIVE_VEHICLES;i++) + { + if (mVehicleUnderConstruction[i] != NULL) + { + if( strcmp(mVehicleUnderConstruction[i]->GetName(),name) == 0) + { + return true; + } + } + } + return false; +} + +bool VehicleCentral::IsCarUnderConstruction(const Vehicle* vehicle) +{ + for(int i=0;i<MAX_ACTIVE_VEHICLES;i++) + { + if (mVehicleUnderConstruction[i] != NULL) + { + if( mVehicleUnderConstruction[i] == vehicle ) + { + return true; + } + } + } + return false; +} + +VehicleAI* VehicleCentral::GetVehicleAI( Vehicle* vehicle ) +{ + if( vehicle ) + { + ////////////////////////// + // if Demo context, it's ok to fetch AI for + // non VT_AI types. Otherwise, we must boot!! + // + // Note: + // Supersprint doesn't worry about these, the AI cars should + // be VT_AI... + // + if( GetGameFlow()->GetCurrentContext() != CONTEXT_DEMO && + vehicle->mVehicleType != VT_AI ) + { + return NULL; + } + + int id = GetVehicleCentral()->GetVehicleId( vehicle ); + VehicleController* controller = GetVehicleCentral()->GetVehicleController( id ); + if( controller ) + { + rAssert( dynamic_cast<VehicleAI*>( controller ) ); + return static_cast<VehicleAI*>( controller ); + } + } + return NULL; +} + + +void VehicleCentral::DetachAllCollectibles() +{ + for ( int i = 0 ; i < GetNumVehicles() ; i++ ) + { + Vehicle* v = GetVehicle( i ); + if ( v != NULL ) + { + v->DetachCollectible( rmt::Vector(0,0,0), false ); + } + } +} diff --git a/game/code/worldsim/vehiclecentral.h b/game/code/worldsim/vehiclecentral.h new file mode 100644 index 0000000..488846f --- /dev/null +++ b/game/code/worldsim/vehiclecentral.h @@ -0,0 +1,205 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: vehiclecentral.h +// +// Description: holds a list of all the vehicles in the game and provides +// easy access for the rest of the game objects that care +// +// History: May 1, 2002 - created, gmayer +// +//============================================================================= + +#ifndef VEHICLECENTRAL_H +#define VEHICLECENTRAL_H + +//======================================== +// Nested Includes +//======================================== +#include <p3d/p3dtypes.hpp> + +#include <loading/loadingmanager.h> + +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/huskpool.h> + + + +//======================================== +// Forward References +//======================================== +class VehicleAI; +class AiVehicleController; +class VehicleController; +class EventLocator; +class tBillboardQuadGroup; + +//============================================================================= +// +// Synopsis: they lived happily ever after.... +// +//============================================================================= + + + +class VehicleCentral : public LoadingManager::ProcessRequestsCallback +{ + public: + enum DriverInit + { + ALLOW_DRIVER, + FORCE_NO_DRIVER, + FORCE_DRIVER + }; + + // Static Methods for accessing this singleton. + static VehicleCentral* GetInstance(); + static VehicleCentral* CreateInstance(); + static void DestroyInstance(); + + void PreLoad(); + void Unload(); + + Vehicle* InitVehicle( const char* name, bool addToActiveVehicleList = true, char* confile = 0, VehicleType vt = VT_USER, + DriverInit s = ALLOW_DRIVER, bool playercar = false, bool startoutofcar = true); + + // returns index if successful, otherwise -1 + int AddVehicleToActiveList(Vehicle* vehicle); + // also make this put vehicles in the dsg. + // + // this is also where the vehicle should ask for a collision index, and insert itself + + // returns whether or not it was even there in the first place + bool RemoveVehicleFromActiveList(Vehicle* vehicle); + // remove from dsg + + //Triage hack, only for demo mode, or until Greg actually + //addrefs and releases --dm 12/01/02 + VehicleController* RemoveVehicleController( int mAIIndex ); + + void KillEmAll(); + + void ClearSpot(rmt::Vector& point, float radius, Vehicle* skipCar); + + //void Suspend(); // temporarily freeze all action - leave vehicles in the active list + //void Resume(); // unfreeze + + int GetNumVehicles() const { return mNumActiveVehicles; } + Vehicle* GetVehicle( int id ) const; + + void SetupConsoleFunctionsForVehicleTuning(); + + void SetVehicleController( int id, VehicleController* pVehicleController ); + VehicleController* GetVehicleController( int id ) const; + + int GetVehicleId( Vehicle* pVehicle, bool checkStrict = true ) const; + + void SubmitStatics(); + void SubmitDynamics(); + void SubmitAnimCollisions(); + + + void PreSubstepUpdate(float dt); + void PostSubstepUpdate(float dt); + void Update(float dt); + void PreCollisionPrep(float dt, bool firstSubstep); + + enum { MAX_ACTIVE_VEHICLES = 30 }; + + static int GetMaxActiveVehicles() {return MAX_ACTIVE_VEHICLES;} + + // *** // + void GetActiveVehicleList(Vehicle** &vList, int& nVehicles); + bool ActiveVehicleListIsFull() const; + // *** // + + // hmmm... is there a nicer way to do this? + // need this so we have object to call script hooks on + Vehicle* mVehicleUnderConstruction[MAX_ACTIVE_VEHICLES]; + unsigned int mCurrentVehicleUnderContructionHead; + unsigned int mCurrentVehicleUnderConstructionTail; + Vehicle* GetCurrentVehicleUnderConstruction() { return mVehicleUnderConstruction[ mCurrentVehicleUnderContructionHead ]; }; + void OnProcessRequestsComplete( void* pUserData ); + + + Vehicle* GetVehicleByName( const char* name ) const; + Vehicle* GetVehicleByUID( tUID uid ) const; + + void ActivateVehicleTriggers(bool); + + HuskPool mHuskPool; // just use default constructor + + void InitHuskPool(); + void FreeHuskPool(); + + void ClearSuppressedDrivers(void); + void AddSuppressedDriver(const char* name); + void RemoveSuppressedDriver(const char* name); + bool IsDriverSuppressed(const char* name); + + void SetupDriver(Vehicle*); + + // + bool IsCarUnderConstruction(const char* name); + bool IsCarUnderConstruction(const Vehicle* vehicle); + + bool GetVehicleTriggersActive(void) {return mbVehicleTriggersActive;} + + VehicleAI* GetVehicleAI( Vehicle* vehicle ); + + // Removes all collectibles attached to various vehicles + void DetachAllCollectibles(); + + // store the headlights here! + enum + { + NUM_HEADLIGHT_BBQGS = 3, + NUM_HEADLIGHT_BBQS = 7 // combined total of BBQs of all headlight BBQGs + }; + tBillboardQuadGroup* mHeadLights[NUM_HEADLIGHT_BBQGS]; + tColour mOriginalHeadLightColours[NUM_HEADLIGHT_BBQS]; + + private: + + // No public access to these, use singleton interface. + VehicleCentral(); + ~VehicleCentral(); + + + // pointer to the single instance + static VehicleCentral* spInstance; + + Vehicle* mActiveVehicleList[MAX_ACTIVE_VEHICLES]; + VehicleController* mActiveVehicleControllerList[ MAX_ACTIVE_VEHICLES ]; + //EventLocator* mDoorTriggerList[ MAX_ACTIVE_VEHICLES ]; + int mNumActiveVehicles; + + //bool mSuspended; + + bool mbVehicleTriggersActive; + + static const unsigned int MAX_SuppressED_DRIVERS = 32; + unsigned int mSuppressedDriverCount; + tName mSuppressedDrivers[MAX_SuppressED_DRIVERS]; + + static AiVehicleController* spGenericAI; +}; + +// A little syntactic sugar for getting at this singleton. +inline VehicleCentral* GetVehicleCentral() { return( VehicleCentral::GetInstance() ); } + + + +// *** // +inline void VehicleCentral::GetActiveVehicleList(Vehicle** &vList, int& nVehicles) +{ + vList = mActiveVehicleList; + nVehicles = mNumActiveVehicles; +} +inline bool VehicleCentral::ActiveVehicleListIsFull() const +{ + return (mNumActiveVehicles>=MAX_ACTIVE_VEHICLES); +} +// *** // + +#endif //VEHICLECENTRAL_H diff --git a/game/code/worldsim/worldcollisionsolveragent.cpp b/game/code/worldsim/worldcollisionsolveragent.cpp new file mode 100644 index 0000000..3362c34 --- /dev/null +++ b/game/code/worldsim/worldcollisionsolveragent.cpp @@ -0,0 +1,365 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: +// +// Description: Implementation of class WorldCollisionSolverAgentManager +// +// History: 6/14/2002 + Created -- NAME +// +//============================================================================= + +//======================================== +// System Includes +//======================================== +// Foundation Tech +#include <raddebug.hpp> + +//======================================== +// Project Includes +//======================================== +#include <worldsim/worldcollisionsolveragent.h> +#include <worldsim/redbrick/redbrickcollisionsolveragent.h> +#include <worldsim/physicsairef.h> +#include <memory/srrmemory.h> + +#include <camera/supercamcentral.h> + +#include <render/DSG/collisionentitydsg.h> + +//****************************************************************************** +// +// Global Data, Local Data, Local Classes +// +//****************************************************************************** + +//****************************************************************************** +// +// Public Member Functions +// +//****************************************************************************** + +//============================================================================== +// WorldCollisionSolverAgentManager::WorldCollisionSolverAgentManager +//============================================================================== +// Description: Constructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +WorldCollisionSolverAgentManager::WorldCollisionSolverAgentManager() +{ + mpCollisionSolverAgentArray[ 0 ] = new(GMA_PERSISTENT) RedBrickCollisionSolverAgent; + mpCollisionSolverAgentArray[ 0 ]->AddRef(); +} + +//============================================================================== +// WorldCollisionSolverAgentManager::~WorldCollisionSolverAgentManager +//============================================================================== +// Description: Destructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +WorldCollisionSolverAgentManager::~WorldCollisionSolverAgentManager() +{ + int i; + for ( i = 0; i < NUM_SOLVERS; i++ ) + { + if ( mpCollisionSolverAgentArray[ i ] ) + { + mpCollisionSolverAgentArray[ i ]->Release(); + mpCollisionSolverAgentArray[ i ] = 0; + } + } +} + + + + +//============================================================================= +// WorldCollisionSolverAgentManager::CollisionEvent +//============================================================================= +// Description: Comment +// +// Parameters: (SimState* inSimStateA, int indexA, SimState* inSimStateB, int indexB, const rmt::Vector& inPos, float inDvN, float inDvT, SimulatedObject** simA, SimulatedObject** simB) +// +// Return: Solving_Answer +// +//============================================================================= +Solving_Answer WorldCollisionSolverAgentManager::CollisionEvent(SimState* inSimStateA, int indexA, SimState* inSimStateB, int indexB, const rmt::Vector& inPos, float inDvN, float inDvT, SimulatedObject** simA, SimulatedObject** simB) +{ + return Solving_Continue; +} + + +/* +============================================================================== +WorldCollisionSolverAgentManager::PreCollisionEvent +============================================================================== +Description: Comment + +Parameters: (Collision& inCollision, int inPass) + +Return: Solving_Answer + +============================================================================= +*/ +Solving_Answer WorldCollisionSolverAgentManager::PreCollisionEvent(Collision& inCollision, int inPass) +{ + CollisionObject* collObjA = inCollision.mCollisionObjectA; + CollisionObject* collObjB = inCollision.mCollisionObjectB; + + SimState* simStateA = collObjA->GetSimState(); + SimState* simStateB = collObjB->GetSimState(); + + + if(inCollision.mCollisionVolumeA->Type() == sim::BBoxVolumeType || inCollision.mCollisionVolumeB->Type() == sim::BBoxVolumeType) + { + return Solving_Aborted; + } + + + + + int i; + for ( i = 0; i < NUM_SOLVERS; i++ ) + { + rAssert( mpCollisionSolverAgentArray[ i ] != (CollisionSolverAgent*)0 ); + if ( Solving_Aborted == mpCollisionSolverAgentArray[ i ]->PreCollisionEvent( inCollision, inPass ) ) + { + return Solving_Aborted; + } + } + + + + // this is a bit messy, but I don't want to have to derive supercamcentral from CollisionEntityDSG + if(simStateA && simStateA->mAIRefIndex == PhysicsAIRef::CameraSphere) + { + rmt::Vector fixOffset = inCollision.mNormal; + float dist = inCollision.mDistance; + + if(dist < 0.0f) + { + dist *= -1.0f; + } + + fixOffset.Scale(dist); + + SuperCamCentral* scc = (SuperCamCentral*)(simStateA->mAIRefPointer); + scc->AddCameraCollisionOffset(fixOffset); + + return Solving_Aborted; + + } + if(simStateB && simStateB->mAIRefIndex == PhysicsAIRef::CameraSphere) + { + // normal always points B to A + // distance -ve means interpenetration + + rmt::Vector fixOffset = inCollision.mNormal; + + + float dist = inCollision.mDistance; + + if(dist < 0.0f) + { + dist *= -1.0f; + } + + fixOffset.Scale(-1.0f * dist); + + SuperCamCentral* scc = (SuperCamCentral*)(simStateB->mAIRefPointer); + scc->AddCameraCollisionOffset(fixOffset); + + return Solving_Aborted; + + } + + + + /* + if( simStateA && simStateB && ( simStateA->mAIRefIndex == PhysicsAIRef::redBrickPhizMoveableGroundPlane || + simStateB->mAIRefIndex == PhysicsAIRef::redBrickPhizMoveableGroundPlane) ) + { + //char buffy[128]; + rReleaseString("something collided with a moveableobject groundplane\n"); + + } + */ + + CollisionEntityDSG* pObjA = static_cast<CollisionEntityDSG*>( simStateA->mAIRefPointer ); + CollisionEntityDSG* pObjB = static_cast<CollisionEntityDSG*>( simStateB->mAIRefPointer ); + + //bool bSolveA = false; + //bool bSolveB = false; + Solving_Answer answerA = Solving_Continue; + Solving_Answer answerB = Solving_Continue; + + if ( pObjA ) + { + //bSolveA = pObjA->ReactToCollision( simStateB, inCollision ); + answerA = pObjA->PreReactToCollision( simStateB, inCollision ); + } + if ( pObjB ) + { + //bSolveB = pObjB->ReactToCollision( simStateA, inCollision ); + answerB = pObjB->PreReactToCollision( simStateA, inCollision ); + } + //if ( bSolveA || bSolveB ) + if(answerA == Solving_Aborted || answerB == Solving_Aborted) + { + return Solving_Aborted; + } + return CollisionSolverAgent::PreCollisionEvent(inCollision, inPass); +} + +/* +============================================================================== +WorldCollisionSolverAgentManager::TestImpulse +============================================================================== +Description: Comment + +Parameters: (rmt::Vector& mImpulse, Collision& inCollision) + +Return: Solving_Answer + +============================================================================= +*/ +Solving_Answer WorldCollisionSolverAgentManager::TestImpulse(rmt::Vector& impulse, Collision& inCollision) +{ + int i; + + // vehicle is special and will be dealt with in the redbrick collision solver agent + + // other ordinary shit will get the PostReactToCollision call here. + + for ( i = 0; i < NUM_SOLVERS; i++ ) + { + rAssert( mpCollisionSolverAgentArray[ i ] != (CollisionSolverAgent*)0 ); + if ( Solving_Aborted == mpCollisionSolverAgentArray[ i ]->TestImpulse( impulse, inCollision ) ) + { + return Solving_Aborted; + } + } + + CollisionObject* collObjA = inCollision.mCollisionObjectA; + CollisionObject* collObjB = inCollision.mCollisionObjectB; + + SimState* simStateA = collObjA->GetSimState(); + SimState* simStateB = collObjB->GetSimState(); + + CollisionEntityDSG* pObjA = static_cast<CollisionEntityDSG*>( simStateA->mAIRefPointer ); + CollisionEntityDSG* pObjB = static_cast<CollisionEntityDSG*>( simStateB->mAIRefPointer ); + + Solving_Answer answerA = Solving_Continue; + Solving_Answer answerB = Solving_Continue; + + if(pObjA) + { + answerA = pObjA->PostReactToCollision(impulse, inCollision); + } + if(pObjB) + { + answerB = pObjB->PostReactToCollision(impulse, inCollision); + } + + if(answerA == Solving_Aborted || answerB == Solving_Aborted) + { + return Solving_Aborted; + } + + /* + if( simStateA && simStateB && ( simStateA->mAIRefIndex == PhysicsAIRef::redBrickPhizMoveableGroundPlane || + simStateB->mAIRefIndex == PhysicsAIRef::redBrickPhizMoveableGroundPlane) ) + { + //char buffy[128]; + rReleaseString("something is testing impulse with a moveableobject groundplane\n"); + + } + */ + + + return CollisionSolverAgent::TestImpulse(impulse, inCollision); + + +} + +/* +============================================================================== +WorldCollisionSolverAgentManager::TestCache +============================================================================== +Description: Comment + +Parameters: (SimState* inSimState, int inIndex) + +Return: Solving_Answer + +============================================================================= +*/ +Solving_Answer WorldCollisionSolverAgentManager::TestCache(SimState* inSimState, int inIndex) +{ + int i; + for ( i = 0; i < NUM_SOLVERS; i++ ) + { + rAssert( mpCollisionSolverAgentArray[ i ] != (CollisionSolverAgent*)0 ); + if ( Solving_Aborted == mpCollisionSolverAgentArray[ i ]->TestCache( inSimState, inIndex ) ) + { + return Solving_Aborted; + } + } + return Solving_Continue; +} + + + + +Solving_Answer WorldCollisionSolverAgentManager::EndObjectCollision(SimState* inSimState, int inIndex) +{ + + int i; + for ( i = 0; i < NUM_SOLVERS; i++ ) + { + rAssert( mpCollisionSolverAgentArray[ i ] != (CollisionSolverAgent*)0 ); + if ( Solving_Aborted == mpCollisionSolverAgentArray[ i ]->EndObjectCollision(inSimState, inIndex) ) + { + return Solving_Aborted; + } + } + return Solving_Continue; + +} + + + +/* +============================================================================== +WorldCollisionSolverAgentManager::ResetCollisionFlags +============================================================================== +Description: Comment + +Parameters: () + +Return: void + +============================================================================= +*/ +void WorldCollisionSolverAgentManager::ResetCollisionFlags() +{ + int i; + for ( i = 0; i < NUM_SOLVERS; i++ ) + { + rAssert( mpCollisionSolverAgentArray[ i ] != (CollisionSolverAgent*)0 ); + mpCollisionSolverAgentArray[ i ]->ResetCollisionFlags(); + } +} +//****************************************************************************** +// +// Private Member Functions +// +//****************************************************************************** diff --git a/game/code/worldsim/worldcollisionsolveragent.h b/game/code/worldsim/worldcollisionsolveragent.h new file mode 100644 index 0000000..c624e01 --- /dev/null +++ b/game/code/worldsim/worldcollisionsolveragent.h @@ -0,0 +1,80 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: worldcollisionsolveragent.h +// +// Description: Blahblahblah +// +// History: 6/14/2002 + Created -- TBJ +// +//============================================================================= + +#ifndef WORLDCOLLISIONSOLVERAGENT_H +#define WORLDCOLLISIONSOLVERAGENT_H + +//======================================== +// Nested Includes +//======================================== +#include <simcollision/impulsebasedcollisionsolver.hpp> + +//======================================== +// Forward References +//======================================== +class RedBrickCollisionSolverAgent; + +using namespace sim; +//============================================================================= +// +// Synopsis: Blahblahblah +// +//============================================================================= +class WorldCollisionSolverAgent +: +public CollisionSolverAgent +{ +public: + virtual void ResetCollisionFlags() {}; +}; + +class WorldCollisionSolverAgentManager +: +public CollisionSolverAgent +{ +public: + WorldCollisionSolverAgentManager(); + ~WorldCollisionSolverAgentManager(); + + // the key method to override + Solving_Answer PreCollisionEvent(Collision& inCollision, int inPass); + Solving_Answer TestImpulse(rmt::Vector& mImpulse, Collision& inCollision); + Solving_Answer TestCache(SimState* inSimState, int inIndex); + + + Solving_Answer EndObjectCollision(SimState* inSimState, int inIndex); + + + // need to override this so that the sim library version doesn't automatically + // switch from ai to sim ctrl when objects are hit + + // this method allows to trigger sounds, animation and modify the objects state. + Solving_Answer CollisionEvent( SimState* inSimStateA, int indexA, + SimState* inSimStateB, int indexB, + const rmt::Vector& inPos, float inDvN, float inDvT, + SimulatedObject** simA, SimulatedObject** simB); + + + + + void ResetCollisionFlags(); +private: + + //Prevent wasteful constructor creation. + WorldCollisionSolverAgentManager( const WorldCollisionSolverAgent& worldcollisionsolveragent ); + WorldCollisionSolverAgentManager& operator=( const WorldCollisionSolverAgentManager& worldcollisionsolveragent ); + + static const int NUM_SOLVERS = 1; + WorldCollisionSolverAgent* mpCollisionSolverAgentArray[ NUM_SOLVERS ]; +}; + + +#endif //WORLDCOLLISIONSOLVERAGENT_H diff --git a/game/code/worldsim/worldobject.cpp b/game/code/worldsim/worldobject.cpp new file mode 100644 index 0000000..e5726f6 --- /dev/null +++ b/game/code/worldsim/worldobject.cpp @@ -0,0 +1,125 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: worldobject.cpp +// +// Description: Implement WorldObject +// +// History: 14/05/2002 + Created -- NAME +// +//============================================================================= + +//======================================== +// System Includes +//======================================== +// Foundation Tech +#include <raddebug.hpp> +#include <p3d/matrixstack.hpp> +#include <p3d/utility.hpp> + +//======================================== +// Project Includes +//======================================== +#include <worldsim/worldobject.h> +#include <debug/profiler.h> + +//****************************************************************************** +// +// Global Data, Local Data, Local Classes +// +//****************************************************************************** + +//****************************************************************************** +// +// Public Member Functions +// +//****************************************************************************** + +//============================================================================== +// WorldObject::WorldObject +//============================================================================== +// Description: Constructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +WorldObject::WorldObject( tDrawable* drawable ) +{ + if( drawable != NULL ) + { + mDrawable = drawable; + mDrawable->AddRef(); + } + else + { + mDrawable = NULL; + } + + mTransform.Identity(); +} + +//============================================================================== +// WorldObject::~WorldObject +//============================================================================== +// Description: Destructor. +// +// Parameters: None. +// +// Return: N/A. +// +//============================================================================== +WorldObject::~WorldObject() +{ + if( mDrawable != NULL ) + { + mDrawable->Release(); + } +} + +//============================================================================= +// WorldObject::SetPosition +//============================================================================= +// Description: Comment +// +// Parameters: ( rmt::Vector &position ) +// +// Return: void +// +//============================================================================= +void WorldObject::SetPosition( rmt::Vector &position ) +{ + mTransform.FillTranslate( position ); +} + +//============================================================================= +// WorldObject::Display +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldObject::Display() +{ + BEGIN_PROFILE("WorldObject::Display") + if( mDrawable != NULL ) + { + p3d::stack->PushMultiply( mTransform ); + + mDrawable->Display(); + + p3d::stack->Pop(); + } + END_PROFILE("WorldObject::Display") +} + + +//****************************************************************************** +// +// Private Member Functions +// +//****************************************************************************** diff --git a/game/code/worldsim/worldobject.h b/game/code/worldsim/worldobject.h new file mode 100644 index 0000000..3cbc3a7 --- /dev/null +++ b/game/code/worldsim/worldobject.h @@ -0,0 +1,60 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: .h +// +// Description: Blahblahblah +// +// History: 14/05/2002 + Created -- NAME +// +//============================================================================= + +#ifndef WORLDOBJECT_H +#define WORLDOBJECT_H + +//======================================== +// Nested Includes +//======================================== + +//======================================== +// Forward References +//======================================== + +#include <radmath/radmath.hpp> +#include <p3d/drawable.hpp> + +//============================================================================= +// +// Synopsis: Blahblahblah +// +//============================================================================= + +class WorldObject : public tDrawable +{ + public: + WorldObject( tDrawable* drawable ); + virtual ~WorldObject(); + + void SetPosition( rmt::Vector &position ); + + virtual void Display(); + + tDrawable* GetDrawable() { return( mDrawable ); } + private: + //Prevent wasteful constructor creation. + WorldObject( const WorldObject& worldObject ); + WorldObject& operator=( const WorldObject& worldObject ); + + tDrawable* mDrawable; + rmt::Matrix mTransform; +}; + + +// Hack name for inventory section to hold sim library caches of SkeletonInfo. +// Tracked to ATG as bug 1259. +// +const char SKELCACHE[] = "SkeletonInfoCache"; + + +#endif // WORLDOBJECT_H + diff --git a/game/code/worldsim/worldphysicsmanager.cpp b/game/code/worldsim/worldphysicsmanager.cpp new file mode 100644 index 0000000..9a3eb24 --- /dev/null +++ b/game/code/worldsim/worldphysicsmanager.cpp @@ -0,0 +1,2921 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: worldphysicsmanager.cpp +// +// Description: bleek +// +// History: May 1, 2002 - created, gmayer +// +//============================================================================= + +//======================================== +// System Includes +//======================================== + +#include <raddebug.hpp> + +#include <simcommon/simstatearticulated.hpp> +#include <simcommon/simenvironment.hpp> +#include <simcollision/collisionmanager.hpp> +#include <simcommon/physicsproperties.hpp> + +#include <simcollision/collisionvolume.hpp> +#include <simcollision/collisiondisplay.hpp> +#include <simcommon/simutility.hpp> + +//======================================== +// Project Includes +//======================================== +#include <render/DSG/animcollisionentitydsg.h> +#include <worldsim/worldphysicsmanager.h> +#include <worldsim/worldcollisionsolveragent.h> + +#include <worldsim/groundplanepool.h> + +#include <worldsim/avatarmanager.h> +#include <worldsim/vehiclecentral.h> + +#include <memory/srrmemory.h> + +#include <worldsim/redbrick/vehicle.h> +#include <worldsim/redbrick/physicslocomotion.h> // just for debug prinout + +#include <camera/supercammanager.h> + +#include <debug/profiler.h> +#include <debug/debuginfo.h> + + +#include <render/Culling/ReserveArray.h> +#include <render/DSG/StaticPhysDSG.h> +#include <render/DSG/StatePropDSG.h> +#include <render/DSG/FenceEntityDSG.h> +#include <render/IntersectManager/IntersectManager.h> + + +#include <worldsim/character/charactermanager.h> +#include <worldsim/character/character.h> +#include <ai/actionbuttonmanager.h> + +#include <constants/maxplayers.h> +#include <constants/maxnpccharacters.h> + +#include <sound/soundmanager.h> + +#include <mission/gameplaymanager.h> + +// TODO +// can remove +// just here to hack in a light so I can see the texture damage states better +#include <render/RenderManager/RenderManager.h> +#include <render/RenderManager/RenderLayer.h> + +//****************************************************************************** +// +// Global Data, Local Data, Local Classes +// +//****************************************************************************** + +WorldPhysicsManager* WorldPhysicsManager::spInstance = 0; + +//============================================================================= +// WorldPhysicsManager::WorldPhysicsManager +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: WorldPhysicsManager +// +//============================================================================= +WorldPhysicsManager::WorldPhysicsManager() +{ + mCollisionDistanceCGS = 2.0f; + mWorldUp.Set(0.0f, 1.0f, 0.0f); + mTotalTime = 0.0f; + updateFrame = 0; + mLoopTime = 0.0f; + + sim::InitializeSimulation(MetersUnits); + + //SetSimUnits(); + + // move to bootupcontext onstart + //Init(); + mLastTime = 30; +} + + +//============================================================================= +// WorldPhysicsManager::~WorldPhysicsManager +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: WorldPhysicsManager +// +//============================================================================= +WorldPhysicsManager::~WorldPhysicsManager() +{ + mSimEnvironment = 0; + + int i, j; + for(i = 0; i < mNumCollisionAreas; i++) + { + for( j = 0; j < mMaxFencePerArea; j++ ) + { + mFences[i][j].mFenceSimState->Release(); + } + + delete [] mCurrentStatics[i]; + delete [] mCurrentAnimCollisions[i]; + delete [] mCurrentUpdateAnimCollisions[i]; + delete [] mFences[i]; + delete [] mCurrentDynamics[i]; + } + delete [] mCurrentStatics; + delete [] mCurrentAnimCollisions; + delete [] mCurrentUpdateAnimCollisions; + delete [] mFences; + delete [] mFencesInEachArea; + delete [] mCurrentDynamics; + delete mGroundPlanePool; + + for( i = 0; i < mMaxFencePerArea; i++ ) + { + mFenceDSGResults[i]->Release(); + } + delete[] mFenceDSGResults; + + mFencePhysicsProperties->Release(); + + mCollisionManager->Release(); + + mpWorldCollisionSolverAgentManager->Release(); + + sim::CleanupLineDrawing (); + + delete [] mCollisionAreaAllocated; + delete [] mCollisionAreaActive; + + sim::ResetSimulation (); +} + + +//============================================================================= +// WorldPhysicsManager::GetInstance +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: WorldPhysicsManager +// +//============================================================================= +WorldPhysicsManager* WorldPhysicsManager::GetInstance() +{ + rAssert(spInstance); + return spInstance; +} + + +//============================================================================= +// WorldPhysicsManager::CreateInstance +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: WorldPhysicsManager +// +//============================================================================= +WorldPhysicsManager* WorldPhysicsManager::CreateInstance() +{ + rAssert(spInstance == 0); + +#ifdef RAD_GAMECUBE + HeapMgr()->PushHeap( GMA_GC_VMM ); + spInstance = new WorldPhysicsManager; + HeapMgr()->PopHeap( GMA_GC_VMM ); +#else + HeapMgr()->PushHeap( GMA_PERSISTENT ); + spInstance = new WorldPhysicsManager; + HeapMgr()->PopHeap( GMA_PERSISTENT ); +#endif + rAssert(spInstance); + + return spInstance; + +} + + +//============================================================================= +// WorldPhysicsManager::Init +//============================================================================= +// Description: +// TODO - need this? - where to call it from? +// ok to call from CreateSingletons +// +// no - it would be called from the a context OnStart if necessary +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::Init() +{ + mSimEnvironment = sim::SimEnvironment::GetDefaultSimEnvironment(); + + rAssert(mSimEnvironment); + //mSimEnvironment->AddRef(); // TODO + mSimEnvironment->SetCollisionDistanceCGS(mCollisionDistanceCGS); + + //mSimEnvironment->SetGravityCGS(0.0f, -981.0f, 0.0f); + //mSimEnvironment->SetGravityCGS(0.0f, -2000.0f, 0.0f); + mSimEnvironment->SetGravityCGS(0.0f, -1600.0f, 0.0f); // plum's setting + //mSimEnvironment->SetGravityCGS(0.0f, -2500.0f, 0.0f); + mTimerTime = 0.0f; + mTimerOn = false; + + InitCollisionManager(); + + sim::SetupLineDrawing (); +} + + +//============================================================================= +// WorldPhysicsManager::StartTimer +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::StartTimer() +{ + mTimerOn = true; +} + + +//============================================================================= +// WorldPhysicsManager::StopTimer +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::StopTimer() +{ + mTimerOn = false; +} + + +//============================================================================= +// WorldPhysicsManager::ResetTimer +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::ResetTimer() +{ + mTimerTime = 0.0f; +} + + +//============================================================================= +// WorldPhysicsManager::ToggleTimerState +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::ToggleTimerState() +{ + if(mTimerOn) + { + mTimerOn = false; + } + else if(mTimerTime > 0.0f) + { + mTimerTime = 0.0f; + } + else + { + mTimerOn = true; + } +} + +int WorldPhysicsManager::ApplyForceToDynamics( int collisionAreaIndex, + const rmt::Vector& position, + float radius, + const rmt::Vector& direction, + float force, + WorldPhysicsManager::NumObjectsHit* pObjectsHit, + CollisionEntityDSGList* pCollisionEntityList ) +{ + rAssert( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ); + + int i; + int numObjectsAffected = 0; + float radiusSqr = radius * radius; + + if( pCollisionEntityList != NULL ) + { + // + // Initialize entity list + // + for( i = 0; i < CollisionEntityDSGList::NUM_COLLISION_LIST_ENTITIES; i++ ) + { + pCollisionEntityList->collisionEntity[i] = NULL; + } + } + + for (i = 0 ; i < mMaxDynamics ; i++) + { + DynaPhysDSG* pDSG = mCurrentDynamics[collisionAreaIndex][i].mDynamicPhysDSG; + if ( pDSG != NULL ) + { + rmt::Vector objPosition; + rmt::Box3D boundingBox; + pDSG->GetBoundingBox( &boundingBox ); + rmt::Sphere boundingSphere( position, radius ); + pDSG->GetPosition(&objPosition); + if ( boundingBox.Intersects( boundingSphere ) ) + { + if ( pDSG->IsCollisionEnabled() ) + { + pDSG->ApplyForce( direction, force ); + + if( ( pCollisionEntityList != NULL ) + && ( numObjectsAffected < CollisionEntityDSGList::NUM_COLLISION_LIST_ENTITIES ) ) + { + pCollisionEntityList->collisionEntity[numObjectsAffected] = pDSG; + } + ++numObjectsAffected; + } + } + } + } + return numObjectsAffected; +} + +int WorldPhysicsManager::ApplyForceToDynamicsSpherical( int collisionAreaIndex, + const rmt::Vector& position, + float radius, + float force, + CollisionEntityDSGList* pCollisionEntityList ) +{ + rAssert( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ); + + int i; + int numObjectsAffected = 0; + float radiusSqr = radius * radius; + + if( pCollisionEntityList != NULL ) + { + // + // Initialize entity list + // + for( i = 0; i < CollisionEntityDSGList::NUM_COLLISION_LIST_ENTITIES; i++ ) + { + pCollisionEntityList->collisionEntity[i] = NULL; + } + } + + for (i = 0 ; i < mMaxDynamics ; i++) + { + DynaPhysDSG* pDSG = mCurrentDynamics[collisionAreaIndex][i].mDynamicPhysDSG; + if ( pDSG != NULL ) + { + rmt::Vector objPosition; + rmt::Box3D boundingBox; + pDSG->GetBoundingBox( &boundingBox ); + rmt::Sphere boundingSphere( position, radius ); + pDSG->GetPosition(&objPosition); + if ( boundingBox.Intersects( boundingSphere ) ) + { + if ( pDSG->IsCollisionEnabled() ) + { + // Calculate the vector from the position to the object + rmt::Vector direction = boundingBox.Mid() - position; + direction.Normalize(); + pDSG->ApplyForce( direction, force ); + if( ( pCollisionEntityList != NULL ) + && ( numObjectsAffected < CollisionEntityDSGList::NUM_COLLISION_LIST_ENTITIES ) ) + { + pCollisionEntityList->collisionEntity[numObjectsAffected] = pDSG; + } + + ++numObjectsAffected; + } + } + } + } + return numObjectsAffected; + +} + + +//============================================================================= +// WorldPhysicsManager::InitCollisionManager +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::InitCollisionManager() +{ +MEMTRACK_PUSH_GROUP( "WorldPhysicsManager" ); + + mCollisionManager = CollisionManager::GetInstance(); + mCollisionManager->AddRef(); + + //mCollisionManager->SetCollisionDistanceCGS(mCollisionDistanceCGS); + + //mCollisionManager->SetCollisionManagerAttributes(CM_DetectIfMoving); // TODO - which attribute should this be? + mCollisionManager->SetCollisionManagerAttributes(CM_DetectAll); // TODO - should be able to use IfMoving + + + + //mReservedCollisionAreas = 2; + mReservedCollisionAreas = 0; + + // setup areas: + // + // total number of areas = max allowed number of active vehicles + // + max active (human) characters {always 1 I think} + // + cameras for active cars or characters + // + // (new) + initial reserved slots + + //mMaxVehicles = GetVehicleCentral()->GetMaxActiveVehicles(); + + //mMaxChars = GetCharacterManager()->GetMaxCharacters(); + + // new + // jan 12, 2003 + // test + // values for vehicles and especially characters are now way too high + // + // only need slots for shit that is in existence simultaneously + + // 10 is probably enough + mMaxVehicles = 15; + + // 12 - 15 would probably be enough + mMaxChars = 18; + + + + mMaxCameras = MAX_PLAYERS; + + // this total is the number of collision areas we need + reserved + + // following indexing standard will be used into the collision areas array: + + /* + + reserved: + + 0 + 1 + 2 + . + . + . + mReservedCollisionAreas - 1 + + vehicles: + + mReservedCollisionAreas + . + . + . + . + . + mReservedCollisionAreas + maxVehicle - 1 + + + characters: + + mReservedCollisionAreas + maxVehicle + . + . + mReservedCollisionAreas+ maxVehicle + maxChars - 1 + + cameras: + + mReservedCollisionAreas + maxVehicle + maxChars + . + . + mReservedCollisionAreas + maxVehicle + maxChars + maxCameras - 1 + + + */ + + mNumCollisionAreas = mReservedCollisionAreas + mMaxVehicles + mMaxChars + mMaxCameras; + mCollisionManager->SetNbArea(mNumCollisionAreas); + + // goddamn, goddamn!! + mCollisionManager->SetUseExclusiveAutoPair(true); + + mCollisionManager->ActivateAllAreas(); + #ifdef RAD_GAMECUBE + HeapMgr()->PushHeap( GMA_GC_VMM ); + #else + HeapMgr()->PushHeap( GMA_PERSISTENT ); + #endif + + mCollisionAreaAllocated = new bool[mNumCollisionAreas]; + mCollisionAreaActive = new bool[mNumCollisionAreas]; + + //-------- + // statics + //-------- + + // TODO - find the right number here + // TODO - different depending on thing? + mMaxStatics = 30; + + mCurrentStatics = new StaticsInCollisionDetection*[mNumCollisionAreas]; + + int i,j; + for(i = 0; i < mNumCollisionAreas; i++) + { + mCollisionAreaAllocated[i] = false; + mCollisionAreaActive[i] = false; + + mCurrentStatics[i] = new StaticsInCollisionDetection[mMaxStatics]; + + for(j = 0; j < mMaxStatics; j++) + { + mCurrentStatics[i][j].mStaticPhysDSG = 0; + mCurrentStatics[i][j].clean = false; + } + } + + + //---------------- + // anim collisions + //---------------- + + // Todo: TBJ - make this value smaller. + // It has to be larger because of the large radius set in CharacterManager. + // When anim objects move in the DSG, we can reduce this to a smaller number. + // + mMaxAnimCollisions = 20; + + mCurrentAnimCollisions = new AnimCollisionInCollisionDetection*[mNumCollisionAreas]; + + for(i = 0; i < mNumCollisionAreas; i++) + { + mCurrentAnimCollisions[i] = new AnimCollisionInCollisionDetection[mMaxAnimCollisions]; + + for(j = 0; j < mMaxAnimCollisions; j++) + { + mCurrentAnimCollisions[i][j].mAnimCollisionEntityDSG = 0; + mCurrentAnimCollisions[i][j].clean = false; + } + } + + + mMaxUpdateAnimCollisions = 64; + + mCurrentUpdateAnimCollisions = new AnimCollisionInCollisionDetection*[mNumCollisionAreas]; + + for(i = 0; i < mNumCollisionAreas; i++) + { + mCurrentUpdateAnimCollisions[i] = new AnimCollisionInCollisionDetection[mMaxUpdateAnimCollisions]; + + for(j = 0; j < mMaxUpdateAnimCollisions; j++) + { + mCurrentUpdateAnimCollisions[i][j].mAnimCollisionEntityDSG = 0; + mCurrentUpdateAnimCollisions[i][j].clean = false; + } + } + + //------- + // fences + //------- + + // TODO - find the right number here! + mMaxFencePerArea = 8; + + mFences = new FencePieces*[mNumCollisionAreas]; + + mFencesInEachArea = new int[mNumCollisionAreas]; + // keep a total for efficient submission/removal + + + // new - to save memory + // May 7, 2003 + // + // make all fences and ground planes in the pool use the same physics properties + // (perhaps later change so that one for fences and one for ground planes) + + mFencePhysicsProperties = new sim::PhysicsProperties; + mFencePhysicsProperties->AddRef(); + + + + for(i = 0; i < mNumCollisionAreas; i++) + { + mFences[i] = new FencePieces[mMaxFencePerArea]; + + for(j = 0; j < mMaxFencePerArea; j++) + { + // need to make collision volumes here! + rmt::Vector center(0.0f, 0.0f, 0.0f); + rmt::Vector o0(1.0f, 0.0f, 0.0f); + rmt::Vector o1(0.0f, 1.0f, 0.0f); + rmt::Vector o2(0.0f, 0.0f, 1.0f); + + sim::OBBoxVolume* tempOBBox = new OBBoxVolume(center, o0, o1, o2, 1.0f, 1.0f, 1.0f); + + + mFences[i][j].mFenceSimState = (sim::ManualSimState*)(SimState::CreateManualSimState(tempOBBox)); + mFences[i][j].mFenceSimState->AddRef(); + + mFences[i][j].mFenceSimState->GetCollisionObject()->SetManualUpdate(true); + //mFences[i][j].mFenceSimState->GetCollisionObject()->SetAutoPair(true); + mFences[i][j].mFenceSimState->GetCollisionObject()->SetAutoPair(false); + + // setting this should free the unnecessarly allocated default one: + //mFences[i][j].mFenceSimState->GetCollisionObject()->SetPhysicsProperties(mFencePhysicsProperties); + mFences[i][j].mFenceSimState->SetPhysicsProperties(this->mFencePhysicsProperties); + + + // give this thing a reasonable name for debug purposes + char buffy[128]; + sprintf(buffy, "fence_a%d_n%d", i, j); + + mFences[i][j].mFenceSimState->GetCollisionObject()->SetName(buffy); + + mFences[i][j].mFenceSimState->mAIRefIndex = PhysicsAIRef::redBrickPhizFence; + mFences[i][j].mFenceSimState->mAIRefPointer = 0; // only set if object is derived from CollisionEntityDSG + + + mFences[i][j].mInCollision = false; + mFences[i][j].mClean = false; + + } + + mFencesInEachArea[i] = 0; + + } + + // debug drawing + mNumDebugFences = 0; + + mFenceDSGResults = new FenceEntityDSG*[mMaxFencePerArea]; + for(i = 0; i < mMaxFencePerArea; i++) + { + mFenceDSGResults[i] = new FenceEntityDSG; + mFenceDSGResults[i]->AddRef(); + } + + //--------- + // dynamics + //--------- + + mMaxDynamics = 20; + + mCurrentDynamics = new DynamicsInCollisionDetection*[mNumCollisionAreas]; + + for(i = 0; i < mNumCollisionAreas; i++) + { + mCurrentDynamics[i] = new DynamicsInCollisionDetection[mMaxDynamics]; + + for(j = 0; j < mMaxDynamics; j++) + { + mCurrentDynamics[i][j].mDynamicPhysDSG = 0; + mCurrentDynamics[i][j].clean = false; + } + } + + //-------------- + // ground planes + //-------------- + + mGroundPlanePool = new GroundPlanePool(mMaxDynamics * 2); // TODO - ????? how many + + + //----------------------- + // collision solver agent + //----------------------- + mpWorldCollisionSolverAgentManager = new WorldCollisionSolverAgentManager; + mpWorldCollisionSolverAgentManager->AddRef(); + + mCollisionManager->GetImpulseBasedCollisionSolver()->SetCollisionSolverAgent(mpWorldCollisionSolverAgentManager); + + + //enum DrawVolumeMethod {DrawVolumeOutline=0, DrawVolumeShape}; + //sim::SetDrawVolumeMethod(DrawVolumeOutline); + sim::SetDrawVolumeMethod(DrawVolumeShape); + + mInInterior = false; + + #ifdef RAD_GAMECUBE + HeapMgr()->PopHeap( GMA_GC_VMM ); + #else + HeapMgr()->PopHeap( GMA_PERSISTENT ); + #endif +MEMTRACK_POP_GROUP("WorldPhysicsManager"); +} + +//============================================================================= +// WorldPhysicsManager::SuspendForInterior +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::SuspendForInterior() +{ + mInInterior = true; +} + + +//============================================================================= +// WorldPhysicsManager::ResumeForOutside +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::ResumeForOutside() +{ + mInInterior = false; +} + + + +//============================================================================= +// WorldPhysicsManager::EmptyCollisionAreaIndex +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::EmptyCollisionAreaIndex(int index) +{ + rAssert( index > WorldPhysicsManager::INVALID_COLLISION_AREA ); + rAssert(mCollisionAreaAllocated[index]); // should not be trying to empty one that is not in use + + mCollisionManager->ResetArea(index); // this will take everything out of that area! + + int i; + for(i = 0; i < mMaxStatics; i++) + { + tRefCounted::Release( mCurrentStatics[index][i].mStaticPhysDSG ); + mCurrentStatics[index][i].mStaticPhysDSG = 0; + mCurrentStatics[index][i].clean = false; + } + + for(i = 0; i < mMaxAnimCollisions; i++) + { + tRefCounted::Release( mCurrentAnimCollisions[index][i].mAnimCollisionEntityDSG ); + mCurrentAnimCollisions[index][i].mAnimCollisionEntityDSG = 0; + mCurrentAnimCollisions[index][i].clean = false; + } + + for(i = 0; i < mMaxUpdateAnimCollisions; i++) + { + tRefCounted::Release( mCurrentUpdateAnimCollisions[index][i].mAnimCollisionEntityDSG ); + mCurrentUpdateAnimCollisions[index][i].mAnimCollisionEntityDSG = 0; + mCurrentUpdateAnimCollisions[index][i].clean = false; + } + + for(i = 0; i < mMaxFencePerArea; i++) + { + // don't need to add ref and release these 'cause they're only internal to the fences. + mFences[index][i].mInCollision = false; + } + //mFencesInEachArea[index] = 0; + + + for(i = 0; i < mMaxDynamics; i++) + { + if(mCurrentDynamics[index][i].mDynamicPhysDSG) + { + //TODO + if(mCurrentDynamics[index][i].mDynamicPhysDSG->GetAIRef() != PhysicsAIRef::redBrickVehicle) + { + mCurrentDynamics[index][i].mDynamicPhysDSG->FreeGroundPlane(); // in case it still has one + + // this is also where the ground plane collision object will be disabled if + // there are no more refs + } + + + + + + tRefCounted::Release( mCurrentDynamics[index][i].mDynamicPhysDSG ); + mCurrentDynamics[index][i].mDynamicPhysDSG = 0; + mCurrentDynamics[index][i].clean = false; + } + + } + + // TODO - setup some sort of enable/disable interface also? +} + +//============================================================================= +// WorldPhysicsManager::FreeCollisionAreaIndex +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::FreeCollisionAreaIndex(int index) +{ + rAssert( index > WorldPhysicsManager::INVALID_COLLISION_AREA ); + rAssert(mCollisionAreaAllocated[index]); // should not be trying to free up one that is not in use + + EmptyCollisionAreaIndex( index ); + + mCollisionAreaAllocated[index] = false; +} +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +void WorldPhysicsManager::FreeAllCollisionAreaIndicies() +{ + int index = 0; + for(; index<mNumCollisionAreas; index++) + { + if(mCollisionAreaAllocated[index]) // should not be trying to free up one that is not in use + { + FreeCollisionAreaIndex(index); + } + } +} + +//============================================================================= +// WorldPhysicsManager::RemoveVehicleFromAnyOtherCurrentDynamicsList +//============================================================================= +// Description: Comment +// +// Parameters: (Vehicle* vehicle) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::RemoveFromAnyOtherCurrentDynamicsListAndCollisionArea(DynaPhysDSG* obj) +{ + int i; + + // Vehicle* vehicle = dynamic_cast<Vehicle*>(obj); + // Character* character= dynamic_cast<Character*>(obj); + Vehicle* vehicle = NULL; + Character* character = NULL; + + if ( obj->GetAIRef() == PhysicsAIRef::NPCharacter || + obj->GetAIRef() == PhysicsAIRef::PlayerCharacter ) + { + rAssert( dynamic_cast< Character* >( obj ) ); + character = static_cast< Character*>( obj ) ; + } + else if ( obj->GetAIRef() == PhysicsAIRef::redBrickVehicle ) + { + rAssert( dynamic_cast< Vehicle* >( obj )); + vehicle = static_cast< Vehicle*>( obj ); + } + else + { + rAssert( dynamic_cast< Character* >( obj )==false); + rAssert( dynamic_cast< Vehicle* >( obj )==false); + } + + for(i = 0; i < mNumCollisionAreas; i++) + { + if(vehicle && (i == vehicle->mCollisionAreaIndex)) + { + continue; + } + + if(character && (i == character->GetCollisionAreaIndex())) + { + continue; + } + + if(!mCollisionAreaAllocated[i]) + { + continue; + } + + int j; + for(j = 0; j < mMaxDynamics; j++) + { + if(mCurrentDynamics[i][j].mDynamicPhysDSG == obj) + { + mCollisionManager->RemoveCollisionObject(obj->GetSimState()->GetCollisionObject(), i); + + + // only deal with ground plane here if the incoming object is not a car + if(!(vehicle)) + { + int groundPlaneIndex = mCurrentDynamics[i][j].mDynamicPhysDSG->GetGroundPlaneIndex(); + if(groundPlaneIndex != -1) // should never be false... + { + sim::CollisionObject* gpCollObj = mGroundPlanePool->GetSimState(groundPlaneIndex)->GetCollisionObject(); + + mCollisionManager->RemoveCollisionObject(gpCollObj, i); + + mCurrentDynamics[i][j].mDynamicPhysDSG->FreeGroundPlane(); // this decrements the groundplaneref, but we still need to take it out of + } + } + + mCurrentDynamics[i][j].mDynamicPhysDSG = 0; + mCurrentDynamics[i][j].clean = false; + + obj->Release(); + + + } + + } + } +} + +//============================================================================= +// WorldPhysicsManager::OnQuitLevel +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::OnQuitLevel() +{ + // make sure ground plane pool has been properly free'd up + if(!(mGroundPlanePool->FreeAllGroundPlanes())) + { + // there was some ground planes that were not yet free'd when this was called! + rAssertMsg(false, "not all the ground planes were free'd by their dynamic object owners! bad dynamic objects!\n"); + } + +} + +//============================================================================= +// WorldPhysicsManager::GetCameraCollisionAreaIndex +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: int +// +//============================================================================= +int WorldPhysicsManager::GetCameraCollisionAreaIndex() +{ + // moved to member data + //int maxVehicles = GetVehicleCentral()->GetMaxActiveVehicles(); + //int maxChars = MAX_PLAYERS; + //int maxCameras = MAX_PLAYERS; + + int start = mReservedCollisionAreas + mMaxVehicles + mMaxChars; + int end = mReservedCollisionAreas + mMaxVehicles + mMaxChars + mMaxCameras - 1; + + int i; + for(i = start; i <= end; i++) + { + // look for first free index + if(mCollisionAreaAllocated[i]) + { + continue; + } + else + { + mCollisionAreaAllocated[i] = true; + return i; + } + } + + rReleaseAssertMsg( 0, "not enough camera collision indices\n" ); + return WorldPhysicsManager::INVALID_COLLISION_AREA; +} + + +//============================================================================= +// WorldPhysicsManager::GetVehicleCollisionAreaIndex +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: int +// +//============================================================================= +int WorldPhysicsManager::GetVehicleCollisionAreaIndex() +{ + //int maxVehicles = GetVehicleCentral()->GetMaxActiveVehicles(); + + int start = mReservedCollisionAreas; + int end = mReservedCollisionAreas + mMaxVehicles - 1; + + int i; + for(i = start; i <= end; i++) + { + // look for first free index + if(mCollisionAreaAllocated[i]) + { + continue; + } + else + { + mCollisionAreaAllocated[i] = true; + return i; + } + } + + rReleaseAssertMsg( 0, "not enough vehicle collision indices\n" ); + return WorldPhysicsManager::INVALID_COLLISION_AREA; +} + + +//============================================================================= +// WorldPhysicsManager::GetCharacterCollisionAreaIndex +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: int +// +//============================================================================= +int WorldPhysicsManager::GetCharacterCollisionAreaIndex() +{ + //int maxVehicles = GetVehicleCentral()->GetMaxActiveVehicles(); + //int maxChars = MAX_PLAYERS; + + int start = mReservedCollisionAreas + mMaxVehicles; + int end = mReservedCollisionAreas + mMaxVehicles + mMaxChars - 1; + + int i; + for(i = start; i <= end; i++) + { + // look for first free index + if(mCollisionAreaAllocated[i]) + { + continue; + } + else + { + mCollisionAreaAllocated[i] = true; + return i; + } + } + + rReleaseAssertMsg( 0, "not enough character collision indices\n" ); + return WorldPhysicsManager::INVALID_COLLISION_AREA; +} + + +//============================================================================= +// WorldPhysicsManager::DestroyInstance +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::DestroyInstance() +{ + rAssert(spInstance); + + // TODO - Release() collision manager + + delete(GMA_PERSISTENT, spInstance); + spInstance = NULL; + + + +} + + + +//============================================================================= +// WorldPhysicsManager::SetSimUnits +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +/* +void WorldPhysicsManager::SetSimUnits() +{ + float LSCALE = 0.01f; + //float MSCALE = 0.001f; + float MSCALE = 1.0f; + float TSCALE = 1.0f; + SimUnitsManager um; + um.SetUnits(LSCALE, MSCALE, TSCALE); +} +*/ + +//============================================================================= +// WorldPhysicsManager::Update +//============================================================================= +// Description: Comment +// +// Parameters: (unsigned int timeDeltaSeconds) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::Update(unsigned int timeDeltaMilliSeconds) +{ + + + BEGIN_PROFILE("WorldPhysicsManager::Update") + + + BEGIN_PROFILE("Vehicle Submits") + GetVehicleCentral()->SubmitStatics(); + GetVehicleCentral()->SubmitAnimCollisions(); + GetVehicleCentral()->SubmitDynamics(); + END_PROFILE("Vehicle Submits") + + + BEGIN_PROFILE("Character Submits") + GetCharacterManager()->SubmitStatics(); + GetCharacterManager()->SubmitAnimCollisions(); // both collision and update I assume + GetCharacterManager()->SubmitDynamics(); + END_PROFILE("Character Submits") + + BEGIN_PROFILE("Cam Submits") + GetSuperCamManager()->SubmitStatics(); + END_PROFILE("Cam Submits") + + + BEGIN_PROFILE("Update Ground") + UpdateSimluatingDynamicObjectGroundPlanes(); + END_PROFILE("Update Ground") + + // new substep logic - nbrooke 12/7/03 + // + // we want to try to minimize it ticking over into two substep mode unless it absolutly neccesary + // and try to run a 30fps on the ps2 all the time therefore I have + // 1) upped the threshold for going to two substeps to 35 ms from 32ms + // 2) only go into multiple substeps if we get two consecutive frames over the threshold or + // one frame WAY over the threshold + unsigned numSubsteps = 1; + + if(((mLastTime > 35) && (timeDeltaMilliSeconds > 35)) || (timeDeltaMilliSeconds > 50)) + { + numSubsteps = (timeDeltaMilliSeconds + 34) / 35; + } + + mLastTime = timeDeltaMilliSeconds; + + float substep = (float(timeDeltaMilliSeconds) * 0.001f) / float(numSubsteps); + float dt = static_cast<float>(timeDeltaMilliSeconds) * 0.001f; + + // cap + if(numSubsteps > 10) + { + rReleasePrintf("\nhit 10 substeps!\n"); + numSubsteps = 10; + } + + + mLoopTime = dt; + + if(mTimerOn) + { + mTimerTime += dt; + } + + BEGIN_PROFILE("WorldPhysicsManager::PreSubstepUpdate") + + GetVehicleCentral()->PreSubstepUpdate(dt); + + END_PROFILE("WorldPhysicsManager::PreSubstepUpdate") + + BEGIN_PROFILE("PreSim") + GetCharacterManager( )->PreSimUpdate( dt ); + END_PROFILE("PreSim") + + bool firstSubstep = true; + //while(countDown > timestep)// * 1.5f) + for(unsigned i = 0; i < numSubsteps; i++) + { + // do shit + WorldSimSubstepGuts(substep, firstSubstep); + firstSubstep = false; + } + + BEGIN_PROFILE("VehiclePost") + GetVehicleCentral()->PostSubstepUpdate(dt); + END_PROFILE("VehiclePost") + + BEGIN_PROFILE("CharPost") + GetCharacterManager()->PostSimUpdate( dt ); + END_PROFILE("CharPost") + + END_PROFILE("WorldPhysicsManager::Update") + + BEGIN_PROFILE("Phys DebugInfo") + DebugInfoDisplay(); + END_PROFILE("Phys DebugInfo") + +} + + +//============================================================================= +// WorldPhysicsManager::DebugInfoDisplay +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::DebugInfoDisplay() +{ + // maybe "Display" is a bit deceiving. + + #ifdef DEBUGINFO_ENABLED + + DEBUGINFO_PUSH_SECTION( "Vehicle Shit" ); + + // just display the vehicles position + // whichever vehicle player is driving + + Avatar* playerAvatar = GetAvatarManager()->GetAvatarForPlayer(0); + + Vehicle* playerVehicle = 0; + + if(playerAvatar) + { + playerVehicle = playerAvatar->GetVehicle(); + + if(playerVehicle) + { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + rmt::Vector pos = playerVehicle->GetPosition(); + x = pos.x; + y = pos.y; + z = pos.z; + + + char buffy[128]; + sprintf( buffy, "position - x: %.2f y: %.2f z: %.2f", x, y, z); + + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + + float life = playerVehicle->GetVehicleLifePercentage(playerVehicle->mHitPoints); + sprintf( buffy, "damage inc. - %.3f", 1.0f - life); + + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + if(playerVehicle->mVehicleState == VS_NORMAL) + { + sprintf( buffy, "VS_NORMAL"); + } + else if(playerVehicle->mVehicleState == VS_EBRAKE_SLIP) + { + sprintf( buffy, "VS_EBRAKE_SLIP"); + } + else + { + sprintf( buffy, "VS_SLIP"); + } + + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + /* + if(playerVehicle->mLosingTractionDueToAccel) + { + sprintf(buffy, "LOSING TRACTION DUE TO ACCELERATION!!"); + } + else + { + sprintf(buffy, "unmodified traction"); + } + + + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + if(playerVehicle->mLoco == VL_PHYSICS) // I think this will always be the case + { + sprintf(buffy, "currentSteeringForce: %.2f", playerVehicle->mPhysicsLocomotion->mCurrentSteeringForce); + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + } + */ + + + sprintf(buffy, "wheel turn angle unmodified input: %.2f", playerVehicle->mUnmodifiedInputWheelTurnAngle); + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + + sprintf(buffy, "wheel turn angle normalized: %.2f", playerVehicle->mWheelTurnAngleInputValue); + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + + sprintf(buffy, "wheel turn angle: %.2f", playerVehicle->mWheelTurnAngle); + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + + + sprintf(buffy, "kmh: %.2f", playerVehicle->mSpeedKmh); + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + + sprintf(buffy, "ebrake: %.2f", playerVehicle->mEBrake); + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + + sprintf(buffy, "gas: %.2f", playerVehicle->mGas); + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + + + sprintf(buffy, "timer: %.3f", mTimerTime); + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + /* + + sprintf(buffy, "speed burst timer: %.3f", playerVehicle->mSpeedBurstTimer); + DEBUGINFO_ADDSCREENTEXT( buffy ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + if(playerVehicle->mDoSpeedBurst) + { + sprintf(buffy, "doing speed burst!"); + } + else + { + sprintf(buffy, "not doing speed burst"); + } + */ + + + } + + } + + + DEBUGINFO_POP_SECTION(); + + + + + if(playerVehicle) + { + char temp[128]; + DebugInfo::GetInstance()->Push( "Vehicle Terrain Type" ); + + + sprintf( temp, "average for vehicle: %d. %sside.", playerVehicle->mTerrainType, playerVehicle->mInterior ? "In" : "Out" ); + DebugInfo::GetInstance()->AddScreenText( temp, tColour( 25, 150, 125 ) ); + + sprintf( temp, "wheel 0: %d", playerVehicle->mPhysicsLocomotion->mTerrainIntersectCache[0].mTerrainType); + DebugInfo::GetInstance()->AddScreenText( temp, tColour( 25, 150, 125 ) ); + + sprintf( temp, "wheel 1: %d", playerVehicle->mPhysicsLocomotion->mTerrainIntersectCache[1].mTerrainType); + DebugInfo::GetInstance()->AddScreenText( temp, tColour( 25, 150, 125 ) ); + + sprintf( temp, "wheel 2: %d", playerVehicle->mPhysicsLocomotion->mTerrainIntersectCache[2].mTerrainType); + DebugInfo::GetInstance()->AddScreenText( temp, tColour( 25, 150, 125 ) ); + + sprintf( temp, "wheel 3: %d", playerVehicle->mPhysicsLocomotion->mTerrainIntersectCache[3].mTerrainType); + DebugInfo::GetInstance()->AddScreenText( temp, tColour( 25, 150, 125 ) ); + + + + DebugInfo::GetInstance()->Pop(); + + + /* + DebugInfo::GetInstance()->Push( "Player Vehicle Collision Info" ); + + int colindex = playerVehicle->mCollisionAreaIndex; + sprintf( temp, "number of collision pairs in player collision area: %d", this->mCollisionManager->GetCollisionObjectPairList(colindex)->ArraySize()); + + DEBUGINFO_ADDSCREENTEXT( temp ); + DEBUGINFO_ADDSCREENTEXT( "" ); //? + + DebugInfo::GetInstance()->Pop(); + */ + + + } + + + + #endif // DEBUGINFO_ENABLED + + +} + +//============================================================================= +// WorldPhysicsManager::WorldSimSubstepGuts +//============================================================================= +// Description: Comment +// +// Parameters: (float dt) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::WorldSimSubstepGuts(float dt, bool firstSubstep) +{ + BEGIN_PROFILE("WorldSimSubstepGuts") + + mTotalTime += dt; + updateFrame++; + + BEGIN_PROFILE("VC::PreCollisionPrep") + if(!mInInterior) + { + GetVehicleCentral()->PreCollisionPrep(dt, firstSubstep); + } + END_PROFILE("VC::PreCollisionPrep") + + BEGIN_PROFILE("CharMgr::PreSimUpdate") + GetCharacterManager()->PreSubstepUpdate( dt ); + END_PROFILE("CharMgr::PreSimUpdate") + + BEGIN_PROFILE("ABMgr::Update") + GetActionButtonManager( )->Update( dt ); + END_PROFILE("ABMgr::Update") + +// BEGIN_PROFILE("SuperCamMgr::PreCollPrep") +// GetSuperCamManager()->PreCollisionPrep(); +// END_PROFILE("SuperCamMgr::PreCollPrep") + + BEGIN_PROFILE("ResetCollisionFlags") + mpWorldCollisionSolverAgentManager->ResetCollisionFlags(); + END_PROFILE("ResetCollisionFlags") + + + +BEGIN_PROFILE("DetectCollisions") + bool printOut = false; + mCollisionManager->ClearCollisions(); + mCollisionManager->DetectCollision(dt, mTotalTime, printOut ); + mCollisionManager->SolveCollision(dt, mTotalTime); + +END_PROFILE("DetectCollisions") + + + BEGIN_PROFILE("GetVehicleCentral()->Update") + GetVehicleCentral()->Update(dt); + END_PROFILE("GetVehicleCentral()->Update") + BEGIN_PROFILE("GetCharacterManager()->Update") + GetCharacterManager()->Update( dt ); + END_PROFILE("GetCharacterManager()->Update") + BEGIN_PROFILE("GetCharacterManager()->PostSimUpdate") + GetCharacterManager()->PostSubstepUpdate(dt); + END_PROFILE("GetCharacterManager()->PostSimUpdate") + + //Update the position of the super camers + BEGIN_PROFILE("GetSuperCamManager()->Update") + GetSuperCamManager()->Update( rmt::FtoL(dt * 1000.0f), firstSubstep ); + END_PROFILE("GetSuperCamManager()->Update") + + //Detect collisions on the new position of the super camera + int area = GetSuperCamManager()->GetSCC( 0 )->mCollisionAreaIndex; + mCollisionManager->ClearCollisions(); + BEGIN_PROFILE("SuperCamMgr::PreCollPrep") + GetSuperCamManager()->PreCollisionPrep(); + END_PROFILE("SuperCamMgr::PreCollPrep") + + mCollisionManager->DetectCollision( area, dt, mTotalTime, printOut ); + mCollisionManager->SolveCollision( area, dt, mTotalTime ); + + //Correct the position of the super camera + GetSuperCamManager()->GetSCC( 0 )->UpdateForPhysics( rmt::FtoL(dt * 1000.0f) ); + + END_PROFILE("WorldSimSubstepGuts") +} + + + +//============================================================================= +// WorldPhysicsManager::DisplayCollisionObjectsExceptVehicleInArea +//============================================================================= +// Description: Comment +// +// Parameters: (int area) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::DisplayFencesInArea(int area) +{ + rAssert( area > WorldPhysicsManager::INVALID_COLLISION_AREA ); + + int i; + for(i = 0; i < mMaxFencePerArea; i++) + { + if(mFences[area][i].mInCollision) + { + sim::ManualSimState* mss = mFences[area][i].mFenceSimState; + sim::DrawCollisionObject(mss->GetCollisionObject()); + } + + } + for(i = 0; i < mNumDebugFences; i++) + { + // debug draw it! + mFenceDSGResults[i]->Display(); + } + +} + +//============================================================================= +// WorldPhysicsManager::SubmitStaticsPseudoCallback +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector& position, float radius, int collisionAreaIndex) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::SubmitStaticsPseudoCallback(rmt::Vector& position, float radius, int collisionAreaIndex, sim::SimState* callerSimState, bool allowAutoPairing) +{ + rAssert( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ); + + BEGIN_PROFILE( "SubmitStaticsPseudoCallback" ); + + ReserveArray<StaticPhysDSG*> staticPhysDSGList; + + int i; + for(i = 0; i < mMaxStatics; i++) + { + mCurrentStatics[collisionAreaIndex][i].clean = false; // probably slower to check if there's + // even something pointed to than just + // to do this + } + + BEGIN_PROFILE( "FindStaticPhysElems" ); + GetIntersectManager()->FindStaticPhysElems(position, radius, staticPhysDSGList); + END_PROFILE( "FindStaticPhysElems" ); + + int numResults = staticPhysDSGList.mUseSize; + + // !!!! + // temp + // + // TODO + // hmmm... what to do here? + // pick different max or try and prioritize further? + // + // also TODO - need different maxes for different lists + + if(numResults > mMaxStatics) + { + numResults = mMaxStatics; + rDebugPrintf("\n!!! too many statics !!!\n"); + } + + + for(i = 0; i < numResults; i++) + { + // go through results and submit to collision. + + // first see if already in our list + StaticPhysDSG* curr = staticPhysDSGList[i]; + + int j; + bool alreadyIn = false; + for(j = 0; j < mMaxStatics; j++) + { + if(mCurrentStatics[collisionAreaIndex][j].mStaticPhysDSG == curr) + { + // this one already in list + mCurrentStatics[collisionAreaIndex][j].clean = true; + alreadyIn = true; + break; + + } + } + + if(!alreadyIn) + { + // add to our list and to collision manager + // + // look for first available slot + int k; + for(k = 0; k < mMaxStatics; k++) + { + if(mCurrentStatics[collisionAreaIndex][k].mStaticPhysDSG == 0) + { + // here is a slot + mCurrentStatics[collisionAreaIndex][k].mStaticPhysDSG = curr; + mCurrentStatics[collisionAreaIndex][k].mStaticPhysDSG->AddRef(); + mCurrentStatics[collisionAreaIndex][k].clean = true; + + sim::CollisionObject* currCollObj = curr->mpSimState()->GetCollisionObject(); + + + BEGIN_PROFILE( "AddCollisionObject" ); + + if(1)//allowAutoPairing) + { + currCollObj->SetAutoPair(true); + } + else + { + currCollObj->SetAutoPair(false); + } + + mCollisionManager->AddCollisionObject(currCollObj, collisionAreaIndex); + + //if(!allowAutoPairing) + { + mCollisionManager->AddPair(currCollObj, callerSimState->GetCollisionObject(), collisionAreaIndex); + } + + + + + END_PROFILE( "AddCollisionObject" ); + break; + } + } + + rAssert(k < mMaxStatics); + // TODO - deal with no available slots.... + } + } + + // now look through remaining list and remove unclean + for(i = 0; i < mMaxStatics; i++) + { + if(mCurrentStatics[collisionAreaIndex][i].mStaticPhysDSG != 0 && + mCurrentStatics[collisionAreaIndex][i].clean == false) + { + BEGIN_PROFILE( "RemoveCollisionObject" ); + mCollisionManager->RemoveCollisionObject(mCurrentStatics[collisionAreaIndex][i].mStaticPhysDSG->mpSimState()->GetCollisionObject(), collisionAreaIndex); + END_PROFILE( "RemoveCollisionObject" ); + mCurrentStatics[collisionAreaIndex][i].mStaticPhysDSG->Release(); + mCurrentStatics[collisionAreaIndex][i].mStaticPhysDSG = 0; + mCurrentStatics[collisionAreaIndex][i].clean = false; // this is redundant + } + } + END_PROFILE( "SubmitStaticsPseudoCallback" ); +} + + +//============================================================================= +// WorldPhysicsManager::SubmitAnimCollisionsPseudoCallback +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector& position, float radius, int collisionAreaIndex) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::SubmitAnimCollisionsPseudoCallback(rmt::Vector& position, float radius, int collisionAreaIndex, sim::SimState* callerSimState) +{ + rAssert( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ); + + ReserveArray<AnimCollisionEntityDSG*> animCollisionEntityDSGList; + + int i; + for(i = 0; i < mMaxAnimCollisions; i++) + { + mCurrentAnimCollisions[collisionAreaIndex][i].clean = false; + } + + GetIntersectManager()->FindAnimPhysElems(position, radius, animCollisionEntityDSGList); + + int numResults = animCollisionEntityDSGList.mUseSize; + + if(numResults > mMaxAnimCollisions) + { + numResults = mMaxAnimCollisions; + rDebugPrintf("\n!!! too many animating collision objects - goddamn !!!\n"); + } + + + for(i = 0; i < numResults; i++) + { + // go through results and submit to collision. + + // first see if already in our list + AnimCollisionEntityDSG* curr = animCollisionEntityDSGList[i]; + + int j; + bool alreadyIn = false; + for(j = 0; j < mMaxAnimCollisions; j++) + { + if(mCurrentAnimCollisions[collisionAreaIndex][j].mAnimCollisionEntityDSG == curr) + { + // this one already in list + mCurrentAnimCollisions[collisionAreaIndex][j].clean = true; + alreadyIn = true; + break; + + } + } + + if(!alreadyIn) + { + // add to our list and to collision manager + // + // look for first available slot + int k; + for(k = 0; k < mMaxAnimCollisions; k++) + { + if(mCurrentAnimCollisions[collisionAreaIndex][k].mAnimCollisionEntityDSG == 0) + { + // here is a slot + mCurrentAnimCollisions[collisionAreaIndex][k].mAnimCollisionEntityDSG = curr; + mCurrentAnimCollisions[collisionAreaIndex][k].mAnimCollisionEntityDSG->AddRef( ); + mCurrentAnimCollisions[collisionAreaIndex][k].clean = true; + + sim::CollisionObject* currCollObj = curr->GetSimState()->GetCollisionObject(); + + currCollObj->SetAutoPair(false); + mCollisionManager->AddCollisionObject(currCollObj, collisionAreaIndex); + mCollisionManager->AddPair(currCollObj, callerSimState->GetCollisionObject(), collisionAreaIndex); + + break; + } + } + + rAssert(k < mMaxAnimCollisions); + // TODO - deal with no available slots.... + } + } + + // now look through remaining list and remove unclean + for(i = 0; i < mMaxAnimCollisions; i++) + { + if(mCurrentAnimCollisions[collisionAreaIndex][i].mAnimCollisionEntityDSG != 0 && + mCurrentAnimCollisions[collisionAreaIndex][i].clean == false) + { + mCollisionManager->RemoveCollisionObject(mCurrentAnimCollisions[collisionAreaIndex][i].mAnimCollisionEntityDSG->GetSimState()->GetCollisionObject(), collisionAreaIndex); + mCurrentAnimCollisions[collisionAreaIndex][i].mAnimCollisionEntityDSG->Release( ); + mCurrentAnimCollisions[collisionAreaIndex][i].mAnimCollisionEntityDSG = 0; + mCurrentAnimCollisions[collisionAreaIndex][i].clean = false; // this is redundant + } + } + +} + + +//============================================================================= +// WorldPhysicsManager::SubmitAnimCollisionsForUpdateOnly +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector& position, float radius, int collisionAreaIndex) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::SubmitAnimCollisionsForUpdateOnly(rmt::Vector& position, float radius, int collisionAreaIndex) +{ + rAssert( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ); + + ReserveArray<AnimCollisionEntityDSG*> animCollisionEntityDSGList; + + int i; + for(i = 0; i < mMaxUpdateAnimCollisions; i++) + { + mCurrentUpdateAnimCollisions[collisionAreaIndex][i].clean = false; + } + + GetIntersectManager()->FindAnimPhysElems(position, radius, animCollisionEntityDSGList); + + int numResults = animCollisionEntityDSGList.mUseSize; + + if(numResults > mMaxUpdateAnimCollisions) + { + numResults = mMaxUpdateAnimCollisions; + rDebugPrintf("\n!!! too many animating collision objects - goddamn !!!\n"); + } + + + for(i = 0; i < numResults; i++) + { + // go through results and submit to collision. + + // first see if already in our list + AnimCollisionEntityDSG* curr = animCollisionEntityDSGList[i]; + + int j; + bool alreadyIn = false; + for(j = 0; j < mMaxUpdateAnimCollisions; j++) + { + if(mCurrentUpdateAnimCollisions[collisionAreaIndex][j].mAnimCollisionEntityDSG == curr) + { + // this one already in list + mCurrentUpdateAnimCollisions[collisionAreaIndex][j].clean = true; + alreadyIn = true; + break; + + } + } + + if(!alreadyIn) + { + // add to our list and to collision manager + // + // look for first available slot + int k; + for(k = 0; k < mMaxUpdateAnimCollisions; k++) + { + if(mCurrentUpdateAnimCollisions[collisionAreaIndex][k].mAnimCollisionEntityDSG == 0) + { + // here is a slot + mCurrentUpdateAnimCollisions[collisionAreaIndex][k].mAnimCollisionEntityDSG = curr; + // Hang on to this object. + // + mCurrentUpdateAnimCollisions[collisionAreaIndex][k].mAnimCollisionEntityDSG->AddRef( ); + mCurrentUpdateAnimCollisions[collisionAreaIndex][k].clean = true; + + break; + } + } + + rAssert(k < mMaxUpdateAnimCollisions); + // TODO - deal with no available slots.... + } + } + + // now look through remaining list and remove unclean + for(i = 0; i < mMaxUpdateAnimCollisions; i++) + { + if(mCurrentUpdateAnimCollisions[collisionAreaIndex][i].mAnimCollisionEntityDSG != 0 && + mCurrentUpdateAnimCollisions[collisionAreaIndex][i].clean == false) + { + // Let this thing go. + // + mCurrentUpdateAnimCollisions[collisionAreaIndex][i].mAnimCollisionEntityDSG->Release( ); + mCurrentUpdateAnimCollisions[collisionAreaIndex][i].mAnimCollisionEntityDSG = 0; + mCurrentUpdateAnimCollisions[collisionAreaIndex][i].clean = false; // this is redundant + } + } +} + +//============================================================================= +// WorldPhysicsManager::SubmitDynamicsPseudoCallback +//============================================================================= +// Description: Comment +// +// Parameters: (rmt::Vector& position, float radius, int collisionAreaIndex) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::SubmitDynamicsPseudoCallback(rmt::Vector& position, float radius, int collisionAreaIndex, sim::SimState* callerSimState, bool allowAutoPairing) +{ + rAssert( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ); + + // hmmm... + // safe to make this an InstDynaPhysDSG list?? - doesn't compile + ReserveArray<DynaPhysDSG*> dynamicsPhysDSGList; + + int i; + for(i = 0; i < mMaxDynamics; i++) + { + mCurrentDynamics[collisionAreaIndex][i].clean = false; // probably slower to check if there's + // even something pointed to than just + // to do this + } + + // this is just commented out until it exists + GetIntersectManager()->FindDynaPhysElems(position, radius, dynamicsPhysDSGList); + + int numResults = dynamicsPhysDSGList.mUseSize; + + if(numResults > 1) + { + int stophere = 1; + } + + + // TODO + // hmmm... what to do here? + if(numResults > mMaxDynamics) + { + numResults = mMaxDynamics; + rDebugPrintf("\n!!! too many dynamics returned from query !!!\n"); + } + + for(i = 0; i < numResults; i++) + { + // go through results and submit to collision. + + // first see if already in our list + DynaPhysDSG* curr = dynamicsPhysDSGList[i]; + + // temp + // + // this actually might be the way to do it? + //if(curr == caller) + if((curr->mpSimState() == callerSimState) || (curr->mpSimState() == NULL)) + { + continue; + } + + // new test + // just for Cary + if(callerSimState->mAIRefIndex == PhysicsAIRef::CameraSphere) + { + if( curr->mpSimState()->mAIRefIndex != PhysicsAIRef::redBrickVehicle && + curr->mpSimState()->mAIRefIndex != PhysicsAIRef::NPCharacter && + curr->mpSimState()->mAIRefIndex != PhysicsAIRef::PlayerCharacter ) + { + continue; + } + + } + + + // not optimization further below + // + // only gonna pair "submitters" with ourselves + // + // this logic here is to see if the current "submitter" is trying to pair itself against another "submitter" vehicle we already called earlier, that + // already set up this pair + if(callerSimState->mAIRefIndex == PhysicsAIRef::redBrickVehicle && curr->mpSimState()->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + + // April 22, 2003 + // problem when cars that are calling this are very close to each other + // + // many duplicate pairs put in more than one collision area + // + // the fastest, most optimal check we can do to minimize this is: + // (first re-order the vehicle calls to this to make sure VT_USER called first) + // then, if we are a car, and return a car, and either it's a VT_USER or it's and AI with + // a lower vehicle central index than us, then skipt it - they've already submitted us, and autopaird all round! + Vehicle* submittingVehicle = (Vehicle*)(callerSimState->mAIRefPointer); + Vehicle* submittedVehicle = (Vehicle*)(curr->mpSimState()->mAIRefPointer); + + rTuneAssert(submittingVehicle->mVehicleCentralIndex != -1); + rTuneAssert(submittedVehicle->mVehicleCentralIndex != -1); + + if(submittingVehicle->mVehicleType == VT_USER) + { + if(submittedVehicle->mVehicleType == VT_USER) + { + if(submittingVehicle->mVehicleCentralIndex > submittedVehicle->mVehicleCentralIndex) + { + // both user vehicles, but... + // the submitted vehicle has been called first, so it wins + continue; + } + } + } + else + { + // not a user vehicle + if(submittedVehicle->mVehicleType == VT_USER) + { + // it has already added us + continue; + } + else if(submittingVehicle->mVehicleType == VT_AI) + { + if(submittingVehicle->mVehicleCentralIndex > submittedVehicle->mVehicleCentralIndex) + { + // the submitted vehicle has been called first, so it wins + continue; + } + } + } + + } + + + int j; + bool alreadyIn = false; + for(j = 0; j < mMaxDynamics; j++) + { + if(mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG == curr) + { + // this one already in list + mCurrentDynamics[collisionAreaIndex][j].clean = true; + alreadyIn = true; + break; + + } + } + + if(!alreadyIn) + { + // add to our list and to collision manager + // + // look for first available slot + int k; + for(k = 0; k < mMaxDynamics; k++) + { + if(mCurrentDynamics[collisionAreaIndex][k].mDynamicPhysDSG == 0) + { + // here is a slot + mCurrentDynamics[collisionAreaIndex][k].mDynamicPhysDSG = curr; + mCurrentDynamics[collisionAreaIndex][k].mDynamicPhysDSG->AddRef( ); + mCurrentDynamics[collisionAreaIndex][k].clean = true; + + sim::CollisionObject* currCollObj = curr->mpSimState()->GetCollisionObject(); + + // final, simplest possible optimization + // + // don't autopair dynamics period + // any dynamic we submit only pairs with us! + // + // who knows what else this thing will do in it's own area (if it has one), so just in case turn + // turn flag back on after + + + // only pair with us + // + + if(allowAutoPairing && curr->mpSimState()->mAIRefIndex != PhysicsAIRef::redBrickVehicle) + { + currCollObj->SetAutoPair(true); + } + else + { + currCollObj->SetAutoPair(false); + } + + mCollisionManager->AddCollisionObject(currCollObj, collisionAreaIndex); + + //if(!allowAutoPairing || curr->mpSimState()->mAIRefIndex == PhysicsAIRef::redBrickVehicle) + { + mCollisionManager->AddPair(currCollObj, callerSimState->GetCollisionObject(), collisionAreaIndex); + } + + + + /* + // new?? + // is this gonna work, and work well? + + //!! + // only do this for the player character! + + + int l; + for(l = 0; l < this->mMaxStatics; l++) + { + if(mCurrentStatics[collisionAreaIndex][l].mStaticPhysDSG) + { + // there's one there... + // make sure we are paired with it + mCollisionManager->AddPair(currCollObj, + mCurrentStatics[collisionAreaIndex][l].mStaticPhysDSG->mpSimState()->GetCollisionObject(), + collisionAreaIndex); + } + } + for(l = 0; l < this->mMaxFencePerArea; l++) + { + if(mFences[collisionAreaIndex][l].mInCollision) + { + mCollisionManager->AddPair(currCollObj, + mFences[collisionAreaIndex][l].mFenceSimState->GetCollisionObject(), + collisionAreaIndex); + + } + + } + */ + + + // all vehicles and player character have their own groundplanes already + if( curr->GetAIRef() != PhysicsAIRef::redBrickVehicle && curr->GetAIRef() != PhysicsAIRef::PlayerCharacter ) + { + int groundPlaneIndex = mCurrentDynamics[collisionAreaIndex][k].mDynamicPhysDSG->FetchGroundPlane(); + + if(groundPlaneIndex != -1) // just in case, though this should never be -1 + { + sim::CollisionObject* gpCollObj = mGroundPlanePool->GetSimState(groundPlaneIndex)->GetCollisionObject(); + gpCollObj->SetRayCastingEnabled( false ); + + + + // recall, collision is disabled on the ground plane until it is set on by the object using it + mCollisionManager->AddCollisionObject( gpCollObj, collisionAreaIndex ); + + mCollisionManager->AddPair(currCollObj, gpCollObj, collisionAreaIndex); + } + + } + + + break; + } + } + + //rAssert(k < mMaxDynamics); + // TODO - deal with no available slots.... + } + } + + // now look through remaining list and remove unclean + // + // at this stage, we can only remove the ones that are unclean and at rest. + + PurgeDynamics( collisionAreaIndex ); + +} + + +//============================================================================= +// static bool IsBreakable +//============================================================================= +// Description: Comment +// +// Parameters: ( DynaPhysDSG* pObject ) +// +// Return: static +// +//============================================================================= +static bool IsBreakable( DynaPhysDSG* pObject ) +{ + bool isBreakable; + CollisionAttributes* attr = pObject->GetCollisionAttributes(); + if ( pObject->GetAIRef() == PhysicsAIRef::StateProp || + pObject->GetAIRef() == PhysicsAIRef::ActorStateProp ) + { + // *some* stateprops can break, but not ones flagged as PROP_ONE_TIME_MOVEABLE + if ( pObject->GetCollisionAttributes()->GetClasstypeid() == PROP_ONETIME_MOVEABLE ) + isBreakable = false; + else + isBreakable = true; + } + else if ( attr != NULL ) + { + if ( attr->GetClasstypeid() == PROP_BREAKABLE ) + { + isBreakable = true; + } + else + { + isBreakable = false; + } + } + else + { + isBreakable = false; + } + + return isBreakable; +} + + +//============================================================================= +// WorldPhysicsManager::PurgeDynamics +//============================================================================= +// Description: Comment +// +// Parameters: ( int collisionAreaIndex ) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::PurgeDynamics( int collisionAreaIndex ) +{ + rAssert( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ); + + for(int i = 0; i < mMaxDynamics; i++) + { + // TODO ! + // test for vehicles must be different; + // they use they're own ground plane! + + if( mCurrentDynamics[collisionAreaIndex][i].mDynamicPhysDSG != 0 && + mCurrentDynamics[collisionAreaIndex][i].clean == false ) + { + DynaPhysDSG* pObject = mCurrentDynamics[collisionAreaIndex][i].mDynamicPhysDSG; + + // need to keep updating when "at rest" if it's a onetime movable that has been hit + // so that it can be removed when it goes out of view + bool atRest = pObject->IsAtRest(); + + if(atRest && pObject->GetCollisionAttributes()) + { + atRest = atRest && !((pObject->GetCollisionAttributes()->GetClasstypeid() == PROP_ONETIME_MOVEABLE) + && pObject->mIsHit); + } + + if ( atRest || IsBreakable( pObject ) ) + { + mCollisionManager->RemoveCollisionObject(pObject->mpSimState()->GetCollisionObject(), collisionAreaIndex); + + // don't do fuck if we are a car - temp TODO!! + // this test doesn't seem to work + if(pObject->GetAIRef() != PhysicsAIRef::redBrickVehicle) + { + int groundPlaneIndex = pObject->GetGroundPlaneIndex(); + if(groundPlaneIndex != -1) // should never be false... + { + sim::CollisionObject* gpCollObj = mGroundPlanePool->GetSimState(groundPlaneIndex)->GetCollisionObject(); + + mCollisionManager->RemoveCollisionObject(gpCollObj, collisionAreaIndex); + + pObject->FreeGroundPlane(); // this decrements the groundplaneref, but we still need to take it out of + // there are no more refs + } + } + pObject->Release( ); + mCurrentDynamics[collisionAreaIndex][i].mDynamicPhysDSG = 0; + mCurrentDynamics[collisionAreaIndex][i].clean = false; // this is redundant + } + } + } +} + + +//============================================================================= +// WorldPhysicsManager::UpdateSimluatingDynamicObjectGroundPlanes +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::UpdateSimluatingDynamicObjectGroundPlanes() +{ + int i; + for(i = 0; i < mNumCollisionAreas; i++) + { + if(!mCollisionAreaAllocated[i]) + { + continue; + } + + + int j; + for(j = 0; j < mMaxDynamics; j++) + { + if( mCurrentDynamics[i][j].mDynamicPhysDSG && + mCurrentDynamics[i][j].mDynamicPhysDSG->GetSimState()->GetControl() == sim::simSimulationCtrl && + mCurrentDynamics[i][j].mDynamicPhysDSG->GetSimState()->mAIRefIndex != PhysicsAIRef::redBrickVehicle && + mCurrentDynamics[i][j].mDynamicPhysDSG->GetSimState()->mAIRefIndex != PhysicsAIRef::PlayerCharacter ) // TODO - vehicles use also? + + // TODO - rest test??? or is that covered under sim control? + + { + // need to update this ground plane + // TODO + // is the position of the DSG object being updated correctly??????? + // + // no implementatino of that for dynamics yet! + + rmt::Vector inPosition = mCurrentDynamics[i][j].mDynamicPhysDSG->rPosition(); + float inRadius = 1.0f; + + // ignore the tri values + rmt::Vector triNormal; + rmt::Vector triPosition; + + bool foundPlane; + rmt::Vector planeNormal; + rmt::Vector planePosition; + + + GetIntersectManager()->FindIntersection(inPosition, + foundPlane, + planeNormal, + planePosition); + + + if(foundPlane) + { + mGroundPlanePool->UpdateGroundPlane(mCurrentDynamics[i][j].mDynamicPhysDSG->GetGroundPlaneIndex(), planePosition, planeNormal); + } + else + { + //rAssert(0); + int stophere = 1; // hopefully not. + } + + } + + } + } + + +} + + +//============================================================================= +// WorldPhysicsManager::GetNewGroundPlane +//============================================================================= +// Description: Comment +// +// Parameters: () +// +// Return: int +// +//============================================================================= +int WorldPhysicsManager::GetNewGroundPlane(sim::SimState* simStateOwner) +{ + return mGroundPlanePool->GetNewGroundPlane(simStateOwner); +} + + +//============================================================================= +// WorldPhysicsManager::FreeGroundPlane +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::FreeGroundPlane(int index) +{ + mGroundPlanePool->FreeGroundPlane(index); +} + + +//============================================================================= +// WorldPhysicsManager::EnableGroundPlaneCollision +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::EnableGroundPlaneCollision(int index) +{ + //char buffy[128]; + //sprintf(buffy, "enabling ground plane index %d\n", index); + + //rReleaseString(buffy); + + mGroundPlanePool->EnableCollision(index); +} + + +//============================================================================= +// WorldPhysicsManager::DisableGroundPlaneCollision +//============================================================================= +// Description: Comment +// +// Parameters: (int index) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::DisableGroundPlaneCollision(int index) +{ + //char buffy[128]; + //sprintf(buffy, "disabling ground plane index %d\n", index); + + //rReleaseString(buffy); + + mGroundPlanePool->DisableCollision(index); +} + +//============================================================================= +// WorldPhysicsManager::UpdateDynamicObjects +//============================================================================= +// Description: Comment +// +// Parameters: (float dt, int collisionAreaIndex) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::UpdateDynamicObjects(float dt, int collisionAreaIndex) +{ + rAssert( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ); + rAssert( mCollisionAreaAllocated[collisionAreaIndex] ); + + int j; + for(j = 0; j < mMaxDynamics; j++) + { + if(mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG) + { + mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG->RestTest(); + + bool atRest = mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG->IsAtRest(); + + // need to keep updating when "at rest" if it's a onetime movable that has been hit + if(atRest && mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG->GetCollisionAttributes()) + { + atRest = atRest && !((mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG->GetCollisionAttributes()->GetClasstypeid() == PROP_ONETIME_MOVEABLE) + && mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG->mIsHit); + } + + // don't update vehicles or characters (they are updated elsewhere), + // stuff at rest, or stuff that has already been updated. + if((mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG->GetSimState()->mAIRefIndex != PhysicsAIRef::redBrickVehicle) && + (mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG->GetSimState()->mAIRefIndex != PhysicsAIRef::PlayerCharacter ) && + (!atRest) && + (mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG->GetLastUpdate() != updateFrame)) + { + // set the update tick so we wont update this again + mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG->SetLastUpdate(updateFrame); + mCurrentDynamics[collisionAreaIndex][j].mDynamicPhysDSG->Update(dt); + } + } + } + +} + +//============================================================================= +// WorldPhysicsManager::UpdateAnimCollisions +//============================================================================= +// Description: Comment +// +// Parameters: (float dt, int collisionAreaIndex) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::UpdateAnimCollisions(float dt, int collisionAreaIndex) +{ + rAssert( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ); + rAssert(mCollisionAreaAllocated[collisionAreaIndex]); + + int j; + for(j = 0; j < mMaxUpdateAnimCollisions; j++) + { + if(mCurrentUpdateAnimCollisions[collisionAreaIndex][j].mAnimCollisionEntityDSG) + { + if(mCurrentUpdateAnimCollisions[collisionAreaIndex][j].mAnimCollisionEntityDSG->GetLastUpdate() != updateFrame) + { + // set the update tick so we wont update this again + mCurrentUpdateAnimCollisions[collisionAreaIndex][j].mAnimCollisionEntityDSG->SetLastUpdate(updateFrame); + mCurrentUpdateAnimCollisions[collisionAreaIndex][j].mAnimCollisionEntityDSG->Update(dt); + } + } + + } +} + + +//============================================================================= +// WorldPhysicsManager::SubmitFencePiecesPseudoCallback +//============================================================================= +// Description: Comment +// +// Parameters: (position, radius, collisionAreaIndex) +// +// Return: void +// +//============================================================================= +void WorldPhysicsManager::SubmitFencePiecesPseudoCallback(rmt::Vector& position, float radius, int collisionAreaIndex, sim::SimState* callerSimState, bool allowAutoPairing) +{ + rAssert( collisionAreaIndex > WorldPhysicsManager::INVALID_COLLISION_AREA ); + + ReserveArray<FenceEntityDSG*> fenceDSGList; + + BEGIN_PROFILE( "FindFenceElems" ); + GetIntersectManager()->FindFenceElems(position, radius, fenceDSGList); + END_PROFILE( "FindFenceElems" ); + + int numResults = fenceDSGList.mUseSize; + + //int numResults = 1; + if(numResults > 0) + { + int stophere = 1; + } + + if(numResults > mMaxFencePerArea) + { + numResults = mMaxFencePerArea; + } + + // fill in debug rendering list: + int i; + + for(i = 0; i < numResults; i++) + { + mFenceDSGResults[i]->mStartPoint = fenceDSGList[i]->mStartPoint; + mFenceDSGResults[i]->mEndPoint = fenceDSGList[i]->mEndPoint; + mFenceDSGResults[i]->mNormal = fenceDSGList[i]->mNormal; + + mFenceDSGResults[i]->mStartPoint.y = position.y; + mFenceDSGResults[i]->mEndPoint.y = position.y; + + + } + mNumDebugFences = numResults; + + // reset list + for(i = 0; i < mMaxFencePerArea; i++) + { + mFences[collisionAreaIndex][i].mClean = false; + } + + for(i = 0; i < numResults; i++) + { + + // i is candidate for submission + + // just in case, do this in function, not here + //fenceDSGList[i]->mStartPoint.y = position.y; + //fenceDSGList[i]->mEndPoint.y = position.y; + + mFences[collisionAreaIndex][i].start = fenceDSGList[i]->mStartPoint; + mFences[collisionAreaIndex][i].end = fenceDSGList[i]->mEndPoint; + + if(UpdateFencePiece(position, mFences[collisionAreaIndex][i].mFenceSimState, + fenceDSGList[i]->mStartPoint, fenceDSGList[i]->mEndPoint, + fenceDSGList[i]->mNormal, + callerSimState)) + { + // ok for this one to be in collision + // + + if(mFences[collisionAreaIndex][i].mInCollision == false) + { + // we need to add this into the collision manager + if(allowAutoPairing) + { + mFences[collisionAreaIndex][i].mFenceSimState->GetCollisionObject()->SetAutoPair(true); + } + else + { + mFences[collisionAreaIndex][i].mFenceSimState->GetCollisionObject()->SetAutoPair(false); + } + + mCollisionManager->AddCollisionObject(mFences[collisionAreaIndex][i].mFenceSimState->GetCollisionObject(), collisionAreaIndex); + mCollisionManager->AddPair(callerSimState->GetCollisionObject(), mFences[collisionAreaIndex][i].mFenceSimState->GetCollisionObject(), collisionAreaIndex); + mFences[collisionAreaIndex][i].mInCollision = true; + + } + mFences[collisionAreaIndex][i].mClean = true; + + } + } + + // update list + for(i = 0; i < mMaxFencePerArea; i++) + { + if(mFences[collisionAreaIndex][i].mInCollision == true && + mFences[collisionAreaIndex][i].mClean == false) + { + mCollisionManager->RemoveCollisionObject(mFences[collisionAreaIndex][i].mFenceSimState->GetCollisionObject(), collisionAreaIndex); + mFences[collisionAreaIndex][i].mInCollision = false; + } + + + } + +} + + +//============================================================================= +// WorldPhysicsManager::UpdateFencePiece +//============================================================================= +// Description: Comment +// +// Parameters: (SimState* fencePiece, rmt::Vector* end0, rmt::Vector* end1, rmt::Vector* normal) +// +// Return: void +// +//============================================================================= +bool WorldPhysicsManager::UpdateFencePiece(rmt::Vector& callerPosition, sim::ManualSimState* fencePiece, rmt::Vector& end0, + rmt::Vector& end1, rmt::Vector& normal, sim::SimState* callerSimState) +{ + + // on the collision volume + // set position, + // mAxis + // mLength + rmt::Vector o0, o1, o2; + float l0, l1, l2; + + // pain in the ass - just get it to fucking work + rmt::Vector localEnd0 = end0; + rmt::Vector localEnd1 = end1; + + localEnd0.y = callerPosition.y; + localEnd1.y = callerPosition.y; + + o1.Set(0.0f, 1.0f, 0.0f); // y is always straight up + + o2 = normal; // safe to assume this is normalized? + + o0.CrossProduct(o1, o2); + + //l1 = 10000.0f; // magic big ass number + l1 = 10.0f; // use a smaller number for now so the debug drawn volumes are easier to see + + rmt::Vector segment; + segment.Sub(localEnd1, localEnd0); + + float segmentLength = segment.Magnitude(); + + l0 = segmentLength * 0.5f; + + + l2 = 10.0f; // ? whatever? TODO - is this value ok? + + if(callerSimState->mAIRefIndex == PhysicsAIRef::CameraSphere) + { + // Cary! + // change this number! + + // this is half the length of the fence piece obbox + + l2 = 2.0f; + //l2 = 10.0f; + } + + rmt::Vector midpoint; + midpoint.Add(localEnd0, localEnd1); + midpoint.Scale(0.5f); + + + // before doing anything else, test if we are pointed at the fucker + // TODO - maybe this test should be moved up to the submit part? + // maybe not + rmt::Vector test; + test.Sub(callerPosition, midpoint); + + if(test.DotProduct(normal) < 0.0f && callerSimState->mAIRefIndex != PhysicsAIRef::CameraSphere) + { + + fencePiece->GetCollisionObject()->SetCollisionEnabled(false); + return false; + } + + rmt::Vector position = midpoint; + rmt::Vector centerAdjust = o2; + centerAdjust.Scale(l2); + position.Sub(centerAdjust); + + + // for convenience: + sim::CollisionObject* co = fencePiece->GetCollisionObject(); + sim::OBBoxVolume* obbox = (OBBoxVolume*)(co->GetCollisionVolume()); + + obbox->Set(position, o0, o1, o2, l0, l1, l2); + + co->PostManualUpdate(); + + //co->Relocated(); + //obbox->UpdateBBox(); + + // only need to do this once + co->SetCollisionEnabled(true); + + + // on the Object call Relocated + // + //virtual void UpdateBBox() {} + // make sure mUpdated is true + // what about dP?? + // + // what about OptimizeAxis + // what about SetRotation? + + // GetCollisionObject()->SetCollisionEnabled(true); + + + //#ifdef RAD_DEBUG + + //sim::DrawCollisionObject(CollisionObject* inObject); + //sim::DrawCollisionVolume(obbox); + + //#endif + + return true; + + +} + +//---------------------------------------------------------------------------- +// Segment intersection, stolen, as usual, from www.magic-software.com +//---------------------------------------------------------------------------- + +// even though we're using 3d vectors (cause that's what we have) this is only a 2d intersection +// in x & z +static bool Find (const rmt::Vector& rkP0, const rmt::Vector& rkD0, + const rmt::Vector& rkP1, const rmt::Vector& rkD1, rmt::Vector& rkDiff, + float& rfD0SqrLen, int& riQuantity, float afT[2]) +{ + // Intersection is a solution to P0+s*D0 = P1+t*D1. Rewrite as + // s*D0 - t*D1 = P1 - P0, a 2x2 system of equations. If D0 = (x0,y0) + // and D1 = (x1,y1) and P1 - P0 = (c0,c1), then the system is + // x0*s - x1*t = c0 and y0*s - y1*t = c1. The error tests are relative + // to the size of the direction vectors, |Cross(D0,D1)| >= e*|D0|*|D1| + // rather than absolute tests |Cross(D0,D1)| >= e. The quantities + // P1-P0, |D0|^2, and |D1|^2 are returned for use by calling functions. + + float fDet = rkD1.x*rkD0.z - rkD1.z*rkD0.x; + rkDiff = rkP1 - rkP0; + rfD0SqrLen = rkD0.MagnitudeSqr(); + + const float fEpsilon = 1e-06f; + if ( fDet*fDet > fEpsilon*rfD0SqrLen*rkD1.MagnitudeSqr() ) + { + // Lines intersect in a single point. Return both s and t values for + // use by calling functions. + float fInvDet = 1.0f/fDet; + riQuantity = 1; + afT[0] = (rkD1.x*rkDiff.z - rkD1.z*rkDiff.x)*fInvDet; + afT[1] = (rkD0.x*rkDiff.z - rkD0.z*rkDiff.x)*fInvDet; + } + else + { + // lines are parallel + fDet = rkD0.x*rkDiff.z - rkD0.z*rkDiff.x; + if ( fDet*fDet > fEpsilon*rfD0SqrLen*rkDiff.MagnitudeSqr() ) + { + // lines are disjoint + riQuantity = 0; + } + else + { + // lines are the same + riQuantity = 2; + } + } + + return riQuantity != 0; +} + +//---------------------------------------------------------------------------- +static bool FindIntersection (const rmt::Vector& origin0, const rmt::Vector& direction0, + const rmt::Vector& origin1, const rmt::Vector& direction1, + int& riQuantity) +{ + float afT[2]; + + rmt::Vector kDiff; + float fD0SqrLen; + bool bIntersects = Find(origin0, direction0, + origin1,direction1,kDiff,fD0SqrLen, + riQuantity,afT); + + if ( bIntersects ) + { + if ( riQuantity == 1 ) + { + if ( afT[0] < 0.0f || afT[0] > 1.0f + || afT[1] < 0.0f || afT[1] > 1.0f ) + { + // lines intersect, but segments do not + riQuantity = 0; + } + } + else + { + // segments are on the same line + float fDotRS = direction0.Dot(direction1); + float fDot0, fDot1; + if ( fDotRS > 0.0f ) + + { + fDot0 = kDiff.Dot(direction0); + fDot1 = fDot0 + fDotRS; + } + else + { + fDot1 = kDiff.Dot(direction0); + fDot0 = fDot1 + fDotRS; + } + + // compute intersection of [t0,t1] and [0,1] + if ( fDot1 < 0.0f || fDot0 > fD0SqrLen ) + { + riQuantity = 0; + } + else if ( fDot1 > 0.0f ) + { + if ( fDot0 < fD0SqrLen ) + { + float fInvLen = 1.0f/fD0SqrLen; + riQuantity = 2; + afT[0] = ( fDot0 < 0.0f ? 0.0f : fDot0*fInvLen ); + afT[1] = ( fDot1 > fD0SqrLen ? 1.0f : fDot1*fInvLen ); + } + else // fT0 == 1 + { + riQuantity = 1; + afT[0] = 1.0f; + } + } + else // fT1 == 0 + { + riQuantity = 1; + afT[0] = 0.0f; + } + } + } + + return riQuantity != 0; +} + +bool WorldPhysicsManager::FenceSanityCheck(unsigned collisionAreaIndex, const rmt::Vector lastFrame, const rmt::Vector thisFrame, rmt::Vector* fixPos) +{ + rmt::Vector dir, dirTweak; + dir.Sub(thisFrame, lastFrame); + + if(dir.MagnitudeSqr() == 0.0f) + { + return false; + } + + dirTweak = dir; + dirTweak.Normalize(); + dirTweak.Scale(0.07f); + dir += dirTweak; + + for(int i = 0; i < mMaxFencePerArea; i++) + { + if(mFences[collisionAreaIndex][i].mClean) + { + rmt::Vector fenceDir, fenceTweak, fenceStart; + fenceDir.Sub(mFences[collisionAreaIndex][i].end, mFences[collisionAreaIndex][i].start); + + fenceTweak = fenceDir; + fenceTweak.Normalize(); + fenceTweak.Scale(0.07f); + + fenceDir += fenceTweak; + fenceDir += fenceTweak; + + fenceStart = mFences[collisionAreaIndex][i].start; + fenceStart -= fenceTweak; + + int quantity; + + if(FindIntersection(lastFrame, dir, fenceStart, fenceDir, quantity)) + { + if(quantity == 1) + { + *fixPos = lastFrame; + fixPos->y = thisFrame.y; + return true; + } + } + } + } + return false; +} + +float WorldPhysicsManager::GetLoopTime() +{ + return mLoopTime; +} diff --git a/game/code/worldsim/worldphysicsmanager.h b/game/code/worldsim/worldphysicsmanager.h new file mode 100644 index 0000000..e8509ce --- /dev/null +++ b/game/code/worldsim/worldphysicsmanager.h @@ -0,0 +1,296 @@ +//============================================================================= +// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. +// +// File: worldphysicsmanager.h +// +// Description: manage the world physics +// +// History: May 1, 2002 - created, gmayer +// +//============================================================================= + +#ifndef WORLDPHYSICSMANAGER_H +#define WORLDPHYSICSMANAGER_H + +//======================================== +// Nested Includes +//======================================== + +//======================================== +// Forward References +//======================================== +class AnimCollisionEntityDSG; +class CollisionEntityDSG; +class DynaPhysDSG; +class WorldCollisionSolverAgentManager; +class StaticPhysDSG; +class GroundPlanePool; +class FenceEntityDSG; +class Vehicle; +namespace sim +{ + class CollisionManager; + class ManualSimState; + class SimEnvironment; + class SimState; + class PhysicsProperties; +} + +//============================================================================= +// +// Synopsis: this is the key tie-in point to the gameplay context to +// run the simulation of the game world +// +//============================================================================= +class WorldPhysicsManager +{ + public: + + // Structure thats filled out upon call to ApplyForceToDynamics + // indicating the counts for the various types of objects that got hit + struct NumObjectsHit + { + NumObjectsHit() : numNPCsHit(0) {} + int numNPCsHit; + }; + + struct CollisionEntityDSGList + { + static const int NUM_COLLISION_LIST_ENTITIES = 3; + + CollisionEntityDSG* collisionEntity[NUM_COLLISION_LIST_ENTITIES]; + }; + + // Static Methods for accessing this singleton. + static WorldPhysicsManager* GetInstance(); + static WorldPhysicsManager* CreateInstance(); + static void DestroyInstance(); + + // Important! + // this method must be called before we load _anything_! + //static void SetSimUnits(); + + void Init(); // TODO - need this? + + void Update(unsigned int timeDeltaMilliSeconds); + + // kind of a hack? + void SuspendForInterior(); + void ResumeForOutside(); + + void OnQuitLevel(); + + sim::SimEnvironment* mSimEnvironment; + float mCollisionDistanceCGS; + rmt::Vector mWorldUp; + + sim::CollisionManager* mCollisionManager; + + WorldCollisionSolverAgentManager* mpWorldCollisionSolverAgentManager; + + int GetCameraCollisionAreaIndex(); // return the next free one + int GetVehicleCollisionAreaIndex(); + int GetCharacterCollisionAreaIndex(); + + void FreeCollisionAreaIndex(int index); + void FreeAllCollisionAreaIndicies(); + void EmptyCollisionAreaIndex(int index); + static const int INVALID_COLLISION_AREA = -1; + void SubmitStaticsPseudoCallback(rmt::Vector& position, float radius, int collisionAreaIndex, sim::SimState* callerSimState, bool allowAutoPairing = false); + void SubmitFencePiecesPseudoCallback(rmt::Vector& position, float radius, int collisionAreaIndex, sim::SimState* callerSimState, bool allowAutoPairing = false); + //void SubmitFencePiecesPseudoCallback(rmt::Vector& position, float radius, int collisionAreaIndex); + void SubmitDynamicsPseudoCallback(rmt::Vector& position, float radius, int collisionAreaIndex, sim::SimState* caller, bool allowAutoPairing = false); + + //bool WorldPhysicsManager::IsASubmitter(sim::SimState* testSimState); + + void SubmitAnimCollisionsPseudoCallback(rmt::Vector& position, float radius, int collisionAreaIndex, sim::SimState* callerSimState); + + void SubmitAnimCollisionsForUpdateOnly(rmt::Vector& position, float radius, int collisionAreaIndex); + + void UpdateAnimCollisions(float dt, int collisionAreaIndex); + void UpdateDynamicObjects(float dt, int collisionAreaIndex); + + + void DisplayFencesInArea(int area); + + // need to do this when we remove a vehicle from an active collision area index... + void RemoveFromAnyOtherCurrentDynamicsListAndCollisionArea(DynaPhysDSG* vehicle); + + // allow access to groundplanepool + int GetNewGroundPlane(sim::SimState* simStateOwner); // user refers to it with the returned index + void FreeGroundPlane(int index); + + // add pair to collision + // called by object + //void SubmitGroundPlaneToCollisionManager(sim::SimState* simStateToPair, int groundPlaneIndex); + //void RemoveGroundPlaneFromCollisionManager(int groundPlaneIndex); + + void EnableGroundPlaneCollision(int index); + void DisableGroundPlaneCollision(int index); + + void StartTimer(); + void StopTimer(); + void ResetTimer(); + + void ToggleTimerState(); + + // Apply the given force to nearby objects. Returns the number of objects affected + int ApplyForceToDynamics( int collisionAreaIndex, + const rmt::Vector& position, + float radius, + const rmt::Vector& direction, + float force, + WorldPhysicsManager::NumObjectsHit* pObjectsHit = NULL, + CollisionEntityDSGList* pCollisionEntityList = NULL ); + + int ApplyForceToDynamicsSpherical( int collisionAreaIndex, + const rmt::Vector& position, + float radius, + float force, + CollisionEntityDSGList* pCollisionEntityList = NULL ); + float GetLoopTime(); + + //-------- + // statics + //-------- + struct StaticsInCollisionDetection + { + StaticPhysDSG* mStaticPhysDSG; + bool clean; + }; + int mMaxStatics; // per area + + // for each collision area, need the 'clean' list + StaticsInCollisionDetection** mCurrentStatics; + + void PurgeDynamics( int collisionAreaIndex ); + + bool FenceSanityCheck(unsigned collisionAreaIndex, const rmt::Vector lastFrame, const rmt::Vector thisFrame, rmt::Vector* fixPos); + + private: + + // No public access to these, use singleton interface. + WorldPhysicsManager(); + ~WorldPhysicsManager(); + + void InitCollisionManager(); + + + void WorldSimSubstepGuts(float dt, bool firstSubstep); + + // pointer to the single instance + static WorldPhysicsManager* spInstance; + + float mTotalTime; + float mTimerTime; + bool mTimerOn; + + + void DebugInfoDisplay(); + + //---------------------- + // anim collision entity + //---------------------- + + // these are for collision detection only + struct AnimCollisionInCollisionDetection + { + AnimCollisionEntityDSG* mAnimCollisionEntityDSG; + bool clean; + }; + int mMaxAnimCollisions; + + AnimCollisionInCollisionDetection** mCurrentAnimCollisions; + + // these ones are for update only - we can and want to update stuff that is too far away to collide with, for appearance sake + AnimCollisionInCollisionDetection** mCurrentUpdateAnimCollisions; + int mMaxUpdateAnimCollisions; + + //--------- + // dynamics + //--------- + struct DynamicsInCollisionDetection + { + //InstDynaPhysDSG* mDynamicPhysDSG; + DynaPhysDSG* mDynamicPhysDSG; + bool clean; + //int groundPlaneIndex; have to hold this in the InstDynaPhysDSG class since object could be in more than one update list + }; + int mMaxDynamics; // per area + + // the 'clean' list + // + DynamicsInCollisionDetection** mCurrentDynamics; + + // this list will hold all dynamics that should be tested for collision against. + // some will be under ai ctrl, and one's that have been collided with will be under + // simulation ctrl + // + // only the simulation ctrl ones need a physics update and ground plane and a rest test + // + // for now... don't remove from the list until object has come to rest + + GroundPlanePool* mGroundPlanePool; + void UpdateSimluatingDynamicObjectGroundPlanes(); + + bool* mCollisionAreaAllocated; // has been requested for use by a vehicle, character, or camera + bool* mCollisionAreaActive; // for intermediate, temporary disabling + int mNumCollisionAreas; + + + // for debugging? + int mReservedCollisionAreas; // reserve 0...mReservedCollisionAreas - 1 + + int mMaxVehicles; + int mMaxChars; + int mMaxCameras; + + //------------- + // fence pieces + //------------- + + sim::PhysicsProperties* mFencePhysicsProperties; + + // have a pool of these + + struct FencePieces + { + sim::ManualSimState* mFenceSimState; + bool mInCollision; + bool mClean; + rmt::Vector start, end; + }; + + FencePieces** mFences; + + // debug drawing: + // note this only really works for one thing calling submit fence pieces! + int mNumDebugFences; + FenceEntityDSG** mFenceDSGResults; + + + //sim::ManualSimState*** mFenceSimStates; // a 2D array of pointers + + int mMaxFencePerArea; + int* mFencesInEachArea; // ? still need this + + bool UpdateFencePiece(rmt::Vector& callerPosition, sim::ManualSimState* fencePiece, rmt::Vector& end0, rmt::Vector& end1, rmt::Vector& normal, sim::SimState* callerSimState); + + + // ? bit of a hack here for player character going into interiors + bool mInInterior; + + // current physics "frame" (for detecting duplicate updating + // when an object is in multip[le lists + unsigned updateFrame; + + float mLoopTime; + unsigned mLastTime; +}; + +// A little syntactic sugar for getting at this singleton. +inline WorldPhysicsManager* GetWorldPhysicsManager() { return( WorldPhysicsManager::GetInstance() ); } + + + +#endif //WORLDPHYSICSMANAGER_H |