etqw-sdk/source/game/physics/Physics_JetPack.cpp

1738 lines
50 KiB
C++
Raw Normal View History

2008-05-29 00:00:00 +00:00
// Copyright (C) 2007 Id Software, Inc.
//
#include "../precompiled.h"
#pragma hdrstop
#if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE )
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#include "Physics_JetPack.h"
#include "../Entity.h"
#include "../Actor.h"
#include "../vehicles/JetPack.h"
#define CHECKCURRENT for ( int index = 0; index < 3; index++ ) { if ( FLOAT_IS_NAN( current.origin[ index ] ) ) { assert( false ); } }
CLASS_DECLARATION( idPhysics_Actor, sdPhysics_JetPack )
END_CLASS
#define ENABLE_JP_FLOAT_CHECKS
#if defined( ENABLE_JP_FLOAT_CHECKS )
#undef FLOAT_CHECK_BAD
#undef VEC_CHECK_BAD
#define FLOAT_CHECK_BAD( x ) \
if ( FLOAT_IS_NAN( x ) ) gameLocal.Error( "Bad floating point number in %s(%i): " #x " is NAN", __FILE__, __LINE__ ); \
if ( FLOAT_IS_INF( x ) ) gameLocal.Error( "Bad floating point number in %s(%i): " #x " is INF", __FILE__, __LINE__ ); \
if ( FLOAT_IS_IND( x ) ) gameLocal.Error( "Bad floating point number in %s(%i): " #x " is IND", __FILE__, __LINE__ ); \
if ( FLOAT_IS_DENORMAL( x ) ) gameLocal.Error( "Bad floating point number in %s(%i): " #x " is DEN", __FILE__, __LINE__ ); \
#define VEC_CHECK_BAD( vec ) FLOAT_CHECK_BAD( ( vec ).x ); FLOAT_CHECK_BAD( ( vec ).y ); FLOAT_CHECK_BAD( ( vec ).z );
#define MAT_CHECK_BAD( m ) VEC_CHECK_BAD( m[ 0 ] ); VEC_CHECK_BAD( m[ 1 ] ); VEC_CHECK_BAD( m[ 2 ] );
#define ANG_CHECK_BAD( ang ) FLOAT_CHECK_BAD( ( ang ).pitch ); FLOAT_CHECK_BAD( ( ang ).roll ); FLOAT_CHECK_BAD( ( ang ).yaw );
#else
#define MAT_CHECK_BAD( m )
#endif
/*
=====================
sdJetPackPhysicsNetworkData::MakeDefault
=====================
*/
void sdJetPackPhysicsNetworkData::MakeDefault( void ) {
origin = vec3_origin;
velocity = vec3_zero;
movementFlags = 0;
}
/*
=====================
sdJetPackPhysicsNetworkData::Write
=====================
*/
void sdJetPackPhysicsNetworkData::Write( idFile* file ) const {
file->WriteVec3( origin );
file->WriteVec3( velocity );
file->WriteInt( movementFlags );
}
/*
=====================
sdJetPackPhysicsNetworkData::Read
=====================
*/
void sdJetPackPhysicsNetworkData::Read( idFile* file ) {
file->ReadVec3( origin );
file->ReadVec3( velocity );
file->ReadInt( movementFlags );
origin.FixDenormals();
velocity.FixDenormals();
}
/*
=====================
sdJetPackPhysicsBroadcastData::MakeDefault
=====================
*/
void sdJetPackPhysicsBroadcastData::MakeDefault( void ) {
pushVelocity = vec3_zero;
}
/*
=====================
sdJetPackPhysicsBroadcastData::Write
=====================
*/
void sdJetPackPhysicsBroadcastData::Write( idFile* file ) const {
file->WriteVec3( pushVelocity );
}
/*
=====================
sdJetPackPhysicsBroadcastData::Read
=====================
*/
void sdJetPackPhysicsBroadcastData::Read( idFile* file ) {
file->ReadVec3( pushVelocity );
}
/*
=====================
sdPhysics_JetPack::EvaluateContacts
=====================
*/
bool sdPhysics_JetPack::EvaluateContacts( CLIP_DEBUG_PARMS_DECLARATION_ONLY ) {
ClearContacts();
idVec3 down;
down = current.origin + gravityNormal; // * CONTACT_EPSILON;
gameLocal.clip.Translation( CLIP_DEBUG_PARMS groundTrace, current.origin, down, clipModel, clipModel->GetAxis(), clipMask | CONTENTS_WATER, self );
// add the ground trace as a contact
if ( groundTrace.fraction < 1.0f ) {
contacts.SetNum( 1, false );
contacts[ 0 ] = groundTrace.c;
} else {
contacts.SetNum( 0, false );
}
AddContactEntitiesForContacts();
return contacts.Num() != 0;
}
/*
=====================
sdPhysics_JetPack::CheckGround
=====================
*/
void sdPhysics_JetPack::CheckGround( void ) {
jetPackPState_t& state = current;
groundTraceValid = true;
EvaluateContacts( CLIP_DEBUG_PARMS_ONLY );
if ( groundTrace.fraction == 1.0f ) {
state.onGround = false;
state.onWater = false;
groundEntityPtr = NULL;
return;
}
groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ];
// HACK: Horrible hack needed because at present groundTrace.c.contents isn't always reliable :|
if ( groundTrace.c.contents == -1 ) {
idPhysics* physics = groundEntityPtr->GetPhysics();
if ( physics != NULL ) {
groundTrace.c.contents = physics->GetContents( groundTrace.c.id );
} else {
groundTrace.c.contents = 0;
}
}
if ( groundTrace.c.contents & CONTENTS_WATER ) {
state.onGround = false;
state.onWater = true;
} else {
state.onGround = true;
state.onWater = false;
// let the entity know about the collision
groundEntityPtr->Hit( groundTrace, state.velocity, self );
self->Collide( groundTrace, state.velocity, -1 );
}
}
/*
================
sdPhysics_JetPack::sdPhysics_JetPack
================
*/
sdPhysics_JetPack::sdPhysics_JetPack( void ) {
memset( &current, 0, sizeof( current ) );
saved = current;
maxStepHeight = 18.0f;
noImpact = false;
memset( &command, 0, sizeof( command ) );
viewAngles.Zero();
movementAllowed = true;
boost = 0.0f;
groundTraceValid = false;
maxSpeed = 320.0f;
maxBoostSpeed = 500.0f;
walkForceScale = 0.4f;
kineticFriction = 10.0f;
jumpForce = 4800.0f;
boostForce = 1200.0f;
fanForce.Zero();
addedVelocity.Zero();
disableBoost = false;
waterFraction = 0.0f;
jetPackSelf = NULL;
shotClipModel = NULL;
}
/*
================
sdPhysics_JetPack::~sdPhysics_JetPack
================
*/
sdPhysics_JetPack::~sdPhysics_JetPack( void ) {
if ( shotClipModel != NULL ) {
gameLocal.clip.DeleteClipModel( shotClipModel );
shotClipModel = NULL;
}
}
/*
================
sdPhysics_JetPack::SetClipModel
================
*/
void sdPhysics_JetPack::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) {
assert( self );
assert( model ); // a clip model is required
assert( model->IsTraceModel() ); // and it should be a trace model
assert( density > 0.0f ); // density should be valid
if ( id == 0 ) {
idPhysics_Actor::SetClipModel( model, density, 0, freeOld );
} else {
if ( shotClipModel != NULL && shotClipModel != model && freeOld ) {
gameLocal.clip.DeleteClipModel( shotClipModel );
}
shotClipModel = model;
LinkShotModel();
}
}
/*
================
sdPhysics_JetPack::LinkShotModel
================
*/
void sdPhysics_JetPack::LinkShotModel( void ) {
if ( shotClipModel != NULL ) {
// calculate the axis
idAngles angles = self->GetAxis().ToAngles();
angles.pitch = 0.0f;
angles.roll = 0.0f;
shotClipModel->Link( gameLocal.clip, self, 1, clipModel->GetOrigin(), angles.ToMat3() );
}
}
/*
================
sdPhysics_JetPack::SetMaxStepHeight
================
*/
void sdPhysics_JetPack::SetMaxStepHeight( const float newMaxStepHeight ) {
maxStepHeight = newMaxStepHeight;
}
/*
================
sdPhysics_JetPack::GetMaxStepHeight
================
*/
float sdPhysics_JetPack::GetMaxStepHeight( void ) const {
return maxStepHeight;
}
/*
================
sdPhysics_JetPack::OnGround
================
*/
bool sdPhysics_JetPack::OnGround( void ) const {
return current.onGround;
}
/*
================
sdPhysics_JetPack::EnableImpact
================
*/
void sdPhysics_JetPack::EnableImpact( void ) {
noImpact = false;
}
/*
================
sdPhysics_JetPack::DisableImpact
================
*/
void sdPhysics_JetPack::DisableImpact( void ) {
noImpact = true;
}
/*
================
sdPhysics_JetPack::GroundMoveForce
================
*/
idVec3 sdPhysics_JetPack::GroundMoveForce( bool skiing, const idVec3& desiredMove, const idVec3& forceSoFar, float boost, float timeStep ) {
VEC_CHECK_BAD( desiredMove );
if ( desiredMove == vec3_origin ) {
return vec3_origin;
}
FLOAT_CHECK_BAD( boost );
if ( boost > 0.0f ) {
return vec3_origin;
}
// find the desired direction vector for this slope
idVec3 surfaceUp = groundTrace.c.normal;
VEC_CHECK_BAD( surfaceUp );
if ( surfaceUp == vec3_origin ) {
surfaceUp = -gravityNormal;
}
idVec3 surfaceRight = surfaceUp.Cross( desiredMove );
VEC_CHECK_BAD( surfaceRight );
idVec3 surfaceDesired = surfaceRight.Cross( surfaceUp );
VEC_CHECK_BAD( surfaceDesired );
surfaceDesired.FixDenormals();
VEC_CHECK_BAD( surfaceDesired );
// calculate the current velocity on the movement plane
idVec3 currentVelocity = current.velocity;
VEC_CHECK_BAD( currentVelocity );
currentVelocity -= ( currentVelocity * surfaceUp ) * surfaceUp;
VEC_CHECK_BAD( currentVelocity );
float currentSpeed = currentVelocity.Length();
FLOAT_CHECK_BAD( currentSpeed );
float maxAccel = walkForceScale * maxSpeed / timeStep;
FLOAT_CHECK_BAD( maxAccel );
// make moving have less effect at high speed
if ( skiing && currentSpeed > maxSpeed ) {
maxAccel *= 0.25f;
FLOAT_CHECK_BAD( maxAccel );
}
// add in the speed
idVec3 accel = surfaceDesired * maxAccel;
VEC_CHECK_BAD( accel );
// ramp down the acceleration up the slope
if ( accel.z > 0.0f ) {
float scaleUp = ( surfaceDesired.z - 0.3f ) / 0.3f;
FLOAT_CHECK_BAD( scaleUp );
scaleUp = idMath::ClampFloat( 0.0f, 1.0f, scaleUp );
accel.z *= scaleUp;
FLOAT_CHECK_BAD( accel.z );
}
idVec3 endVel = current.velocity + accel * timeStep;
VEC_CHECK_BAD( endVel );
// factor in the forces applied so far (NOTE: don't have to cancel out the vertical
// component since this only includes the friction force!)
VEC_CHECK_BAD( forceSoFar );
idVec3 accelFromForceSoFar = ( forceSoFar - ( ( forceSoFar * surfaceUp ) * surfaceUp ) ) * timeStep;
VEC_CHECK_BAD( accelFromForceSoFar );
endVel += accelFromForceSoFar;
VEC_CHECK_BAD( endVel );
float clampSpeed = maxSpeed;
FLOAT_CHECK_BAD( clampSpeed );
if ( skiing && currentSpeed > clampSpeed ) {
// when skiing, the speed you're going at can be the maximum
clampSpeed = currentSpeed;
}
// clamp the resultant speed
idVec3 endVelDir = endVel;
VEC_CHECK_BAD( endVelDir );
float endSpeed = endVelDir.Normalize();
VEC_CHECK_BAD( endVelDir );
FLOAT_CHECK_BAD( endSpeed );
if ( endSpeed > clampSpeed ) {
endSpeed = clampSpeed;
FLOAT_CHECK_BAD( endSpeed );
endVel = endVelDir * endSpeed;
VEC_CHECK_BAD( endVel );
accel = ( endVel - current.velocity - accelFromForceSoFar ) / timeStep;
VEC_CHECK_BAD( accel );
}
VEC_CHECK_BAD( accel );
return accel;
}
/*
================
sdPhysics_JetPack::FrictionForce
================
*/
idVec3 sdPhysics_JetPack::FrictionForce( bool skiing, const idVec3& desiredMove, const idVec3& forceSoFar, float boost, float timeStep ) {
float frictionScale = 1.0f;
FLOAT_CHECK_BAD( frictionScale );
if ( skiing ) {
// skiing simply drastically scales back the friction received
frictionScale = 0.01f;
}
if ( groundTrace.c.normal.z < 0.6f && !current.onWater ) {
frictionScale *= ( groundTrace.c.normal.z - 0.3f ) / 0.3f;
frictionScale = idMath::ClampFloat( 0.0f, 1.0f, frictionScale );
}
// ignore slope movement, remove all velocity in not on the plane
VEC_CHECK_BAD( forceSoFar );
VEC_CHECK_BAD( current.velocity );
idVec3 vel = current.velocity + forceSoFar * timeStep;
VEC_CHECK_BAD( vel );
VEC_CHECK_BAD( groundTrace.c.normal );
vel -= ( vel * groundTrace.c.normal ) * groundTrace.c.normal;
VEC_CHECK_BAD( vel );
float speed = vel.Length();
FLOAT_CHECK_BAD( speed );
if ( speed < idMath::FLT_EPSILON ) {
return vec3_origin;
}
float control = speed;
FLOAT_CHECK_BAD( control );
if ( !skiing ) {
control = speed < 100.0f ? 100.0f : speed;
}
FLOAT_CHECK_BAD( control );
float drop = control * kineticFriction * frictionScale * timeStep;
FLOAT_CHECK_BAD( drop );
float newspeed = speed - drop;
FLOAT_CHECK_BAD( newspeed );
idVec3 newVel;
if ( newspeed < idMath::FLT_EPSILON || speed < idMath::FLT_EPSILON ) {
newspeed = 0.0f;
newVel.Zero();
} else {
newVel = vel * ( newspeed / speed );
VEC_CHECK_BAD( newVel );
}
VEC_CHECK_BAD( newVel );
return ( newVel - vel ) / timeStep;
}
/*
================
sdPhysics_JetPack::JumpForce
================
*/
idVec3 sdPhysics_JetPack::JumpForce( bool skiing, float upMove, float boost, float timeStep ) {
if ( skiing ) {
return vec3_origin;
}
FLOAT_CHECK_BAD( upMove );
if ( upMove < 0.5f ) {
return vec3_origin;
}
if ( ( current.movementFlags & JPF_JUMPED ) || ( current.movementFlags & JPF_JUMP_HELD ) ) {
return vec3_origin;
}
// ensure that it doesn't attempt to StepMove now that we've jumped
current.onGround = false;
current.onWater = false;
current.movementFlags |= JPF_JUMPED | JPF_JUMP_HELD;
return -jumpForce * gravityNormal;
}
/*
================
sdPhysics_JetPack::BoostForce
================
*/
idVec3 sdPhysics_JetPack::BoostForce( bool skiing, const idVec3& desiredMove, float boost, float timeStep ) {
if ( boost < idMath::FLT_EPSILON ) {
return vec3_origin;
}
idVec3 boostDir = -gravityNormal;
VEC_CHECK_BAD( boostDir );
idVec3 origBoostDir = boostDir;
bool chopBoost = false;
float upVel = boostDir * current.velocity;
FLOAT_CHECK_BAD( upVel );
if ( upVel > maxBoostSpeed ) {
boostDir.Zero();
chopBoost = true;
}
// add part of the directional movement to the boost
// as long as its not exceeding the speed of that
if ( desiredMove != vec3_origin ) {
float moveDirSpeed = desiredMove * current.velocity;
FLOAT_CHECK_BAD( moveDirSpeed );
if ( moveDirSpeed < maxSpeed ) {
boostDir += desiredMove;
VEC_CHECK_BAD( boostDir );
} else {
// chop the boost so it FEELS like the input is being taken into account
// instead of getting a vertical speedup whenever max horizontal speed is reached
chopBoost = true;
}
boostDir.Normalize();
VEC_CHECK_BAD( boostDir );
if ( chopBoost ) {
boostDir *= 0.7071f;
VEC_CHECK_BAD( boostDir );
}
}
idVec3 extraAccel = vec3_origin;
if ( ( current.onGround || current.onWater ) && skiing ) {
// boosted whilst on the ground & skiing
// jump again
if ( -groundTrace.c.normal * gravityNormal > 0.7f ) {
current.movementFlags |= JPF_JUMPED;
extraAccel = -jumpForce * gravityNormal;
VEC_CHECK_BAD( extraAccel );
}
}
// don't run StepMove
current.onGround = false;
current.onWater = false;
current.movementFlags |= JPF_JUMP_HELD;
fanForce = boost * origBoostDir * boostForce + extraAccel;
return boost * boostDir * boostForce + extraAccel;
}
/*
================
sdPhysics_JetPack::Evaluate
================
*/
bool sdPhysics_JetPack::Evaluate( int timeStepMSec, int endTimeMSec ) {
VEC_CHECK_BAD( current.velocity );
VEC_CHECK_BAD( current.origin );
current.velocity.FixDenormals();
current.origin.FixDenormals();
fanForce.Zero();
float timeStep = MS2SEC( timeStepMSec );
if ( timeStep == 0.f ) {
return false;
}
if ( !groundTraceValid ) {
CheckGround();
}
addedVelocity += current.pushVelocity;
idVec3 oldOrigin = current.origin;
// move the velocity into the frame of a pusher
VEC_CHECK_BAD( current.pushVelocity );
current.velocity -= current.pushVelocity;
current.velocity -= addedVelocity;
VEC_CHECK_BAD( current.velocity );
// get the axes
idVec3 viewForward, viewRight;
ANG_CHECK_BAD( viewAngles );
viewAngles.ToVectors( &viewForward, NULL, NULL );
VEC_CHECK_BAD( viewForward );
viewForward *= clipModelAxis;
VEC_CHECK_BAD( viewForward );
viewForward -= ( viewForward * gravityNormal ) * gravityNormal;
VEC_CHECK_BAD( viewForward );
viewForward.Normalize();
VEC_CHECK_BAD( viewForward );
viewRight = gravityNormal.Cross( viewForward );
VEC_CHECK_BAD( viewRight );
viewRight.Normalize();
VEC_CHECK_BAD( viewRight );
// calculate the move desired by the player
float forwardFrac = command.forwardmove / 127.f;
float rightFrac = command.rightmove / 127.f;
float upFrac = command.upmove / 127.f;
FLOAT_CHECK_BAD( forwardFrac );
FLOAT_CHECK_BAD( rightFrac );
FLOAT_CHECK_BAD( upFrac );
idVec3 desired = forwardFrac * viewForward + rightFrac * viewRight;
VEC_CHECK_BAD( desired );
desired.Normalize();
VEC_CHECK_BAD( desired );
CalcWaterFraction();
current.onWater |= waterFraction > 0.1f;
// figure out the boost factor
float useBoost = boost;
if ( disableBoost || waterFraction > 0.5f ) {
useBoost = 0.0f;
}
FLOAT_CHECK_BAD( useBoost );
// scale boost for flight ceiling
float height = current.origin.z;
FLOAT_CHECK_BAD( height );
if ( useBoost > 0.0f && height > gameLocal.flightCeilingLower ) {
// get our height off the heightmap to help with this too
float minBoostScale = 0.0f;
const sdPlayZone* playZoneHeight = gameLocal.GetPlayZone( current.origin, sdPlayZone::PZF_HEIGHTMAP );
if ( playZoneHeight != NULL ) {
const sdHeightMapInstance& heightMap = playZoneHeight->GetHeightMap();
if ( heightMap.IsValid() ) {
float heightMapHeight = heightMap.GetInterpolatedHeight( current.origin );
minBoostScale = ( current.origin.z - heightMapHeight ) / 512.0f;
minBoostScale = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, minBoostScale );
minBoostScale = idMath::Pow( minBoostScale, 0.1f );
}
}
if ( height > gameLocal.flightCeilingUpper ) {
useBoost = 0.0f;
} else {
float boostScale = ( 1.0f - ( height - gameLocal.flightCeilingLower ) / ( gameLocal.flightCeilingUpper - gameLocal.flightCeilingLower ) );
FLOAT_CHECK_BAD( boostScale );
boostScale = idMath::ClampFloat( 0.0f, 1.0f, boostScale );
FLOAT_CHECK_BAD( boostScale );
useBoost *= idMath::Sqrt( boostScale );
FLOAT_CHECK_BAD( useBoost );
}
if ( useBoost < minBoostScale ) {
useBoost = minBoostScale;
}
}
// clear out flags
current.movementFlags &= ~( JPF_JUMPED | JPF_STEPPED_UP | JPF_STEPPED_DOWN );
current.stepUp = 0.0f;
bool skiing = ( current.movementFlags & JPF_JUMP_HELD ) && ( current.onGround || current.onWater );
//
// Gravity
//
idVec3 currentForce = vec3_origin;
if ( !current.onWater ) {
currentForce = gravityVector;
} else if ( useBoost == 0.0f ) {
float normalSpeed = current.velocity * gravityNormal;
if ( jetPackSelf != NULL && jetPackSelf->GetPositionManager().IsEmpty() ) {
currentForce = gravityVector * 0.5f;
currentForce -= gravityNormal * normalSpeed * 3.0f;
} else {
float normalSpeed = current.velocity * gravityNormal;
currentForce = -gravityVector * waterFraction - gravityNormal * normalSpeed * 3.0f;
}
}
VEC_CHECK_BAD( currentForce );
//
// Booster
//
currentForce += BoostForce( skiing, desired, useBoost, timeStep );
VEC_CHECK_BAD( currentForce );
fanForce += desired * boostForce;
//
// Ground Movement
//
if ( current.onGround || current.onWater ) {
// check if the player doesn't want to ski any more
if ( upFrac < 0.1f ) {
current.movementFlags &= ~JPF_JUMP_HELD;
}
// drop out of skiing if the velocity has dropped too far
idVec3 surfaceVelocity = current.velocity - ( current.velocity * groundTrace.c.normal ) * groundTrace.c.normal;
VEC_CHECK_BAD( surfaceVelocity );
if ( skiing && surfaceVelocity.Length() < maxSpeed * 0.5f ) {
skiing = false;
}
idVec3 frictionForce = FrictionForce( skiing, desired, currentForce, useBoost, timeStep );
VEC_CHECK_BAD( frictionForce );
if ( current.onWater ) {
// reduced friction in water, and none against the direction of gravity
frictionForce *= 0.1f;
frictionForce -= ( frictionForce * gravityNormal ) * gravityNormal;
}
currentForce += frictionForce;
if ( !current.onWater ) {
// can't jump off water
currentForce += JumpForce( skiing, upFrac, useBoost, timeStep );
VEC_CHECK_BAD( currentForce );
}
currentForce += GroundMoveForce( skiing, desired, currentForce, useBoost, timeStep );
VEC_CHECK_BAD( currentForce );
}
idVec3 newVelocity = current.velocity + currentForce * timeStep;
VEC_CHECK_BAD( newVelocity );
newVelocity += addedVelocity;
//
// Test if we can skip all the tracing code
//
bool skipTracing = false;
if ( current.onGround ) {
// if the velocity on the plane is zero
idVec3 testVelocity = newVelocity - ( ( newVelocity * groundTrace.c.normal ) * groundTrace.c.normal );
VEC_CHECK_BAD( testVelocity );
if ( addedVelocity.IsZero() && testVelocity.Compare( vec3_origin, 0.0001f ) ) {
skipTracing = true;
newVelocity = testVelocity;
}
}
if ( current.onWater && skipTracing ) {
// if the velocity on the plane is zero & its not bobbing much
idVec3 testVelocity = newVelocity - ( ( newVelocity * gravityNormal ) * gravityNormal );
VEC_CHECK_BAD( testVelocity );
float bobbing = idMath::Fabs( newVelocity * gravityNormal );
if ( addedVelocity.IsZero() && testVelocity.Compare( vec3_origin, 0.1f ) && waterFraction < 0.15f && bobbing < 15.0f ) {
skipTracing = true;
newVelocity = testVelocity;
}
}
addedVelocity.Zero();
if ( skipTracing ) {
current.velocity = newVelocity;
current.velocity += current.pushVelocity;
current.pushVelocity.Zero();
self->OnPhysicsRested();
return false;
}
//
// Update to the new velocity and move
//
current.velocity = newVelocity;
VEC_CHECK_BAD( current.velocity );
ActivateContactEntities();
clipModel->Unlink( gameLocal.clip );
VEC_CHECK_BAD( current.origin );
VEC_CHECK_BAD( current.velocity );
VEC_CHECK_BAD( current.pushVelocity );
SlideMove( true, true, true, 0, timeStep );
current.origin.FixDenormals();
current.velocity.FixDenormals();
current.pushVelocity.FixDenormals();
VEC_CHECK_BAD( current.origin );
VEC_CHECK_BAD( current.velocity );
VEC_CHECK_BAD( current.pushVelocity );
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
LinkShotModel();
// check if on the ground
CheckGround();
// move the monster velocity back into the world frame
current.velocity += current.pushVelocity;
VEC_CHECK_BAD( current.velocity );
current.pushVelocity.Zero();
return ( current.origin != oldOrigin );
}
/*
==================
sdPhysics_JetPack::SlideMove
Returns true if the velocity was clipped in some way
==================
*/
#define MAX_CLIP_PLANES 5
const float MIN_WALK_NORMAL = 0.7f; // can't walk on very steep slopes
const float PM_OVERCLIP = 1.001f;
const float CONST_PM_STEPSCALE = 1.0f;
bool sdPhysics_JetPack::SlideMove( bool stepUp, bool stepDown, bool push, int vehiclePush, float frametime ) {
int i, j, k, pushFlags;
int bumpcount, numbumps, numplanes;
float d, time_left, into, totalMass;
idVec3 dir, planes[MAX_CLIP_PLANES];
idVec3 end, stepEnd, primal_velocity, endVelocity, endClipVelocity, clipVelocity;
trace_t trace, stepTrace, downTrace;
bool nearGround, stepped, pushed, vehiclePushed;
numbumps = 4;
primal_velocity = current.velocity;
bool groundPlane = current.onGround;
endVelocity = current.velocity;
time_left = frametime;
// never turn against the ground plane
if ( groundPlane ) {
numplanes = 1;
planes[0] = groundTrace.c.normal;
float normalGroundVel = current.velocity * groundTrace.c.normal;
if ( normalGroundVel < 0.0f ) {
current.velocity -= normalGroundVel * groundTrace.c.normal;
}
} else {
numplanes = 0;
}
// never turn against original velocity
planes[numplanes] = current.velocity;
planes[numplanes].Normalize();
numplanes++;
for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) {
// calculate position we are trying to move to
end = current.origin + time_left * current.velocity;
// see if we can make it there
gameLocal.clip.Translation( CLIP_DEBUG_PARMS trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
time_left -= time_left * trace.fraction;
current.origin = trace.endpos;
CHECKCURRENT
// if moved the entire distance
if ( trace.fraction >= 1.0f ) {
break;
}
stepped = pushed = vehiclePushed = false;
// if we are allowed to step up
if ( stepUp && ( trace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) {
nearGround = groundPlane || waterFraction < 0.2f;
if ( !nearGround ) {
// trace down to see if the player is near the ground
// step checking when near the ground allows the player to move up stairs smoothly while jumping
stepEnd = current.origin + maxStepHeight * gravityNormal;
gameLocal.clip.Translation( CLIP_DEBUG_PARMS downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
nearGround = ( downTrace.fraction < 1.0f && (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL );
}
// may only step up if near the ground or on a ladder or only shallowly in the water
if ( nearGround ) {
// step up
stepEnd = current.origin - maxStepHeight * gravityNormal;
gameLocal.clip.Translation( CLIP_DEBUG_PARMS downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
// trace along velocity
stepEnd = downTrace.endpos + time_left * current.velocity;
gameLocal.clip.Translation( CLIP_DEBUG_PARMS stepTrace, downTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
// step down
stepEnd = stepTrace.endpos + maxStepHeight * gravityNormal;
gameLocal.clip.Translation( CLIP_DEBUG_PARMS downTrace, stepTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
if ( downTrace.fraction >= 1.0f || (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL ) {
// if moved the entire distance
if ( stepTrace.fraction >= 1.0f ) {
time_left = 0;
current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
current.origin = downTrace.endpos;
CHECKCURRENT
current.movementFlags |= JPF_STEPPED_UP;
current.velocity *= CONST_PM_STEPSCALE;
break;
}
// if the move is further when stepping up
if ( stepTrace.fraction > trace.fraction ) {
time_left -= time_left * stepTrace.fraction;
current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
current.origin = downTrace.endpos;
CHECKCURRENT
current.movementFlags |= JPF_STEPPED_UP;
current.velocity *= CONST_PM_STEPSCALE;
trace = stepTrace;
stepped = true;
}
}
}
}
// if we can push other entities and not blocked by the world
if ( push && trace.c.entityNum != ENTITYNUM_WORLD ) {
clipModel->SetPosition( current.origin, clipModel->GetAxis(), gameLocal.clip );
// clip movement, only push idMoveables, don't push entities the player is standing on
// apply impact to pushed objects
pushFlags = PUSHFL_CLIP|PUSHFL_ONLYMOVEABLE|PUSHFL_NOGROUNDENTITIES|PUSHFL_APPLYIMPULSE;
// clip & push
totalMass = gameLocal.push.ClipTranslationalPush( trace, self, pushFlags, end, end - current.origin, clipModel );
if ( totalMass > 0.0f ) {
// decrease velocity based on the total mass of the objects being pushed ?
current.velocity *= 1.0f - idMath::ClampFloat( 0.0f, 1000.0f, totalMass - 20.0f ) * ( 1.0f / 950.0f );
pushed = true;
}
current.origin = trace.endpos;
CHECKCURRENT
time_left -= time_left * trace.fraction;
// if moved the entire distance
if ( trace.fraction >= 1.0f ) {
break;
}
}
// try to vehiclepush things out of the way
if ( !stepped && !pushed && vehiclePush && trace.c.entityNum != ENTITYNUM_WORLD ) {
idEntity* other = gameLocal.entities[ trace.c.entityNum ];
idPhysics* otherPhysics = other->GetPhysics();
idPhysics_Actor* actorPhysics = otherPhysics->Cast< idPhysics_Actor >();
if ( actorPhysics != NULL ) {
clipModel->Disable();
idVec3 move = end - trace.endpos;
if ( actorPhysics->VehiclePush( false, time_left, move, clipModel, vehiclePush ) == VPUSH_OK ) {
vehiclePushed = true;
}
clipModel->Enable();
}
}
if ( !stepped && !vehiclePushed ) {
// let the entity know about the collision
idEntity *ent = gameLocal.entities[ trace.c.entityNum ];
ent->Hit( trace, current.velocity, self );
self->Collide( trace, current.velocity, -1 );
}
if ( numplanes >= MAX_CLIP_PLANES ) {
// MrElusive: I think we have some relatively high poly LWO models with a lot of slanted tris
// where it may hit the max clip planes
current.velocity = vec3_origin;
return true;
}
//
// if this is the same plane we hit before, nudge velocity out along it,
// which fixes some epsilon issues with non-axial planes
//
for ( i = 0; i < numplanes; i++ ) {
if ( ( trace.c.normal * planes[i] ) > 0.999f ) {
// clip into the trace normal just in case this normal is almost but not exactly the same as the groundTrace normal
current.velocity.ProjectOntoPlane( trace.c.normal, PM_OVERCLIP );
// also add the normal to nudge the velocity out
current.velocity += trace.c.normal;
break;
}
}
if ( i < numplanes ) {
continue;
}
planes[numplanes] = trace.c.normal;
numplanes++;
//
// modify velocity so it parallels all of the clip planes
//
// find a plane that it enters
for ( i = 0; i < numplanes; i++ ) {
into = current.velocity * planes[i];
if ( into >= 0.1f ) {
continue; // move doesn't interact with the plane
}
// slide along the plane
clipVelocity = current.velocity;
clipVelocity.ProjectOntoPlane( planes[i], PM_OVERCLIP );
// slide along the plane
endClipVelocity = endVelocity;
endClipVelocity.ProjectOntoPlane( planes[i], PM_OVERCLIP );
// see if there is a second plane that the new move enters
for ( j = 0; j < numplanes; j++ ) {
if ( j == i ) {
continue;
}
if ( ( clipVelocity * planes[j] ) >= 0.1f ) {
continue; // move doesn't interact with the plane
}
// try clipping the move to the plane
clipVelocity.ProjectOntoPlane( planes[j], PM_OVERCLIP );
endClipVelocity.ProjectOntoPlane( planes[j], PM_OVERCLIP );
// see if it goes back into the first clip plane
if ( ( clipVelocity * planes[i] ) >= 0 ) {
continue;
}
// slide the original velocity along the crease
dir = planes[i].Cross( planes[j] );
dir.Normalize();
d = dir * current.velocity;
clipVelocity = d * dir;
dir = planes[i].Cross( planes[j] );
dir.Normalize();
d = dir * endVelocity;
endClipVelocity = d * dir;
// see if there is a third plane the the new move enters
for ( k = 0; k < numplanes; k++ ) {
if ( k == i || k == j ) {
continue;
}
if ( ( clipVelocity * planes[k] ) >= 0.1f ) {
continue; // move doesn't interact with the plane
}
// stop dead at a tripple plane interaction
current.velocity = vec3_origin;
return true;
}
}
// if we have fixed all interactions, try another move
current.velocity = clipVelocity;
endVelocity = endClipVelocity;
break;
}
}
// step down
if ( stepDown && groundPlane ) {
// don't evaluate the step if its opposing the direction of movement
idVec3 stepDelta = gravityNormal * maxStepHeight;
float stepDirectionality = stepDelta * current.velocity;
if ( stepDirectionality > 0.0f ) {
stepEnd = current.origin + stepDelta;
gameLocal.clip.Translation( CLIP_DEBUG_PARMS downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
if ( downTrace.fraction > 1e-4f && downTrace.fraction < 1.0f ) {
current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
current.origin = downTrace.endpos;
CHECKCURRENT
current.movementFlags |= JPF_STEPPED_DOWN;
current.velocity *= CONST_PM_STEPSCALE;
}
}
}
// come to a dead stop when the velocity orthogonal to the gravity flipped
clipVelocity = current.velocity - gravityNormal * current.velocity * gravityNormal;
endClipVelocity = endVelocity - gravityNormal * endVelocity * gravityNormal;
if ( clipVelocity * endClipVelocity < 0.0f ) {
current.velocity = gravityNormal * current.velocity * gravityNormal;
}
return bumpcount == 0;
}
/*
================
sdPhysics_JetPack::UpdateTime
================
*/
void sdPhysics_JetPack::UpdateTime( int endTimeMSec ) {
}
/*
================
sdPhysics_JetPack::GetTime
================
*/
int sdPhysics_JetPack::GetTime( void ) const {
return gameLocal.time;
}
/*
================
sdPhysics_JetPack::GetImpactInfo
================
*/
void sdPhysics_JetPack::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const {
info->invMass = invMass;
info->invInertiaTensor.Zero();
info->position.Zero();
info->velocity = current.velocity;
}
/*
================
sdPhysics_JetPack::ApplyImpulse
================
*/
void sdPhysics_JetPack::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) {
if ( noImpact ) {
return;
}
addedVelocity += impulse * invMass;
current.velocity += impulse * invMass;
Activate();
}
/*
================
sdPhysics_JetPack::SaveState
================
*/
void sdPhysics_JetPack::SaveState( void ) {
saved = current;
}
/*
================
sdPhysics_JetPack::RestoreState
================
*/
void sdPhysics_JetPack::RestoreState( void ) {
current = saved;
CHECKCURRENT
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
LinkShotModel();
CheckGround();
}
/*
================
idPhysics_Player::GetOrigin
================
*/
const idVec3& sdPhysics_JetPack::GetOrigin( int id ) const {
return current.origin;
}
/*
================
sdPhysics_JetPack::SetOrigin
================
*/
void sdPhysics_JetPack::SetOrigin( const idVec3 &newOrigin, int id ) {
current.origin = newOrigin;
CHECKCURRENT
clipModel->Link( gameLocal.clip, self, 0, newOrigin, clipModel->GetAxis() );
LinkShotModel();
Activate();
}
/*
================
sdPhysics_JetPack::SetAxis
================
*/
void sdPhysics_JetPack::SetAxis( const idMat3 &newAxis, int id ) {
clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), newAxis );
LinkShotModel();
Activate();
}
/*
================
sdPhysics_JetPack::Translate
================
*/
void sdPhysics_JetPack::Translate( const idVec3 &translation, int id ) {
current.origin += translation;
CHECKCURRENT
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
LinkShotModel();
Activate();
}
/*
================
sdPhysics_JetPack::Rotate
================
*/
void sdPhysics_JetPack::Rotate( const idRotation &rotation, int id ) {
current.origin *= rotation;
CHECKCURRENT
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() );
LinkShotModel();
Activate();
}
/*
================
sdPhysics_JetPack::SetLinearVelocity
================
*/
void sdPhysics_JetPack::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) {
current.velocity = newLinearVelocity;
Activate();
}
/*
================
sdPhysics_JetPack::GetLinearVelocity
================
*/
const idVec3 &sdPhysics_JetPack::GetLinearVelocity( int id ) const {
return current.velocity;
}
/*
================
sdPhysics_JetPack::SetPushed
================
*/
void sdPhysics_JetPack::SetPushed( int deltaTime ) {
// velocity with which the monster is pushed
current.pushVelocity += ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC );
}
/*
================
sdPhysics_JetPack::GetPushedLinearVelocity
================
*/
const idVec3 &sdPhysics_JetPack::GetPushedLinearVelocity( const int id ) const {
return current.pushVelocity;
}
const float JETPACK_ORIGIN_MAX = 32767.0f;
const int JETPACK_ORIGIN_TOTAL_BITS = 24;
const int JETPACK_ORIGIN_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( JETPACK_ORIGIN_MAX ) ) + 1;
const int JETPACK_ORIGIN_MANTISSA_BITS = JETPACK_ORIGIN_TOTAL_BITS - 1 - JETPACK_ORIGIN_EXPONENT_BITS;
const float JETPACK_VELOCITY_MAX = 4000.0f;
const int JETPACK_VELOCITY_TOTAL_BITS = 16;
const int JETPACK_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( JETPACK_VELOCITY_MAX ) ) + 1;
const int JETPACK_VELOCITY_MANTISSA_BITS = JETPACK_VELOCITY_TOTAL_BITS - 1 - JETPACK_VELOCITY_EXPONENT_BITS;
const int JETPACK_MOVEMENT_FLAGS_BITS = 4;
/*
================
sdPhysics_JetPack::CheckNetworkStateChanges
================
*/
bool sdPhysics_JetPack::CheckNetworkStateChanges( networkStateMode_t mode, const sdEntityStateNetworkData& baseState ) const {
if ( mode == NSM_VISIBLE ) {
NET_GET_BASE( sdJetPackPhysicsNetworkData );
if ( !baseData.origin.Compare( current.origin, idMath::FLT_EPSILON ) ) {
return true;
}
if ( !baseData.velocity.Compare( current.velocity, idMath::FLT_EPSILON ) ) {
return true;
}
NET_CHECK_FIELD( movementFlags, current.movementFlags );
return false;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_BASE( sdJetPackPhysicsBroadcastData );
NET_CHECK_FIELD( pushVelocity, current.pushVelocity );
return false;
}
return false;
}
/*
================
sdPhysics_JetPack::WriteNetworkState
================
*/
void sdPhysics_JetPack::WriteNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, idBitMsg& msg ) const {
if ( mode == NSM_VISIBLE ) {
NET_GET_STATES( sdJetPackPhysicsNetworkData );
newData.origin = current.origin;
newData.velocity = current.velocity;
newData.movementFlags = current.movementFlags;
msg.WriteDeltaVector( baseData.origin, newData.origin, JETPACK_ORIGIN_EXPONENT_BITS, JETPACK_ORIGIN_MANTISSA_BITS );
msg.WriteDeltaVector( baseData.velocity, newData.velocity, JETPACK_VELOCITY_EXPONENT_BITS, JETPACK_VELOCITY_MANTISSA_BITS );
msg.WriteDelta( baseData.movementFlags, newData.movementFlags, JETPACK_MOVEMENT_FLAGS_BITS );
return;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_STATES( sdJetPackPhysicsBroadcastData );
newData.pushVelocity = current.pushVelocity;
msg.WriteDeltaVector( baseData.pushVelocity, newData.pushVelocity, JETPACK_VELOCITY_EXPONENT_BITS, JETPACK_VELOCITY_MANTISSA_BITS );
return;
}
}
/*
================
sdPhysics_JetPack::ApplyNetworkState
================
*/
void sdPhysics_JetPack::ApplyNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& newState ) {
traceCollection.ForceNextUpdate();
if ( mode == NSM_VISIBLE ) {
NET_GET_NEW( sdJetPackPhysicsNetworkData );
current.origin = newData.origin;
CHECKCURRENT
current.velocity = newData.velocity;
current.movementFlags = newData.movementFlags;
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
LinkShotModel();
groundTraceValid = false;
self->UpdateVisuals();
return;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_NEW( sdJetPackPhysicsBroadcastData );
current.pushVelocity = newData.pushVelocity;
return;
}
}
/*
================
sdPhysics_JetPack::ReadNetworkState
================
*/
void sdPhysics_JetPack::ReadNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, const idBitMsg& msg ) const {
if ( mode == NSM_VISIBLE ) {
NET_GET_STATES( sdJetPackPhysicsNetworkData );
newData.origin = msg.ReadDeltaVector( baseData.origin, JETPACK_ORIGIN_EXPONENT_BITS, JETPACK_ORIGIN_MANTISSA_BITS );
newData.velocity = msg.ReadDeltaVector( baseData.velocity, JETPACK_VELOCITY_EXPONENT_BITS, JETPACK_VELOCITY_MANTISSA_BITS );
newData.movementFlags = msg.ReadDelta( baseData.movementFlags, JETPACK_MOVEMENT_FLAGS_BITS );
newData.origin.FixDenormals();
newData.velocity.FixDenormals();
self->OnNewOriginRead( newData.origin );
return;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_STATES( sdJetPackPhysicsBroadcastData );
newData.pushVelocity = msg.ReadDeltaVector( baseData.pushVelocity, JETPACK_VELOCITY_EXPONENT_BITS, JETPACK_VELOCITY_MANTISSA_BITS );
return;
}
}
/*
================
sdPhysics_JetPack::CreateNetworkStructure
================
*/
sdEntityStateNetworkData* sdPhysics_JetPack::CreateNetworkStructure( networkStateMode_t mode ) const {
if ( mode == NSM_VISIBLE ) {
return new sdJetPackPhysicsNetworkData();
}
if ( mode == NSM_BROADCAST ) {
return new sdJetPackPhysicsBroadcastData();
}
return NULL;
}
/*
================
sdPhysics_JetPack::ResetNetworkState
================
*/
void sdPhysics_JetPack::ResetNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& newState ) {
traceCollection.ForceNextUpdate();
if ( mode == NSM_BROADCAST ) {
NET_GET_NEW( sdJetPackPhysicsBroadcastData );
current.pushVelocity = newData.pushVelocity;
return;
}
}
/*
================
sdPhysics_JetPack::SetPlayerInput
================
*/
void sdPhysics_JetPack::SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles, bool allowMovement ) {
command = cmd;
viewAngles = newViewAngles; // can't use cmd.angles cause of the delta_angles
movementAllowed = allowMovement;
ANG_CHECK_BAD( viewAngles );
}
/*
================
sdPhysics_JetPack::Activate
================
*/
void sdPhysics_JetPack::Activate( void ) {
groundTraceValid = false;
self->BecomeActive( TH_PHYSICS );
}
/*
=============
sdPhysics_JetPack::CalcWaterFraction
=============
*/
void sdPhysics_JetPack::CalcWaterFraction( void ) {
waterFraction = 0.f;
idBounds absBounds = GetAbsBounds();
const idClipModel* waterModel;
int count = gameLocal.clip.ClipModelsTouchingBounds( CLIP_DEBUG_PARMS absBounds, CONTENTS_WATER, &waterModel, 1, NULL );
if ( count && waterModel->GetNumCollisionModels() ) {
idCollisionModel* model = waterModel->GetCollisionModel( 0 );
int numPlanes = model->GetNumBrushPlanes();
const idBounds& modelBounds = model->GetBounds();
self->CheckWater( waterModel->GetOrigin(), waterModel->GetAxis(), model );
for ( int i = 0; i < numPlanes; i++ ) {
idPlane plane = model->GetBrushPlane( i );
plane.TranslateSelf( waterModel->GetOrigin() );
plane.Normal() *= waterModel->GetAxis();
if ( plane.Distance( current.origin ) > 0 ) {
// outside of water clipmodel
return;
}
}
float height = waterModel->GetOrigin().z - current.origin.z + modelBounds.GetMaxs().z;
waterFraction = height / absBounds.Size().z;
if ( waterFraction > 1.f ) {
waterFraction = 1.f;
}
}
}
/*
================
sdPhysics_JetPack::VehiclePush
================
*/
#define VEHICLE_PUSH_EPSILON 0.5f
int sdPhysics_JetPack::VehiclePush( bool stuck, float timeDelta, idVec3& move, idClipModel* pusher, int pushCount ) {
if ( pushCount > 3 ) {
move.Zero();
return VPUSH_BLOCKED;
}
VEC_CHECK_BAD( move );
// remove small components into the ground
// not strictly necessary but it makes the player physics do one less trace
if ( current.onGround ) {
float groundMove = move * groundTrace.c.normal;
if ( groundMove < 0.0f && groundMove > -0.1f ) {
move -= groundMove * groundTrace.c.normal;
}
}
VEC_CHECK_BAD( move );
if ( !stuck ) {
// change the velocity so it satisfies the push
// note it has to be changed, then restored back
// could combine it and the required velocity of the move now, but it'd
// mean that you get double-moved by the current velocity - exploits++
idVec3 oldVelocity = current.velocity;
idVec3 oldOrigin = current.origin;
VEC_CHECK_BAD( oldVelocity );
VEC_CHECK_BAD( oldOrigin );
if ( timeDelta < MS2SEC( 1 ) ) {
timeDelta = MS2SEC( 1 );
}
current.velocity = move / timeDelta;
VEC_CHECK_BAD( current.velocity );
// pusher->Disable();
SlideMove( true, true, false, pushCount + 1, timeDelta );
// pusher->Enable();
current.velocity.FixDenormals();
current.origin.FixDenormals();
VEC_CHECK_BAD( current.velocity );
VEC_CHECK_BAD( current.origin );
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
LinkShotModel();
CheckGround();
// HACK - force the driver to update its position
if ( jetPackSelf != NULL ) {
idPlayer* driver = jetPackSelf->GetPositionManager().FindDriver();
if ( driver != NULL ) {
driver->GetPhysics()->Evaluate( SEC2MS( timeDelta ), gameLocal.time + gameLocal.msec );
driver->fl.allowPredictionErrorDecay = true;
driver->UpdateAnimation();
}
jetPackSelf->UpdateJetPackVisuals();
}
VEC_CHECK_BAD( current.velocity );
VEC_CHECK_BAD( current.origin );
// see if we ended up moving the right way
idVec3 moveDirection = move;
VEC_CHECK_BAD( moveDirection );
float moveNeeded = moveDirection.Normalize();
FLOAT_CHECK_BAD( moveNeeded );
idVec3 movedDelta = current.origin - oldOrigin;
VEC_CHECK_BAD( movedDelta );
float movedAmount = moveDirection * movedDelta;
FLOAT_CHECK_BAD( movedAmount );
current.velocity = oldVelocity;
VEC_CHECK_BAD( current.velocity );
float oldMoveDirSpeed = current.velocity * moveDirection;
FLOAT_CHECK_BAD( oldMoveDirSpeed );
current.pushVelocity = ( moveNeeded - oldMoveDirSpeed ) * moveDirection;
current.pushVelocity.FixDenormals();
VEC_CHECK_BAD( current.pushVelocity );
current.velocity += current.pushVelocity ;
VEC_CHECK_BAD( current.velocity );
// gameRenderWorld->DebugArrow( colorYellow, current.origin + idVec3(0,0,64), current.origin + idVec3(0,0,64) + moveDirection * 128.0f, 4 );
if ( movedAmount < moveNeeded - VEHICLE_PUSH_EPSILON ) {
// didn't move all the way!
// update the amount that we move
move = move * movedAmount / moveNeeded;
VEC_CHECK_BAD( move );
return VPUSH_BLOCKED;
}
} else {
// that means we're inside the vehicle and its trying to push us out
// this is normally caused because we're being pushed by the vehicle
// and we try to walk towards it
// try to find a point on the outside of the vehicle that we can teleport to
// start by finding the normal of a nearby surface
contactInfo_t contacts[ 2 ];
int numContacts = gameLocal.clip.ContactsModel( CLIP_DEBUG_PARMS contacts, 2, current.origin, NULL, 4.0f, clipModel, clipModel->GetAxis(), -1, pusher, pusher->GetOrigin(), pusher->GetAxis() );
idVec3 foundNormal = vec3_origin;
if ( numContacts ) {
// average out the normals of the contacts - this provides a good approximation
for ( int i = 0; i < numContacts; i++ ) {
// gameRenderWorld->DebugSphere( colorGreen, idSphere( contacts[ i ].point, 4.0f ) );
// gameRenderWorld->DebugArrow( colorGreen, contacts[ i ].point, contacts[ i ].point + contacts[ i ].normal * 128.0f, 4 );
foundNormal += contacts[ i ].normal;
}
foundNormal /= numContacts;
} else {
// embedded so far it doesn't find any contacts O_o
// use a trace to try to find the normal
idVec3 traceDir = pusher->GetAbsBounds().GetCenter() - current.origin;
float traceDirLength = traceDir.Normalize();
VEC_CHECK_BAD( traceDir );
if ( traceDirLength == 0.0f ) {
// well and truly embedded - nothing we can do here
move.Zero();
return VPUSH_BLOCKED;
}
idVec3 normalFinderStart = current.origin - traceDir * 128.0f;
idVec3 normalFinderEnd = current.origin + traceDir * 128.0f;
VEC_CHECK_BAD( normalFinderStart );
VEC_CHECK_BAD( normalFinderEnd );
trace_t normalFinder;
gameLocal.clip.TranslationModel( CLIP_DEBUG_PARMS normalFinder, normalFinderStart, normalFinderEnd, clipModel,
clipModel->GetAxis(), GetClipMask(), pusher,
pusher->GetOrigin(), pusher->GetAxis() );
if ( normalFinder.fraction == 1.0f ) {
// well and truly embedded - nothing we can do here
move.Zero();
return VPUSH_BLOCKED;
}
foundNormal = normalFinder.c.normal;
}
VEC_CHECK_BAD( foundNormal );
// gameRenderWorld->DebugArrow( colorYellow, current.origin, current.origin + foundNormal * 128.0f, 4 );
idVec3 start = current.origin + foundNormal * 32.0f;
idVec3 end = current.origin - foundNormal * 32.0f;
VEC_CHECK_BAD( start );
VEC_CHECK_BAD( end );
trace_t tr;
gameLocal.clip.TranslationModel( CLIP_DEBUG_PARMS tr, start, end, clipModel, clipModel->GetAxis(), GetClipMask(), pusher,
pusher->GetOrigin(), pusher->GetAxis() );
if ( tr.fraction < 1.0f ) {
VEC_CHECK_BAD( tr.endpos );
// push to the new spot
idEntity* other = pusher->GetEntity();
if ( other != NULL ) {
other->DisableClip( false );
} else {
pusher->Disable();
}
idVec3 pushOutMove = tr.endpos - current.origin;
int pushOutResult = VehiclePush( false, timeDelta, pushOutMove, pusher, pushCount );
if ( other != NULL ) {
other->EnableClip();
} else {
pusher->Enable();
}
if ( pushOutResult == VPUSH_OK ) {
// heres a point thats not inside the vehicle, and not inside the world! move there, use it as our new origin
// proceed with the move, adjust it for the new origin
return VehiclePush( false, timeDelta, move, pusher, pushCount );
} else {
return VPUSH_BLOCKED;
}
}
}
return VPUSH_OK;
}
/*
================
sdPhysics_JetPack::SetSelf
================
*/
void sdPhysics_JetPack::SetSelf( idEntity *e ) {
jetPackSelf = e->Cast< sdJetPack >();
assert( jetPackSelf != NULL );
idPhysics_Actor::SetSelf( e );
}
/*
================
sdPhysics_JetPack::UnlinkClip
================
*/
void sdPhysics_JetPack::UnlinkClip( void ) {
clipModel->Unlink( gameLocal.clip );
shotClipModel->Unlink( gameLocal.clip );
}
/*
================
sdPhysics_JetPack::LinkClip
================
*/
void sdPhysics_JetPack::LinkClip( void ) {
clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), clipModel->GetAxis() );
LinkShotModel();
}
/*
================
sdPhysics_JetPack::DisableClip
================
*/
void sdPhysics_JetPack::DisableClip( bool activateContacting ) {
if ( activateContacting ) {
WakeEntitiesContacting( self, clipModel );
}
clipModel->Disable();
shotClipModel->Disable();
}
/*
================
sdPhysics_JetPack::EnableClip
================
*/
void sdPhysics_JetPack::EnableClip( void ) {
clipModel->Enable();
shotClipModel->Enable();
LinkClip();
}