The-Simpsons-Hit-and-Run/game/code/worldsim/character/character.cpp

5172 lines
161 KiB
C++

#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() );
}
}