aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/phys_controller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mp/src/game/server/phys_controller.cpp')
-rw-r--r--mp/src/game/server/phys_controller.cpp1078
1 files changed, 1078 insertions, 0 deletions
diff --git a/mp/src/game/server/phys_controller.cpp b/mp/src/game/server/phys_controller.cpp
new file mode 100644
index 00000000..35bbf29b
--- /dev/null
+++ b/mp/src/game/server/phys_controller.cpp
@@ -0,0 +1,1078 @@
+//========= Copyright 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"
+
+#define SF_THRUST_STARTACTIVE 0x0001
+#define SF_THRUST_FORCE 0x0002
+#define SF_THRUST_TORQUE 0x0004
+#define SF_THRUST_LOCAL_ORIENTATION 0x0008
+#define SF_THRUST_MASS_INDEPENDENT 0x0010
+#define SF_THRUST_IGNORE_POS 0x0020
+
+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 );
+
+ // 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:
+ Vector m_localOrigin;
+};
+
+LINK_ENTITY_TO_CLASS( phys_thruster, CPhysThruster );
+
+BEGIN_DATADESC( CPhysThruster )
+
+ DEFINE_FIELD( m_localOrigin, FIELD_VECTOR ),
+
+END_DATADESC()
+
+
+
+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, &currentRotAxis );
+ // 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.f, 1.f );
+ 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 &currentSpeed, 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;
+}
+