|
|
//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "entitylist.h"
#include "physics.h"
#include "vphysics/constraints.h"
#include "physics_saverestore.h"
#include "phys_controller.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
class CPhysThruster;
//-----------------------------------------------------------------------------
// Purpose: This class only implements the IMotionEvent-specific behavior
// It keeps track of the forces so they can be integrated
//-----------------------------------------------------------------------------
class CConstantForceController : public IMotionEvent { DECLARE_SIMPLE_DATADESC();
public: void Init( IMotionEvent::simresult_e controlType ) { m_controlType = controlType; }
void SetConstantForce( const Vector &linear, const AngularImpulse &angular ); void ScaleConstantForce( float scale ); IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ); IMotionEvent::simresult_e m_controlType; Vector m_linear; AngularImpulse m_angular; Vector m_linearSave; AngularImpulse m_angularSave; };
BEGIN_SIMPLE_DATADESC( CConstantForceController ) DEFINE_FIELD( m_controlType, FIELD_INTEGER ), DEFINE_FIELD( m_linear, FIELD_VECTOR ), DEFINE_FIELD( m_angular, FIELD_VECTOR ), DEFINE_FIELD( m_linearSave, FIELD_VECTOR ), DEFINE_FIELD( m_angularSave, FIELD_VECTOR ), END_DATADESC()
void CConstantForceController::SetConstantForce( const Vector &linear, const AngularImpulse &angular ) { m_linear = linear; m_angular = angular; // cache these for scaling later
m_linearSave = linear; m_angularSave = angular; }
void CConstantForceController::ScaleConstantForce( float scale ) { m_linear = m_linearSave * scale; m_angular = m_angularSave * scale; }
IMotionEvent::simresult_e CConstantForceController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) { linear = m_linear; angular = m_angular; return m_controlType; }
// UNDONE: Make these logical entities
//-----------------------------------------------------------------------------
// Purpose: This is a general entity that has a force/motion controller that
// simply integrates a constant linear/angular acceleration
//-----------------------------------------------------------------------------
abstract_class CPhysForce : public CPointEntity { public: DECLARE_CLASS( CPhysForce, CPointEntity );
CPhysForce(); ~CPhysForce();
DECLARE_DATADESC();
virtual void OnRestore( ); void Spawn( void ); void Activate( void );
void ForceOn( void ); void ForceOff( void ); void ActivateForce( void ); void SetAttachedObject( EHANDLE hObject ) { m_attachedObject = hObject; }
// Input handlers
void InputActivate( inputdata_t &inputdata ); void InputDeactivate( inputdata_t &inputdata ); void InputForceScale( inputdata_t &inputdata );
void SaveForce( void ); void ScaleForce( float scale );
// MUST IMPLEMENT THIS IN DERIVED CLASS
virtual void SetupForces( IPhysicsObject *pPhys, Vector &linear, AngularImpulse &angular ) = 0;
// optional
virtual void OnActivate( void ) {}
protected: IPhysicsMotionController *m_pController;
string_t m_nameAttach; float m_force; float m_forceTime; EHANDLE m_attachedObject; bool m_wasRestored;
CConstantForceController m_integrator; };
BEGIN_DATADESC( CPhysForce )
DEFINE_PHYSPTR( m_pController ), DEFINE_KEYFIELD( m_nameAttach, FIELD_STRING, "attach1" ), DEFINE_KEYFIELD( m_force, FIELD_FLOAT, "force" ), DEFINE_KEYFIELD( m_forceTime, FIELD_FLOAT, "forcetime" ),
DEFINE_FIELD( m_attachedObject, FIELD_EHANDLE ), //DEFINE_FIELD( m_wasRestored, FIELD_BOOLEAN ), // NOTE: DO NOT save/load this - it's used to detect loads
DEFINE_EMBEDDED( m_integrator ), DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ), DEFINE_INPUTFUNC( FIELD_FLOAT, "scale", InputForceScale ), // Function Pointers
DEFINE_FUNCTION( ForceOff ),
END_DATADESC()
CPhysForce::CPhysForce( void ) { m_pController = NULL; m_wasRestored = false; }
CPhysForce::~CPhysForce() { if ( m_pController ) { physenv->DestroyMotionController( m_pController ); } }
void CPhysForce::Spawn( void ) { if ( m_spawnflags & SF_THRUST_LOCAL_ORIENTATION ) { m_integrator.Init( IMotionEvent::SIM_LOCAL_ACCELERATION ); } else { m_integrator.Init( IMotionEvent::SIM_GLOBAL_ACCELERATION ); } }
void CPhysForce::OnRestore( ) { BaseClass::OnRestore();
if ( m_pController ) { m_pController->SetEventHandler( &m_integrator ); } m_wasRestored = true; }
void CPhysForce::Activate( void ) { BaseClass::Activate();
if ( m_pController ) { m_pController->WakeObjects(); } if ( m_wasRestored ) return;
if ( m_attachedObject == NULL ) { m_attachedObject = gEntList.FindEntityByName( NULL, m_nameAttach ); } // Let the derived class set up before we throw the switch
OnActivate();
if ( m_spawnflags & SF_THRUST_STARTACTIVE ) { ForceOn(); } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysForce::InputActivate( inputdata_t &inputdata ) { ForceOn(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysForce::InputDeactivate( inputdata_t &inputdata ) { ForceOff(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysForce::InputForceScale( inputdata_t &inputdata ) { ScaleForce( inputdata.value.Float() ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysForce::ForceOn( void ) { if ( m_pController ) return;
ActivateForce(); if ( m_forceTime ) { SetNextThink( gpGlobals->curtime + m_forceTime ); SetThink( &CPhysForce::ForceOff ); } }
void CPhysForce::ActivateForce( void ) { IPhysicsObject *pPhys = NULL; if ( m_attachedObject ) { pPhys = m_attachedObject->VPhysicsGetObject(); } if ( !pPhys ) return;
Vector linear; AngularImpulse angular;
SetupForces( pPhys, linear, angular );
m_integrator.SetConstantForce( linear, angular ); m_pController = physenv->CreateMotionController( &m_integrator ); m_pController->AttachObject( pPhys, true ); // Make sure the object is simulated
pPhys->Wake(); }
void CPhysForce::ForceOff( void ) { if ( !m_pController ) return;
physenv->DestroyMotionController( m_pController ); m_pController = NULL; SetThink( NULL ); SetNextThink( TICK_NEVER_THINK ); IPhysicsObject *pPhys = NULL; if ( m_attachedObject ) { pPhys = m_attachedObject->VPhysicsGetObject(); if ( pPhys ) { pPhys->Wake(); } } }
void CPhysForce::ScaleForce( float scale ) { if ( !m_pController ) ForceOn();
m_integrator.ScaleConstantForce( scale ); m_pController->WakeObjects(); }
//-----------------------------------------------------------------------------
// Purpose: A rocket-engine/thruster based on the force controller above
// Calculate the force (and optional torque) that the engine would create
//-----------------------------------------------------------------------------
class CPhysThruster : public CPhysForce { DECLARE_CLASS( CPhysThruster, CPhysForce ); public: DECLARE_DATADESC();
virtual void OnActivate( void ); virtual void SetupForces( IPhysicsObject *pPhys, Vector &linear, AngularImpulse &angular );
private: friend CBaseEntity *CreatePhysThruster( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, float flForce, float flForcetime, bool bActive, int nFlags ); Vector m_localOrigin; };
LINK_ENTITY_TO_CLASS( phys_thruster, CPhysThruster );
BEGIN_DATADESC( CPhysThruster )
DEFINE_FIELD( m_localOrigin, FIELD_VECTOR ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Use this to spawn a keepupright controller via code instead of map-placed
//-----------------------------------------------------------------------------
CBaseEntity *CreatePhysThruster( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, float flForce, float flForcetime, bool bActive, int nFlags ) { CPhysThruster *pThruster = ( CPhysThruster* )CBaseEntity::Create( "phys_thruster", vecOrigin, vecAngles, pOwner ); if ( pThruster ) { pThruster->AddSpawnFlags( nFlags ); pThruster->m_attachedObject = pOwner; pThruster->m_force = flForce; pThruster->m_forceTime = flForcetime; pThruster->Spawn(); pThruster->Activate(); if ( bActive ) pThruster->ForceOn(); }
return pThruster; }
void CPhysThruster::OnActivate( void ) { if ( m_attachedObject != NULL ) { matrix3x4_t worldToAttached, thrusterToAttached; MatrixInvert( m_attachedObject->EntityToWorldTransform(), worldToAttached ); ConcatTransforms( worldToAttached, EntityToWorldTransform(), thrusterToAttached ); MatrixGetColumn( thrusterToAttached, 3, m_localOrigin );
if ( HasSpawnFlags( SF_THRUST_LOCAL_ORIENTATION ) ) { QAngle angles; MatrixAngles( thrusterToAttached, angles ); SetLocalAngles( angles ); } // maintain the local relationship with this entity
// it may move before the thruster is activated
if ( HasSpawnFlags( SF_THRUST_IGNORE_POS ) ) { m_localOrigin.Init(); } } }
// utility function to duplicate this call in local space
void CalculateVelocityOffsetLocal( IPhysicsObject *pPhys, const Vector &forceLocal, const Vector &positionLocal, Vector &outVelLocal, AngularImpulse &outAngular ) { Vector posWorld, forceWorld; pPhys->LocalToWorld( &posWorld, positionLocal ); pPhys->LocalToWorldVector( &forceWorld, forceLocal ); Vector velWorld; pPhys->CalculateVelocityOffset( forceWorld, posWorld, &velWorld, &outAngular ); pPhys->WorldToLocalVector( &outVelLocal, velWorld ); }
void CPhysThruster::SetupForces( IPhysicsObject *pPhys, Vector &linear, AngularImpulse &angular ) { Vector thrustVector; AngleVectors( GetLocalAngles(), &thrustVector ); thrustVector *= m_force;
// multiply the force by mass (it's actually just an acceleration)
if ( m_spawnflags & SF_THRUST_MASS_INDEPENDENT ) { thrustVector *= pPhys->GetMass(); } if ( m_spawnflags & SF_THRUST_LOCAL_ORIENTATION ) { CalculateVelocityOffsetLocal( pPhys, thrustVector, m_localOrigin, linear, angular ); } else { Vector position; VectorTransform( m_localOrigin, m_attachedObject->EntityToWorldTransform(), position ); pPhys->CalculateVelocityOffset( thrustVector, position, &linear, &angular ); }
if ( !(m_spawnflags & SF_THRUST_FORCE) ) { // clear out force
linear.Init(); }
if ( !(m_spawnflags & SF_THRUST_TORQUE) ) { // clear out torque
angular.Init(); } }
//-----------------------------------------------------------------------------
// Purpose: A controllable motor - exerts torque
//-----------------------------------------------------------------------------
class CPhysTorque : public CPhysForce { DECLARE_CLASS( CPhysTorque, CPhysForce ); public: DECLARE_DATADESC(); void Spawn( void ); virtual void SetupForces( IPhysicsObject *pPhys, Vector &linear, AngularImpulse &angular ); private: Vector m_axis; };
BEGIN_DATADESC( CPhysTorque )
DEFINE_KEYFIELD( m_axis, FIELD_VECTOR, "axis" ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( phys_torque, CPhysTorque );
void CPhysTorque::Spawn( void ) { // force spawnflags to agree with implementation of this class
m_spawnflags |= SF_THRUST_TORQUE | SF_THRUST_MASS_INDEPENDENT; m_spawnflags &= ~SF_THRUST_FORCE;
m_axis -= GetAbsOrigin(); VectorNormalize(m_axis); UTIL_SnapDirectionToAxis( m_axis ); BaseClass::Spawn(); }
void CPhysTorque::SetupForces( IPhysicsObject *pPhys, Vector &linear, AngularImpulse &angular ) { // clear out force
linear.Init();
matrix3x4_t matrix; pPhys->GetPositionMatrix( &matrix );
// transform motor axis to local space
Vector axis_ls; VectorIRotate( m_axis, matrix, axis_ls );
// Set torque to be around selected axis
angular = axis_ls * m_force; }
//-----------------------------------------------------------------------------
// Purpose: This class only implements the IMotionEvent-specific behavior
// It keeps track of the forces so they can be integrated
//-----------------------------------------------------------------------------
class CMotorController : public IMotionEvent { DECLARE_SIMPLE_DATADESC();
public: IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ); float m_speed; float m_maxTorque; Vector m_axis; float m_inertiaFactor;
float m_lastSpeed; float m_lastAcceleration; float m_lastForce; float m_restistanceDamping; };
BEGIN_SIMPLE_DATADESC( CMotorController )
DEFINE_FIELD( m_speed, FIELD_FLOAT ), DEFINE_FIELD( m_maxTorque, FIELD_FLOAT ), DEFINE_KEYFIELD( m_axis, FIELD_VECTOR, "axis" ), DEFINE_KEYFIELD( m_inertiaFactor, FIELD_FLOAT, "inertiafactor" ), DEFINE_FIELD( m_lastSpeed, FIELD_FLOAT ), DEFINE_FIELD( m_lastAcceleration, FIELD_FLOAT ), DEFINE_FIELD( m_lastForce, FIELD_FLOAT ), DEFINE_FIELD( m_restistanceDamping, FIELD_FLOAT ),
END_DATADESC()
IMotionEvent::simresult_e CMotorController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) { linear = vec3_origin; angular = vec3_origin;
if ( m_speed == 0 ) return SIM_NOTHING;
matrix3x4_t matrix; pObject->GetPositionMatrix( &matrix ); AngularImpulse currentRotAxis; // currentRotAxis is in local space
pObject->GetVelocity( NULL, ¤tRotAxis ); // transform motor axis to local space
Vector motorAxis_ls; VectorIRotate( m_axis, matrix, motorAxis_ls ); float currentSpeed = DotProduct( currentRotAxis, motorAxis_ls ); float inertia = DotProductAbs( pObject->GetInertia(), motorAxis_ls );
// compute absolute acceleration, don't integrate over the timestep
float accel = m_speed - currentSpeed; float rotForce = accel * inertia * m_inertiaFactor;
// BUGBUG: This heuristic is a little flaky
// UNDONE: Make a better heuristic for speed control
if ( fabsf(m_lastAcceleration) > 0 ) { float deltaSpeed = currentSpeed - m_lastSpeed; // make sure they are going the same way
if ( deltaSpeed * accel > 0 ) { float factor = deltaSpeed / m_lastAcceleration; factor = 1 - clamp( factor, 0, 1 ); rotForce += m_lastForce * factor * m_restistanceDamping; } else { if ( currentSpeed != 0 ) { // have we reached a steady state that isn't our target?
float increase = deltaSpeed / m_lastAcceleration; if ( fabsf(increase) < 0.05 ) { rotForce += m_lastForce * m_restistanceDamping; } } } } // -------------------------------------------------------
if ( m_maxTorque != 0 ) { if ( rotForce > m_maxTorque ) { rotForce = m_maxTorque; } else if ( rotForce < -m_maxTorque ) { rotForce = -m_maxTorque; } }
m_lastForce = rotForce; m_lastAcceleration = (rotForce / inertia); m_lastSpeed = currentSpeed;
// this is in local space
angular = motorAxis_ls * rotForce; return SIM_LOCAL_FORCE; }
#define SF_MOTOR_START_ON 0x0001 // starts on by default
#define SF_MOTOR_NOCOLLIDE 0x0002 // don't collide with world geometry
#define SF_MOTOR_HINGE 0x0004 // motor also acts as a hinge constraining the object to this axis
// NOTE: THIS DOESN'T WORK YET
#define SF_MOTOR_LOCAL 0x0008 // Maintain local relationship with the attached object
class CPhysMotor : public CLogicalEntity { DECLARE_CLASS( CPhysMotor, CLogicalEntity ); public: ~CPhysMotor(); DECLARE_DATADESC(); void Spawn( void ); void Activate( void ); void Think( void );
void TurnOn( void ); void TargetSpeedChanged( void ); void OnRestore();
void InputSetTargetSpeed( inputdata_t &inputdata ); void InputTurnOn( inputdata_t &inputdata ); void InputTurnOff( inputdata_t &inputdata ); void CalculateAcceleration();
string_t m_nameAttach; EHANDLE m_attachedObject; float m_spinUp; float m_additionalAcceleration; float m_angularAcceleration; float m_lastTime; // FIXME: can we remove m_flSpeed from CBaseEntity?
//float m_flSpeed;
IPhysicsConstraint *m_pHinge; IPhysicsMotionController *m_pController; CMotorController m_motor; };
BEGIN_DATADESC( CPhysMotor )
DEFINE_KEYFIELD( m_nameAttach, FIELD_STRING, "attach1" ), DEFINE_FIELD( m_attachedObject, FIELD_EHANDLE ), DEFINE_KEYFIELD( m_spinUp, FIELD_FLOAT, "spinup" ), DEFINE_KEYFIELD( m_additionalAcceleration, FIELD_FLOAT, "addangaccel" ), DEFINE_FIELD( m_angularAcceleration, FIELD_FLOAT ), DEFINE_FIELD( m_lastTime, FIELD_TIME ), DEFINE_PHYSPTR( m_pHinge ), DEFINE_PHYSPTR( m_pController ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeed", InputSetTargetSpeed ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
DEFINE_EMBEDDED( m_motor ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( phys_motor, CPhysMotor );
void CPhysMotor::CalculateAcceleration() { if ( m_spinUp ) { m_angularAcceleration = fabsf(m_flSpeed / m_spinUp); } else { m_angularAcceleration = fabsf(m_flSpeed); } }
//-----------------------------------------------------------------------------
// Purpose: Input handler that sets a speed to spin up or down to.
//-----------------------------------------------------------------------------
void CPhysMotor::InputSetTargetSpeed( inputdata_t &inputdata ) { if ( m_flSpeed == inputdata.value.Float() ) return;
m_flSpeed = inputdata.value.Float(); TargetSpeedChanged(); CalculateAcceleration(); }
void CPhysMotor::TargetSpeedChanged( void ) { SetNextThink( gpGlobals->curtime ); m_lastTime = gpGlobals->curtime; m_pController->WakeObjects(); }
//------------------------------------------------------------------------------
// Purpose: Input handler that turns the motor on.
//------------------------------------------------------------------------------
void CPhysMotor::InputTurnOn( inputdata_t &inputdata ) { TurnOn(); }
//------------------------------------------------------------------------------
// Purpose: Input handler that turns the motor off.
//------------------------------------------------------------------------------
void CPhysMotor::InputTurnOff( inputdata_t &inputdata ) { m_motor.m_speed = 0; SetNextThink( TICK_NEVER_THINK ); }
CPhysMotor::~CPhysMotor() { if ( m_attachedObject && m_pHinge ) { IPhysicsObject *pPhys = m_attachedObject->VPhysicsGetObject(); if ( pPhys ) { PhysClearGameFlags(pPhys, FVPHYSICS_NO_PLAYER_PICKUP); } }
physenv->DestroyConstraint( m_pHinge ); physenv->DestroyMotionController( m_pController ); }
void CPhysMotor::Spawn( void ) { m_motor.m_axis -= GetLocalOrigin(); float axisLength = VectorNormalize(m_motor.m_axis); // double check that the axis is at least a unit long. If not, warn and self-destruct.
if ( axisLength > 1.0f ) { UTIL_SnapDirectionToAxis( m_motor.m_axis ); } else { Warning("phys_motor %s does not have a valid axis helper, and self-destructed!\n", GetDebugName());
m_motor.m_speed = 0; SetNextThink( TICK_NEVER_THINK );
UTIL_Remove(this); } }
void CPhysMotor::TurnOn( void ) { CBaseEntity *pAttached = m_attachedObject; if ( !pAttached ) return;
IPhysicsObject *pPhys = pAttached->VPhysicsGetObject(); if ( pPhys ) { m_pController->WakeObjects(); // If the current speed is zero, the objects can run a tick without getting torque'd and go back to sleep
// so force a think now and have some acceleration happen before the controller gets called.
m_lastTime = gpGlobals->curtime - TICK_INTERVAL; Think(); } }
void CPhysMotor::Activate( void ) { BaseClass::Activate(); // This gets called after all objects spawn and after all objects restore
if ( m_attachedObject == NULL ) { CBaseEntity *pAttach = gEntList.FindEntityByName( NULL, m_nameAttach ); if ( pAttach && pAttach->GetMoveType() == MOVETYPE_VPHYSICS ) { m_attachedObject = pAttach; IPhysicsObject *pPhys = m_attachedObject->VPhysicsGetObject(); CalculateAcceleration(); matrix3x4_t matrix; pPhys->GetPositionMatrix( &matrix ); Vector motorAxis_ls; VectorIRotate( m_motor.m_axis, matrix, motorAxis_ls ); float inertia = DotProductAbs( pPhys->GetInertia(), motorAxis_ls ); m_motor.m_maxTorque = inertia * m_motor.m_inertiaFactor * (m_angularAcceleration + m_additionalAcceleration); m_motor.m_restistanceDamping = 1.0f; } }
if ( m_attachedObject ) { IPhysicsObject *pPhys = m_attachedObject->VPhysicsGetObject();
// create a hinge constraint for this object?
if ( m_spawnflags & SF_MOTOR_HINGE ) { // UNDONE: Don't do this on restore?
if ( !m_pHinge ) { constraint_hingeparams_t hingeParams; hingeParams.Defaults(); hingeParams.worldAxisDirection = m_motor.m_axis; hingeParams.worldPosition = GetLocalOrigin();
m_pHinge = physenv->CreateHingeConstraint( g_PhysWorldObject, pPhys, NULL, hingeParams ); m_pHinge->SetGameData( (void *)this ); // can't grab this object
PhysSetGameFlags(pPhys, FVPHYSICS_NO_PLAYER_PICKUP); }
if ( m_spawnflags & SF_MOTOR_NOCOLLIDE ) { PhysDisableEntityCollisions( g_PhysWorldObject, pPhys ); } } else { m_pHinge = NULL; }
// NOTE: On restore, this path isn't run because m_pController will not be NULL
if ( !m_pController ) { m_pController = physenv->CreateMotionController( &m_motor ); m_pController->AttachObject( m_attachedObject->VPhysicsGetObject(), false );
if ( m_spawnflags & SF_MOTOR_START_ON ) { TurnOn(); } } } }
void CPhysMotor::OnRestore() { BaseClass::OnRestore(); // Need to do this on restore since there's no good way to save this
if ( m_pController ) { m_pController->SetEventHandler( &m_motor ); } }
void CPhysMotor::Think( void ) { // angular acceleration is always positive - it should be treated as a magnitude - the controller
// will apply it in the proper direction
Assert(m_angularAcceleration>=0);
m_motor.m_speed = UTIL_Approach( m_flSpeed, m_motor.m_speed, m_angularAcceleration*(gpGlobals->curtime-m_lastTime) ); m_lastTime = gpGlobals->curtime; if ( m_motor.m_speed != m_flSpeed ) { SetNextThink( gpGlobals->curtime ); } }
//======================================================================================
// KEEPUPRIGHT CONTROLLER
//======================================================================================
class CKeepUpright : public CPointEntity, public IMotionEvent { DECLARE_CLASS( CKeepUpright, CPointEntity ); public: DECLARE_DATADESC();
CKeepUpright(); ~CKeepUpright(); void Spawn(); void Activate();
// IMotionEvent
virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
// Inputs
void InputTurnOn( inputdata_t &inputdata ) { m_bActive = true; } void InputTurnOff( inputdata_t &inputdata ) { m_bActive = false; }
void InputSetAngularLimit( inputdata_t &inputdata ) { m_angularLimit = inputdata.value.Float(); }
private: friend CBaseEntity *CreateKeepUpright( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, float flAngularLimit, bool bActive );
Vector m_worldGoalAxis; Vector m_localTestAxis; IPhysicsMotionController *m_pController; string_t m_nameAttach; EHANDLE m_attachedObject; float m_angularLimit; bool m_bActive; bool m_bDampAllRotation; };
#define SF_KEEPUPRIGHT_START_INACTIVE 0x0001
LINK_ENTITY_TO_CLASS( phys_keepupright, CKeepUpright );
BEGIN_DATADESC( CKeepUpright )
DEFINE_FIELD( m_worldGoalAxis, FIELD_VECTOR ), DEFINE_FIELD( m_localTestAxis, FIELD_VECTOR ), DEFINE_PHYSPTR( m_pController ), DEFINE_KEYFIELD( m_nameAttach, FIELD_STRING, "attach1" ), DEFINE_FIELD( m_attachedObject, FIELD_EHANDLE ), DEFINE_KEYFIELD( m_angularLimit, FIELD_FLOAT, "angularlimit" ), DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), DEFINE_FIELD( m_bDampAllRotation, FIELD_BOOLEAN ),
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetAngularLimit", InputSetAngularLimit ),
END_DATADESC()
CKeepUpright::CKeepUpright() { // by default, recover from up to 15 degrees / sec angular velocity
m_angularLimit = 15; m_attachedObject = NULL; m_bDampAllRotation = false; }
CKeepUpright::~CKeepUpright() { if ( m_pController ) { physenv->DestroyMotionController( m_pController ); m_pController = NULL; } }
void CKeepUpright::Spawn() { // align the object's local Z axis
m_localTestAxis.Init( 0, 0, 1 ); // Use our Up axis so mapmakers can orient us arbitrarily
GetVectors( NULL, NULL, &m_worldGoalAxis );
SetMoveType( MOVETYPE_NONE );
if ( m_spawnflags & SF_KEEPUPRIGHT_START_INACTIVE ) { m_bActive = false; } else { m_bActive = true; } }
void CKeepUpright::Activate() { BaseClass::Activate(); if ( !m_pController ) { // This case occurs when spawning
IPhysicsObject *pPhys; if ( m_attachedObject ) { pPhys = m_attachedObject->VPhysicsGetObject(); } else { pPhys = FindPhysicsObjectByName( STRING(m_nameAttach), this ); }
if ( !pPhys ) { UTIL_Remove(this); return; } // HACKHACK: Due to changes in the vehicle simulator the keepupright controller used in coast_01 is unstable
// force it to have perfect damping to compensate.
// detect it using the hack of angular limit == 150, attached to a vehicle
// Fixing it in the code is the simplest course of action presently
#ifdef HL2_DLL
if ( m_angularLimit == 150.0f ) { CBaseEntity *pEntity = static_cast<CBaseEntity *>(pPhys->GetGameData()); if ( pEntity && pEntity->GetServerVehicle() && Q_stristr( gpGlobals->mapname.ToCStr(), "d2_coast_01" ) ) { m_bDampAllRotation = true; } } #endif
m_pController = physenv->CreateMotionController( (IMotionEvent *)this ); m_pController->AttachObject( pPhys, false ); } else { // This case occurs when restoring
m_pController->SetEventHandler( this ); } }
//-----------------------------------------------------------------------------
// Purpose: Use this to spawn a keepupright controller via code instead of map-placed
//-----------------------------------------------------------------------------
CBaseEntity *CreateKeepUpright( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, float flAngularLimit, bool bActive ) { CKeepUpright *pKeepUpright = (CKeepUpright*)CBaseEntity::Create( "phys_keepupright", vecOrigin, vecAngles, pOwner ); if ( pKeepUpright ) { pKeepUpright->m_attachedObject = pOwner; pKeepUpright->m_angularLimit = flAngularLimit; if ( !bActive ) { pKeepUpright->AddSpawnFlags( SF_KEEPUPRIGHT_START_INACTIVE ); } pKeepUpright->Spawn(); pKeepUpright->Activate(); }
return pKeepUpright; }
IMotionEvent::simresult_e CKeepUpright::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) { if ( !m_bActive ) return SIM_NOTHING;
linear.Init();
AngularImpulse angVel; pObject->GetVelocity( NULL, &angVel );
matrix3x4_t matrix; // get the object's local to world transform
pObject->GetPositionMatrix( &matrix );
// Get the alignment axis in object space
Vector currentLocalTargetAxis; VectorIRotate( m_worldGoalAxis, matrix, currentLocalTargetAxis );
float invDeltaTime = (1/deltaTime);
if ( m_bDampAllRotation ) { angular = ComputeRotSpeedToAlignAxes( m_localTestAxis, currentLocalTargetAxis, angVel, 0, invDeltaTime, m_angularLimit ); angular -= angVel; angular *= invDeltaTime; return SIM_LOCAL_ACCELERATION; }
angular = ComputeRotSpeedToAlignAxes( m_localTestAxis, currentLocalTargetAxis, angVel, 1.0, invDeltaTime, m_angularLimit ); angular *= invDeltaTime;
#if 0
Vector position, out, worldAxis; MatrixGetColumn( matrix, 3, position ); out = angular * 0.1; VectorRotate( m_localTestAxis, matrix, worldAxis ); NDebugOverlay::Line( position, position + worldAxis * 100, 255, 0, 0, 0, 0 ); NDebugOverlay::Line( position, position + m_worldGoalAxis * 100, 255, 0, 0, 0, 0 ); NDebugOverlay::Line( position, position + out, 255, 255, 0, 0, 0 ); #endif
return SIM_LOCAL_ACCELERATION; }
// computes the torque necessary to align testAxis with alignAxis
AngularImpulse ComputeRotSpeedToAlignAxes( const Vector &testAxis, const Vector &alignAxis, const AngularImpulse ¤tSpeed, float damping, float scale, float maxSpeed ) { Vector rotationAxis = CrossProduct( testAxis, alignAxis );
// atan2() is well defined, so do a Dot & Cross instead of asin(Cross)
float cosine = DotProduct( testAxis, alignAxis ); float sine = VectorNormalize( rotationAxis ); float angle = atan2( sine, cosine );
angle = RAD2DEG(angle); AngularImpulse angular = rotationAxis * scale * angle; angular -= rotationAxis * damping * DotProduct( currentSpeed, rotationAxis );
float len = VectorNormalize( angular );
if ( len > maxSpeed ) { len = maxSpeed; }
return angular * len; }
|