1738 lines
50 KiB
C++
1738 lines
50 KiB
C++
// 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( ¤t, 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();
|
|
}
|