source-engine/vphysics/physics_shadow.cpp
2021-10-23 14:41:59 +03:00

1422 lines
41 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "physics_shadow.h"
#include "vphysics/player_controller.h"
#include "physics_friction.h"
#include "vphysics/friction.h"
// IsInContact
#include "ivp_mindist.hxx"
#include "ivp_mindist_intern.hxx"
#include "ivp_core.hxx"
#include "ivp_friction.hxx"
#include "ivp_listener_object.hxx"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
struct vphysics_save_cshadowcontroller_t;
struct vphysics_save_shadowcontrolparams_t;
// UNDONE: Try this controller!
//damping is usually 1.0
//frequency is usually in the range 1..16
void ComputePDControllerCoefficients( float *coefficientsOut, const float frequency, const float damping, const float dt )
{
const float ks = 9.0f * frequency * frequency;
const float kd = 4.5f * frequency * damping;
const float scale = 1.0f / ( 1.0f + kd * dt + ks * dt * dt );
coefficientsOut[0] = ks * scale;
coefficientsOut[1] = ( kd + ks * dt ) * scale;
// Use this controller like:
// speed += (coefficientsOut[0] * (targetPos - currentPos) + coefficientsOut[1] * (targetSpeed - currentSpeed)) * dt
}
void ComputeController( IVP_U_Float_Point &currentSpeed, const IVP_U_Float_Point &delta, float maxSpeed, float maxDampSpeed, float scaleDelta, float damping, IVP_U_Float_Point *pOutImpulse = NULL )
{
if ( currentSpeed.quad_length() < 1e-6 )
{
currentSpeed.set_to_zero();
}
// scale by timestep
IVP_U_Float_Point acceleration;
if ( maxSpeed > 0 )
{
acceleration.set_multiple( &delta, scaleDelta );
float speed = acceleration.real_length();
if ( speed > maxSpeed )
{
speed = maxSpeed / speed;
acceleration.mult( speed );
}
}
else
{
acceleration.set_to_zero();
}
IVP_U_Float_Point dampAccel;
if ( maxDampSpeed > 0 )
{
dampAccel.set_multiple( &currentSpeed, -damping );
float speed = dampAccel.real_length();
if ( speed > maxDampSpeed )
{
speed = maxDampSpeed / speed;
dampAccel.mult( speed );
}
}
else
{
dampAccel.set_to_zero();
}
currentSpeed.add( &dampAccel );
currentSpeed.add( &acceleration );
if ( pOutImpulse )
{
*pOutImpulse = acceleration;
}
}
void ComputeController( IVP_U_Float_Point &currentSpeed, const IVP_U_Float_Point &delta, const IVP_U_Float_Point &maxSpeed, float scaleDelta, float damping, IVP_U_Float_Point *pOutImpulse )
{
// scale by timestep
IVP_U_Float_Point acceleration;
acceleration.set_multiple( &delta, scaleDelta );
if ( currentSpeed.quad_length() < 1e-6 )
{
currentSpeed.set_to_zero();
}
acceleration.add_multiple( &currentSpeed, -damping );
for(int i=2; i>=0; i--)
{
if(IVP_Inline_Math::fabsd(acceleration.k[i]) < maxSpeed.k[i])
continue;
// clip force
acceleration.k[i] = (acceleration.k[i] < 0) ? -maxSpeed.k[i] : maxSpeed.k[i];
}
currentSpeed.add( &acceleration );
if ( pOutImpulse )
{
*pOutImpulse = acceleration;
}
}
static bool IsOnGround( IVP_Real_Object *pivp )
{
IPhysicsFrictionSnapshot *pSnapshot = CreateFrictionSnapshot( pivp );
bool bGround = false;
while (pSnapshot->IsValid())
{
Vector normal;
pSnapshot->GetSurfaceNormal( normal );
if ( normal.z < -0.7f )
{
bGround = true;
break;
}
pSnapshot->NextFrictionData();
}
DestroyFrictionSnapshot( pSnapshot );
return bGround;
}
class CPlayerController : public IVP_Controller_Independent, public IPhysicsPlayerController, public IVP_Listener_Object
{
public:
CPlayerController( CPhysicsObject *pObject );
~CPlayerController( void );
// ipion interfaces
void do_simulation_controller( IVP_Event_Sim *es,IVP_U_Vector<IVP_Core> *cores);
virtual IVP_CONTROLLER_PRIORITY get_controller_priority() { return (IVP_CONTROLLER_PRIORITY) (IVP_CP_MOTION+1); }
virtual const char *get_controller_name() { return "vphysics:player"; }
void SetObject( IPhysicsObject *pObject );
void SetEventHandler( IPhysicsPlayerControllerEvent *handler );
void Update( const Vector& position, const Vector& velocity, float secondsToArrival, bool onground, IPhysicsObject *ground );
void MaxSpeed( const Vector &velocity );
bool IsInContact( void );
virtual bool WasFrozen()
{
IVP_Real_Object *pivp = m_pObject->GetObject();
IVP_Core *pCore = pivp->get_core();
return pCore->temporarily_unmovable ? true : false;
}
void ForceTeleportToCurrentPosition()
{
m_forceTeleport = true;
}
int GetShadowPosition( Vector *position, QAngle *angles )
{
IVP_U_Matrix matrix;
IVP_Environment *pEnv = m_pObject->GetObject()->get_environment();
double psi = pEnv->get_next_PSI_time().get_seconds();
m_pObject->GetObject()->calc_at_matrix( psi, &matrix );
if ( angles )
{
ConvertRotationToHL( matrix, *angles );
}
if ( position )
{
ConvertPositionToHL( matrix.vv, *position );
}
return 1;
}
void GetShadowVelocity( Vector *velocity );
virtual void GetLastImpulse( Vector *pOut )
{
ConvertPositionToHL( m_lastImpulse, *pOut );
}
virtual void StepUp( float height );
virtual void Jump();
virtual IPhysicsObject *GetObject() { return m_pObject; }
virtual void SetPushMassLimit( float maxPushMass )
{
m_pushableMassLimit = maxPushMass;
}
virtual void SetPushSpeedLimit( float maxPushSpeed )
{
m_pushableSpeedLimit = maxPushSpeed;
}
virtual float GetPushMassLimit() { return m_pushableMassLimit; }
virtual float GetPushSpeedLimit() { return m_pushableSpeedLimit; }
// Object listener
virtual void event_object_deleted( IVP_Event_Object *pEvent)
{
Assert( pEvent->real_object == m_pGround->GetObject() );
m_pGround = NULL;
}
virtual void event_object_created( IVP_Event_Object *) {}
virtual void event_object_revived( IVP_Event_Object *) {}
virtual void event_object_frozen ( IVP_Event_Object *) {}
private:
void AttachObject( void );
void DetachObject( void );
int TryTeleportObject( void );
void SetGround( CPhysicsObject *pGroundObject );
CPhysicsObject *m_pObject;
IVP_U_Float_Point m_saveRot;
CPhysicsObject *m_pGround; // Uses object listener to clear - so ok to hold over frames
IPhysicsPlayerControllerEvent *m_handler;
float m_maxDeltaPosition;
float m_dampFactor;
float m_secondsToArrival;
float m_pushableMassLimit;
float m_pushableSpeedLimit;
IVP_U_Point m_targetPosition;
IVP_U_Float_Point m_groundPosition;
IVP_U_Float_Point m_maxSpeed;
IVP_U_Float_Point m_currentSpeed;
IVP_U_Float_Point m_lastImpulse;
bool m_enable : 1;
bool m_onground : 1;
bool m_forceTeleport : 1;
bool m_updatedSinceLast : 5;
};
CPlayerController::CPlayerController( CPhysicsObject *pObject )
{
m_pGround = NULL;
m_pObject = pObject;
m_handler = NULL;
m_maxDeltaPosition = ConvertDistanceToIVP( 24 );
m_dampFactor = 1.0f;
m_targetPosition.k[0] = m_targetPosition.k[1] = m_targetPosition.k[2] = 0;
m_pushableMassLimit = VPHYSICS_MAX_MASS;
m_pushableSpeedLimit = 1e4f;
m_forceTeleport = false;
AttachObject();
}
CPlayerController::~CPlayerController( void )
{
DetachObject();
}
void CPlayerController::SetGround( CPhysicsObject *pGroundObject )
{
if ( m_pGround != pGroundObject )
{
if ( m_pGround && m_pGround->GetObject() )
{
m_pGround->GetObject()->remove_listener_object(this);
}
m_pGround = pGroundObject;
if ( m_pGround && m_pGround->GetObject() )
{
m_pGround->GetObject()->add_listener_object(this);
}
}
}
void CPlayerController::AttachObject( void )
{
m_pObject->EnableDrag( false );
IVP_Real_Object *pivp = m_pObject->GetObject();
IVP_Core *pCore = pivp->get_core();
m_saveRot = pCore->rot_speed_damp_factor;
pCore->rot_speed_damp_factor = IVP_U_Float_Point( 100, 100, 100 );
pCore->calc_calc();
BEGIN_IVP_ALLOCATION();
pivp->get_environment()->get_controller_manager()->add_controller_to_core( this, pCore );
END_IVP_ALLOCATION();
m_pObject->AddCallbackFlags( CALLBACK_IS_PLAYER_CONTROLLER );
}
void CPlayerController::DetachObject( void )
{
if ( !m_pObject )
return;
IVP_Real_Object *pivp = m_pObject->GetObject();
IVP_Core *pCore = pivp->get_core();
pCore->rot_speed_damp_factor = m_saveRot;
pCore->calc_calc();
m_pObject->RemoveCallbackFlags( CALLBACK_IS_PLAYER_CONTROLLER );
m_pObject = NULL;
pivp->get_environment()->get_controller_manager()->remove_controller_from_core( this, pCore );
SetGround(NULL);
}
void CPlayerController::SetObject( IPhysicsObject *pObject )
{
CPhysicsObject *obj = (CPhysicsObject *)pObject;
if ( obj == m_pObject )
return;
DetachObject();
m_pObject = obj;
AttachObject();
}
int CPlayerController::TryTeleportObject( void )
{
if ( m_handler && !m_forceTeleport )
{
Vector hlPosition;
ConvertPositionToHL( m_targetPosition, hlPosition );
if ( !m_handler->ShouldMoveTo( m_pObject, hlPosition ) )
return 0;
}
IVP_Real_Object *pivp = m_pObject->GetObject();
IVP_U_Quat targetOrientation;
IVP_U_Point outPosition;
pivp->get_quat_world_f_object_AT( &targetOrientation, &outPosition );
if ( pivp->is_collision_detection_enabled() )
{
m_pObject->EnableCollisions( false );
pivp->beam_object_to_new_position( &targetOrientation, &m_targetPosition, IVP_TRUE );
m_pObject->EnableCollisions( true );
}
else
{
pivp->beam_object_to_new_position( &targetOrientation, &m_targetPosition, IVP_TRUE );
}
m_forceTeleport = false;
return 1;
}
void CPlayerController::StepUp( float height )
{
if ( height == 0.0f )
return;
Vector step( 0, 0, height );
IVP_Real_Object *pIVP = m_pObject->GetObject();
IVP_U_Quat world_f_object;
IVP_U_Point positionIVP, deltaIVP;
ConvertPositionToIVP( step, deltaIVP );
pIVP->get_quat_world_f_object_AT( &world_f_object, &positionIVP );
positionIVP.add( &deltaIVP );
pIVP->beam_object_to_new_position( &world_f_object, &positionIVP, IVP_TRUE );
}
void CPlayerController::Jump()
{
#if 0
// float for one tick to allow stepping and jumping to work properly
IVP_Real_Object *pIVP = m_pObject->GetObject();
const IVP_U_Point *pgrav = pIVP->get_environment()->get_gravity();
IVP_U_Float_Point gravSpeed;
gravSpeed.set_multiple( pgrav, pIVP->get_environment()->get_delta_PSI_time() );
pIVP->get_core()->speed.subtract( &gravSpeed );
#endif
}
const int MAX_LIST_NORMALS = 8;
class CNormalList
{
public:
bool IsFull() { return m_Normals.Count() == MAX_LIST_NORMALS; }
void AddNormal( const Vector &normal )
{
if ( IsFull() )
return;
for ( int i = m_Normals.Count(); --i >= 0; )
{
if ( DotProduct( m_Normals[i], normal ) > 0.99f )
return;
}
m_Normals.AddToTail( normal );
}
bool HasPositiveProjection( const Vector &vec )
{
for ( int i = m_Normals.Count(); --i >= 0; )
{
if ( DotProduct( m_Normals[i], vec ) > 0 )
return true;
}
return false;
}
// UNDONE: Handle the case better where we clamp to multiple planes
// and still have a projection, but don't exceed limitVel. Currently that will stop.
// when this is done, remove the ground exception below.
Vector ClampVector( const Vector &inVector, float limitVel )
{
if ( m_Normals.Count() > 2 )
{
for ( int i = 0; i < m_Normals.Count(); i++ )
{
if ( DotProduct(inVector, m_Normals[i]) > 0 )
{
return vec3_origin;
}
}
}
else
{
if ( m_Normals.Count() == 2 )
{
Vector crease;
CrossProduct( m_Normals[0], m_Normals[1], crease );
float dot = DotProduct( inVector, crease );
return crease * dot;
}
else if (m_Normals.Count() == 1)
{
float dot = DotProduct( inVector, m_Normals[0] );
if ( dot > limitVel )
{
return inVector + m_Normals[0]*(limitVel - dot);
}
}
}
return inVector;
}
private:
CUtlVectorFixed<Vector, MAX_LIST_NORMALS> m_Normals;
};
void CPlayerController::do_simulation_controller( IVP_Event_Sim *es,IVP_U_Vector<IVP_Core> *)
{
if ( !m_enable )
return;
IVP_Real_Object *pivp = m_pObject->GetObject();
IVP_Core *pCore = pivp->get_core();
Assert(!pCore->pinned && !pCore->physical_unmoveable);
// current situation
const IVP_U_Matrix *m_world_f_core = pCore->get_m_world_f_core_PSI();
const IVP_U_Point *cur_pos_ws = m_world_f_core->get_position();
IVP_U_Float_Point baseVelocity;
baseVelocity.set_to_zero();
// ---------------------------------------------------------
// Translation
// ---------------------------------------------------------
if ( m_pGround )
{
const IVP_U_Matrix *pMatrix = m_pGround->GetObject()->get_core()->get_m_world_f_core_PSI();
pMatrix->vmult4( &m_groundPosition, &m_targetPosition );
m_pGround->GetObject()->get_core()->get_surface_speed( &m_groundPosition, &baseVelocity );
pCore->speed.subtract( &baseVelocity );
}
IVP_U_Float_Point delta_position; delta_position.subtract( &m_targetPosition, cur_pos_ws);
if (!pivp->flags.shift_core_f_object_is_zero)
{
IVP_U_Float_Point shift_cs_os_ws;
m_world_f_core->vmult3( pivp->get_shift_core_f_object(), &shift_cs_os_ws);
delta_position.subtract( &shift_cs_os_ws );
}
IVP_DOUBLE qdist = delta_position.quad_length();
// UNDONE: This is totally bogus! Measure error using last known estimate
// not current position!
if ( m_forceTeleport || qdist > m_maxDeltaPosition * m_maxDeltaPosition )
{
if ( TryTeleportObject() )
return;
}
// float to allow stepping
const IVP_U_Point *pgrav = es->environment->get_gravity();
IVP_U_Float_Point gravSpeed;
gravSpeed.set_multiple( pgrav, es->delta_time );
if ( m_onground )
{
pCore->speed.subtract( &gravSpeed );
}
float fraction = 1.0;
if ( m_secondsToArrival > 0 )
{
fraction = es->delta_time / m_secondsToArrival;
if ( fraction > 1 )
{
fraction = 1;
}
}
if ( !m_updatedSinceLast )
{
// we haven't received an update from the game code since the last controller step
// This means we haven't gotten feedback integrated into the motion plan, so the error may be
// exaggerated. Assume that the first updated tick had valid information, and limit
// all subsequent ticks to the same size impulses.
// NOTE: Don't update the saved impulse - so any subsequent ticks will still have the last
// known good information.
float len = m_lastImpulse.real_length();
// cap the max speed to the length of the last known good impulse
IVP_U_Float_Point tmp;
tmp.set( len, len, len );
ComputeController( pCore->speed, delta_position, tmp, fraction / es->delta_time, m_dampFactor, NULL );
}
else
{
ComputeController( pCore->speed, delta_position, m_maxSpeed, fraction / es->delta_time, m_dampFactor, &m_lastImpulse );
}
pCore->speed.add( &baseVelocity );
m_updatedSinceLast = false;
// UNDONE: Assumes gravity points down
Vector lastImpulseHL;
ConvertPositionToHL( pCore->speed, lastImpulseHL );
IPhysicsFrictionSnapshot *pSnapshot = CreateFrictionSnapshot( pivp );
bool bGround = false;
float invMass = pivp->get_core()->get_inv_mass();
float limitVel = m_pushableSpeedLimit;
CNormalList normalList;
while (pSnapshot->IsValid())
{
Vector normal;
pSnapshot->GetSurfaceNormal( normal );
if ( normal.z < -0.7f )
{
bGround = true;
}
// remove this when clamp works better
if ( normal.z > -0.99f )
{
IPhysicsObject *pOther = pSnapshot->GetObject(1);
if ( !pOther->IsMoveable() || pOther->GetMass() > m_pushableMassLimit )
{
limitVel = 0.0f;
}
float pushSpeed = DotProduct( lastImpulseHL, normal );
float contactVel = pSnapshot->GetNormalForce() * invMass;
float pushTotal = pushSpeed + contactVel;
if ( pushTotal > limitVel )
{
normalList.AddNormal( normal );
}
}
pSnapshot->NextFrictionData();
}
DestroyFrictionSnapshot( pSnapshot );
Vector clamped = normalList.ClampVector( lastImpulseHL, limitVel );
Vector limit = clamped - lastImpulseHL;
IVP_U_Float_Point limitIVP;
ConvertPositionToIVP( limit, limitIVP );
pivp->get_core()->speed.add( &limitIVP );
m_lastImpulse.add( &limitIVP );
if ( bGround )
{
float gravDt = gravSpeed.real_length();
// moving down? Press down with full gravity and no more
if ( m_lastImpulse.k[1] >= 0 )
{
float delta = gravDt - m_lastImpulse.k[1];
pivp->get_core()->speed.k[1] += delta;
m_lastImpulse.k[1] += delta;
}
}
// if we have time left, subtract it off
m_secondsToArrival -= es->delta_time;
if ( m_secondsToArrival < 0 )
{
m_secondsToArrival = 0;
}
}
void CPlayerController::SetEventHandler( IPhysicsPlayerControllerEvent *handler )
{
m_handler = handler;
}
void CPlayerController::Update( const Vector& position, const Vector& velocity, float secondsToArrival, bool onground, IPhysicsObject *ground )
{
IVP_U_Point targetPositionIVP;
IVP_U_Float_Point targetSpeedIVP;
ConvertPositionToIVP( position, targetPositionIVP );
ConvertPositionToIVP( velocity, targetSpeedIVP );
m_updatedSinceLast = true;
// if the object hasn't moved, abort
if ( targetSpeedIVP.quad_distance_to( &m_currentSpeed ) < 1e-6 )
{
if ( targetPositionIVP.quad_distance_to( &m_targetPosition ) < 1e-6 )
{
return;
}
}
m_targetPosition.set( &targetPositionIVP );
m_secondsToArrival = secondsToArrival < 0 ? 0 : secondsToArrival;
// Sanity check to make sure the position is good.
#ifdef _DEBUG
float large = 1024 * 512;
Assert( m_targetPosition.k[0] >= -large && m_targetPosition.k[0] <= large );
Assert( m_targetPosition.k[1] >= -large && m_targetPosition.k[1] <= large );
Assert( m_targetPosition.k[2] >= -large && m_targetPosition.k[2] <= large );
#endif
m_currentSpeed.set( &targetSpeedIVP );
IVP_Real_Object *pivp = m_pObject->GetObject();
IVP_Core *pCore = pivp->get_core();
IVP_Environment *pEnv = pivp->get_environment();
pEnv->get_controller_manager()->ensure_core_in_simulation(pCore);
m_enable = true;
// m_onground makes this object anti-grav
// UNDONE: Re-evaluate this
m_onground = false;//onground;
if ( velocity.LengthSqr() <= 0.1f )
{
// no input velocity, just go where physics takes you.
m_enable = false;
ground = NULL;
}
else
{
MaxSpeed( velocity );
}
CPhysicsObject *pGroundObject = static_cast<CPhysicsObject *>(ground);
SetGround( pGroundObject );
if ( m_pGround )
{
const IVP_U_Matrix *pMatrix = m_pGround->GetObject()->get_core()->get_m_world_f_core_PSI();
pMatrix->vimult4( &m_targetPosition, &m_groundPosition );
}
}
void CPlayerController::MaxSpeed( const Vector &velocity )
{
IVP_Core *pCore = m_pObject->GetObject()->get_core();
IVP_U_Float_Point ivpVel;
ConvertPositionToIVP( velocity, ivpVel );
IVP_U_Float_Point available = ivpVel;
// normalize and save length
float length = ivpVel.real_length_plus_normize();
IVP_U_Float_Point baseVelocity;
baseVelocity.set_to_zero();
float dot = ivpVel.dot_product( &pCore->speed );
if ( dot > 0 )
{
ivpVel.mult( dot * length );
available.subtract( &ivpVel );
}
pCore->speed.add( &baseVelocity );
IVP_Float_PointAbs( m_maxSpeed, available );
}
void CPlayerController::GetShadowVelocity( Vector *velocity )
{
IVP_Core *core = m_pObject->GetObject()->get_core();
if ( velocity )
{
IVP_U_Float_Point speed;
speed.add( &core->speed, &core->speed_change );
if ( m_pGround )
{
IVP_U_Float_Point baseVelocity;
m_pGround->GetObject()->get_core()->get_surface_speed( &m_groundPosition, &baseVelocity );
speed.subtract( &baseVelocity );
}
ConvertPositionToHL( speed, *velocity );
}
}
bool CPlayerController::IsInContact( void )
{
IVP_Real_Object *pivp = m_pObject->GetObject();
if ( !pivp->flags.collision_detection_enabled )
return false;
IVP_Synapse_Friction *pfriction = pivp->get_first_friction_synapse();
while ( pfriction )
{
extern IVP_Real_Object *GetOppositeSynapseObject( IVP_Synapse_Friction *pfriction );
IVP_Real_Object *pobj = GetOppositeSynapseObject( pfriction );
if ( pobj->flags.collision_detection_enabled )
{
// skip if this is a static object
if ( !pobj->get_core()->physical_unmoveable && !pobj->get_core()->pinned )
{
CPhysicsObject *pPhys = static_cast<CPhysicsObject *>(pobj->client_data);
// If this is a game-controlled shadow object, then skip it.
// otherwise, we're in contact with something physically simulated
if ( !pPhys->IsControlledByGame() )
return true;
}
}
pfriction = pfriction->get_next();
}
return false;
}
IPhysicsPlayerController *CreatePlayerController( CPhysicsObject *pObject )
{
return new CPlayerController( pObject );
}
void DestroyPlayerController( IPhysicsPlayerController *pController )
{
delete pController;
}
void QuaternionDiff( const IVP_U_Quat &p, const IVP_U_Quat &q, IVP_U_Quat &qt )
{
IVP_U_Quat q2;
// decide if one of the quaternions is backwards
q2.set_invert_unit_quat( &q );
qt.set_mult_quat( &q2, &p );
qt.normize_quat();
}
void QuaternionAxisAngle( const IVP_U_Quat &q, Vector &axis, float &angle )
{
angle = 2 * acos(q.w);
if ( angle > M_PI )
{
angle -= 2*M_PI;
}
axis.Init( q.x, q.y, q.z );
VectorNormalize( axis );
}
void GetObjectPosition_IVP( IVP_U_Point &origin, IVP_Real_Object *pivp )
{
const IVP_U_Matrix *m_world_f_core = pivp->get_core()->get_m_world_f_core_PSI();
origin.set( m_world_f_core->get_position() );
if (!pivp->flags.shift_core_f_object_is_zero)
{
IVP_U_Float_Point shift_cs_os_ws;
m_world_f_core->vmult3( pivp->get_shift_core_f_object(), &shift_cs_os_ws );
origin.add(&shift_cs_os_ws);
}
}
bool IsZeroVector( const IVP_U_Point &vec )
{
return (vec.k[0] == 0.0 && vec.k[1] == 0.0 && vec.k[2] == 0.0 ) ? true : false;
}
float ComputeShadowControllerIVP( IVP_Real_Object *pivp, shadowcontrol_params_t &params, float secondsToArrival, float dt )
{
// resample fraction
// This allows us to arrive at the target at the requested time
float fraction = 1.0;
if ( secondsToArrival > 0 )
{
fraction = dt / secondsToArrival;
if ( fraction > 1 )
{
fraction = 1;
}
}
secondsToArrival -= dt;
if ( secondsToArrival < 0 )
{
secondsToArrival = 0;
}
if ( fraction <= 0 )
return secondsToArrival;
// ---------------------------------------------------------
// Translation
// ---------------------------------------------------------
IVP_U_Point positionIVP;
GetObjectPosition_IVP( positionIVP, pivp );
IVP_U_Float_Point delta_position;
delta_position.subtract( &params.targetPosition, &positionIVP);
// BUGBUG: Save off velocities and estimate final positions
// measure error against these final sets
// also, damp out 100% saved velocity, use max additional impulses
// to correct error and damp out error velocity
// extrapolate position
if ( params.teleportDistance > 0 )
{
IVP_DOUBLE qdist;
if ( !IsZeroVector(params.lastPosition) )
{
IVP_U_Float_Point tmpDelta;
tmpDelta.subtract( &positionIVP, &params.lastPosition );
qdist = tmpDelta.quad_length();
}
else
{
// UNDONE: This is totally bogus! Measure error using last known estimate
// not current position!
qdist = delta_position.quad_length();
}
if ( qdist > params.teleportDistance * params.teleportDistance )
{
if ( pivp->is_collision_detection_enabled() )
{
pivp->enable_collision_detection( IVP_FALSE );
pivp->beam_object_to_new_position( &params.targetRotation, &params.targetPosition, IVP_TRUE );
pivp->enable_collision_detection( IVP_TRUE );
}
else
{
pivp->beam_object_to_new_position( &params.targetRotation, &params.targetPosition, IVP_TRUE );
}
delta_position.set_to_zero();
}
}
float invDt = 1.0f / dt;
IVP_Core *pCore = pivp->get_core();
ComputeController( pCore->speed, delta_position, params.maxSpeed, params.maxDampSpeed, fraction * invDt, params.dampFactor, &params.lastImpulse );
params.lastPosition.add_multiple( &positionIVP, &pCore->speed, dt );
IVP_U_Float_Point deltaAngles;
// compute rotation offset
IVP_U_Quat deltaRotation;
QuaternionDiff( params.targetRotation, pCore->q_world_f_core_next_psi, deltaRotation );
// convert offset to angular impulse
Vector axis;
float angle;
QuaternionAxisAngle( deltaRotation, axis, angle );
VectorNormalize(axis);
deltaAngles.k[0] = axis.x * angle;
deltaAngles.k[1] = axis.y * angle;
deltaAngles.k[2] = axis.z * angle;
ComputeController( pCore->rot_speed, deltaAngles, params.maxAngular, params.maxDampAngular, fraction * invDt, params.dampFactor, NULL );
return secondsToArrival;
}
void ConvertShadowControllerToIVP( const hlshadowcontrol_params_t &in, shadowcontrol_params_t &out )
{
ConvertPositionToIVP( in.targetPosition, out.targetPosition );
ConvertRotationToIVP( in.targetRotation, out.targetRotation );
out.teleportDistance = ConvertDistanceToIVP( in.teleportDistance );
out.maxSpeed = ConvertDistanceToIVP( in.maxSpeed );
out.maxDampSpeed = ConvertDistanceToIVP( in.maxDampSpeed );
out.maxAngular = ConvertAngleToIVP( in.maxAngular );
out.maxDampAngular = ConvertAngleToIVP( in.maxDampAngular );
out.dampFactor = in.dampFactor;
}
void ConvertShadowControllerToHL( const shadowcontrol_params_t &in, hlshadowcontrol_params_t &out )
{
ConvertPositionToHL( in.targetPosition, out.targetPosition );
ConvertRotationToHL( in.targetRotation, out.targetRotation );
out.teleportDistance = ConvertDistanceToHL( in.teleportDistance );
out.maxSpeed = ConvertDistanceToHL( in.maxSpeed );
out.maxDampSpeed = ConvertDistanceToHL( in.maxDampSpeed );
out.maxAngular = ConvertAngleToHL( in.maxAngular );
out.maxDampAngular = ConvertAngleToHL( in.maxDampAngular );
out.dampFactor = in.dampFactor;
}
float ComputeShadowControllerHL( CPhysicsObject *pObject, const hlshadowcontrol_params_t &params, float secondsToArrival, float dt )
{
shadowcontrol_params_t ivpParams;
ConvertShadowControllerToIVP( params, ivpParams );
return ComputeShadowControllerIVP( pObject->GetObject(), ivpParams, secondsToArrival, dt );
}
class CShadowController : public IVP_Controller_Independent, public IPhysicsShadowController, public CAlignedNewDelete<16>
{
public:
CShadowController();
CShadowController( CPhysicsObject *pObject, bool allowTranslation, bool allowRotation );
~CShadowController( void );
// ipion interfaces
void do_simulation_controller( IVP_Event_Sim *es,IVP_U_Vector<IVP_Core> *cores);
virtual IVP_CONTROLLER_PRIORITY get_controller_priority() { return IVP_CP_MOTION; }
virtual const char *get_controller_name() { return "vphysics:shadow"; }
void SetObject( IPhysicsObject *pObject );
void Update( const Vector &position, const QAngle &angles, float secondsToArrival );
void MaxSpeed( float maxSpeed, float maxAngularSpeed );
virtual void StepUp( float height );
virtual void SetTeleportDistance( float teleportDistance );
virtual bool AllowsTranslation() { return m_allowsTranslation; }
virtual bool AllowsRotation() { return m_allowsRotation; }
virtual void GetLastImpulse( Vector *pOut )
{
ConvertPositionToHL( m_shadow.lastImpulse, *pOut );
}
bool IsEnabled() { return m_enabled; }
void Enable( bool bEnable )
{
m_enabled = bEnable;
}
void WriteToTemplate( vphysics_save_cshadowcontroller_t &controllerTemplate );
void InitFromTemplate( const vphysics_save_cshadowcontroller_t &controllerTemplate );
virtual void SetPhysicallyControlled( bool isPhysicallyControlled ) { m_isPhysicallyControlled = isPhysicallyControlled; }
virtual bool IsPhysicallyControlled() { return m_isPhysicallyControlled; }
virtual void UseShadowMaterial( bool bUseShadowMaterial )
{
if ( !m_pObject )
return;
int current = m_pObject->GetMaterialIndexInternal();
int target = bUseShadowMaterial ? MATERIAL_INDEX_SHADOW : m_savedMaterialIndex;
if ( target != current )
{
m_pObject->SetMaterialIndex( target );
}
}
virtual void ObjectMaterialChanged( int materialIndex )
{
if ( !m_pObject )
return;
m_savedMaterialIndex = materialIndex;
}
//Basically get the last inputs to IPhysicsShadowController::Update(), returns last input to timeOffset in Update()
virtual float GetTargetPosition( Vector *pPositionOut, QAngle *pAnglesOut );
virtual float GetTeleportDistance( void );
virtual void GetMaxSpeed( float *pMaxSpeedOut, float *pMaxAngularSpeedOut );
private:
void AttachObject( void );
void DetachObject( void );
shadowcontrol_params_t m_shadow;
IVP_U_Float_Point m_saveRot;
IVP_U_Float_Point m_savedRI;
CPhysicsObject *m_pObject;
float m_secondsToArrival;
float m_savedMass;
unsigned int m_savedFlags;
unsigned short m_savedMaterialIndex;
bool m_enabled : 1;
bool m_allowsTranslation : 1;
bool m_allowsRotation : 1;
bool m_isPhysicallyControlled : 1;
};
CShadowController::CShadowController()
{
m_shadow.targetPosition.set_to_zero();
m_shadow.targetRotation.init();
}
CShadowController::CShadowController( CPhysicsObject *pObject, bool allowTranslation, bool allowRotation )
{
m_pObject = pObject;
m_shadow.dampFactor = 1.0f;
m_shadow.teleportDistance = 0;
m_shadow.maxDampSpeed = 0;
m_shadow.maxDampAngular = 0;
m_shadow.targetPosition.set_to_zero();
m_shadow.targetRotation.init();
m_allowsTranslation = allowTranslation;
m_allowsRotation = allowRotation;
m_isPhysicallyControlled = false;
m_enabled = false;
AttachObject();
}
CShadowController::~CShadowController( void )
{
DetachObject();
}
void CShadowController::AttachObject( void )
{
IVP_Real_Object *pivp = m_pObject->GetObject();
IVP_Core *pCore = pivp->get_core();
m_saveRot = pCore->rot_speed_damp_factor;
m_savedRI = *pCore->get_rot_inertia();
m_savedMass = pCore->get_mass();
m_savedMaterialIndex = m_pObject->GetMaterialIndexInternal();
Vector position;
QAngle angles;
m_pObject->GetPosition( &position, &angles );
ConvertPositionToIVP( position, m_shadow.targetPosition );
ConvertRotationToIVP( angles, m_shadow.targetRotation );
UseShadowMaterial( true );
pCore->rot_speed_damp_factor = IVP_U_Float_Point( 100, 100, 100 );
if ( !m_allowsRotation )
{
IVP_U_Float_Point ri( 1e14f, 1e14f, 1e14f );
pCore->set_rotation_inertia( &ri );
}
if ( !m_allowsTranslation )
{
m_pObject->SetMass( VPHYSICS_MAX_MASS );
//pCore->inv_rot_inertia.hesse_val = 0.0f;
m_pObject->EnableGravity( false );
}
m_savedFlags = m_pObject->CallbackFlags();
unsigned int flags = m_savedFlags | CALLBACK_SHADOW_COLLISION;
flags &= ~CALLBACK_GLOBAL_FRICTION;
flags &= ~CALLBACK_GLOBAL_COLLIDE_STATIC;
m_pObject->SetCallbackFlags( flags );
m_pObject->EnableDrag( false );
pCore->calc_calc();
pivp->get_environment()->get_controller_manager()->add_controller_to_core( this, pCore );
m_shadow.lastPosition.set_to_zero();
}
void CShadowController::DetachObject( void )
{
IVP_Real_Object *pivp = m_pObject->GetObject();
IVP_Core *pCore = pivp->get_core();
// don't bother if we're just doing this to delete the object
if ( !(m_pObject->GetCallbackFlags() & CALLBACK_MARKED_FOR_DELETE) )
{
pCore->rot_speed_damp_factor = m_saveRot;
pCore->set_mass( m_savedMass );
m_pObject->SetCallbackFlags( m_savedFlags );
m_pObject->EnableDrag( true );
m_pObject->EnableGravity( true );
UseShadowMaterial( false );
pCore->set_rotation_inertia( &m_savedRI ); // this calls calc_calc()
}
m_pObject = NULL;
pivp->get_environment()->get_controller_manager()->remove_controller_from_core( this, pCore );
}
void CShadowController::SetObject( IPhysicsObject *pObject )
{
CPhysicsObject *obj = (CPhysicsObject *)pObject;
if ( obj == m_pObject )
return;
DetachObject();
m_pObject = obj;
AttachObject();
}
void CShadowController::StepUp( float height )
{
Vector step( 0, 0, height );
IVP_Real_Object *pIVP = m_pObject->GetObject();
IVP_U_Quat world_f_object;
IVP_U_Point positionIVP, deltaIVP;
ConvertPositionToIVP( step, deltaIVP );
pIVP->get_quat_world_f_object_AT( &world_f_object, &positionIVP );
positionIVP.add( &deltaIVP );
pIVP->beam_object_to_new_position( &world_f_object, &positionIVP, IVP_TRUE );
}
void CShadowController::SetTeleportDistance( float teleportDistance )
{
m_shadow.teleportDistance = ConvertDistanceToIVP( teleportDistance );
}
float CShadowController::GetTeleportDistance( void )
{
return ConvertDistanceToHL( m_shadow.teleportDistance );
}
void CShadowController::do_simulation_controller( IVP_Event_Sim *es,IVP_U_Vector<IVP_Core> *)
{
if ( IsEnabled() )
{
IVP_Real_Object *pivp = m_pObject->GetObject();
Assert(!pivp->get_core()->pinned && !pivp->get_core()->physical_unmoveable);
ComputeShadowControllerIVP( pivp, m_shadow, m_secondsToArrival, es->delta_time );
if ( m_allowsTranslation )
{
// UNDONE: Assumes gravity points down
const IVP_U_Point *pgrav = pivp->get_environment()->get_gravity();
float gravDt = pgrav->k[1] * es->delta_time;
if ( m_shadow.lastImpulse.k[1] > gravDt )
{
if ( IsOnGround( pivp ) )
{
float delta = gravDt - m_shadow.lastImpulse.k[1];
pivp->get_core()->speed.k[1] += delta;
m_shadow.lastImpulse.k[1] += delta;
}
}
}
// if we have time left, subtract it off
m_secondsToArrival -= es->delta_time;
if ( m_secondsToArrival < 0 )
{
m_secondsToArrival = 0;
}
}
else
{
m_shadow.lastPosition.set_to_zero();
}
}
// NOTE: This isn't a test for equivalent orientations, it's a test for calling update
// with EXACTLY the same data repeatedly
static bool IsEqual( const IVP_U_Point &pt0, const IVP_U_Point &pt1 )
{
return pt0.quad_distance_to( &pt1 ) < 1e-8f ? true : false;
}
// NOTE: This isn't a test for equivalent orientations, it's a test for calling update
// with EXACTLY the same data repeatedly
static bool IsEqual( const IVP_U_Quat &pt0, const IVP_U_Quat &pt1 )
{
float delta = fabs(pt0.x - pt1.x);
delta += fabs(pt0.y - pt1.y);
delta += fabs(pt0.z - pt1.z);
delta += fabs(pt0.w - pt1.w);
return delta < 1e-8f ? true : false;
}
void CShadowController::Update( const Vector &position, const QAngle &angles, float secondsToArrival )
{
IVP_U_Point targetPosition = m_shadow.targetPosition;
IVP_U_Quat targetRotation = m_shadow.targetRotation;
ConvertPositionToIVP( position, m_shadow.targetPosition );
m_secondsToArrival = secondsToArrival < 0 ? 0 : secondsToArrival;
ConvertRotationToIVP( angles, m_shadow.targetRotation );
Enable( true );
if ( IsEqual( targetPosition, m_shadow.targetPosition ) && IsEqual( targetRotation, m_shadow.targetRotation ) )
return;
m_pObject->Wake();
}
float CShadowController::GetTargetPosition( Vector *pPositionOut, QAngle *pAnglesOut )
{
if( pPositionOut )
ConvertPositionToHL( m_shadow.targetPosition, *pPositionOut );
if( pAnglesOut )
ConvertRotationToHL( m_shadow.targetRotation, *pAnglesOut );
return m_secondsToArrival;
}
void CShadowController::MaxSpeed( float maxSpeed, float maxAngularSpeed )
{
// UNDONE: Turn this on when shadow controllers are having velocity updated per frame
// right now this has the effect of making dampspeed zero by default.
#if 0
IVP_Core *pCore = m_pObject->GetObject()->get_core();
{
// limit additional velocity to that which is not amplifying the current velocity
float availableSpeed = ConvertDistanceToIVP( maxSpeed );
float currentSpeed = pCore->speed.real_length();
m_shadow.maxDampSpeed = min(currentSpeed, availableSpeed);
m_shadow.maxSpeed = availableSpeed - m_shadow.maxDampSpeed;
}
{
// limit additional velocity to that which is not amplifying the current velocity
float availableAngularSpeed = ConvertAngleToIVP( maxAngularSpeed );
float currentAngularSpeed = pCore->rot_speed.real_length();
m_shadow.maxDampAngular = min(currentAngularSpeed, availableAngularSpeed);
m_shadow.maxAngular = availableAngularSpeed - m_shadow.maxDampAngular;
}
#else
m_shadow.maxSpeed = maxSpeed;
m_shadow.maxDampSpeed = maxSpeed;
m_shadow.maxAngular = maxAngularSpeed;
m_shadow.maxDampAngular = maxAngularSpeed;
#endif
}
void CShadowController::GetMaxSpeed( float *pMaxSpeedOut, float *pMaxAngularSpeedOut )
{
if( pMaxSpeedOut )
*pMaxSpeedOut = m_shadow.maxSpeed;
if( pMaxAngularSpeedOut )
*pMaxAngularSpeedOut = m_shadow.maxAngular;
}
struct vphysics_save_shadowcontrolparams_t : public hlshadowcontrol_params_t
{
DECLARE_SIMPLE_DATADESC();
};
BEGIN_SIMPLE_DATADESC( vphysics_save_shadowcontrolparams_t )
DEFINE_FIELD( targetPosition, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( targetRotation, FIELD_VECTOR ),
DEFINE_FIELD( maxSpeed, FIELD_FLOAT ),
DEFINE_FIELD( maxDampSpeed, FIELD_FLOAT ),
DEFINE_FIELD( maxAngular, FIELD_FLOAT ),
DEFINE_FIELD( maxDampAngular, FIELD_FLOAT ),
DEFINE_FIELD( dampFactor, FIELD_FLOAT ),
DEFINE_FIELD( teleportDistance, FIELD_FLOAT ),
END_DATADESC()
struct vphysics_save_cshadowcontroller_t
{
CPhysicsObject *pObject;
float secondsToArrival;
IVP_U_Float_Point saveRot;
IVP_U_Float_Point savedRI;
IVP_U_Float_Point currentSpeed;
float savedMass;
int savedMaterial;
unsigned int savedFlags;
bool enable;
bool allowPhysicsMovement;
bool allowPhysicsRotation;
bool isPhysicallyControlled;
hlshadowcontrol_params_t shadowParams;
DECLARE_SIMPLE_DATADESC();
};
BEGIN_SIMPLE_DATADESC( vphysics_save_cshadowcontroller_t )
//DEFINE_VPHYSPTR( pObject ),
DEFINE_FIELD( secondsToArrival, FIELD_FLOAT ),
DEFINE_ARRAY( saveRot.k, FIELD_FLOAT, 3 ),
DEFINE_ARRAY( savedRI.k, FIELD_FLOAT, 3 ),
DEFINE_ARRAY( currentSpeed.k, FIELD_FLOAT, 3 ),
DEFINE_FIELD( savedMass, FIELD_FLOAT ),
DEFINE_FIELD( savedFlags, FIELD_INTEGER ),
DEFINE_CUSTOM_FIELD( savedMaterial, MaterialIndexDataOps() ),
DEFINE_FIELD( enable, FIELD_BOOLEAN ),
DEFINE_FIELD( allowPhysicsMovement, FIELD_BOOLEAN ),
DEFINE_FIELD( allowPhysicsRotation, FIELD_BOOLEAN ),
DEFINE_FIELD( isPhysicallyControlled, FIELD_BOOLEAN ),
DEFINE_EMBEDDED_OVERRIDE( shadowParams, vphysics_save_shadowcontrolparams_t ),
END_DATADESC()
void CShadowController::WriteToTemplate( vphysics_save_cshadowcontroller_t &controllerTemplate )
{
controllerTemplate.pObject = m_pObject;
controllerTemplate.secondsToArrival = m_secondsToArrival;
controllerTemplate.saveRot = m_saveRot;
controllerTemplate.savedRI = m_savedRI;
controllerTemplate.savedMass = m_savedMass;
controllerTemplate.savedFlags = m_savedFlags;
controllerTemplate.savedMaterial = m_savedMaterialIndex;
controllerTemplate.enable = IsEnabled();
controllerTemplate.allowPhysicsMovement = m_allowsTranslation;
controllerTemplate.allowPhysicsRotation = m_allowsRotation;
controllerTemplate.isPhysicallyControlled = m_isPhysicallyControlled;
ConvertShadowControllerToHL( m_shadow, controllerTemplate.shadowParams );
}
void CShadowController::InitFromTemplate( const vphysics_save_cshadowcontroller_t &controllerTemplate )
{
m_pObject = controllerTemplate.pObject;
m_secondsToArrival = controllerTemplate.secondsToArrival;
m_saveRot = controllerTemplate.saveRot;
m_savedRI = controllerTemplate.savedRI;
m_savedMass = controllerTemplate.savedMass;
m_savedFlags = controllerTemplate.savedFlags;
m_savedMaterialIndex = controllerTemplate.savedMaterial;
Enable( controllerTemplate.enable );
m_allowsTranslation = controllerTemplate.allowPhysicsMovement;
m_allowsRotation = controllerTemplate.allowPhysicsRotation;
m_isPhysicallyControlled = controllerTemplate.isPhysicallyControlled;
ConvertShadowControllerToIVP( controllerTemplate.shadowParams, m_shadow );
m_pObject->GetObject()->get_environment()->get_controller_manager()->add_controller_to_core( this, m_pObject->GetObject()->get_core() );
}
IPhysicsShadowController *CreateShadowController( CPhysicsObject *pObject, bool allowTranslation, bool allowRotation )
{
return new CShadowController( pObject, allowTranslation, allowRotation );
}
bool SavePhysicsShadowController( const physsaveparams_t &params, IPhysicsShadowController *pIShadow )
{
vphysics_save_cshadowcontroller_t controllerTemplate;
memset( &controllerTemplate, 0, sizeof(controllerTemplate) );
CShadowController *pShadowController = (CShadowController *)pIShadow;
pShadowController->WriteToTemplate( controllerTemplate );
params.pSave->WriteAll( &controllerTemplate );
return true;
}
bool RestorePhysicsShadowController( const physrestoreparams_t &params, IPhysicsShadowController **ppShadowController )
{
return false;
}
bool RestorePhysicsShadowControllerInternal( const physrestoreparams_t &params, IPhysicsShadowController **ppShadowController, CPhysicsObject *pObject )
{
vphysics_save_cshadowcontroller_t controllerTemplate;
memset( &controllerTemplate, 0, sizeof(controllerTemplate) );
params.pRestore->ReadAll( &controllerTemplate );
// HACKHACK: pass this in
controllerTemplate.pObject = pObject;
CShadowController *pShadow = new CShadowController();
pShadow->InitFromTemplate( controllerTemplate );
*ppShadowController = pShadow;
return true;
}
bool SavePhysicsPlayerController( const physsaveparams_t &params, CPlayerController *pPlayerController )
{
return false;
}
bool RestorePhysicsPlayerController( const physrestoreparams_t &params, CPlayerController **ppPlayerController )
{
return false;
}
//HACKHACK: The physics object transfer system needs to temporarily detach a shadow controller from an object, then reattach without other repercussions
void ControlPhysicsShadowControllerAttachment_Silent( IPhysicsShadowController *pController, IVP_Real_Object *pivp, bool bAttach )
{
if( bAttach )
{
pivp->get_environment()->get_controller_manager()->add_controller_to_core( (CShadowController *)pController, pivp->get_core() );
}
else
{
pivp->get_environment()->get_controller_manager()->remove_controller_from_core( (CShadowController *)pController, pivp->get_core() );
}
}
void ControlPhysicsPlayerControllerAttachment_Silent( IPhysicsPlayerController *pController, IVP_Real_Object *pivp, bool bAttach )
{
if( bAttach )
{
pivp->get_environment()->get_controller_manager()->add_controller_to_core( (CPlayerController *)pController, pivp->get_core() );
}
else
{
pivp->get_environment()->get_controller_manager()->remove_controller_from_core( (CPlayerController *)pController, pivp->get_core() );
}
}