etqw-sdk/source/game/vehicles/TransportComponents.cpp
2008-05-29 00:00:00 +00:00

5832 lines
175 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 "TransportComponents.h"
#include "Transport.h"
#include "Vehicle_RigidBody.h"
#include "../anim/Anim.h"
#include "Attachments.h"
#include "../ContentMask.h"
#include "../IK.h"
#include "VehicleIK.h"
#include "../../framework/CVarSystem.h"
#include "VehicleSuspension.h"
#include "VehicleControl.h"
#include "../Player.h"
#include "../client/ClientMoveable.h"
#include "../physics/Physics_JetPack.h"
#include "../../decllib/DeclSurfaceType.h"
#include "../script/Script_Helper.h"
#include "../script/Script_ScriptObject.h"
#include "../effects/TireTread.h"
const float minParticleCreationSpeed = 64.f;
idCVar g_disableTransportDebris( "g_disableTransportDebris", "0", CVAR_GAME | CVAR_BOOL, "" );
idCVar g_maxTransportDebrisExtraHigh( "g_maxTransportDebrisExtraHigh", "8", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "The maximum number of pieces of extra high priority (really large) debris. -1 means no limit." );
idCVar g_maxTransportDebrisHigh( "g_maxTransportDebrisHigh", "8", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "The maximum number of pieces of high priority (large) debris. -1 means no limit." );
idCVar g_maxTransportDebrisMedium( "g_maxTransportDebrisMedium", "8", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "The maximum number of pieces of medium priority (middling) debris. -1 means no limit." );
idCVar g_maxTransportDebrisLow( "g_maxTransportDebrisLow", "8", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "The maximum number of pieces of low priority (small) debris. -1 means no limit." );
idCVar g_transportDebrisExtraHighCutoff("g_transportDebrisExtraHighCutoff", "8192", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "Beyond this distance from the viewpoint extra high priority debris will not be spawned. -1 means no limit." );
idCVar g_transportDebrisHighCutoff( "g_transportDebrisHighCutoff", "4096", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "Beyond this distance from the viewpoint high priority debris will not be spawned. -1 means no limit." );
idCVar g_transportDebrisMediumCutoff( "g_transportDebrisMediumCutoff", "2048", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "Beyond this distance from the viewpoint medium priority debris will not be spawned. -1 means no limit." );
idCVar g_transportDebrisLowCutoff( "g_transportDebrisLowCutoff", "1024", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "Beyond this distance from the viewpoint low priority debris will not be spawned. -1 means no limit." );
sdVehicleDriveObject::debrisList_t sdVehicleDriveObject::extraHighDebris;
sdVehicleDriveObject::debrisList_t sdVehicleDriveObject::highDebris;
sdVehicleDriveObject::debrisList_t sdVehicleDriveObject::mediumDebris;
sdVehicleDriveObject::debrisList_t sdVehicleDriveObject::lowDebris;
/*
===============================================================================
sdVehicleDriveObject
===============================================================================
*/
ABSTRACT_DECLARATION( idClass, sdVehicleDriveObject )
END_CLASS
/*
================
sdVehicleDriveObject::sdVehicleDriveObject
================
*/
sdVehicleDriveObject::sdVehicleDriveObject( void ) {
hidden = false;
scriptObject = NULL;
}
/*
================
sdVehicleDriveObject::~sdVehicleDriveObject
================
*/
sdVehicleDriveObject::~sdVehicleDriveObject( void ) {
if ( scriptObject ) {
gameLocal.program->FreeScriptObject( scriptObject );
}
}
/*
================
sdVehicleDriveObject::PostInit
================
*/
void sdVehicleDriveObject::PostInit( void ) {
sdTransport* parent = GetParent();
assert( parent );
if ( GoesInPartList() ) {
parent->AddActiveDriveObject( this );
}
}
/*
================
sdVehicleDriveObject::Hide
================
*/
void sdVehicleDriveObject::Hide( void ) {
if ( hidden ) {
return;
}
hidden = true;
sdTransport* parent = GetParent();
assert( parent );
if ( GoesInPartList() ) {
parent->RemoveActiveDriveObject( this );
if ( IsType( sdVehicleRigidBodyWheel::Type ) ) {
parent->RemoveCriticalDrivePart();
}
}
}
/*
================
sdVehicleDriveObject::Show
================
*/
void sdVehicleDriveObject::Show( void ) {
if ( !hidden ) {
return;
}
hidden = false;
sdTransport* parent = GetParent();
assert( parent );
if ( GoesInPartList() ) {
parent->AddActiveDriveObject( this );
if ( IsType( sdVehicleRigidBodyWheel::Type ) ) {
parent->AddCriticalDrivePart();
}
}
}
/*
================
sdVehicleDriveObject::CanAddDebris
================
*/
bool sdVehicleDriveObject::CanAddDebris( debrisPriority_t priority, const idVec3& origin ) {
debrisList_t* list = &highDebris;
int limit = g_maxTransportDebrisHigh.GetInteger();
float distanceLimit = g_transportDebrisHighCutoff.GetFloat();
if ( priority == PRIORITY_MEDIUM ) {
list = &mediumDebris;
limit = g_maxTransportDebrisMedium.GetInteger();
distanceLimit = g_transportDebrisMediumCutoff.GetFloat();
} else if ( priority == PRIORITY_LOW ) {
list = &lowDebris;
limit = g_maxTransportDebrisLow.GetInteger();
distanceLimit = g_transportDebrisLowCutoff.GetFloat();
} else if ( priority == PRIORITY_EXTRA_HIGH ) {
list = &extraHighDebris;
limit = g_maxTransportDebrisExtraHigh.GetInteger();
distanceLimit = g_transportDebrisExtraHighCutoff.GetFloat();
}
// clean out NULLs
for ( int i = 0; i < list->Num(); i++ ) {
if ( !(*list)[ i ].IsValid() ) {
list->RemoveIndexFast( i );
i--;
continue;
}
}
// don't let it exceed the limit
if ( limit != -1 && list->Num() >= limit ) {
return false;
}
idPlayer* player = gameLocal.GetLocalViewPlayer();
if ( distanceLimit != -1 && player != NULL ) {
float distance = ( player->GetViewPos() - origin ).Length();
if ( distance > distanceLimit ) {
return false;
}
}
return true;
}
/*
================
sdVehicleDriveObject::AddDebris
================
*/
void sdVehicleDriveObject::AddDebris( rvClientMoveable* debris, debrisPriority_t priority ) {
debrisList_t* list = &highDebris;
int limit = g_maxTransportDebrisHigh.GetInteger();
if ( priority == PRIORITY_MEDIUM ) {
list = &mediumDebris;
limit = g_maxTransportDebrisMedium.GetInteger();
} else if ( priority == PRIORITY_LOW ) {
list = &lowDebris;
limit = g_maxTransportDebrisLow.GetInteger();
} else if ( priority == PRIORITY_EXTRA_HIGH ) {
list = &extraHighDebris;
limit = g_maxTransportDebrisExtraHigh.GetInteger();
}
// -1 means no limit
if ( limit == -1 ) {
return;
}
rvClientEntityPtr< rvClientMoveable >* entry = list->Alloc();
if ( entry != NULL ) {
entry->operator=( debris );
}
}
/*
===============================================================================
sdVehiclePart
===============================================================================
*/
extern const idEventDef EV_GetAngles;
extern const idEventDef EV_GetHealth;
extern const idEventDef EV_GetOrigin;
const idEventDef EV_VehiclePart_GetParent( "getParent", 'e', DOC_TEXT( "Returns the vehicle this part belongs to." ), 0, "This will never return $null$." );
const idEventDef EV_VehiclePart_GetJoint( "getJoint", 's', DOC_TEXT( "Returns the name of the joint this part is associated with." ), 0, "An empty string will be returned if the lookup fails." );
ABSTRACT_DECLARATION( sdVehicleDriveObject, sdVehiclePart )
EVENT( EV_GetHealth, sdVehiclePart::Event_GetHealth )
EVENT( EV_GetOrigin, sdVehiclePart::Event_GetOrigin )
EVENT( EV_GetAngles, sdVehiclePart::Event_GetAngles )
EVENT( EV_VehiclePart_GetParent, sdVehiclePart::Event_GetParent )
EVENT( EV_VehiclePart_GetJoint, sdVehiclePart::Event_GetJoint )
END_CLASS
/*
================
sdVehiclePart::sdVehiclePart
================
*/
sdVehiclePart::sdVehiclePart( void ) {
bodyId = -1;
waterEffects = NULL;
scriptObject = NULL;
reattachTime = 0;
}
/*
================
sdVehiclePart::AddSurface
================
*/
void sdVehiclePart::AddSurface( const char* surfaceName ) {
int id = GetParent()->FindSurfaceId( surfaceName );
if ( id != -1 ) {
surfaces.Alloc() = id;
} else {
gameLocal.Warning( "sdVehiclePart::AddSurface Invalid Surface '%s'", surfaceName );
}
}
/*
================
sdVehiclePart::Detach
================
*/
void sdVehiclePart::Detach( bool createDebris, bool decay ) {
if ( IsHidden() ) {
return;
}
Hide();
if ( !noAutoHide ) {
HideSurfaces();
}
idPhysics* physics = GetParent()->GetPhysics();
if( bodyId != -1 ) {
oldcontents = physics->GetContents( bodyId );
oldclipmask = physics->GetClipMask( bodyId );
physics->SetContents( 0, bodyId );
physics->SetClipMask( 0, bodyId );
}
if ( createDebris && brokenPart ) {
if ( decay == false && physics->InWater() < 1.0f ) {
CreateExplosionDebris();
} else {
CreateDecayDebris();
}
}
}
/*
================
sdVehiclePart::CreateExplosionDebris
================
*/
void sdVehiclePart::CreateExplosionDebris( void ) {
if ( g_disableTransportDebris.GetBool() ) {
return;
}
sdTransport* transport = GetParent();
idPhysics* parentPhysics = transport->GetPhysics();
idVec3 org;
idMat3 axis;
GetWorldOrigin( org );
GetWorldAxis( axis );
// make sure we can have another part of this priority
debrisPriority_t priority = ( debrisPriority_t )brokenPart->dict.GetInt( "priority" );
if ( !CanAddDebris( priority, org ) ) {
return;
}
if ( transport->GetMasterDestroyedPart() != NULL ) {
parentPhysics = transport->GetMasterDestroyedPart()->GetPhysics();
}
const idVec3& vel = parentPhysics->GetLinearVelocity();
const idVec3& aVel = parentPhysics->GetAngularVelocity();
rvClientMoveable* cent = gameLocal.SpawnClientMoveable( brokenPart->GetName(), 5000, org, axis, vec3_origin, vec3_origin );
AddDebris( cent, priority );
if ( cent != NULL ) {
gameLocal.PlayEffect( brokenPart->dict, colorWhite.ToVec3(), "fx_explode", NULL, cent->GetPhysics()->GetOrigin(), cent->GetPhysics()->GetAxis(), false );
if ( flipMaster ) {
transport->SetMasterDestroyedPart( cent );
}
//
const idVec3& centCOM = cent->GetPhysics()->GetCenterOfMass();
const idVec3& parentCOM = parentPhysics->GetCenterOfMass();
const idVec3& parentOrg = parentPhysics->GetOrigin();
const idMat3& parentAxis = parentPhysics->GetAxis();
idVec3 radiusVector = ( centCOM*axis + org ) - ( parentCOM * parentAxis + parentOrg );
// calculate the actual linear velocity of this point
idVec3 myVelocity = vel + aVel.Cross( radiusVector );
cent->GetPhysics()->SetLinearVelocity( myVelocity );
//
cent->GetPhysics()->SetContents( 0, 0 );
cent->GetPhysics()->SetClipMask( CONTENTS_SOLID | CONTENTS_BODY, 0 );
if ( flipPower != 0.0f ) {
idBounds bounds = cent->GetPhysics()->GetBounds();
// choose a random point in its bounds to apply an upwards impulse
idVec3 size = bounds.Size();
idVec3 point;
// pick whether it should flip forwards or backwards based on the velocity
if ( myVelocity * axis[ 0 ] >= 0.0f ) {
point.x = 0.0f;
} else {
point.x = size.x;
}
point.y = size.y * gameLocal.random.RandomFloat();
point.z = size.z * gameLocal.random.RandomFloat();
point += bounds[ 0 ];
point *= axis;
point += org;
const idVec3& gravityNormal = -cent->GetPhysics()->GetGravityNormal();
idVec3 flipDirection = -gravityNormal;
float flipAmount = -flipDirection * cent->GetPhysics()->GetGravity();
if ( parentPhysics != transport->GetPhysics() ) {
// this is a slaved part
flipDirection = radiusVector;
flipDirection -= ( flipDirection*gravityNormal )*gravityNormal;
flipDirection.Normalize();
}
float scaleByVel = idMath::ClampFloat( 0.75f, 1.0f, myVelocity.Length() / 500.0f );
// calculate the impulse to apply
idVec3 impulse = scaleByVel * flipPower * MS2SEC( gameLocal.msec ) * cent->GetPhysics()->GetMass() * flipDirection * flipAmount;
cent->GetPhysics()->ApplyImpulse( 0, point, impulse );
}
}
}
/*
================
sdVehiclePart::CreateDecayDebris
================
*/
void sdVehiclePart::CreateDecayDebris( void ) {
if ( g_disableTransportDebris.GetBool() ) {
return;
}
sdTransport* transport = GetParent();
idVec3 org;
idMat3 axis;
GetWorldOrigin( org );
GetWorldAxis( axis );
// make sure we can have another part of this priority
debrisPriority_t priority = ( debrisPriority_t )brokenPart->dict.GetInt( "priority" );
if ( !CanAddDebris( priority, org ) ) {
return;
}
idVec3 velMax = brokenPart->dict.GetVector( "decay_velocity_max", "300 0 0" );
idVec3 aVelMax = brokenPart->dict.GetVector( "decay_angular_velocity_max", "0.5 1 2" );
idVec3 vel, aVel;
float random = gameLocal.random.RandomFloat();
for( int i = 0; i < 3; i++ ) {
// jrad - disable damage-based motion for now...
vel[ i ] = random * ( velMax[ i ] /*+ damageAmount * -damageDirection[ i ] */ );
aVel[ i ] = random * aVelMax[ i ];
}
vel *= axis;
vel += transport->GetPhysics()->GetLinearVelocity();
aVel += transport->GetPhysics()->GetAngularVelocity();
vel *= transport->GetRenderEntity()->axis;
aVel *= transport->GetRenderEntity()->axis;
rvClientMoveable* cent = gameLocal.SpawnClientMoveable( brokenPart->GetName(), 5000, org, axis, vel, aVel, 1 );
AddDebris( cent, priority );
if ( cent != NULL ) {
gameLocal.PlayEffect( brokenPart->dict, colorWhite.ToVec3(), "fx_decay", NULL, cent->GetPhysics()->GetOrigin(), cent->GetPhysics()->GetAxis(), false );
cent->GetPhysics()->SetContents( 0 );
cent->GetPhysics()->SetClipMask( CONTENTS_SOLID | CONTENTS_BODY );
}
}
/*
================
sdVehiclePart::Reattach
================
*/
void sdVehiclePart::Reattach( void ) {
if ( !IsHidden() ) {
return;
}
Show();
ShowSurfaces();
idPhysics* physics = GetParent()->GetPhysics();
if( bodyId != -1 ) {
physics->SetContents( oldcontents, bodyId );
physics->SetClipMask( oldclipmask, bodyId );
}
physics->Activate();
reattachTime = gameLocal.time;
}
/*
================
sdVehiclePart::Repair
================
*/
int sdVehiclePart::Repair( int repair ) {
if ( health >= maxHealth ) {
return 0;
}
int heal = maxHealth - health;
if( repair > heal ) {
repair = heal;
}
health += repair;
return repair;
}
/*
================
sdVehiclePart::HideSurfaces
================
*/
void sdVehiclePart::HideSurfaces( void ) {
bool surfacesChanged = false;
idRenderModel* renderModel = GetParent()->GetRenderEntity()->hModel;
for ( int i = 0; i < surfaces.Num(); i++ ) {
GetParent()->GetRenderEntity()->hideSurfaceMask.Set( surfaces[ i ] );
surfacesChanged = true;
}
// remove bound things in that bounds
idBounds partBounds;
idVec3 origin;
idMat3 axis;
GetWorldOrigin( origin );
GetWorldAxis( axis );
GetBounds( partBounds );
partBounds.RotateSelf( axis );
partBounds.TranslateSelf( origin );
GetParent()->RemoveBinds( &partBounds, true );
if ( GetParent()->GetModelDefHandle() != -1 ) {
gameRenderWorld->RemoveDecals( GetParent()->GetModelDefHandle() );
}
if ( surfacesChanged ) {
GetParent()->BecomeActive( TH_UPDATEVISUALS );
}
}
/*
================
sdVehiclePart::ShowSurfaces
================
*/
void sdVehiclePart::ShowSurfaces( void ) {
bool surfacesChanged = false;
for ( int i = 0; i < surfaces.Num(); i++ ) {
GetParent()->GetRenderEntity()->hideSurfaceMask.Clear( surfaces[ i ] );
surfacesChanged = true;
}
if ( surfacesChanged ) {
GetParent()->BecomeActive( TH_UPDATEVISUALS );
}
}
/*
================
sdVehiclePart::Damage
================
*/
void sdVehiclePart::Damage( int damage, idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const trace_t* collision ) {
if ( gameLocal.isClient ) {
return;
}
if ( IsHidden() ) {
return;
}
health -= damage;
if ( health < -1 ) {
health = -1;
}
sdTransport* transport = GetParent();
if ( health <= 0 || transport->GetHealth() <= 0 ) {
Detach( true, false );
OnKilled();
transport->GetPhysics()->Activate();
}
}
/*
================
sdVehiclePart::Decay
This makes a part fall off, instead of blowing it off like Damage will.
================
*/
void sdVehiclePart::Decay( void ) {
if ( gameLocal.isClient ) {
return;
}
if ( IsHidden() ) {
return;
}
// destroy it instantly
health = -1;
sdTransport* transport = GetParent();
//HideSurfaces(); ?!? detach calls this already
Detach( true, true );
OnKilled();
transport->GetPhysics()->Activate();
}
/*
================
sdVehiclePart::~sdVehiclePart
================
*/
sdVehiclePart::~sdVehiclePart() {
delete waterEffects;
}
/*
================
sdVehiclePart::GetBounds
================
*/
void sdVehiclePart::GetBounds( idBounds& bounds ) {
if( !GetParent() || bodyId == -1 ) {
bounds = partBounds;
return;
}
bounds = GetParent()->GetPhysics()->GetBounds( bodyId );
}
/*
================
sdVehiclePart::GetWorldOrigin
================
*/
void sdVehiclePart::GetWorldOrigin( idVec3& vec ) {
if( !GetParent() || bodyId == -1 ) {
vec = GetParent()->GetPhysics()->GetOrigin();
return;
}
vec = GetParent()->GetPhysics()->GetOrigin( bodyId );
}
/*
================
sdVehiclePart::GetWorldAxis
================
*/
void sdVehiclePart::GetWorldAxis( idMat3& axis ) {
if( !GetParent() || bodyId == -1 ) {
axis = GetParent()->GetPhysics()->GetAxis();
return;
}
axis = GetParent()->GetPhysics()->GetAxis( bodyId );
}
/*
================
sdVehiclePart::Init
================
*/
void sdVehiclePart::Init( const sdDeclVehiclePart& part ) {
name = part.data.GetString( "name" );
maxHealth = health = part.data.GetInt( "health" );
brokenPart = gameLocal.declEntityDefType[ part.data.GetString( "def_brokenPart" ) ];
damageInfo.damageScale = part.data.GetFloat( "damageScale", "1.0" );
damageInfo.collisionScale = part.data.GetFloat( "collisionScale", "1.0" );
damageInfo.collisionMinSpeed = part.data.GetFloat( "collisionMinSpeed", "256.0" );
damageInfo.collisionMaxSpeed = part.data.GetFloat( "collisionMaxSpeed", "1024.0" );
noAutoHide = part.data.GetBool( "noAutoHide", "0" );
flipPower = part.data.GetFloat( "flip_power", "5" );
flipMaster = part.data.GetBool( "flip_master" );
const idKeyValue* kv = NULL;
while ( kv = part.data.MatchPrefix( "surface", kv ) ) {
AddSurface( kv->GetValue() );
}
partBounds.Clear();
if ( brokenPart ) {
renderEntity_t renderEnt;
gameEdit->ParseSpawnArgsToRenderEntity( brokenPart->dict, renderEnt );
if ( renderEnt.hModel ) {
partBounds = renderEnt.hModel->Bounds();
}
}
waterEffects = sdWaterEffects::SetupFromSpawnArgs( part.data );
if ( waterEffects ) {
waterEffects->SetMaxVelocity( 300.0f );
}
}
/*
================
sdVehiclePart::CheckWater
================
*/
void sdVehiclePart::CheckWater( const idVec3& waterBodyOrg, const idMat3& waterBodyAxis, idCollisionModel* waterBodyModel ) {
if ( waterEffects ) {
idVec3 temp;
idMat3 temp2;
GetWorldOrigin( temp );
GetWorldAxis( temp2 );
waterEffects->SetOrigin( temp );
waterEffects->SetAxis( temp2 );
waterEffects->SetVelocity( GetParent()->GetPhysics()->GetLinearVelocity() );
waterEffects->CheckWater( GetParent(), waterBodyOrg, waterBodyAxis, waterBodyModel );
}
}
/*
================
sdVehiclePart::CalcSurfaceBounds
================
*/
idBounds sdVehiclePart::CalcSurfaceBounds( jointHandle_t joint ) {
idBounds res;
//res[0] = idVec3(-20,-20,-20);
//res[1] = idVec3(20,20,20);
res.Clear();
if ( !GetParent() ) {
return res;
}
for ( int i = 0; i < surfaces.Num(); i++ ) {
idBounds b;
GetParent()->GetAnimator()->GetMeshBounds( joint, surfaces[ i ], gameLocal.time, b, true );
res.AddBounds( b );
}
return res;
}
/*
================
sdVehiclePart::Event_GetHealth
================
*/
void sdVehiclePart::Event_GetHealth( void ) {
sdProgram::ReturnFloat( health );
}
/*
================
sdVehiclePart::Event_GetOrigin
================
*/
void sdVehiclePart::Event_GetOrigin( void ) {
idVec3 temp;
GetWorldOrigin( temp );
sdProgram::ReturnVector( temp );
}
/*
================
sdVehiclePart::Event_GetAngles
================
*/
void sdVehiclePart::Event_GetAngles( void ) {
idMat3 temp;
GetWorldAxis( temp );
idAngles ang = temp.ToAngles();
sdProgram::ReturnVector( idVec3( ang[0], ang[1], ang[2] ) );
}
/*
================
sdVehiclePart::Event_GetParent
================
*/
void sdVehiclePart::Event_GetParent( void ) {
sdProgram::ReturnEntity( GetParent() );
}
/*
================
sdVehiclePart::Event_GetJoint
================
*/
void sdVehiclePart::Event_GetJoint( void ) {
if ( !GetParent() ) {
sdProgram::ReturnString("");
return;
}
if ( !GetParent()->GetAnimator() ) {
sdProgram::ReturnString("");
return;
}
sdProgram::ReturnString( GetParent()->GetAnimator()->GetJointName( GetJoint() ) );
}
/*
===============================================================================
sdVehiclePartSimple
===============================================================================
*/
CLASS_DECLARATION( sdVehiclePart, sdVehiclePartSimple )
END_CLASS
/*
============
sdVehiclePart::ShouldDisplayDebugInfo
============
*/
bool sdVehiclePartSimple::ShouldDisplayDebugInfo() const {
return true;
// idPlayer* player = gameLocal.GetLocalPlayer();
// return ( player && parent == player->GetProxyEntity() );
}
/*
================
sdVehiclePartSimple::Init
================
*/
void sdVehiclePartSimple::Init( const sdDeclVehiclePart& part, sdTransport* _parent ) {
parent = _parent;
sdVehiclePart::Init( part );
joint = _parent->GetAnimator()->GetJointHandle( part.data.GetString( "joint" ) );
if ( joint == INVALID_JOINT ) {
gameLocal.Error( "sdVehiclePartSimple::Init Invalid Joint Name '%s'", part.data.GetString( "joint" ) );
}
partBounds = CalcSurfaceBounds( joint );
}
/*
================
sdVehiclePartSimple::GetWorldAxis
================
*/
void sdVehiclePartSimple::GetWorldAxis( idMat3& axis ) {
parent->GetWorldAxis( joint, axis );
}
/*
================
sdVehiclePartSimple::GetWorldOrigin
================
*/
void sdVehiclePartSimple::GetWorldOrigin( idVec3& vec ) {
parent->GetWorldOrigin( joint, vec );
}
/*
================
sdVehiclePartSimple::GetWorldPhysicsAxis
================
*/
void sdVehiclePartSimple::GetWorldPhysicsAxis( idMat3& axis ) {
GetWorldAxis( axis );
// transform this to be relative to the physics. can't use the render info as physics input!
const idMat3& physicsAxis = parent->GetPhysics()->GetAxis();
const idMat3& renderAxis = parent->GetRenderEntity()->axis;
axis = physicsAxis * renderAxis.TransposeMultiply( axis );
}
/*
================
sdVehiclePartSimple::GetWorldPhysicsOrigin
================
*/
void sdVehiclePartSimple::GetWorldPhysicsOrigin( idVec3& vec ) {
GetWorldOrigin( vec );
// transform this to be relative to the physics. can't use the render info as physics input!
const idVec3& physicsOrigin = parent->GetPhysics()->GetOrigin();
const idVec3& renderOrigin = parent->GetRenderEntity()->origin;
const idMat3& physicsAxis = parent->GetPhysics()->GetAxis();
const idMat3& renderAxis = parent->GetRenderEntity()->axis;
vec = physicsAxis * renderAxis.TransposeMultiply( vec - renderOrigin ) + physicsOrigin;
}
/*
============
sdVehicleRigidBodyPart::ShouldDisplayDebugInfo
============
*/
bool sdVehicleRigidBodyPart::ShouldDisplayDebugInfo() const {
idPlayer* player = gameLocal.GetLocalPlayer();
return ( player && parent == player->GetProxyEntity() );
}
/*
===============================================================================
sdVehiclePartScripted
===============================================================================
*/
CLASS_DECLARATION( sdVehiclePartSimple, sdVehiclePartScripted )
END_CLASS
/*
================
sdVehiclePartScripted::Init
================
*/
void sdVehiclePartScripted::Init( const sdDeclVehiclePart& part, sdTransport* _parent ) {
sdVehiclePartSimple::Init( part, _parent );
onKilled = NULL;
onPostDamage = NULL;
if ( !scriptObject ) {
const char *name = part.data.GetString( "scriptObject" );
if ( *name ) {
scriptObject = gameLocal.program->AllocScriptObject( this, name );
}
}
if ( scriptObject ) {
onKilled = scriptObject->GetFunction( "OnKilled" );
onPostDamage = scriptObject->GetFunction( "OnPostDamage" );
}
}
/*
================
sdVehiclePartScripted::OnKilled
================
*/
void sdVehiclePartScripted::OnKilled( void ) {
if ( onKilled ) {
sdScriptHelper helper;
scriptObject->CallNonBlockingScriptEvent( onKilled, helper );
}
}
/*
================
sdVehiclePartScripted::Damage
================
*/
void sdVehiclePartScripted::Damage( int damage, idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const trace_t* collision ) {
int oldHealth = health;
sdVehiclePartSimple::Damage( damage, inflictor, attacker, dir, collision );
if ( onPostDamage ) {
sdScriptHelper helper;
helper.Push( attacker ? attacker->GetScriptObject() : NULL );
helper.Push( oldHealth );
helper.Push( health );
scriptObject->CallNonBlockingScriptEvent( onPostDamage, helper );
}
}
/*
===============================================================================
sdVehicleRigidBodyPartSimple
===============================================================================
*/
CLASS_DECLARATION( sdVehiclePart, sdVehicleRigidBodyPartSimple )
END_CLASS
/*
================
sdVehicleRigidBodyPartSimple::Init
================
*/
void sdVehicleRigidBodyPartSimple::Init( const sdDeclVehiclePart& part, sdTransport_RB* _parent ) {
parent = _parent;
sdVehiclePart::Init( part );
joint = _parent->GetAnimator()->GetJointHandle( part.data.GetString( "joint" ) );
if ( joint == INVALID_JOINT ) {
gameLocal.Error( "sdVehiclePartSimple::Init Invalid Joint Name '%s'", part.data.GetString( "joint" ) );
}
partBounds = CalcSurfaceBounds( joint );
}
/*
================
sdVehicleRigidBodyPartSimple::GetParent
================
*/
sdTransport* sdVehicleRigidBodyPartSimple::GetParent( void ) const {
return parent;
}
/*
================
sdVehicleRigidBodyPartSimple::GetWorldOrigin
================
*/
void sdVehicleRigidBodyPartSimple::GetWorldOrigin( idVec3& vec ) {
sdTransport* transport = GetParent();
if( !transport ) {
vec.Zero();
return;
}
transport->GetWorldOrigin( joint, vec );
}
/*
================
sdVehicleRigidBodyPartSimple::GetWorldPhysicsOrigin
================
*/
void sdVehicleRigidBodyPartSimple::GetWorldPhysicsOrigin( idVec3& vec ) {
GetWorldOrigin( vec );
// transform this to be relative to the physics. can't use the render info as physics input!
const idVec3& physicsOrigin = parent->GetPhysics()->GetOrigin();
const idVec3& renderOrigin = parent->GetRenderEntity()->origin;
const idMat3& physicsAxis = parent->GetPhysics()->GetAxis();
const idMat3& renderAxis = parent->GetRenderEntity()->axis;
vec = physicsAxis * renderAxis.TransposeMultiply( vec - renderOrigin ) + physicsOrigin;
}
/*
===============================================================================
sdVehicleRigidBodyPart
===============================================================================
*/
CLASS_DECLARATION( sdVehiclePart, sdVehicleRigidBodyPart )
END_CLASS
/*
================
sdVehicleRigidBodyPart::Init
================
*/
void sdVehicleRigidBodyPart::Init( const sdDeclVehiclePart& part, sdTransport_RB* _parent ) {
parent = _parent;
sdVehiclePart::Init( part );
idClipModel* cm = NULL;
const char* clipModelName = part.data.GetString( "cm_model" );
if ( *clipModelName ) {
idTraceModel trm;
if ( !gameLocal.clip.LoadTraceModel( clipModelName, trm ) ) {
gameLocal.Error( "sdVehicleRigidBodyPart::Init Could not convert '%s' to a trace model", clipModelName );
}
cm = new idClipModel( trm, false );
} else {
idTraceModel trm;
idVec3 mins = part.data.GetVector( "mins" );
idVec3 maxs = part.data.GetVector( "maxs" );
idBounds bounds( mins, maxs );
if ( bounds.GetVolume() < 0.0f ) {
gameLocal.Warning( "sdVehicleRigidBodyPart::Init Invalid mins/maxs: Volume is negative!" );
// fix the bounds so the volume isn't negative
bounds.Clear();
bounds.AddPoint( mins );
bounds.AddPoint( maxs );
}
const char* typeName = part.data.GetString( "type", "box" );
if ( !idStr::Icmp( typeName, "box" ) ) {
trm.SetupBox( bounds );
} else if ( !idStr::Icmp( typeName, "cylinder" ) ) {
trm.SetupCylinder( bounds, part.data.GetInt( "sides" ), part.data.GetFloat( "angleOffset" ), part.data.GetInt( "option" ) );
} else if ( !idStr::Icmp( typeName, "frustum" ) ) {
trm.SetupFrustum( bounds, part.data.GetFloat( "topOffset" ) );
} else {
gameLocal.Error( "sdVehicleRigidBodyPart::Init Invalid Rigid Body Part Type '%s'", typeName );
}
cm = new idClipModel( trm, false );
}
sdPhysics_RigidBodyMultiple& rigidBody = *_parent->GetRBPhysics();
int index = rigidBody.GetNumClipModels();
bodyId = index;
rigidBody.SetClipModel( cm, 1.f, bodyId );
rigidBody.SetContactFriction( bodyId, part.data.GetVector( "contactFriction" ) );
rigidBody.SetMass( part.data.GetFloat( "mass", "1" ), bodyId );
rigidBody.SetBodyOffset( bodyId, part.data.GetVector( "offset" ) );
rigidBody.SetBodyBuoyancy( bodyId, part.data.GetFloat( "buoyancy", "0.01" ) );
rigidBody.SetBodyWaterDrag( bodyId, part.data.GetFloat( "waterDrag", "0" ) );
if ( part.data.GetBool( "noCollision" ) ) {
rigidBody.SetClipMask( 0, bodyId );
rigidBody.SetContents( 0, bodyId );
} else {
rigidBody.SetClipMask( MASK_VEHICLESOLID | CONTENTS_MONSTER, bodyId );
rigidBody.SetContents( CONTENTS_PLAYERCLIP | CONTENTS_IKCLIP | CONTENTS_VEHICLECLIP | CONTENTS_FLYERHIVECLIP, bodyId );
}
joint = _parent->GetAnimator()->GetJointHandle( part.data.GetString( "joint" ) );
partBounds = CalcSurfaceBounds( joint );
}
/*
================
sdVehicleRigidBodyPart::GetParent
================
*/
sdTransport* sdVehicleRigidBodyPart::GetParent( void ) const {
return parent;
}
/*
================
sdVehicleRigidBodyPart::sdVehicleRigidBodyPart
================
*/
void sdVehicleRigidBodyPart::GetWorldOrigin( idVec3& vec ) {
parent->GetRBPhysics()->GetBodyOrigin( vec, bodyId );
}
/*
============
sdVehicleRigidBodyPartSimple::ShouldDisplayDebugInfo
============
*/
bool sdVehicleRigidBodyPartSimple::ShouldDisplayDebugInfo() const {
idPlayer* player = gameLocal.GetLocalPlayer();
return ( player && parent == player->GetProxyEntity() );
}
/*
===============================================================================
sdVehicleRigidBodyWheel
===============================================================================
*/
CLASS_DECLARATION( sdVehicleRigidBodyPart, sdVehicleRigidBodyWheel )
END_CLASS
/*
================
sdVehicleRigidBodyWheel::sdVehicleRigidBodyWheel
================
*/
sdVehicleRigidBodyWheel::sdVehicleRigidBodyWheel( void ) {
frictionAxes.Identity();
currentFriction.Zero();
wheelModel = NULL;
suspension = NULL;
wheelOffset = 0;
totalWheels = -1;
memset( &groundTrace, 0, sizeof( groundTrace ) );
wheelFractionMemory.SetNum( MAX_WHEEL_MEMORY );
for ( int i = 0; i < MAX_WHEEL_MEMORY; i++ ) {
wheelFractionMemory[ i ] = 1.0f;
}
currentMemoryFrame = 0;
currentMemoryIndex = 0;
suspensionInterface.Init( this );
}
/*
================
sdVehicleRigidBodyWheel::~sdVehicleRigidBodyWheel
================
*/
sdVehicleRigidBodyWheel::~sdVehicleRigidBodyWheel( void ) {
gameLocal.clip.DeleteClipModel( wheelModel );
delete suspension;
}
/*
================
sdVehicleRigidBodyWheel::Init
================
*/
void sdVehicleRigidBodyWheel::TrackWheelInit( const sdDeclVehiclePart& track, int index, sdTransport_RB* _parent ) {
sdVehicleRigidBodyPartSimple::Init( track, _parent );
name = track.data.GetString( va( "wheel_joint_%i", index + 1 ) );
health = maxHealth = -1;
brokenPart = NULL;
partBounds.Clear();
joint = _parent->GetAnimator()->GetJointHandle( name.c_str() );
if ( joint == INVALID_JOINT ) {
gameLocal.Error( "sdVehicleRigidBodyWheel::TrackWheelInit Invalid Joint Name '%s'", name.c_str() );
}
partBounds = CalcSurfaceBounds( joint );
CommonInit( track );
sdVehicleSuspension_Vertical* verticalSuspension = new sdVehicleSuspension_Vertical();
suspension = verticalSuspension;
if ( verticalSuspension != NULL ) {
verticalSuspension->Init( &suspensionInterface, track.data.GetString( va( "wheel_suspension_%i", index + 1 ) ) );
}
traceIndex = track.data.GetInt( va( "wheel_trace_index_%i", index + 1 ), "-1" );
wheelFlags.partOfTrack = true;
}
/*
================
sdVehicleRigidBodyWheel::Init
================
*/
void sdVehicleRigidBodyWheel::Init( const sdDeclVehiclePart& wheel, sdTransport_RB* _parent ) {
sdVehicleRigidBodyPartSimple::Init( wheel, _parent );
CommonInit( wheel );
const sdDeclStringMap* suspensionInfoDecl = gameLocal.declStringMapType[ wheel.data.GetString( "suspension" ) ];
if ( suspensionInfoDecl != NULL ) {
suspension = sdVehicleSuspension::GetSuspension( suspensionInfoDecl->GetDict().GetString( "type" ) );
if ( suspension != NULL ) {
suspension->Init( &suspensionInterface, suspensionInfoDecl->GetDict() );
}
}
traceIndex = wheel.data.GetInt( "trace_index", "-1" );
wheelFlags.partOfTrack = false;
}
/*
================
sdVehicleRigidBodyWheel::CommonInit
================
*/
void sdVehicleRigidBodyWheel::CommonInit( const sdDeclVehiclePart& part ) {
if ( part.data.GetBool( "turn" ) ) {
wheelFlags.hasSteering = true;
wheelFlags.inverseSteering = false;
} else if ( part.data.GetBool( "inverseturn" ) ) {
wheelFlags.hasSteering = true;
wheelFlags.inverseSteering = true;
} else {
wheelFlags.hasSteering = false;
wheelFlags.inverseSteering = false;
}
wheelFlags.noPhysics = part.data.GetBool( "noPhysics" );
wheelFlags.hasDrive = part.data.GetBool( "drive" );
wheelFlags.noRotation = part.data.GetBool( "noRotation" );
wheelFlags.slowsOnLeft = part.data.GetBool( "slowOnLeft" );
wheelFlags.slowsOnRight = part.data.GetBool( "slowOnRight" );
angle = 0.f;
steerAngle = 0.f;
idealSteerAngle = 0.f;
state.moving = false;
state.changed = true;
state.steeringChanged = false;
state.grounded = false;
state.suspensionDisabled = false;
radius = part.data.GetFloat( "radius" );
rotationspeed = 0.f;
normalFriction = part.data.GetVector( "contactFriction" );
currentFriction = normalFriction;
steerScale = part.data.GetFloat( "steerScale", "1" );
suspensionInfo.velocityScale = part.data.GetFloat( "suspensionVelocityScale", "1" );
suspensionInfo.kCompress = part.data.GetFloat( "suspensionKCompress" );
suspensionInfo.upTrace = part.data.GetFloat( "suspensionUpTrace" );
suspensionInfo.downTrace = part.data.GetFloat( "suspensionDownTrace" );
suspensionInfo.totalDist = suspensionInfo.upTrace + suspensionInfo.downTrace;
suspensionInfo.damping = part.data.GetFloat( "suspensionDamping" );
suspensionInfo.base = part.data.GetFloat( "suspensionBase" );
suspensionInfo.range = part.data.GetFloat( "suspensionRange" );
suspensionInfo.maxRestVelocity = part.data.GetFloat( "suspensionMaxRestVelocity", "5" );
suspensionInfo.aggressiveDampening = part.data.GetBool( "aggressiveDampening" );
suspensionInfo.slowScale = part.data.GetFloat( "slowScale", "0" );
suspensionInfo.slowScaleSpeed = part.data.GetFloat( "slowScaleSpeed", "400" );
suspensionInfo.hardStopScale = 1.0f / Max( 1.0f, part.data.GetFloat( "hardStopFrames", "4" ) );
suspensionInfo.alternateSuspensionModel = part.data.GetBool( "alternateSuspensionModel" );
wheelSpinForceThreshold = part.data.GetFloat( "wheelSpinForceThreshhold" );
wheelSkidVelocityThreshold = part.data.GetFloat( "wheelSkidVelocityThreshold", "150" );
state.setSteering = part.data.GetBool( "control_steering" );
brakingForce = part.data.GetFloat( "brakingForce", "500000" );
handBrakeSlipScale = part.data.GetFloat( "handBrakeSlipScale", "1" );
maxSlip = part.data.GetFloat( "maxSlip", "100000" );
wheelFlags.hasHandBrake = part.data.GetBool( "hasHandBrake" );
traction.AssureSize( gameLocal.declSurfaceTypeType.Num() );
int i;
for ( i = 0; i < gameLocal.declSurfaceTypeType.Num(); i++ ) {
traction[ i ] = part.data.GetFloat( va( "traction_%s", gameLocal.declSurfaceTypeType[ i ]->GetName() ), "1" );
}
static idVec3 rightWheelWinding[ 4 ] = {
idVec3( 1.0f, 0.f, 0.0f ),
idVec3( -1.0f, 0.f, 0.0f ),
idVec3( -1.0f, 1.f, 0.0f ),
idVec3( 1.0f, 1.f, 0.0f )
};
static idVec3 leftWheelWinding[ 4 ] = {
idVec3( 1.0f, -1.f, 0.0f ),
idVec3( -1.0f, -1.f, 0.0f ),
idVec3( -1.0f, 0.f, 0.0f ),
idVec3( 1.0f, 0.f, 0.0f )
};
parent->GetAnimator()->GetJointTransform( joint, gameLocal.time, baseOrg, baseAxes );
baseOrgOffset = part.data.GetVector( "base_org_offset" );
if ( part.data.GetBool( "localRotation" ) ) {
rotationAxis = baseAxes[ 0 ];
} else {
rotationAxis.Set( 0.f, -1.f, 0.f );
}
wheelFlags.isLeftWheel = baseOrg[ 1 ] < 0;
wheelFlags.isFrontWheel = baseOrg[ 0 ] > 0;
idVec3* wheelWinding = IsLeftWheel() ? leftWheelWinding : rightWheelWinding;
idVec3 verts[ 4 ];
float footprint = part.data.GetFloat( "footprint" );
if ( footprint < idMath::FLT_EPSILON ) {
gameLocal.Error( "sdVehicleRigidBodyWheel::CommonInit \"footprint\" too small: %.6f in vscript: %s", footprint, parent->GetVehicleScript()->GetName() );
}
for ( i = 0; i < 4; i++ ) {
verts[ i ] = wheelWinding[ i ] * footprint;
}
idTraceModel trm;
trm.SetupPolygon( verts, 4 );
wheelModel = new idClipModel( trm, false );
idIK* ik = parent->GetIK();
if ( ik && ik->IsType( sdIK_WheeledVehicle::Type ) ) {
sdIK_WheeledVehicle* wheelIK = reinterpret_cast< sdIK_WheeledVehicle* >( ik );
wheelIK->AddWheel( *this );
}
treadId = 0;
parent->InitEffectList( dustEffects, "fx_wheeldust", numSurfaceTypesAtSpawn );
parent->InitEffectList( spinEffects, "fx_wheelspin", numSurfaceTypesAtSpawn );
parent->InitEffectList( skidEffects, "fx_skid", numSurfaceTypesAtSpawn );
stroggTread = parent->spawnArgs.GetBool( "stroggTread" );
}
/*
================
sdVehicleRigidBodyWheel::GetLinearSpeed
================
*/
float sdVehicleRigidBodyWheel::GetLinearSpeed( void ) {
idVec3 temp;
parent->GetRBPhysics()->GetPointVelocity( groundTrace.c.point, temp );
temp -= ( temp * groundTrace.c.normal ) * groundTrace.c.normal;
return temp * ( frictionAxes * parent->GetRBPhysics()->GetAxis()[ 0 ] );
}
idCVar cm_drawTraces( "cm_drawTraces", "0", CVAR_GAME | CVAR_BOOL, "draw polygon and edge normals" );
idCVar g_skipVehicleFrictionFeedback( "g_skipVehicleFrictionFeedback", "0", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "ignore the effects of surface friction" );
idCVar g_debugVehicleFrictionFeedback( "g_debugVehicleFrictionFeedback", "0", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "show info about wheeled surface friction feedback" );
idCVar g_debugVehicleDriveForces( "g_debugVehicleDriveForces", "0", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "show info about wheeled drive forces" );
idCVar g_debugVehicleWheelForces( "g_debugVehicleWheelForces", "0", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "show info about wheel forces" );
idCVar g_vehicleWheelTracesPerFrame( "g_vehicleWheelTracesPerFrame", "0.5", CVAR_GAME | CVAR_FLOAT | CVAR_CHEAT, "What fraction of the wheels are updated per frame" );
struct cm_logger_t {
};
/*
================
sdVehicleRigidBodyWheel::UpdateSuspension
================
*/
void sdVehicleRigidBodyWheel::UpdateSuspension( const sdVehicleInput& input ) {
sdPhysics_RigidBodyMultiple& physics = *parent->GetRBPhysics();
if ( totalWheels == -1 ) {
idIK* ik = parent->GetIK();
if ( ik && ik->IsType( sdIK_WheeledVehicle::Type ) ) {
sdIK_WheeledVehicle* wheelIK = reinterpret_cast< sdIK_WheeledVehicle* >( ik );
totalWheels = wheelIK->GetNumWheels();
}
// auto-assign for 4 wheeled vehicles
if ( traceIndex == -1 && totalWheels == 4 ) {
if ( wheelFlags.isLeftWheel ) {
if ( wheelFlags.isFrontWheel ) {
traceIndex = 0;
} else {
traceIndex = 3;
}
}
if ( !wheelFlags.isLeftWheel ) {
if ( wheelFlags.isFrontWheel ) {
traceIndex = 2;
} else {
traceIndex = 1;
}
}
}
}
if( HasSteering() ) {
idealSteerAngle = input.GetSteerAngle();
if( HasInverseSteering() ) {
idealSteerAngle = -idealSteerAngle;
}
idealSteerAngle *= steerScale;
} else {
idealSteerAngle = 0.f;
}
if ( idealSteerAngle != steerAngle ) {
steerAngle = idealSteerAngle;
if ( state.setSteering ) {
parent->SetSteerVisualAngle( steerAngle );
}
state.steeringChanged = true;
idAngles::YawToMat3( -steerAngle, frictionAxes );
}
if ( !input.GetBraking() && !input.GetHandBraking() && HasDrive() ) {
physics.Activate();
}
if ( !parent->GetPhysics()->IsAtRest() ) {
idVec3 org = parent->GetPhysics()->GetOrigin();
idMat3 axis = parent->GetPhysics()->GetAxis();
org = org + ( ( baseOrg + baseOrgOffset ) * axis );
idVec3 start = org + ( axis[ 2 ] * suspensionInfo.upTrace );
idVec3 end = org - ( axis[ 2 ] * suspensionInfo.downTrace );
bool doTraceThisFrame = false;
if ( traceIndex != -1 && g_vehicleWheelTracesPerFrame.GetFloat() < 1.0f ) {
// find out if this wheel is to be updated this frame
assert( totalWheels != -1 );
int numPerFrame = Max( 1, ( int )( g_vehicleWheelTracesPerFrame.GetFloat() * totalWheels ) );
if ( parent->GetAORPhysicsLOD() >= 2 ) {
numPerFrame = 1;
}
const int wheelToUpdate = ( gameLocal.framenum * numPerFrame );
const int nextWheelToUpdate = wheelToUpdate + numPerFrame;
for ( int i = wheelToUpdate; i < nextWheelToUpdate; i++ ) {
if ( i % totalWheels == traceIndex ) {
doTraceThisFrame = true;
break;
}
}
} else {
doTraceThisFrame = true;
}
if ( !doTraceThisFrame ) {
// check if the previous frame is in the memory bank
int prevFrameWrap = ( gameLocal.framenum - 1 ) / MAX_WHEEL_MEMORY;
int prevFrameIndex = ( gameLocal.framenum - 1 ) % MAX_WHEEL_MEMORY;
int curFrameWrap = currentMemoryFrame / MAX_WHEEL_MEMORY;
if ( prevFrameWrap < curFrameWrap - 1 ) {
// too far in the past
doTraceThisFrame = true;
} else if ( prevFrameWrap == curFrameWrap - 1 ) {
// in the previous wrap - check that the index
// is greater than the current one
if ( prevFrameIndex <= currentMemoryIndex ) {
// too far in the past
doTraceThisFrame = true;
}
} else {
// in the current wrap - check that its before this one
if ( gameLocal.framenum - 1 > currentMemoryFrame ) {
// WTF? Future?
doTraceThisFrame = true;
}
}
}
if ( doTraceThisFrame ) {
memset( &groundTrace, 0, sizeof( groundTrace ) );
if ( parent->GetAORPhysicsLOD() == 0 ) {
physics.GetTraceCollection().Translation( CLIP_DEBUG_PARMS groundTrace, start, end, wheelModel, axis, MASK_VEHICLESOLID | CONTENTS_MONSTER );
} else {
// cheaper point trace
physics.GetTraceCollection().Translation( CLIP_DEBUG_PARMS groundTrace, start, end, NULL, mat3_identity, MASK_VEHICLESOLID | CONTENTS_MONSTER );
}
if( g_debugVehicleWheelForces.GetBool() && ShouldDisplayDebugInfo() ) {
gameRenderWorld->DebugArrow( colorGreen, start, groundTrace.endpos, 10 );
gameRenderWorld->DebugArrow( colorRed, groundTrace.endpos, end, 10 );
if ( parent->GetAORPhysicsLOD() == 0 ) {
wheelModel->Draw( groundTrace.endpos, axis );
}
}
// store the fraction in the memory bank
currentMemoryFrame = gameLocal.framenum;
currentMemoryIndex = gameLocal.framenum % MAX_WHEEL_MEMORY;
wheelFractionMemory[ currentMemoryIndex ] = groundTrace.fraction;
} else {
// get the fraction from the previous frame
int prevFrameIndex = ( gameLocal.framenum - 1 ) % MAX_WHEEL_MEMORY;
float prevFraction = wheelFractionMemory[ prevFrameIndex ];
// fake it to seem like it did a trace this frame
idVec3 newEnd = Lerp( start, end, prevFraction );
if ( prevFraction < 1.0f ) {
idVec3 vel;
physics.GetPointVelocity( newEnd, vel );
float suspensionDelta = ( vel * axis[ 2 ] )*MS2SEC( gameLocal.msec );
newEnd -= axis[ 2 ] * suspensionDelta * 0.5f;
}
// calculate the new fraction
groundTrace.fraction = ( ( newEnd - start )*axis[ 2 ] ) / ( ( end - start )*axis[ 2 ] );
groundTrace.endpos = newEnd;
// store the fraction in the memory bank
currentMemoryFrame = gameLocal.framenum;
currentMemoryIndex = gameLocal.framenum % MAX_WHEEL_MEMORY;
wheelFractionMemory[ currentMemoryIndex ] = groundTrace.fraction;
}
groundTrace.c.point = groundTrace.endpos;
groundTrace.c.selfId = -1;
wheelOffset = suspensionInfo.upTrace + radius - ( suspensionInfo.totalDist * groundTrace.fraction );
state.rested = true;
state.spinning = false;
state.skidding = false;
if ( groundTrace.fraction != 1.0f ) {
CalcForces( suspensionForce, suspensionVelocity );
if ( idMath::Fabs( suspensionVelocity ) > suspensionInfo.maxRestVelocity ) {
state.rested = false;
parent->GetPhysics()->Activate();
} else {
suspensionVelocity = 0.f;
}
}
state.grounded = groundTrace.fraction != 1.f;
if ( suspension ) {
suspension->Update();
}
} else {
state.rested = true;
}
currentFriction = normalFriction;
UpdateFriction( input );
}
/*
================
sdVehicleRigidBodyWheel::UpdateMotor
================
*/
void sdVehicleRigidBodyWheel::UpdateMotor( const sdVehicleInput& input, float inputMotorForce ) {
if ( parent->IsFrozen() ) {
state.skidding = false;
state.spinning = false;
state.moving = false;
state.rested = true;
return;
}
sdPhysics_RigidBodyMultiple& physics = *parent->GetRBPhysics();
motorForce = 0.f;
motorSpeed = 0.f;
if ( !input.GetBraking() && !input.GetHandBraking() ) {
if( HasDrive() ) {
motorForce = idMath::Fabs( inputMotorForce );
motorSpeed = GetInputSpeed( input );
// adjust wheel velocity for better steering because there are no differentials between the wheels
if(( steerAngle < 0.0f && SlowsOnLeft()) ||
( steerAngle > 0.0f && SlowsOnRight() )) {
motorSpeed *= 0.5f;
}
}
}
bool braking = input.GetBraking();
bool handBraking = wheelFlags.hasHandBrake && input.GetHandBraking();
if ( braking || handBraking ) {
motorForce = brakingForce;
motorSpeed = 0.0f;
if ( !( handBraking && !braking ) ) {
float scale = ( idMath::Sqrt( 1.0f - groundTrace.c.normal.z ) )*5.0f;
motorForce *= scale + 1.5f;
}
}
if ( !parent->GetPhysics()->IsAtRest() ) {
if ( groundTrace.fraction != 1.0f ) {
if( !( braking || handBraking ) && groundTrace.c.surfaceType ) {
if( motorForce > 0.0f && ( motorForce / traction[ groundTrace.c.surfaceType->Index() ] > wheelSpinForceThreshold )) {
state.spinning = true;
}
}
UpdateSkidding();
}
}
}
/*
================
sdVehicleRigidBodyWheel::UpdatePrePhysics
================
*/
void sdVehicleRigidBodyWheel::UpdatePrePhysics( const sdVehicleInput& input ) {
if ( wheelFlags.noPhysics || state.suspensionDisabled ) {
return;
}
UpdateSuspension( input );
UpdateMotor( input, input.GetForce() );
if ( !parent->GetPhysics()->IsAtRest() ) {
if( g_debugVehicleDriveForces.GetBool() && ShouldDisplayDebugInfo() ) {
idVec3 org = parent->GetPhysics()->GetOrigin();
idMat3 axis = parent->GetPhysics()->GetAxis();
org = org + ( ( baseOrg + baseOrgOffset ) * axis );
gameRenderWorld->DrawText( va( "motor sp: %.2f\nmotor f: %.2f\n", motorSpeed, motorForce ), org, 0.25f, colorGreen, axis, 0 );
}
}
if ( !state.skidding && treadId ) {
tireTreadManager->StopSkid( treadId );
treadId = 0;
}
}
/*
================
sdVehicleRigidBodyWheel::UpdateFriction
================
*/
void sdVehicleRigidBodyWheel::UpdateFriction( const sdVehicleInput& input ) {
sdPhysics_RigidBodyMultiple& physics = *parent->GetRBPhysics();
idVec3 wheelOrigin = groundTrace.c.point;
idMat3 worldFrictionAxes = frictionAxes * physics.GetAxis();
// vary the lateral friction with the lateral slip
idVec3 contactWRTground;
physics.GetPointVelocity( wheelOrigin, contactWRTground );
idVec3 carWRTground = physics.GetLinearVelocity();
float lateralSlip = ( contactWRTground - carWRTground ) * worldFrictionAxes[ 1 ];
idVec3 slidingFriction = normalFriction * 0.8f; // assume kinetic friction is 80% of static friction
// make it much more sensitive to slip when handbraking
if ( wheelFlags.hasHandBrake && input.GetHandBraking() ) {
lateralSlip *= handBrakeSlipScale;
slidingFriction *= 0.5f;
}
float slideFactor = ( idMath::Fabs( lateralSlip ) ) / maxSlip;
slideFactor = idMath::ClampFloat( 0.0f, 1.0f, slideFactor );
currentFriction = Lerp( normalFriction, slidingFriction, slideFactor );
}
/*
================
sdVehicleRigidBodyWheel::UpdateSkidding
================
*/
void sdVehicleRigidBodyWheel::UpdateSkidding() {
idBounds bb;
GetBounds( bb );
if ( bb.IsCleared() ) {
return;
}
//
// get info about the parent
//
idPhysics* physics = GetParent()->GetPhysics();
const idVec3& origin = physics->GetOrigin();
const idMat3& axis = physics->GetAxis();
idMat3 axisTranspose = physics->GetAxis().Transpose();
const idVec3& velocity = physics->GetLinearVelocity();
const idVec3& angVel = physics->GetAngularVelocity();
const idVec3& com = physics->GetCenterOfMass() * axis + origin;
//
// get info about self
//
idVec3 delta = ( groundTrace.c.point - com ) * axisTranspose;
idVec3 pointVelocity = velocity + angVel.Cross( delta );
float slipVelocity = idMath::Fabs( pointVelocity * axis[ 1 ] );
if ( slipVelocity > wheelSkidVelocityThreshold ) {
state.skidding = true;
if ( gameLocal.isNewFrame ) {
if ( treadId == 0 ) {
treadId = tireTreadManager->StartSkid( stroggTread );
}
if ( treadId != 0 ) {
idVec3 point;
GetWorldOrigin( point );
idMat3 wheelaxis;
GetWorldAxis( wheelaxis );
point += wheelaxis[2] * (bb[0][2] * 0.8f);
if ( !tireTreadManager->AddSkidPoint( treadId, point/*groundTrace.c.point*/, pointVelocity, groundTrace.c.normal, groundTrace.c.surfaceType ) ) {
treadId = 0;
}
}
}
// gameRenderWorld->DebugArrow( colorYellow, com, com + velocity*0.2f, 8 );
// gameRenderWorld->DebugArrow( colorRed, com, com + delta * axis, 8 );
// gameRenderWorld->DebugArrow( colorGreen, groundTrace.c.point, groundTrace.c.point + slipVelocity*axis[ 1 ]*0.2f, 8 );
} else {
if ( treadId != 0 && gameLocal.isNewFrame ) {
tireTreadManager->StopSkid( treadId );
treadId = 0;
}
}
}
/*
================
sdVehicleRigidBodyWheel::UpdatePostPhysics
================
*/
void sdVehicleRigidBodyWheel::UpdatePostPhysics( const sdVehicleInput& input ) {
if ( !wheelFlags.noRotation ) {
UpdateRotation( input );
}
if ( gameLocal.isNewFrame ) {
UpdateParticles( input );
}
}
idCVar g_skipVehicleTurnFeedback( "g_skipVehicleTurnFeedback", "0", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "skip turn ducking effects on wheeled suspensions" );
idCVar g_skipVehicleAccelFeedback( "g_skipVehicleAccelFeedback", "0", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "skip acceleration effects on wheeled suspensions" );
idCVar g_debugVehicleFeedback( "g_debugVehicleFeedback", "0", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "show info about wheeled suspension feedback" );
/*
================
sdVehicleRigidBodyWheel::CalcForces
================
*/
void sdVehicleRigidBodyWheel::CalcForces( float& maxForce, float& velocity ) {
sdPhysics_RigidBodyMultiple& physics = *parent->GetRBPhysics();
idVec3 vel;
physics.GetPointVelocity( groundTrace.endpos, vel );
float compressionScale = idMath::ClampFloat( 0.0f, 1.0f, 1.0f - ( physics.GetLinearVelocity().Length() / suspensionInfo.slowScaleSpeed ) );
compressionScale = 1.0f + compressionScale*suspensionInfo.slowScale;
float springLength = groundTrace.fraction * suspensionInfo.totalDist;
float loss = suspensionInfo.totalDist - suspensionInfo.range;
springLength -= loss;
float springForce = 0.0f;
float invTimeStep = 1.0f / MS2SEC( gameLocal.msec );
float shockVelocity = vel * physics.GetAxis()[ 2 ];
// scale down the spring force allowed to be applied after reattaching wheels
float raiseUpFactor = 1.0f;
if ( reattachTime != 0 ) {
raiseUpFactor = idMath::ClampFloat( 0.0f, 1.0f, ( gameLocal.time - reattachTime ) / 1000.0f );
}
if ( springLength < 0.f && raiseUpFactor >= 1.0f ) {
// hard-stop
velocity = Min( suspensionInfo.range * -0.1f, springLength ) * ( invTimeStep * suspensionInfo.hardStopScale );
springForce = idMath::INFINITY * 0.5f;;
} else {
if ( !suspensionInfo.alternateSuspensionModel ) {
// regular
float compression = idMath::Sqrt( Max( 0.0f, ( suspensionInfo.range - springLength ) / suspensionInfo.range ) ) * suspensionInfo.range;
if ( !suspensionInfo.aggressiveDampening ) {
springForce = ( compression * compression * suspensionInfo.kCompress * compressionScale ) + suspensionInfo.base;
float s = 1.f - ( springLength / suspensionInfo.range );
s = s * s;
s = ( s * suspensionInfo.velocityScale * ( 1.0f / ( 3.0f * ( compressionScale - 1.0f ) +1.0f ) ) ) + 1.f;
float scale = s / ( float )gameLocal.msec;
velocity = -( compression - ( shockVelocity * suspensionInfo.damping * compressionScale ) ) * scale;
} else {
// the "agressive dampening" version does it differently
springForce = ( compression * compression * suspensionInfo.kCompress * compressionScale ) + suspensionInfo.base;
springForce -= shockVelocity * suspensionInfo.damping * compressionScale;
velocity = 0.0f;
}
} else {
float compression = suspensionInfo.range - springLength;
springForce = compression * suspensionInfo.kCompress - suspensionInfo.damping * shockVelocity;
// velocity needs to be the velocity this should be moving at. we can predict this, roughly
velocity = shockVelocity - MS2SEC( gameLocal.msec ) * springForce / ( parent->GetPhysics()->GetMass() / totalWheels );
velocity /= compressionScale;
springForce = springForce * compressionScale;
}
springForce *= raiseUpFactor;
}
if ( springForce < 0.0f ) {
springForce = 0.0f;
}
if( g_debugVehicleWheelForces.GetBool() && ShouldDisplayDebugInfo() ) {
idVec3 org = parent->GetPhysics()->GetOrigin();
idMat3 axis = parent->GetPhysics()->GetAxis();
org = org + ( ( baseOrg + baseOrgOffset ) * axis );
gameRenderWorld->DrawText( va( "velocity: %.2f\nmaxForce: %.2f", velocity, maxForce ), org, 0.25f, colorGreen, axis );
}
maxForce = springForce;
}
/*
================
sdVehicleRigidBodyWheel::EvaluateContacts
================
*/
int sdVehicleRigidBodyWheel::EvaluateContacts( contactInfo_t* list, contactInfoExt_t* listExt, int max ) {
if ( IsHidden() || max < 1 || state.suspensionDisabled ) {
return 0;
}
if ( state.grounded ) {
listExt[ 0 ].contactForceMax = suspensionForce;
listExt[ 0 ].contactForceVelocity = suspensionVelocity;
listExt[ 0 ].contactFriction = currentFriction;
listExt[ 0 ].frictionAxes = frictionAxes;
listExt[ 0 ].motorForce = motorForce;
if ( groundTrace.c.surfaceType ) {
listExt[ 0 ].motorForce *= traction[ groundTrace.c.surfaceType->Index() ];
}
if ( groundTrace.fraction < 1.0f ) {
// smoothly scale down the force applied, based on the slope of the surface
// so shallow slopes keep much of the force, but steep slopes rapidly lose traction
float temp = idMath::ClampFloat( 0.0f, 1.0f, groundTrace.c.normal.z );
float normalScaleDown = -0.5f*idMath::Cos( temp*temp*temp*idMath::PI ) + 0.5f;
if ( normalScaleDown < 0.00001f ) {
normalScaleDown = 0.0f;
}
listExt[ 0 ].motorForce *= normalScaleDown;
listExt[ 0 ].contactForceMax *= normalScaleDown;
}
listExt[ 0 ].motorDirection = frictionAxes[ 0 ];
listExt[ 0 ].motorSpeed = motorSpeed;
listExt[ 0 ].rested = state.rested;
list[ 0 ] = groundTrace.c;
return 1;
}
return 0;
}
/*
================
sdVehicleRigidBodyWheel::GetBaseWorldOrg
================
*/
idVec3 sdVehicleRigidBodyWheel::GetBaseWorldOrg( void ) const {
return parent->GetRenderEntity()->origin + ( baseOrg * parent->GetRenderEntity()->axis );
}
/*
================
sdVehicleRigidBodyWheel::GetInputSpeed
================
*/
float sdVehicleRigidBodyWheel::GetInputSpeed( const sdVehicleInput& input ) {
return ( IsLeftWheel() ? input.GetLeftSpeed() : input.GetRightSpeed() );
}
/*
================
sdVehicleRigidBodyWheel::UpdateRotation
================
*/
void sdVehicleRigidBodyWheel::UpdateRotation( float speed ) {
rotationspeed = speed;
angle += 360 * ( rotationspeed * MS2SEC( gameLocal.msec ) / ( 2 * idMath::PI * radius ) );
state.changed |= ( rotationspeed != 0 ) || ( state.steeringChanged );
state.steeringChanged = false;
state.moving = idMath::Fabs( speed ) > minParticleCreationSpeed;
}
/*
================
sdVehicleRigidBodyWheel::UpdateRotation
================
*/
void sdVehicleRigidBodyWheel::UpdateRotation( const sdVehicleInput& input ) {
sdPhysics_RigidBodyMultiple& physics = *parent->GetRBPhysics();
float oldangle = angle;
float s = 0.f;
if ( state.grounded ) {
if( !physics.IsAtRest() && !( input.GetHandBraking() && wheelFlags.hasHandBrake ) ) {
float vel = GetLinearSpeed();
s = vel;
if ( groundTrace.c.surfaceType ) {
s /= traction[ groundTrace.c.surfaceType->Index() ];
}
if ( fabs( s ) < 0.5f ) {
s = 0.f;
}
}
} else {
if ( !physics.InWater() ) {
float speed = GetInputSpeed( input );
if ( HasDrive() && speed ) {
s = speed;
} else {
float timeStep = MS2SEC( gameLocal.msec );
float rotationSlowingFactor;
if ( !physics.HasGroundContacts() ) {
rotationSlowingFactor = 0.2f;
} else if ( gameLocal.msec > 0 ) {
rotationSlowingFactor = 0.2f / timeStep;
} else {
rotationSlowingFactor = 0.0f;
}
s = rotationspeed - ( MS2SEC( gameLocal.msec ) * rotationspeed * rotationSlowingFactor );
if ( idMath::Fabs( s ) < 10.f ) {
s = 0.f;
}
}
}
}
UpdateRotation( s );
}
/*
================
sdVehicleRigidBodyWheel::UpdateParticles
================
*/
void sdVehicleRigidBodyWheel::UpdateParticles( const sdVehicleInput& input ) {
sdPhysics_RigidBodyMultiple& physics = *parent->GetRBPhysics();
int surfaceTypeIndex = -1;
if ( groundTrace.fraction < 1.0f ) {
surfaceTypeIndex = 0;
}
if ( groundTrace.c.surfaceType ) {
surfaceTypeIndex = groundTrace.c.surfaceType->Index() + 1;
}
if ( surfaceTypeIndex != -1 ) {
if ( state.grounded && state.moving ) {
renderEffect_t& renderEffect = dustEffects[ surfaceTypeIndex ].GetRenderEffect();
renderEffect.origin = groundTrace.c.point;
float vel = idMath::Fabs( rotationspeed );
renderEffect.attenuation = vel > 1000.0f ? 1.0f : 0.5f * ( 2 / Square( 1000.0f ) ) * Square( vel );
renderEffect.gravity = gameLocal.GetGravity();
dustEffects[ surfaceTypeIndex ].Start( gameLocal.time );
dustEffects[ surfaceTypeIndex ].GetNode().AddToEnd( activeEffects );
} else {
dustEffects[ surfaceTypeIndex ].Stop();
dustEffects[ surfaceTypeIndex ].GetNode().Remove();
}
if( state.grounded && state.spinning ) {
renderEffect_t& renderEffect = spinEffects[ surfaceTypeIndex ].GetRenderEffect();
renderEffect.origin = groundTrace.c.point;
renderEffect.gravity = gameLocal.GetGravity();
spinEffects[ surfaceTypeIndex ].Start( gameLocal.time );
spinEffects[ surfaceTypeIndex ].GetNode().AddToEnd( activeEffects );
} else {
spinEffects[ surfaceTypeIndex ].Stop();
spinEffects[ surfaceTypeIndex ].GetNode().Remove();
}
if( state.grounded && state.skidding ) {
renderEffect_t& renderEffect = skidEffects[ surfaceTypeIndex ].GetRenderEffect();
renderEffect.origin = groundTrace.c.point;
renderEffect.gravity = gameLocal.GetGravity();
skidEffects[ surfaceTypeIndex ].Start( gameLocal.time );
skidEffects[ surfaceTypeIndex ].GetNode().AddToEnd( activeEffects );
} else {
skidEffects[ surfaceTypeIndex ].Stop();
skidEffects[ surfaceTypeIndex ].GetNode().Remove();
}
}
sdEffect* effect = activeEffects.Next();
for( ; effect != NULL; effect = effect->GetNode().Next() ) {
effect->Update();
// stop playing any effects for surfaces we're no longer on
if ( ( surfaceTypeIndex == -1 ) || ( effect != &dustEffects[ surfaceTypeIndex ]
&& effect != &spinEffects[ surfaceTypeIndex ]
&& effect != &skidEffects[ surfaceTypeIndex ] ) ) {
effect->Stop();
}
}
}
/*
================
sdVehicleRigidBodyWheel::CreateDecayDebris
================
*/
void sdVehicleRigidBodyWheel::CreateDecayDebris( void ) {
if ( g_disableTransportDebris.GetBool() ) {
return;
}
sdTransport* transport = GetParent();
idVec3 org;
idMat3 axis;
GetWorldOrigin( org );
GetWorldAxis( axis );
// make sure we can have another part of this priority
debrisPriority_t priority = ( debrisPriority_t )brokenPart->dict.GetInt( "priority" );
if ( !CanAddDebris( priority, org ) ) {
return;
}
float velMin = brokenPart->dict.GetFloat( "decay_wheel_velocity_min", "50" );
float velMax = brokenPart->dict.GetFloat( "decay_wheel_velocity_max", "80" );
float avelMin = brokenPart->dict.GetFloat( "decay_wheel_angular_velocity_min", "30" );
float avelMax = brokenPart->dict.GetFloat( "decay_wheel_angular_velocity_max", "50" );
// get some axes of the transport
idVec3 forward = transport->GetPhysics()->GetAxis()[0];
idVec3 left = transport->GetPhysics()->GetAxis()[1];
// get the difference between this and the transport's origin
idVec3 difference = org - transport->GetPhysics()->GetOrigin();
// vary the falling-off velocity a bit
float velocity = (gameLocal.random.RandomFloat() * (velMax - velMin)) + velMin;
idVec3 vel = left * velocity;
// make an angular velocity about the forwards vector that will cause the wheels to topple over
float angVelocity = (gameLocal.random.RandomFloat() * (avelMax - avelMin)) + avelMin;
idVec3 aVel = forward * angVelocity;
// use the dot product to determine what direction its in
if (difference * left < 0.0f) {
vel *= -1.f;
aVel *= -1.f;
}
vel += transport->GetPhysics()->GetLinearVelocity();
aVel += transport->GetPhysics()->GetAngularVelocity();
rvClientMoveable* cent = gameLocal.SpawnClientMoveable( brokenPart->GetName(), 5000, org, axis, vel, aVel, 1 );
if ( cent != NULL ) {
cent->GetPhysics()->SetContents( 0 );
cent->GetPhysics()->SetClipMask( CONTENTS_SOLID | CONTENTS_BODY );
}
}
/*
================
sdVehicleRigidBodyWheel::CheckWater
================
*/
void sdVehicleRigidBodyWheel::CheckWater( const idVec3& waterBodyOrg, const idMat3& waterBodyAxis, idCollisionModel* waterBodyModel ) {
if ( waterEffects ) {
idVec3 mountOrg;
idMat3 mountAxis;
parent->GetAnimator()->GetJointTransform( joint, gameLocal.time, mountOrg, mountAxis );
waterEffects->SetOrigin( mountOrg * parent->GetRenderEntity()->axis + parent->GetRenderEntity()->origin );
waterEffects->SetAxis( parent->GetRenderEntity()->axis );
waterEffects->SetMaxVelocity( 200.0f );
waterEffects->SetVelocity( idVec3( idMath::Abs( rotationspeed ), 0.0f, 0.0f ) );
waterEffects->CheckWater( parent, waterBodyOrg, waterBodyAxis, waterBodyModel );
}
}
/*
================
sdVehicleRigidBodyWheel::UpdateSuspensionIK
================
*/
bool sdVehicleRigidBodyWheel::UpdateSuspensionIK( void ) {
if ( suspension != NULL ) {
return suspension->UpdateIKJoints( parent->GetAnimator() );
}
return false;
}
/*
================
sdVehicleRigidBodyWheel::ClearSuspensionIK
================
*/
void sdVehicleRigidBodyWheel::ClearSuspensionIK( void ) {
if ( suspension != NULL ) {
suspension->ClearIKJoints( parent->GetAnimator() );
}
}
/*
===============================================================================
sdVehicleTrack
===============================================================================
*/
CLASS_DECLARATION( sdVehicleDriveObject, sdVehicleTrack )
END_CLASS
/*
================
sdVehicleTrack::~sdVehicleTrack
================
*/
sdVehicleTrack::~sdVehicleTrack( void ) {
for ( int i = 1; i < wheels.Num() - 1; i++ ) {
delete wheels[ i ];
}
}
/*
================
sdVehicleTrack::Init
================
*/
void sdVehicleTrack::Init( const sdDeclVehiclePart& track, sdTransport_RB* _parent ) {
spawnPart = &track;
parent = _parent;
joint = parent->GetAnimator()->GetJointHandle( track.data.GetString( "joint" ) );
direction = track.data.GetVector( "direction" );
shaderParmIndex = track.data.GetInt( "shaderParmIndex" );
name = track.data.GetString( "name" );
idVec3 origin;
parent->GetAnimator()->GetJointTransform( joint, gameLocal.time, origin );
leftTrack = origin[ 1 ] > 0;
parent->GetRenderEntity()->shaderParms[ shaderParmIndex ] = 0;
}
/*
================
sdVehicleTrack::GetParent
================
*/
sdTransport* sdVehicleTrack::GetParent( void ) const {
return parent;
}
/*
================
sdVehicleTrack::GetInputSpeed
================
*/
float sdVehicleTrack::GetInputSpeed( const sdVehicleInput& input ) const {
return ( IsLeftTrack() ? input.GetLeftSpeed() : input.GetRightSpeed() );
}
/*
================
sdVehicleTrack::UpdatePrePhysics
================
*/
void sdVehicleTrack::UpdatePrePhysics( const sdVehicleInput& input ) {
int numOnGround = 0;
for ( int i = 1; i < wheels.Num() - 1; i++ ) {
wheels[ i ]->UpdateSuspension( input );
if ( wheels[ i ]->IsGrounded() ) {
numOnGround++;
}
}
float inputMotorForce = input.GetForce();
float maxInputMotorForce = inputMotorForce * 2.0f;
int idealNumOnGround = wheels.Num() - 2;
if ( numOnGround > 0 && numOnGround < idealNumOnGround ) {
// compensate for having some wheels off the ground
float fullForce = inputMotorForce * idealNumOnGround;
inputMotorForce = fullForce / numOnGround;
if ( inputMotorForce > maxInputMotorForce ) {
inputMotorForce = maxInputMotorForce;
}
}
for ( int i = 1; i < wheels.Num() - 1; i++ ) {
wheels[ i ]->UpdateMotor( input, inputMotorForce );
}
}
/*
================
sdVehicleTrack::UpdatePostPhysics
================
*/
void sdVehicleTrack::UpdatePostPhysics( const sdVehicleInput& input ) {
int i;
for ( i = 1; i < wheels.Num() - 1; i++ ) {
wheels[ i ]->UpdatePostPhysics( input );
}
bool grounded = false;
float speed;
for ( i = 0; i < wheels.Num(); i++ ) {
if ( wheels[ i ]->IsGrounded() ) {
grounded = true;
break;
}
}
if ( grounded ) {
idVec3 pos;
parent->GetWorldOrigin( joint, pos );
idVec3 velocity;
speed = parent->GetRBPhysics()->GetPointVelocity( pos, velocity ) * ( direction * parent->GetPhysics()->GetAxis() );
} else {
speed = GetInputSpeed( input );
}
if ( idMath::Fabs( speed ) < 0.5f ) {
speed = 0.f;
}
parent->GetRenderEntity()->shaderParms[ shaderParmIndex ] += speed;
for ( i = 0; i < wheels.Num(); i++ ) {
wheels[ i ]->UpdateRotation( speed );
}
}
/*
================
sdVehicleTrack::EvaluateContacts
================
*/
int sdVehicleTrack::EvaluateContacts( contactInfo_t* list, contactInfoExt_t* listExt, int max ) {
int num = 0;
for ( int i = 1; i < wheels.Num() - 1; i++ ) {
num += wheels[ i ]->EvaluateContacts( list + num, listExt + num, max - num );
}
return num;
}
/*
================
sdVehicleTrack::CheckWater
================
*/
void sdVehicleTrack::CheckWater( const idVec3& waterBodyOrg, const idMat3& waterBodyAxis, idCollisionModel* waterBodyModel ) {
for ( int i = 1; i < wheels.Num() - 1; i++ ) {
wheels[ i ]->CheckWater( waterBodyOrg, waterBodyAxis, waterBodyModel );
}
}
/*
================
sdVehicleTrack::GetSurfaceType
================
*/
const sdDeclSurfaceType* sdVehicleTrack::GetSurfaceType( void ) const {
for ( int i = 1; i < wheels.Num() - 1; i++ ) {
const sdDeclSurfaceType* surface = wheels[ i ]->GetSurfaceType();
if ( surface != NULL ) {
return surface;
}
}
return NULL;
}
/*
================
sdVehicleTrack::PostInit
================
*/
void sdVehicleTrack::PostInit( void ) {
sdVehicleDriveObject::PostInit();
int numWheels = spawnPart->data.GetInt( "num_true_wheels" ) + 2;
if ( numWheels > TRACK_MAX_WHEELS ) {
gameLocal.Error( "sdVehicleTrack::PostInit - number of wheels exceeds TRACK_MAX_WHEELS" );
}
wheels.SetNum( numWheels );
// get the start wheel
sdVehicleDriveObject* object = parent->GetDriveObject( spawnPart->data.GetString( "start_wheel" ) );
if ( object == NULL ) {
gameLocal.Error( "sdVehicleTrack::PostInit - no start_wheel" );
}
wheels[ 0 ] = reinterpret_cast< sdVehicleRigidBodyWheel* >( object );
if ( wheels[ 0 ] == NULL ) {
gameLocal.Error( "sdVehicleTrack::PostInit - start_wheel is not a sdVehicleRigidBodyWheel" );
}
// get the end wheel
object = parent->GetDriveObject( spawnPart->data.GetString( "end_wheel" ) );
if ( object == NULL ) {
gameLocal.Error( "sdVehicleTrack::PostInit - no end_wheel" );
}
wheels[ numWheels - 1 ] = reinterpret_cast< sdVehicleRigidBodyWheel* >( object );
if ( wheels[ numWheels - 1 ] == NULL ) {
gameLocal.Error( "sdVehicleTrack::PostInit - end_wheel is not a sdVehicleRigidBodyWheel" );
}
// create the other wheels
for ( int i = 1; i < numWheels - 1; i++ ) {
sdVehicleRigidBodyWheel* part = new sdVehicleRigidBodyWheel;
part->SetIndex( -1 );
part->TrackWheelInit( *spawnPart, i, parent );
wheels[ i ] = part;
}
for ( int i = 1; i < numWheels - 1; i++ ) {
wheels[ i ]->PostInit();
}
}
/*
================
sdVehicleTrack::UpdateSuspensionIK
================
*/
bool sdVehicleTrack::UpdateSuspensionIK( void ) {
bool changed = false;
for ( int i = 1; i < wheels.Num() - 1; i++ ) {
changed |= wheels[ i ]->UpdateSuspensionIK();
}
return changed;
}
/*
================
sdVehicleTrack::ClearSuspensionIK
================
*/
void sdVehicleTrack::ClearSuspensionIK( void ) {
for ( int i = 1; i < wheels.Num() - 1; i++ ) {
wheels[ i ]->ClearSuspensionIK();
}
}
/*
================
sdVehicleTrack::IsGrounded
================
*/
bool sdVehicleTrack::IsGrounded( void ) const {
int numGrounded = 0;
for ( int i = 1; i < wheels.Num() - 1; i++ ) {
if ( wheels[ i ]->IsGrounded() ) {
numGrounded++;
}
}
return numGrounded >= 1;
}
/*
===============================================================================
sdVehicleThruster
===============================================================================
*/
const idEventDef EV_Thruster_SetThrust( "setThrust", '\0', DOC_TEXT( "Controls the thrust multiplier used by this thruster." ), 1, NULL, "f", "scale", "Scale factor to set." );
CLASS_DECLARATION( sdVehicleDriveObject, sdVehicleThruster )
EVENT( EV_Thruster_SetThrust, sdVehicleThruster::Event_SetThrust )
END_CLASS
/*
================
sdVehicleThruster::sdVehicleThruster
================
*/
void sdVehicleThruster::Init( const sdDeclVehiclePart& thruster, sdTransport_RB* _parent ) {
parent = _parent;
direction = thruster.data.GetVector( "direction" );
fixedDirection = thruster.data.GetVector( "direction_fixed" );
origin = thruster.data.GetVector( "origin" );
reverseScale = thruster.data.GetFloat( "reverse_scale", "1" );
float force = thruster.data.GetFloat( "force" );
direction *= force;
fixedDirection *= force;
name = thruster.data.GetString( "name" );
needWater = thruster.data.GetBool( "need_water" );
inWater = false;
thrustScale = 0.f;
}
/*
================
sdVehicleThruster::GetParent
================
*/
sdTransport* sdVehicleThruster::GetParent( void ) const {
return parent;
}
/*
================
sdVehicleThruster::CalcPos
================
*/
void sdVehicleThruster::CalcPos( idVec3& pos ) {
pos = parent->GetPhysics()->GetOrigin() + ( origin * parent->GetPhysics()->GetAxis() );
}
/*
================
sdVehicleThruster::SetThrust
================
*/
void sdVehicleThruster::SetThrust( float thrust ) {
thrustScale = thrust;
}
/*
================
sdVehicleThruster::Event_SetThrust
================
*/
void sdVehicleThruster::Event_SetThrust( float thrust ) {
SetThrust( thrust );
}
/*
================
sdVehicleThruster::UpdatePrePhysics
================
*/
void sdVehicleThruster::UpdatePrePhysics( const sdVehicleInput& input ) {
bool reallyInWater = inWater || parent->GetRBPhysics()->InWater();
if ( needWater ) {
if ( !reallyInWater ) {
return;
}
} else {
if ( reallyInWater ) {
return;
}
}
if ( thrustScale != 0.f ) {
float height = parent->GetPhysics()->GetOrigin()[ 2 ];
float forceScale = 1.0f;
if ( height > gameLocal.flightCeilingLower ) {
if ( height > gameLocal.flightCeilingUpper ) {
forceScale = 0.f;
} else {
forceScale = ( 1.f - ( height - gameLocal.flightCeilingLower ) / ( gameLocal.flightCeilingUpper - gameLocal.flightCeilingLower ) );
}
}
if ( thrustScale < 0.f ) {
thrustScale *= reverseScale;
}
idVec3 pos;
CalcPos( pos );
idVec3 dir = parent->GetPhysics()->GetAxis() * ( ( direction * thrustScale ) + ( fixedDirection * idMath::Fabs( thrustScale ) ) );
parent->GetPhysics()->AddForce( 0, pos, dir * forceScale );
}
}
/*
================
sdVehicleThruster::CheckWater
================
*/
void sdVehicleThruster::CheckWater( const idVec3& waterBodyOrg, const idMat3& waterBodyAxis, idCollisionModel* waterBodyModel ) {
if ( !needWater ) {
return;
}
idVec3 pos;
CalcPos( pos );
pos -= waterBodyOrg;
pos *= waterBodyAxis.Transpose();
inWater = waterBodyModel->GetBounds().ContainsPoint( pos );
}
/*
===============================================================================
sdVehicleAirBrake
===============================================================================
*/
CLASS_DECLARATION( sdVehicleDriveObject, sdVehicleAirBrake )
END_CLASS
/*
================
sdVehicleAirBrake::sdVehicleThruster
================
*/
void sdVehicleAirBrake::Init( const sdDeclVehiclePart& airBrake, sdTransport_RB* _parent ) {
parent = _parent;
enabled = false;
factor = airBrake.data.GetFloat( "factor" );
maxSpeed = airBrake.data.GetFloat( "max_speed" );
name = airBrake.data.GetString( "name" );
}
/*
================
sdVehicleAirBrake::UpdatePrePhysics
================
*/
void sdVehicleAirBrake::UpdatePrePhysics( const sdVehicleInput& input ) {
if ( !enabled ) {
return;
}
const idMat3& ownerAxis = parent->GetPhysics()->GetAxis();
idVec3 forward = ownerAxis[ 0 ];
forward.z = 0.f;
float speed = parent->GetPhysics()->GetLinearVelocity() * forward;
if ( speed > 0.f ) {
idVec3 vel = speed * forward;
idVec3 impulse = ( -vel * factor );
impulse.Truncate( maxSpeed );
impulse *= parent->GetPhysics()->GetMass();
idVec3 com = parent->GetPhysics()->GetOrigin() + ( parent->GetPhysics()->GetCenterOfMass() * ownerAxis );
parent->GetPhysics()->ApplyImpulse( 0, com, impulse );
}
}
/*
===============================================================================
sdVehicleRigidBodyRotor
===============================================================================
*/
CLASS_DECLARATION( sdVehicleRigidBodyPartSimple, sdVehicleRigidBodyRotor )
END_CLASS
/*
================
sdVehicleRigidBodyRotor::sdVehicleRigidBodyRotor
================
*/
sdVehicleRigidBodyRotor::sdVehicleRigidBodyRotor( void ) {
}
/*
================
sdVehicleRigidBodyRotor::~sdVehicleRigidBodyRotor
================
*/
sdVehicleRigidBodyRotor::~sdVehicleRigidBodyRotor( void ) {
}
/*
================
sdVehicleRigidBodyRotor::Init
================
*/
void sdVehicleRigidBodyRotor::Init( const sdDeclVehiclePart& rotor, sdTransport_RB* _parent ) {
sdVehicleRigidBodyPartSimple::Init( rotor, _parent );
liftCoefficient = rotor.data.GetFloat( "lift" );
const char* typeName = rotor.data.GetString( "rotortype", "main" );
if ( !idStr::Icmp( typeName, "main" ) ) {
type = RT_MAIN;
} else if ( !idStr::Icmp( typeName, "tail" ) ) {
type = RT_TAIL;
} else {
gameLocal.Error( "sdVehicleRigidBodyRotor::Init Invalid Rotor Type '%s'", typeName );
}
maxPitchDeflect = rotor.data.GetFloat( "maxPitchDeflect" );
maxYawDeflect = rotor.data.GetFloat( "maxYawDeflect" );
advanced.cyclicBankRate = rotor.data.GetFloat( "cyclicBankRate" ) * 0.033f;
advanced.cyclicPitchRate = rotor.data.GetFloat( "cyclicPitchRate" ) * 0.033f;
oldPitch = 0.f;
oldYaw = 0.f;
int count = rotor.data.GetInt( "num_blades" );
for ( int i = 0; i < count; i++ ) {
rotorJoint_t& j = animJoints.Alloc();
const char* bladeJointName = rotor.data.GetString( va( "blade%i_joint", i + 1 ) );
j.joint = _parent->GetAnimator()->GetJointHandle( bladeJointName );
if ( j.joint == INVALID_JOINT ) {
gameLocal.Warning( "sdVehicleRigidBodyRotor::Init Invalid Joint '%s'", bladeJointName );
j.jointAxes.Identity();
} else {
_parent->GetAnimator()->GetJointTransform( j.joint, gameLocal.time, j.jointAxes );
}
j.speedScale = rotor.data.GetFloat( va( "blade%i_speedScale", i + 1 ) );
j.isYaw = rotor.data.GetInt( va("blade%i_yaw", i+1 ) ) != 0;
j.angle = 0.f;
}
speed = 0;
sideScale = rotor.data.GetFloat( "force_side_scale", "1" );
zOffset = rotor.data.GetFloat( "z_offset", "0" );
}
/*
================
sdVehicleRigidBodyRotor::UpdatePrePhysics_Main
================
*/
void sdVehicleRigidBodyRotor::UpdatePrePhysics_Main( const sdVehicleInput& input ) {
sdPhysics_RigidBodyMultiple* physics = parent->GetRBPhysics();
const idMat3& bodyaxis = physics->GetAxis();
idVec3 bodyorg = physics->GetOrigin() + ( physics->GetMainCenterOfMass() * bodyaxis ); // + physics->GetBodyOffset( bodyId );
bool alive = parent->GetHealth() > 0;
idVec3 rotorAxis = bodyaxis[ 2 ];
bool deathThroes = parent->InDeathThroes();
bool careening = parent->IsCareening() && !deathThroes;
const idVec3& angVelocity = physics->GetAngularVelocity();
const idAngles bodyangles = bodyaxis.ToAngles();
bool drowned = physics->InWater() > 0.5f;
bool simpleControls = input.GetPlayer() != NULL && !input.GetPlayer()->GetUserInfo().advancedFlightControls;
idMat3 rotateAxes;
idAngles::YawToMat3( -( bodyangles.yaw ), rotateAxes );
rotorAxis *= rotateAxes;
Swap( rotorAxis[ 0 ], rotorAxis[ 2 ] );
idAngles angles = rotorAxis.ToAngles();
angles.pitch = idMath::AngleNormalize180( angles.pitch );
if ( angles.pitch < 0.f ) {
angles.pitch += maxPitchDeflect;
if ( angles.pitch > 0.f ) {
angles.pitch = 0.f;
}
} else {
angles.pitch -= maxPitchDeflect;
if ( angles.pitch < 0.f ) {
angles.pitch = 0.f;
}
}
angles.yaw = idMath::AngleNormalize180( angles.yaw );
if ( angles.yaw < 0.f ) {
angles.yaw += maxYawDeflect;
if ( angles.yaw > 0.f ) {
angles.yaw = 0.f;
}
} else {
angles.yaw -= maxYawDeflect;
if ( angles.yaw < 0.f ) {
angles.yaw = 0.f;
}
}
rotorAxis = angles.ToForward();
Swap( rotorAxis[ 0 ], rotorAxis[ 2 ] );
rotorAxis *= rotateAxes.Transpose();
rotorAxis[ 0 ] *= sideScale;
rotorAxis[ 0 ] = idMath::ClampFloat( -1.f, 1.f, rotorAxis[ 0 ] );
rotorAxis[ 1 ] *= sideScale;
rotorAxis[ 1 ] = idMath::ClampFloat( -1.f, 1.f, rotorAxis[ 1 ] );
// gameRenderWorld->DebugLine( colorYellow, worldOrg, worldOrg + ( rotorAxis * 32 ) );
const sdVehicleControlBase* control = parent->GetVehicleControl();
bool deadZone = ( angles.pitch == 0 ) && ( angles.yaw == 0 ) && ( !control || physics->HasGroundContacts() );
float deadZoneFraction = 1.0f - ( control != NULL ? control->GetDeadZoneFraction() : 0.0f );
deadZoneFraction = idMath::ClampFloat( 0.0f, 1.0f, deadZoneFraction );
deadZoneFraction = idMath::Sqrt( deadZoneFraction );
if ( alive ) {
float inputRoll = 0.0f;
float inputPitch = 0.0f;
if ( !deadZone ) {
if ( input.GetPlayer() && !input.GetUserCmd().buttons.btn.tophat ) {
inputRoll = input.GetRoll();
inputPitch = input.GetPitch();
}
}
inputRoll = idMath::ClampFloat( -2.f, 2.f, inputRoll );
inputPitch = idMath::ClampFloat( -2.f, 2.f, inputPitch );
advanced.cyclicBank = ( inputRoll / 180.f ) * advanced.cyclicBankRate;
advanced.cyclicPitch = ( inputPitch / 180.f ) * advanced.cyclicPitchRate;
if ( deathThroes ) {
advanced.cyclicPitch = 45.0f;
}
if ( control && careening ) {
advanced.cyclicBank = control->GetCareeningRollAmount();
advanced.cyclicPitch = control->GetCareeningPitchAmount();
}
bodyorg += advanced.cyclicPitch * bodyaxis[ 0 ];
bodyorg += advanced.cyclicBank * bodyaxis[ 1 ];
}
float frac;
if ( control ) {
int start = control->GetLandingChangeTime();
int length = control->GetLandingChangeEndTime() - start;
frac = length ? idMath::ClampFloat( 0.f, 1.f, ( gameLocal.time - start ) / static_cast< float >( length ) ) : 1.f;
frac = Square( frac );
} else {
frac = 1.f;
}
if ( control && careening ) {
frac = control->GetCareeningLiftScale();
}
if ( !control || control->IsLanding() && !( careening && !deathThroes ) ) {
speed = GetTopGoalSpeed() * ( 1.f - frac );
} else {
speed = GetTopGoalSpeed() * frac;
}
float lift = 1.f;
const float liftSpeedFrac = 0.90f;
float speedFrac = speed / GetTopGoalSpeed();
if ( speedFrac > liftSpeedFrac ) {
lift += input.GetCollective();
if ( control && careening ) {
lift += control->GetCareeningCollectiveAmount();
}
lift *= speed * liftCoefficient;
float height = physics->GetOrigin()[ 2 ];
if ( height > gameLocal.flightCeilingLower ) {
if ( height > gameLocal.flightCeilingUpper ) {
lift = 0.f;
} else {
lift *= ( 1.f - ( height - gameLocal.flightCeilingLower ) / ( gameLocal.flightCeilingUpper - gameLocal.flightCeilingLower ) );
}
}
if ( drowned ) {
lift = 0.0f;
}
idVec3 force = rotorAxis * lift;
physics->AddForce( bodyId, bodyorg, force );
parent->GetRenderEntity()->shaderParms[ SHADERPARM_ALPHA ] = ( speedFrac - liftSpeedFrac ) / ( 1.f - liftSpeedFrac );
} else {
parent->GetRenderEntity()->shaderParms[ SHADERPARM_ALPHA ] = 0.0f;
}
if ( drowned ) {
// try to resist all movement
const idVec3& linearVelocity = physics->GetLinearVelocity();
idVec3 stoppingAcceleration = -0.05f * linearVelocity / MS2SEC( gameLocal.msec );
physics->AddForce( stoppingAcceleration * physics->GetMass( -1 ) );
const idVec3& angularVelocity = physics->GetAngularVelocity();
idVec3 stoppingAlpha = -0.05f * angularVelocity / MS2SEC( gameLocal.msec );
physics->AddTorque( stoppingAlpha * physics->GetInertiaTensor( -1 ) );
}
}
const float goalSpeedTail = 1000.f;
/*
================
sdVehicleRigidBodyRotor::UpdatePrePhysics_Tail
================
*/
void sdVehicleRigidBodyRotor::UpdatePrePhysics_Tail( const sdVehicleInput& input ) {
sdPhysics_RigidBodyMultiple* physics = parent->GetRBPhysics();
if ( physics->InWater() > 0.5f ) {
return;
}
const idMat3& bodyaxis = physics->GetAxis();
idVec3 bodyorg;
idVec3 rotorAxis = bodyaxis[ 1 ];
const idVec3 comWorld = physics->GetOrigin() + ( physics->GetCenterOfMass() * bodyaxis );
GetWorldPhysicsOrigin( bodyorg );
bodyorg += bodyaxis[ 2 ] * zOffset;
bool simpleControls = input.GetPlayer() != NULL && !input.GetPlayer()->GetUserInfo().advancedFlightControls;
if ( !simpleControls ) {
// advanced controls fly the vehicle in local space
float arm = ( bodyorg - comWorld ).Length();
rotorAxis.Set( 0.0f, 1.0f, 0.0f );
bodyorg.Set( -arm, 0.0f, 0.0f );
}
const sdVehicleControlBase* control = parent->GetVehicleControl();
float frac;
if ( control ) {
int start = control->GetLandingChangeTime();
int length = control->GetLandingChangeEndTime() - start;
frac = length ? idMath::ClampFloat( 0.f, 1.f, ( gameLocal.time - start ) / static_cast< float >( length ) ) : 1.f;
frac = Square( frac );
} else {
frac = 1.f;
}
if ( !control || control->IsLanding() ) {
speed = goalSpeedTail * ( 1.f - frac );
} else {
speed = goalSpeedTail * frac;
}
bool deathThroes = parent->InDeathThroes();
bool careening = parent->IsCareening() && !deathThroes;
if ( deathThroes || careening ) {
frac = 1.0f;
speed = goalSpeedTail;
}
if ( input.GetPlayer() || ( ( deathThroes || careening ) && !physics->HasGroundContacts() ) ) {
float lift = -0.5f * input.GetYaw();
if ( deathThroes ) {
lift -= 1.0f;
} else if ( control && careening ) {
lift = control->GetCareeningYawAmount();
}
if( lift ) {
lift *= speed / goalSpeedTail;
idVec3 force = rotorAxis * lift * liftCoefficient;
if ( !simpleControls ) {
// use a torque to turn
// without adverse sideways movement
float torqueMag = force.y * bodyorg.x;
const idMat3& itt = physics->GetInertiaTensor();
// find the moment in the z axis
float zMoment = idVec3( 0.0f, 0.0f, 1.0f ) * (itt * idVec3( 0.0f, 0.0f, 1.0f ));
// calculate the torque to do a clean rotation about the vehicle's z axis
idVec3 alpha( 0.0f, 0.0f, torqueMag / zMoment );
idVec3 cleanTorque = itt * alpha;
physics->AddTorque( ( cleanTorque ) * bodyaxis );
} else {
// calculate the effect the force WOULD have if we applied it right now
idVec3 torque = ( bodyorg - comWorld ).Cross( force );
const idMat3 worldIT = bodyaxis.Transpose() * physics->GetInertiaTensor() * bodyaxis;
const idMat3 worldInvIT = worldIT.Inverse();
idVec3 alpha = worldInvIT * torque;
// find the alpha with only yaw (wrt world)
float desiredAlphaMagnitude = alpha.LengthFast();
if ( alpha.z < 0.0f ) {
desiredAlphaMagnitude = -desiredAlphaMagnitude;
}
alpha.Set( 0.0f, 0.0f, desiredAlphaMagnitude );
// calculate the torque needed
torque = worldIT * alpha;
// calculate the effect this will have
idVec3 torqueAlpha = worldInvIT * torque;
physics->AddTorque( torque );
}
}
}
}
/*
================
sdVehicleRigidBodyRotor::UpdatePrePhysics
================
*/
void sdVehicleRigidBodyRotor::UpdatePrePhysics( const sdVehicleInput& input ) {
switch( type ) {
case RT_MAIN:
UpdatePrePhysics_Main( input );
break;
case RT_TAIL:
UpdatePrePhysics_Tail( input );
break;
}
}
/*
================
sdVehicleRigidBodyRotor::UpdatePostPhysics_Main
================
*/
void sdVehicleRigidBodyRotor::UpdatePostPhysics_Main( const sdVehicleInput& input ) {
if ( speed > 0.5f ) {
int i;
for ( i = 0; i < animJoints.Num(); i++ ) {
rotorJoint_t& joint = animJoints[ i ];
joint.angle += speed * MS2SEC( gameLocal.msec ) * joint.speedScale;
idMat3 rotation;
idAngles::YawToMat3( joint.angle, rotation );
parent->GetAnimator()->SetJointAxis( joint.joint, JOINTMOD_WORLD_OVERRIDE, joint.jointAxes * rotation );
}
}
}
/*
================
sdVehicleRigidBodyRotor::UpdatePostPhysics_Tail
================
*/
void sdVehicleRigidBodyRotor::UpdatePostPhysics_Tail( const sdVehicleInput& input ) {
if( speed > 0.5f ) {
int i;
for ( i = 0; i < animJoints.Num(); i++ ) {
rotorJoint_t& joint = animJoints[ i ];
joint.angle += speed * MS2SEC( gameLocal.msec ) * joint.speedScale;
idMat3 rotation;
if ( joint.isYaw ) {
idAngles::YawToMat3( joint.angle, rotation );
} else {
idAngles::PitchToMat3( joint.angle, rotation );
}
parent->GetAnimator()->SetJointAxis( joint.joint, JOINTMOD_WORLD_OVERRIDE, joint.jointAxes * rotation );
}
}
}
/*
================
sdVehicleRigidBodyRotor::UpdatePostPhysics
================
*/
void sdVehicleRigidBodyRotor::UpdatePostPhysics( const sdVehicleInput& input ) {
switch( type ) {
case RT_MAIN:
UpdatePostPhysics_Main( input );
break;
case RT_TAIL:
UpdatePostPhysics_Tail( input );
break;
}
}
/*
================
sdVehicleRigidBodyRotor::GetTopGoalSpeed
================
*/
float sdVehicleRigidBodyRotor::GetTopGoalSpeed( void ) const {
sdPhysics_RigidBodyMultiple* physics = parent->GetRBPhysics();
return ( physics->GetMainMass() * -physics->GetGravity()[ 2 ] ) / liftCoefficient;
}
/*
================
sdVehicleRigidBodyRotor::ResetCollective
================
*/
void sdVehicleRigidBodyRotor::ResetCollective( void ) {
advanced.collective = 0.f;
}
/*
===============================================================================
sdVehicleRigidBodyHoverPad
===============================================================================
*/
CLASS_DECLARATION( sdVehicleRigidBodyPartSimple, sdVehicleRigidBodyHoverPad )
END_CLASS
/*
================
sdVehicleRigidBodyHoverPad::sdVehicleRigidBodyHoverPad
================
*/
sdVehicleRigidBodyHoverPad::sdVehicleRigidBodyHoverPad( void ) {
}
/*
================
sdVehicleRigidBodyHoverPad::~sdVehicleRigidBodyHoverPad
================
*/
sdVehicleRigidBodyHoverPad::~sdVehicleRigidBodyHoverPad( void ) {
}
/*
================
sdVehicleRigidBodyHoverPad::Init
================
*/
void sdVehicleRigidBodyHoverPad::Init( const sdDeclVehiclePart& part, sdTransport_RB* _parent ) {
sdVehicleRigidBodyPartSimple::Init( part, _parent );
traceDir = part.data.GetVector( "direction", "0 0 -1" );
traceDir.Normalize();
minAngles = part.data.GetAngles( "min_angles", "-10 -10 -10" );
maxAngles = part.data.GetAngles( "max_angles", "10 10 10" );
maxTraceLength = part.data.GetFloat( "distance", "64" );
shaderParmIndex = part.data.GetInt( "shaderParmIndex", "0" );
adaptSpeed = part.data.GetFloat( "adaption_speed", "0.005" );
_parent->GetAnimator()->GetJointTransform( joint, gameLocal.time, baseOrg, baseAxes );
idMat3 ident; ident.Identity();
currentAxes = ident.ToQuat();
// Setup the lighting effect
renderEffect_t &effect = engineEffect.GetRenderEffect();
effect.declEffect = gameLocal.FindEffect( _parent->spawnArgs.GetString( "fx_hoverpad" ) );
effect.axis = mat3_identity;
effect.attenuation = 1.f;
effect.hasEndOrigin = true;
effect.loop = false;
effect.shaderParms[SHADERPARM_RED] = 1.0f;
effect.shaderParms[SHADERPARM_GREEN] = 1.0f;
effect.shaderParms[SHADERPARM_BLUE] = 1.0f;
effect.shaderParms[SHADERPARM_ALPHA] = 1.0f;
effect.shaderParms[SHADERPARM_BRIGHTNESS] = 1.0f;
nextBeamTime = gameLocal.time + 2000 + gameLocal.random.RandomInt( 1000 );
beamTargetInfo = gameLocal.declTargetInfoType[ _parent->spawnArgs.GetString( "ti_lightning" ) ];
lastVelocity.Zero();
}
idCVar g_debugVehicleHoverPads( "g_debugVehicleHoverPads", "0", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "show info about hoverpads" );
void sdVehicleRigidBodyHoverPad::UpdatePostPhysics( const sdVehicleInput& input ) {
if ( !input.GetPlayer() || parent->GetPhysics()->GetAxis()[ 2 ].z < 0.f ) {
return;
}
if ( !gameLocal.DoClientSideStuff() ) {
return;
}
idMat3 axis;
idVec3 origin;
GetWorldAxis( axis );
GetWorldOrigin( origin );
idMat3 ownerAxis = parent->GetPhysics()->GetAxis();
idVec3 velocity = parent->GetPhysics()->GetLinearVelocity();
idVec3 acceleration = ( velocity - lastVelocity ) / MS2SEC( gameLocal.msec );
lastVelocity = velocity;
// tilt things based on velocity & acceleration
idVec3 forward = ownerAxis[0];
idVec3 right = ownerAxis[1];
idVec3 up = ownerAxis[2];
float forwardAccel = acceleration * forward;
float rightAccel = acceleration * right;
float forwardVel = velocity * forward;
float rightVel = velocity * right;
// want to rotate the joint on its axis to look like its tilting with the speed
idAngles target( 0.0f, 0.0f, 0.0f );
target.pitch = 0.05f * ( forwardAccel * 0.1f + forwardVel * 0.1f );
target.roll = -0.05f * ( rightAccel * 0.1f + rightVel * 0.1f );
// create a rotation about the offset axis
float rotSpeed = parent->GetPhysics()->GetAngularVelocity() * up;
idVec3 rotAxis = baseOrg;
rotAxis.Normalize();
idRotation rotationRot( vec3_zero, rotAxis, rotSpeed * 15.0f );
idMat3 rotationMat = rotationRot.ToMat3();
idMat3 translationMat = target.ToMat3();
idMat3 totalMat = rotationMat * translationMat;
target = totalMat.ToAngles();
target.Clamp( minAngles, maxAngles );
// Lerp with the current rotations
// this is a semi hack, you have to do an slerp to be totally correct, but we just do a linear lerp + renormalize
// this causes or rotation speeds to be non uniform but who cares... the rotations are still correct
float lerp = ( gameLocal.msec * adaptSpeed );
currentAxes = target.ToQuat() * lerp + currentAxes * ( 1.0f - lerp );
currentAxes.Normalize();
currentAxes.FixDenormals( 0.00001f );
// Add the local joint transform on top of this
idMat3 mat = baseAxes * currentAxes.ToMat3();
parent->GetAnimator()->SetJointAxis( joint, JOINTMOD_WORLD_OVERRIDE, mat );
// Enable disable engine shader effect on model
if ( parent->GetEngine().GetState() ) {
parent->GetRenderEntity()->shaderParms[shaderParmIndex] = 1.0f;
} else {
parent->GetRenderEntity()->shaderParms[shaderParmIndex] = 0.0f;
}
// Lightning bolts from engines
// (visuals only no physics here)
//
if ( gameLocal.time > nextBeamTime ) {
idMat3 parentAxis = parent->GetRBPhysics()->GetAxis();
idVec3 tempTraceDir = traceDir * axis;
idVec3 end = origin + ( tempTraceDir * maxTraceLength );
idEntity *entList[ 100 ];
bool foundAttractor = false;
int numFound = gameLocal.EntitiesWithinRadius( origin, 256.0f, entList, 100 );
engineEffect.Stop(); // stop old bolt
for ( int i = 0; i < numFound; i++ ) {
if ( entList[ i ] != parent && beamTargetInfo->FilterEntity( entList[ i ] ) ) {
idPhysics* entPhysics = entList[ i ]->GetPhysics();
idVec3 end = entPhysics->GetOrigin() + entPhysics->GetBounds().GetCenter();
engineEffect.GetRenderEffect().endOrigin = end;
// If it is to far or going up don't bother
idVec3 diff = end - origin;
if ( ( diff.z < 0.f ) && ( diff.LengthSqr() < Square( 1000.f ) ) ) {
engineEffect.Start( gameLocal.time );
nextBeamTime = gameLocal.time + 100 + gameLocal.random.RandomInt( 250 ); //Make the lightning a bit more "explosive"
foundAttractor = true;
break;
}
}
}
if ( !foundAttractor ) {
// Random vector in negative z cone
idVec3 dir = gameLocal.random.RandomVectorInCone( 80.f );
dir.z = -dir.z;
end = origin + dir * 256.f;
trace_t beamTrace;
memset( &beamTrace, 0, sizeof( beamTrace ) );
gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS beamTrace, origin, end, MASK_VEHICLESOLID, parent );
// If it hit anything then spawn a beam towards that
if ( beamTrace.fraction < 1.f ) {
engineEffect.GetRenderEffect().endOrigin = beamTrace.endpos;
engineEffect.Start( gameLocal.time );
}
nextBeamTime = gameLocal.time + 1000 + gameLocal.random.RandomInt( 2500 );
}
}
engineEffect.GetRenderEffect().origin = origin;
engineEffect.GetRenderEffect().suppressSurfaceInViewID = parent->GetRenderEntity()->suppressSurfaceInViewID;
engineEffect.GetRenderEffect().suppressLightsInViewID = parent->GetRenderEntity()->suppressSurfaceInViewID;
engineEffect.Update();
}
/*
================
sdVehicleRigidBodyHoverPad::EvaluateContacts
================
*/
int sdVehicleRigidBodyHoverPad::EvaluateContacts( contactInfo_t* list, contactInfoExt_t* listExt, int max ) {
return 0;
}
/*
===============================================================================
sdVehicleSuspensionPoint
===============================================================================
*/
CLASS_DECLARATION( sdVehicleDriveObject, sdVehicleSuspensionPoint )
END_CLASS
/*
================
sdVehicleSuspensionPoint::sdVehicleSuspensionPoint
================
*/
sdVehicleSuspensionPoint::sdVehicleSuspensionPoint( void ) {
suspension = NULL;
offset = 0;
suspensionInterface.Init( this );
}
/*
================
sdVehicleSuspensionPoint::~sdVehicleSuspensionPoint
================
*/
sdVehicleSuspensionPoint::~sdVehicleSuspensionPoint( void ) {
delete suspension;
}
/*
================
sdVehicleSuspensionPoint::Init
================
*/
void sdVehicleSuspensionPoint::Init( const sdDeclVehiclePart& point, sdTransport_RB* _parent ) {
parent = _parent;
state.grounded = false;
state.rested = true;
suspensionInfo.velocityScale= point.data.GetFloat( "suspensionVelocityScale", "1" );
suspensionInfo.kCompress = point.data.GetFloat( "suspensionKCompress" );
suspensionInfo.damping = point.data.GetFloat( "suspensionDamping" );
radius = point.data.GetFloat( "radius" );
joint = _parent->GetAnimator()->GetJointHandle( point.data.GetString( "joint" ) );
startJoint = _parent->GetAnimator()->GetJointHandle( point.data.GetString( "startJoint" ) );
if ( joint == INVALID_JOINT ) {
gameLocal.Error( "sdVehicleSuspensionPoint::Init Invalid Joint Name '%s'", point.data.GetString( "joint" ) );
}
if ( startJoint == INVALID_JOINT ) {
gameLocal.Error( "sdVehicleSuspensionPoint::Init Invalid Joint Name '%s'", point.data.GetString( "startJoint" ) );
}
_parent->GetAnimator()->GetJointTransform( joint, gameLocal.time, baseOrg );
_parent->GetAnimator()->GetJointTransform( startJoint, gameLocal.time, baseStartOrg );
suspensionInfo.totalDist = ( baseOrg - baseStartOrg ).Length();
const sdDeclStringMap* suspenionInfo = gameLocal.declStringMapType[ point.data.GetString( "suspension" ) ];
if ( suspenionInfo ) {
gameLocal.CacheDictionaryMedia( suspenionInfo->GetDict() );
suspension = sdVehicleSuspension::GetSuspension( suspenionInfo->GetDict().GetString( "type" ) );
if ( suspension ) {
suspension->Init( &suspensionInterface, suspenionInfo->GetDict() );
}
}
frictionAxes.Identity();
contactFriction = point.data.GetVector( "contactFriction", "0 0 0" );
aggressiveDampening = point.data.GetBool( "aggressiveDampening" );
}
/*
================
sdVehicleSuspensionPoint::GetParent
================
*/
sdTransport* sdVehicleSuspensionPoint::GetParent( void ) const {
return parent;
}
/*
================
sdVehicleSuspensionPoint::UpdatePrePhysics
================
*/
void sdVehicleSuspensionPoint::UpdatePrePhysics( const sdVehicleInput& input ) {
idVec3 org;
idVec3 startOrg;
idMat3 axis = mat3_identity;
if ( !parent->GetPhysics()->IsAtRest() ) {
const renderEntity_t& renderEntity = *parent->GetRenderEntity();
org = renderEntity.origin + ( baseOrg * renderEntity.axis );
startOrg = renderEntity.origin + ( baseStartOrg * renderEntity.axis );
idVec3 start = startOrg;
idVec3 end = org;
memset( &groundTrace, 0, sizeof( groundTrace ) );
gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS groundTrace, start, end, MASK_VEHICLESOLID | CONTENTS_MONSTER, parent );
groundTrace.c.selfId = -1;
offset = radius - ( 1.0f - groundTrace.fraction ) * suspensionInfo.totalDist;
state.rested = true;
if ( groundTrace.fraction != 1.0f ) {
groundTrace.c.selfId = -1;
groundTrace.c.normal = start - end;
groundTrace.c.normal.Normalize();
CalcForces( suspensionForce, suspensionVelocity, -groundTrace.c.normal );
if ( suspensionVelocity < -5.f ) {
state.rested = false;
parent->GetPhysics()->Activate();
} else {
suspensionVelocity = 0.f;
}
}
state.grounded = groundTrace.fraction != 1.f;
if ( suspension ) {
suspension->Update();
}
frictionAxes = axis;
} else {
state.rested = true;
}
}
/*
================
sdVehicleSuspensionPoint::EvaluateContacts
================
*/
int sdVehicleSuspensionPoint::EvaluateContacts( contactInfo_t* list, contactInfoExt_t* listExt, int max ) {
if ( IsHidden() || max < 1 ) {
return 0;
}
if ( state.grounded ) {
listExt[ 0 ].contactForceMax = suspensionForce;
listExt[ 0 ].contactForceVelocity = suspensionVelocity;
listExt[ 0 ].contactFriction = contactFriction;
listExt[ 0 ].frictionAxes = frictionAxes;
listExt[ 0 ].motorForce = 0.0f;
listExt[ 0 ].motorDirection.Zero();
listExt[ 0 ].motorSpeed = 0.0f;
listExt[ 0 ].rested = state.rested;
list[ 0 ] = groundTrace.c;
return 1;
}
return 0;
}
/*
================
sdVehicleSuspensionPoint::CalcForces
================
*/
void sdVehicleSuspensionPoint::CalcForces( float& maxForce, float& velocity, const idVec3& traceDir ) {
idVec3 v;
parent->GetRBPhysics()->GetPointVelocity( groundTrace.endpos, v );
float dampingForce = suspensionInfo.damping * idMath::Fabs( v * -traceDir );
float compression = suspensionInfo.totalDist - suspensionInfo.totalDist * groundTrace.fraction;
state.grounded = true;
maxForce = compression * suspensionInfo.kCompress;
if ( groundTrace.fraction < 0.5f ) { // TODO: Make this configurable
maxForce *= 2.f;
}
if ( !aggressiveDampening ) {
velocity = dampingForce - ( compression * suspensionInfo.velocityScale );
} else {
velocity = 0.0f;
}
}
/*
================
sdVehicleSuspensionPoint::UpdateSuspensionIK
================
*/
bool sdVehicleSuspensionPoint::UpdateSuspensionIK( void ) {
if ( suspension != NULL ) {
return suspension->UpdateIKJoints( parent->GetAnimator() );
}
return false;
}
/*
================
sdVehicleSuspensionPoint::ClearSuspensionIK
================
*/
void sdVehicleSuspensionPoint::ClearSuspensionIK( void ) {
if ( suspension != NULL ) {
suspension->ClearIKJoints( parent->GetAnimator() );
}
}
/*
===============================================================================
sdVehicleRigidBodyVtol
===============================================================================
*/
CLASS_DECLARATION( sdVehicleRigidBodyPartSimple, sdVehicleRigidBodyVtol )
END_CLASS
/*
================
sdVehicleRigidBodyVtol::sdVehicleRigidBodyVtol
================
*/
sdVehicleRigidBodyVtol::sdVehicleRigidBodyVtol( void ) {
}
/*
================
sdVehicleRigidBodyVtol::~sdVehicleRigidBodyVtol
================
*/
sdVehicleRigidBodyVtol::~sdVehicleRigidBodyVtol( void ) {
}
/*
================
sdVehicleRigidBodyVtol::Init
================
*/
void sdVehicleRigidBodyVtol::Init( const sdDeclVehiclePart& part, sdTransport_RB* _parent ) {
sdVehicleRigidBodyPartSimple::Init( part, _parent );
shoulderAnglesBounds = part.data.GetVec2( "shoulderBounds", "-10 10" );
elbowAnglesBounds = part.data.GetVec2( "elbowBounds", "-10 10" );
shoulderAxis = part.data.GetInt( "shoulderAxis", "1" ); // Which major axis to rotate along
elbowAxis = part.data.GetInt( "elbowAxis", "0" );
shoulderAngleScale = part.data.GetInt( "shoulderAngleScale", "1" );
elbowAngleScale = part.data.GetInt( "elbowAngleScale", "1" );
elbowJoint = _parent->GetAnimator()->GetJointHandle( part.data.GetString( "elbowJoint" ) );
if ( elbowJoint == INVALID_JOINT ) {
gameLocal.Error( "sdVehiclePart_AF::Init '%s' can't find VTOL joint '%s'", name.c_str(), part.data.GetString( "elbowJoint" ) );
}
effectJoint = _parent->GetAnimator()->GetJointHandle( part.data.GetString( "effectJoint" ) );
if ( effectJoint == INVALID_JOINT ) {
gameLocal.Error( "sdVehiclePart_AF::Init '%s' can't find VTOL joint '%s'", name.c_str(), part.data.GetString( "elbowJoint" ) );
}
idVec3 shoulderBaseOrg; idVec3 elbowBaseOrg;
// Extract initial joint positions
_parent->GetAnimator()->GetJointTransform( joint, gameLocal.time, shoulderBaseOrg, shoulderBaseAxes );
_parent->GetAnimator()->GetJointTransform( elbowJoint, gameLocal.time, elbowBaseOrg, elbowBaseAxes );
// Setup the engine effect
renderEffect_t &effect = engineEffect.GetRenderEffect();
effect.declEffect = gameLocal.FindEffect( _parent->spawnArgs.GetString( part.data.GetString( "effect", "fx_vtol" ) ) );
effect.axis = mat3_identity;
effect.attenuation = 1.f;
effect.hasEndOrigin = false;
effect.loop = true;
effect.shaderParms[SHADERPARM_RED] = 1.0f;
effect.shaderParms[SHADERPARM_GREEN] = 1.0f;
effect.shaderParms[SHADERPARM_BLUE] = 1.0f;
effect.shaderParms[SHADERPARM_ALPHA] = 1.0f;
effect.shaderParms[SHADERPARM_BRIGHTNESS] = 1.0f;
_parent->GetAnimator()->GetJointTransform( effectJoint, gameLocal.time, engineEffect.GetRenderEffect().origin, engineEffect.GetRenderEffect().axis );
//engineEffect.Start( gameLocal.time );
oldShoulderAngle = 0.f;
oldElbowAngle = 0.f;
/*
shaderParmIndex = part.data.GetInt( "shaderParmIndex", "0" );
adaptSpeed = part.data.GetFloat( "adaption_speed", "0.005" );
velocityInfluence = part.data.GetFloat( "linear_velocity_influence", "0.3" );
angVelocityInfluence = part.data.GetFloat( "angular_velocity_influence", "-0.3" );
noiseFreq = part.data.GetFloat( "random_motion_freq", "0.001" );
noisePhase = part.data.GetFloat( "random_motion_amplitude", "3" );
restFraction = part.data.GetFloat( "restFraction", "0.9" );
minAngles.Set( minAnglesValues.x, minAnglesValues.y, minAnglesValues.z );
maxAngles.Set( maxAnglesValues.x, maxAnglesValues.y, maxAnglesValues.z );
traceDir.Normalize();
groundNormal = idVec3( 0.0f, 0.0f, 1.0f );
_parent->GetAnimator()->GetJointTransform( joint, gameLocal.time, baseOrg, baseAxes );
idMat3 ident; ident.Identity();
currentAxes = ident.ToQuat();
noisePhase = gameLocal.random.CRandomFloat() * 4000.0f;
//Dir is the direction the pads should "point to" when the vehicle is rotating along it's z-axis
//(so it can turn on the spot (velocity == 0)) and still look cool
//This gives directions on the rotation circle (like the arms of a swastika)
angularDir.Cross( baseOrg, idVec3( 0, 0, 1 ) );
angularDir.Normalize();
// Setup the lighting effect
renderEffect_t &effect = engineEffect.GetRenderEffect();
effect.declEffect = gameLocal.declEffectsType.Find( _parent->spawnArgs.GetString( "fx_hoverpad" ) );
effect.axis = mat3_identity;
effect.attenuation = 1.f;
effect.hasEndOrigin = true;
effect.loop = false;
effect.shaderParms[SHADERPARM_RED] = 1.0f;
effect.shaderParms[SHADERPARM_GREEN] = 1.0f;
effect.shaderParms[SHADERPARM_BLUE] = 1.0f;
effect.shaderParms[SHADERPARM_ALPHA] = 1.0f;
effect.shaderParms[SHADERPARM_BRIGHTNESS] = 1.0f;
nextBeamTime = gameLocal.time + 2000 + gameLocal.random.RandomInt( 1000 );
beamTargetInfo = (const sdDeclTargetInfo *)gameLocal.declTargetInfoType.Find( _parent->spawnArgs.GetString( "ti_lightning" ) );*/
}
/*
================
sdVehicleRigidBodyHoverPad::UpdatePrePhysics
================
*/
void sdVehicleRigidBodyVtol::UpdatePrePhysics( const sdVehicleInput& input ) {
// We just want to do some animation
}
void sdVehicleRigidBodyVtol::UpdatePostPhysics( const sdVehicleInput& input ) {
const idMat3 &modelAxis = parent->GetRenderEntity()->axis;
const idVec3 &modelOrg = parent->GetRenderEntity()->origin;
float shoulderAngle = RAD2DEG( idMath::ACos( modelAxis[0] * idVec3(0,0,1) ) ) - 90.0f;
float elbowAngle = RAD2DEG( idMath::ACos( modelAxis[1] * idVec3(0,0,1) ) ) - 90.0;
// "Shoulder"
idVec3 axis = vec3_origin;
axis[ shoulderAxis ] = 1.0f;
shoulderAngle *= shoulderAngleScale;
if( shoulderAngle < shoulderAnglesBounds.x ) {
shoulderAngle = shoulderAnglesBounds.x;
} else if ( shoulderAngle > shoulderAnglesBounds.y ) {
shoulderAngle = shoulderAnglesBounds.y;
}
if ( idMath::Fabs( oldShoulderAngle - shoulderAngle ) > 0.1f ) {
oldShoulderAngle = shoulderAngle;
idRotation rot( vec3_origin, axis, shoulderAngle );
idMat3 mat = shoulderBaseAxes * rot.ToMat3();
parent->GetAnimator()->SetJointAxis( joint, JOINTMOD_WORLD_OVERRIDE, mat );
}
// "Elbow"
axis = vec3_origin;
axis[ elbowAxis ] = 1.0f;
elbowAngle *= elbowAngleScale;
if( elbowAngle < elbowAnglesBounds.x ) {
elbowAngle = elbowAnglesBounds.x;
} else if ( elbowAngle > elbowAnglesBounds.y ) {
elbowAngle = elbowAnglesBounds.y;
}
if ( idMath::Fabs( oldElbowAngle - elbowAngle ) > 0.1f ) {
elbowAngle = elbowAngle;
idRotation rot = idRotation( vec3_origin, axis, elbowAngle );
parent->GetAnimator()->SetJointAxis( elbowJoint, JOINTMOD_LOCAL, rot.ToMat3() );
}
if ( parent->GetEngine().GetState() ) {
idMat3 effectAxis;
idVec3 effectOrg;
parent->GetAnimator()->GetJointTransform( effectJoint, gameLocal.time, effectOrg, effectAxis );
engineEffect.GetRenderEffect().axis = effectAxis * modelAxis;
engineEffect.GetRenderEffect().origin = effectOrg * modelAxis + modelOrg;
engineEffect.Start( gameLocal.time );
engineEffect.Update();
} else {
engineEffect.Stop();
}
}
/*
===============================================================================
sdVehicleRigidBodyAntiGrav
===============================================================================
*/
CLASS_DECLARATION( sdVehiclePartSimple, sdVehicleRigidBodyAntiGrav )
END_CLASS
/*
================
sdVehicleRigidBodyAntiGrav::sdVehicleRigidBodyAntiGrav
================
*/
sdVehicleRigidBodyAntiGrav::sdVehicleRigidBodyAntiGrav( void ) {
clientParent = NULL;
oldVelocity.Zero();
}
/*
================
sdVehicleRigidBodyAntiGrav::~sdVehicleRigidBodyAntiGrav
================
*/
sdVehicleRigidBodyAntiGrav::~sdVehicleRigidBodyAntiGrav( void ) {
}
/*
================
sdVehicleRigidBodyAntiGrav::Init
================
*/
void sdVehicleRigidBodyAntiGrav::Init( const sdDeclVehiclePart& part, sdTransport* _parent ) {
sdVehiclePartSimple::Init( part, _parent );
rotAxis = part.data.GetInt( "rotAxis", "1" );
tailUpAxis = part.data.GetInt( "tailUpAxis", "0" );
tailSideAxis = part.data.GetInt( "tailSideAxis", "2" );
{
renderEffect_t &effect = engineMainEffect.GetRenderEffect();
effect.declEffect = gameLocal.FindEffect( _parent->spawnArgs.GetString( part.data.GetString( "effect", "fx_engine" ) ) );
effect.axis = mat3_identity;
effect.attenuation = 1.f;
effect.hasEndOrigin = false;
effect.loop = true;
effect.shaderParms[SHADERPARM_RED] = 1.0f;
effect.shaderParms[SHADERPARM_GREEN] = 1.0f;
effect.shaderParms[SHADERPARM_BLUE] = 1.0f;
effect.shaderParms[SHADERPARM_ALPHA] = 1.0f;
effect.shaderParms[SHADERPARM_BRIGHTNESS] = 1.0f;
}
{
renderEffect_t &effect = engineBoostEffect.GetRenderEffect();
effect.declEffect = gameLocal.FindEffect( _parent->spawnArgs.GetString( part.data.GetString( "effect_boost", "fx_engine_boost" ) ) );
effect.axis = mat3_identity;
effect.attenuation = 1.f;
effect.hasEndOrigin = false;
effect.loop = true;
effect.shaderParms[SHADERPARM_RED] = 1.0f;
effect.shaderParms[SHADERPARM_GREEN] = 1.0f;
effect.shaderParms[SHADERPARM_BLUE] = 1.0f;
effect.shaderParms[SHADERPARM_ALPHA] = 1.0f;
effect.shaderParms[SHADERPARM_BRIGHTNESS] = 1.0f;
}
oldAngle = 0.0f;
fanRotation = 0.f;
targetAngle = 0.f;
fanJointName = part.data.GetString( "fanJoint" );
tailJointName = part.data.GetString( "tailJoint" );
SetupJoints( _parent->GetAnimator() );
lastGroundEffectsTime = 0;
fanSpeedMultiplier = parent->spawnArgs.GetFloat( "fan_speed_multiplier", "0.9" );
fanSpeedOffset = parent->spawnArgs.GetFloat( "fan_speed_offset", "500" );
fanSpeedMax = parent->spawnArgs.GetFloat( "fan_pitch_max", "800" );
fanSpeedRampRate = parent->spawnArgs.GetFloat( "fan_ramp_rate", "0.1" );
lastFanSpeed = fanSpeedOffset;
}
/*
================
sdVehicleRigidBodyAntiGrav::SetupJoints
================
*/
void sdVehicleRigidBodyAntiGrav::SetupJoints( idAnimator* targetAnimator ) {
fanJoint = targetAnimator->GetJointHandle( fanJointName );
if ( fanJoint == INVALID_JOINT ) {
gameLocal.Error( "sdVehiclePart_AF::Init '%s' can't find AntiGrav joint '%s'", name.c_str(), fanJointName.c_str() );
}
tailJoint = targetAnimator->GetJointHandle( tailJointName );
if ( tailJoint == INVALID_JOINT ) {
gameLocal.Error( "sdVehiclePart_AF::Init '%s' can't find AntiGrav joint '%s'", name.c_str(), tailJointName.c_str() );
}
idVec3 baseOrg;
// Extract initial joint positions
targetAnimator->GetJointTransform( joint, gameLocal.time, baseOrg, baseAxes );
// Setup the engine effect
targetAnimator->GetJointTransform( joint, gameLocal.time, engineMainEffect.GetRenderEffect().origin, engineMainEffect.GetRenderEffect().axis );
targetAnimator->GetJointTransform( joint, gameLocal.time, engineBoostEffect.GetRenderEffect().origin, engineBoostEffect.GetRenderEffect().axis );
}
/*
================
sdVehicleRigidBodyAntiGrav::SetClientParent
================
*/
void sdVehicleRigidBodyAntiGrav::SetClientParent( rvClientEntity* p ) {
clientParent = p;
if ( p ) {
SetupJoints( p->GetAnimator() );
}
}
/*
================
sdVehicleRigidBodyAntiGrav::UpdatePrePhysics
================
*/
void sdVehicleRigidBodyAntiGrav::UpdatePrePhysics( const sdVehicleInput& input ) {
// We just want to do some animation
}
/*
================
sdVehicleRigidBodyAntiGrav::UpdatePostPhysics
================
*/
void sdVehicleRigidBodyAntiGrav::UpdatePostPhysics( const sdVehicleInput& input ) {
idPlayer* player = parent->GetPositionManager().FindDriver();
sdPhysics_JetPack* jetpackPhysics = parent->GetPhysics()->Cast< sdPhysics_JetPack >();
idAnimator* targetAnimator;
idVec3 modelOrg;
idMat3 modelAxis;
if ( clientParent ) {
modelOrg = clientParent->GetOrigin();
modelAxis = clientParent->GetAxis();
targetAnimator = clientParent->GetAnimator();
} else {
modelOrg = parent->GetPhysics()->GetOrigin();
modelAxis = parent->GetRenderEntity()->axis;
targetAnimator = parent->GetAnimator();
}
idVec3 velocity, localVelocity;// = parent->GetPhysics()->GetLinearVelocity() * modelAxis.Transpose();
if ( jetpackPhysics ) {
localVelocity = jetpackPhysics->GetFanForce() * modelAxis.Transpose();
} else {
localVelocity.Zero();
}
// zero out lateral velocities, as not represented in fan movement
velocity = localVelocity;
velocity.y = 0.f;
// remove jittery small velocities
if ( fabsf( velocity.x ) < 0.0001f ) {
velocity.x = 0.f;
}
if ( fabsf( velocity.z ) < 0.0001f ) {
velocity.z = 0.f;
}
// If we are not really going up just blend towards the forward pose
//if ( velocity.z < 0.001f ) {
// velocity = idVec3( velocity.x + 100.0f, velocity.y, 0 );
//}
oldVelocity.FixDenormals();
idVec3 targetVelocity = velocity * 0.9f + oldVelocity * 0.1f;
targetVelocity.FixDenormals();
float blendSpeed = 0.4f;
if ( targetVelocity.Length() < 0.01f && (!jetpackPhysics || jetpackPhysics->OnGround()) ) {
targetAngle = 0;//15;
blendSpeed = 0.05f;
}
float angle = targetAngle * blendSpeed + oldAngle * (1.f - blendSpeed);
if ( idMath::Fabs( angle ) < idMath::FLT_EPSILON ) {
angle = 0.0f;
}
idRotation rot( vec3_origin, baseAxes[rotAxis], angle );
targetAnimator->SetJointAxis( joint, JOINTMOD_WORLD_OVERRIDE, rot.ToMat3() );
oldAngle = angle;
if ( !targetVelocity.Compare( oldVelocity, 0.00001f ) )
{
// Engine hubs
idVec3 dir = targetVelocity ;
dir.y = 0.0f; // Ignore strafing :)
dir.Normalize();
targetAngle = RAD2DEG( idMath::ACos( dir * idVec3( 1, 0, 0 ) ) );
// Tail stabilizers
dir = targetVelocity;
dir.Normalize();
idVec3 angles;
angles.Zero();
angles[tailUpAxis] = dir.z * 2.0f;
angles[tailSideAxis] = dir.y * 5.0f;
targetAnimator->SetJointAxis( tailJoint, JOINTMOD_LOCAL, idAngles( angles ).ToMat3() );
oldVelocity = targetVelocity;
}
if ( player ) {
const idVec3& upAxis = parent->GetPhysics()->GetAxis()[ 2 ];
float absSpeedKPH = ( localVelocity - ( localVelocity*upAxis )*upAxis ).Length();
float idealNewSpeed = ( absSpeedKPH * fanSpeedMultiplier ) + fanSpeedOffset;
if ( idealNewSpeed > fanSpeedMax ) {
idealNewSpeed = fanSpeedMax;
}
float speed = Lerp( lastFanSpeed, idealNewSpeed, fanSpeedRampRate );
lastFanSpeed = speed;
if ( jetpackPhysics ) {
speed += jetpackPhysics->GetBoost() * 1000.f;
}
// HACK - right fan spin in opposite direction
if ( fanJointName[ 0 ] == 'r' ) {
speed = -speed;
}
fanRotation += MS2SEC( gameLocal.msec ) * speed;
idAngles rot( 0.0f, 0.0f, fanRotation );
targetAnimator->SetJointAxis( fanJoint, JOINTMOD_LOCAL_OVERRIDE, rot.ToMat3() );
}
if ( !clientParent ) {
UpdateEffect();
}
}
/*
================
sdVehicleRigidBodyAntiGrav::UpdateEffect
================
*/
void sdVehicleRigidBodyAntiGrav::UpdateEffect() {
idAnimator* targetAnimator;
idVec3 modelOrg;
idMat3 modelAxis;
if ( clientParent ) {
modelOrg = clientParent->GetOrigin();
modelAxis = clientParent->GetAxis();
targetAnimator = clientParent->GetAnimator();
} else {
modelOrg = parent->GetPhysics()->GetOrigin();
modelAxis = parent->GetRenderEntity()->axis;
targetAnimator = parent->GetAnimator();
}
if ( parent->GetPositionManager().FindDriver() ) {
idMat3 mountAxis;
idVec3 mountOrg;
targetAnimator->GetJointTransform( fanJoint, gameLocal.time, mountOrg, mountAxis );
bool boost = false;
sdPhysics_JetPack* jetpackPhysics = parent->GetPhysics()->Cast< sdPhysics_JetPack >();
if ( jetpackPhysics ) {
boost = jetpackPhysics->GetBoost() > 0.5f;
}
engineBoostEffect.GetRenderEffect().axis = ( mountAxis * modelAxis )[ 0 ].ToMat3();
engineBoostEffect.GetRenderEffect().origin = mountOrg * modelAxis + modelOrg;
engineMainEffect.GetRenderEffect().axis = ( mountAxis * modelAxis )[ 0 ].ToMat3();
engineMainEffect.GetRenderEffect().origin = mountOrg * modelAxis + modelOrg;
engineMainEffect.Start( gameLocal.time );
engineMainEffect.Update();
if ( boost ) {
engineBoostEffect.Start( gameLocal.time );
} else {
engineBoostEffect.Stop();
}
engineBoostEffect.Update();
if ( boost ) {
idMat3 orient = mountAxis * modelAxis;
idVec3 dist(-800.f, 0.f, 0.f);
dist = orient * dist;
idVec3 traceOrg = mountOrg * modelAxis + modelOrg;
idVec3 traceEnd = traceOrg + dist;
trace_t traceObject;
gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS traceObject, traceOrg, traceEnd, MASK_SOLID | CONTENTS_WATER | MASK_OPAQUE, parent );
traceEnd = traceObject.endpos;
if ( gameLocal.time >= ( lastGroundEffectsTime + 100 )
&& traceObject.fraction < 1.0f ) {
const char* surfaceTypeName = NULL;
if ( traceObject.c.surfaceType ) {
surfaceTypeName = traceObject.c.surfaceType->GetName();
}
parent->PlayEffect( "fx_groundeffect", colorWhite.ToVec3(), surfaceTypeName, traceObject.endpos, traceObject.c.normal.ToMat3(), 0 );
lastGroundEffectsTime = gameLocal.time;
}
}
} else {
engineMainEffect.Stop();
engineBoostEffect.Stop();
}
}
/*
===============================================================================
sdVehicleRigidBodyPseudoHover
===============================================================================
*/
idCVar g_debugVehiclePseudoHover( "g_debugVehiclePseudoHover", "0", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "show info about the pseudoHover component" );
#define MASK_PSEUDOHOVERCLIP (CONTENTS_SOLID | CONTENTS_VEHICLECLIP | CONTENTS_WATER | CONTENTS_MONSTER)
CLASS_DECLARATION( sdVehiclePart, sdVehicleRigidBodyPseudoHover )
END_CLASS
/*
================
sdVehicleRigidBodyPseudoHover::sdVehicleRigidBodyPseudoHover
================
*/
sdVehicleRigidBodyPseudoHover::sdVehicleRigidBodyPseudoHover( void ) {
}
/*
================
sdVehicleRigidBodyPseudoHover::~sdVehicleRigidBodyPseudoHover
================
*/
sdVehicleRigidBodyPseudoHover::~sdVehicleRigidBodyPseudoHover( void ) {
gameLocal.clip.DeleteClipModel( mainClipModel );
}
/*
================
sdVehicleRigidBodyPseudoHover::Init
================
*/
#define SIGNEDPOWER( a, b ) ( idMath::Sign( a ) * idMath::Pow( idMath::Fabs( a ), b ) )
void sdVehicleRigidBodyPseudoHover::Init( const sdDeclVehiclePart& part, sdTransport_RB* _parent ) {
sdVehiclePart::Init( part );
oldDriver = NULL;
parent = _parent;
grounded = false;
parkMode = false;
lockedPark = false;
hoverHeight = part.data.GetFloat( "height", "60" );
parkHeight = part.data.GetFloat( "height_park", "30" );
repulsionSpeedCoeff = part.data.GetFloat( "repulsion_speed_coeff" );
repulsionSpeedHeight = part.data.GetFloat( "repulsion_speed_height" );
repulsionSpeedMax = part.data.GetFloat( "repulsion_speed_max" );
yawCoeff = part.data.GetFloat( "yaw_coeff" );
fwdCoeff = part.data.GetFloat( "fwd_coeff" );
fwdSpeedDampCoeff = part.data.GetFloat( "fwd_speed_damping_coeff" );
fwdSpeedDampPower = part.data.GetFloat( "fwd_speed_damping_pow" );
fwdSpeedDampMax = part.data.GetFloat( "fwd_speed_damping_max" );
frontCastPos = part.data.GetFloat( "front_cast" );
backCastPos = part.data.GetFloat( "back_cast" );
castOffset = part.data.GetFloat( "cast_offset" );
maxSlope = part.data.GetFloat( "max_slope" );
slopeDropoff = part.data.GetFloat( "slope_dropoff" );
parkTime = part.data.GetFloat( "park_time", "0.25" );
effectJoint = parent->GetAnimator()->GetJointHandle( parent->spawnArgs.GetString( "joint_park_fx", "origin" ) );
lastFrictionScale = 1.0f;
startParkTime = 0;
endParkTime = gameLocal.time;
evalState.surfaceNormal.Set( 0.0f, 0.0f, 1.0f );
//
// TWTODO: Take these from the vscript
//
castStarts.Append( idVec3( frontCastPos, -80.0f, castOffset ) );
castStarts.Append( idVec3( frontCastPos, 80.0f, castOffset ) );
castStarts.Append( idVec3( backCastPos, -80.0f, castOffset ) );
castStarts.Append( idVec3( backCastPos, 80.0f, castOffset ) );
castDirections.Append( idVec3( 128.0f, -64.0f, 0.0f ) );
castDirections.Append( idVec3( 128.0f, 64.0f, 0.0f ) );
castDirections.Append( idVec3( -64.0f, -64.0f, 0.0f ) );
castDirections.Append( idVec3( -64.0f, 64.0f, 0.0f ) );
mainClipModel = NULL;
targetVelocity.Zero();
targetQuat.Set( 1.0f, 0.0f, 0.0f, 0.0f );
lastParkEffectTime = 0;
lastUnparkEffectTime = 0;
chosenParkAxis.Identity();
chosenParkOrigin.Zero();
}
/*
================
sdVehicleRigidBodyPseudoHover::RepulsionForCast
================
*/
void sdVehicleRigidBodyPseudoHover::DoRepulsionForCast( const idVec3& start, const idVec3& end, float desiredFraction, const trace_t& trace, float& height, int numForces ) {
idVec3 traceDir = end - start;
float fullLength = traceDir.Normalize();
float traceLength = trace.fraction * fullLength;
float idealLength = desiredFraction * fullLength;
if ( trace.c.normal.z < 0.2f ) {
// HACK - make it pretend like a trace went straight down the full distance if it touched
// a too-steep slope
const_cast< idVec3& >( trace.c.normal ) = evalState.axis[ 2 ];
traceLength = idealLength;
}
//
// Calculate the repulsion
//
float reachingTime = 0.5f;
if ( trace.fraction < 0.2f ) {
reachingTime = 0.3f;
}
if ( trace.fraction < 0.1f ) {
reachingTime = 0.15f;
}
if ( trace.fraction < 1.0f ) {
float delta = idealLength - traceLength;
float futureDistMoved = evalState.linVelocity * traceDir * reachingTime * 0.5f;
float repulseAccel = 2 * ( delta + futureDistMoved ) / ( reachingTime*reachingTime );
idVec3 hoverAccel = -repulseAccel*traceDir - evalState.gravity;
// TODO: Use the hover force generated to create a torque and use that to re-orient the vehicle
// Will need to make the surface angle matching etc code take that into account!
evalState.hoverForce += hoverAccel * evalState.mass / numForces;
evalState.surfaceNormal += ( 1.0f - trace.fraction ) * trace.c.normal;
}
height += trace.fraction;
}
/*
================
sdVehicleRigidBodyPseudoHover::DoRepulsors
================
*/
void sdVehicleRigidBodyPseudoHover::DoRepulsors() {
const idVec3& upVector = evalState.axis[ 2 ];
//
// We want to cast out a bit forwards so that we don't tend to
// bottom out when the slope of the surface changes
//
idVec3 forwardLeading = evalState.linVelocity * 0.3f;
if ( startParkTime != 0 ) {
forwardLeading.Zero();
} else {
forwardLeading -= ( upVector*forwardLeading ) * upVector;
float fwdLeadLength = forwardLeading.Length();
if ( fwdLeadLength > 200.0f ) {
forwardLeading = forwardLeading * ( 200.0f / fwdLeadLength );
}
}
idVec3 traceDirection = -upVector;
traceDirection.Normalize();
evalState.hoverForce.Zero();
float mainTraceLength = hoverHeight;
if ( startParkTime != 0 ) {
mainTraceLength = hoverHeight * 3.0f;
}
float extraTraceLength = hoverHeight * 3.0f;
evalState.surfaceNormal.Zero();
float realHeight = 0.0f;
//
// Main body
//
if ( g_debugVehiclePseudoHover.GetBool() ) {
mainClipModel->Draw( evalState.origin, evalState.axis );
}
memset( &groundTrace, 0, sizeof( groundTrace ) );
idVec3 start = evalState.origin + 0.3f * castOffset * upVector;
idVec3 end = start + mainTraceLength * traceDirection;
end += forwardLeading;
evalState.clipLocale.Translation( CLIP_DEBUG_PARMS groundTrace, start, end, mainClipModel, evalState.axis, MASK_PSEUDOHOVERCLIP );
if ( groundTrace.c.normal.z < 0.2f ) {
idVec3 end = start + idVec3( 0.0f, 0.0f, -1.0f ) * extraTraceLength;
evalState.clipLocale.TracePoint( CLIP_DEBUG_PARMS groundTrace, start, end, MASK_PSEUDOHOVERCLIP );
}
DoRepulsionForCast( start, end, hoverHeight / mainTraceLength, groundTrace, realHeight, 2 );
realHeight = 0.0f;
if ( g_debugVehiclePseudoHover.GetBool() ) {
mainClipModel->Draw( groundTrace.endpos, evalState.axis );
}
//
// Do a bunch of other position casts
int numCasts = castStarts.Num() < castDirections.Num() ? castStarts.Num() : castDirections.Num();
for ( int i = 0; i < numCasts; i++ ) {
trace_t currentTrace;
idVec3 castStart = evalState.origin + castStarts[ i ] * evalState.axis;
idVec3 castEnd = castStart + extraTraceLength * traceDirection;
castEnd += forwardLeading;
castEnd += castDirections[ i ] * evalState.axis;
evalState.clipLocale.TracePoint( CLIP_DEBUG_PARMS currentTrace, castStart, castEnd, MASK_PSEUDOHOVERCLIP );
if ( currentTrace.c.normal.z < 0.2f ) {
idVec3 castEnd = castStart + idVec3( 0.0f, 0.0f, -1.0f ) * extraTraceLength;
evalState.clipLocale.TracePoint( CLIP_DEBUG_PARMS currentTrace, castStart, castEnd, MASK_PSEUDOHOVERCLIP );
}
if ( g_debugVehiclePseudoHover.GetBool() ) {
gameRenderWorld->DebugArrow( colorRed, castStart, currentTrace.endpos, 4 );
}
DoRepulsionForCast( castStart, castEnd, hoverHeight / extraTraceLength, currentTrace, realHeight, numCasts + 1 );
}
// Calculate the estimates of the normal & height
realHeight = realHeight * extraTraceLength / numCasts;
float normalLength = evalState.surfaceNormal.Normalize();
if ( normalLength < idMath::FLT_EPSILON ) {
evalState.surfaceNormal.Set( 0.0f, 0.0f, 1.0f );
}
// Don't let the generated up velocity exceed 300ups
float currentUpVel = evalState.linVelocity * evalState.axis[ 2 ];
float upForce = evalState.hoverForce*evalState.axis[ 2 ];
if ( idMath::Fabs( upForce ) > idMath::FLT_EPSILON ) {
float futureVel = currentUpVel + evalState.timeStep * upForce / evalState.mass ;
float clampedFutureVel = idMath::ClampFloat( -300.0f, 300.0f, futureVel );
float newUpForce = evalState.mass * ( clampedFutureVel - currentUpVel ) / evalState.timeStep;
if ( realHeight > hoverHeight && newUpForce > 0.0f ) {
// scale the up force down to prevent it getting ridiculously high up
float scale = Lerp( 1.0f, 0.3f, ( realHeight - hoverHeight ) / hoverHeight * 2.0f );
scale = idMath::ClampFloat( 0.3f, 1.0f, scale );
newUpForce *= scale;
}
evalState.hoverForce *= newUpForce / upForce;
}
//
// Up vector based velocity dampening
//
grounded = false;
if ( realHeight < repulsionSpeedHeight ) {
// calculate the acceleration required to put a stop to this vertical velocity
const idVec3 estFutureVelocity = evalState.linVelocity + evalState.hoverForce*( evalState.timeStep / evalState.mass );
const float upVelocity = ( estFutureVelocity ) * evalState.surfaceNormal;
float coefficient = repulsionSpeedCoeff;
if ( upVelocity > 0.0f ) {
coefficient *= 0.5f;
}
const float neededUpAccel = -upVelocity / evalState.timeStep;
float upAccel = idMath::ClampFloat( -repulsionSpeedMax, repulsionSpeedMax, neededUpAccel * coefficient );
evalState.hoverForce += evalState.mass * upAccel*evalState.surfaceNormal;
grounded = true;
}
}
/*
================
sdVehicleRigidBodyPseudoHover::CalculateSurfaceAxis
================
*/
void sdVehicleRigidBodyPseudoHover::CalculateSurfaceAxis() {
if ( evalState.surfaceNormal == vec3_origin ) {
evalState.surfaceNormal.Set( 0.0f, 0.0f, 1.0f );
}
idVec3 surfaceRight = evalState.surfaceNormal.Cross( evalState.axis[ 0 ] );
surfaceRight.Normalize();
idVec3 surfaceForward = surfaceRight.Cross( evalState.surfaceNormal );
surfaceForward.Normalize();
// construct the surface matrix
idMat3 surfaceAxis( surfaceForward, surfaceRight, evalState.surfaceNormal );
// slerp the surface axis with the old one to add some dampening
evalState.surfaceQuat = surfaceAxis.ToQuat();
evalState.surfaceAxis = evalState.surfaceQuat.ToMat3();
}
/*
================
sdVehicleRigidBodyPseudoHover::CalculateDrivingForce
================
*/
void sdVehicleRigidBodyPseudoHover::CalculateDrivingForce( const sdVehicleInput& input ) {
if ( parkMode ) {
evalState.drivingForce.Zero();
return;
}
const idVec3& upVector = evalState.axis[ 2 ];
const idVec3& surfaceUp = evalState.surfaceAxis[ 2 ];
// "go forwards" impetus
idVec3 fwdAccel( input.GetForward(), -input.GetCollective(), 0.0f );
fwdAccel.Normalize();
idVec3 inputAccel = fwdAccel * fwdCoeff * input.GetForce();
// HACK: these should really go in the vscript or the def file.
float targetSpeed = 380.0f;
if ( input.GetForce() > 1.0f ) {
targetSpeed = 605.0f;
}
float currentSpeed = evalState.linVelocity * ( fwdAccel * evalState.axis );
float maxDelta = fwdCoeff * input.GetForce();
float speedDelta = targetSpeed - currentSpeed;
if ( speedDelta > 0.0f ) {
speedDelta = Lerp( speedDelta, maxDelta, ( speedDelta / targetSpeed ) * 0.15f + 0.85f );
if ( speedDelta > maxDelta ) {
speedDelta = maxDelta;
}
} else {
speedDelta = 0.0f;
}
inputAccel = speedDelta * fwdAccel;
// figure out what that means in terms of a velocity delta
idVec3 vDelta = inputAccel * evalState.timeStep;
// figure out what force is needed to get that sort of vDelta, considering the hovering forces
idVec3 neededForce = evalState.surfaceAxis*inputAccel*evalState.mass - evalState.hoverForce;
neededForce -= ( neededForce*surfaceUp )*surfaceUp;
evalState.drivingForce = neededForce;
//
// Figure out if the slope is too steep, and ramp the force down
//
float angleBetween = idMath::ACos( idVec3( 0.0f, 0.0f, 1.0f ) * surfaceUp ) * idMath::M_RAD2DEG;
if ( angleBetween > maxSlope) {
// calculate the direction vectors directly up and tangential to the slope
idVec3 slopeRight = surfaceUp.Cross( idVec3( 0.0f, 0.0f, 1.0f ) );
idVec3 slopeForward = slopeRight.Cross( surfaceUp );
slopeRight.Normalize();
slopeForward.Normalize();
// ramp down the force
float accelScale = 1.0f - ( angleBetween - maxSlope ) * slopeDropoff;
if ( accelScale < 0.0f ) {
accelScale = 0.0f;
}
// HACK: Drop off the force quicker when boosting
if ( input.GetForce() > 1.0f ) {
accelScale *= accelScale;
}
if ( angleBetween > maxSlope * 2.0f ) {
// way beyond the max slope, zero all force up the slope
accelScale = 0.0f;
}
// remove the component up the slope
float upSlopeComponent = evalState.drivingForce * slopeForward;
if ( upSlopeComponent > 0.0f ) {
evalState.drivingForce = evalState.drivingForce - slopeForward * upSlopeComponent * ( 1.f - accelScale );
}
float hoverUpSlopeComponent = evalState.hoverForce * slopeForward;
if ( hoverUpSlopeComponent > 0.0f ) {
evalState.hoverForce = evalState.hoverForce - slopeForward * hoverUpSlopeComponent * ( 1.f - accelScale );
}
// remove the component into the slope
float intoSlopeComponent = evalState.drivingForce * surfaceUp;
if ( intoSlopeComponent > 0.0f ) {
evalState.drivingForce = evalState.drivingForce - surfaceUp * intoSlopeComponent * ( 1.f - accelScale );
}
float hoverIntoSlopeComponent = evalState.hoverForce * slopeForward;
if ( hoverIntoSlopeComponent > 0.0f ) {
evalState.hoverForce = evalState.hoverForce - slopeForward * hoverIntoSlopeComponent * ( 1.f - accelScale );
}
}
}
/*
================
sdVehicleRigidBodyPseudoHover::CalculateFrictionForce
================
*/
void sdVehicleRigidBodyPseudoHover::CalculateFrictionForce( const sdVehicleInput& input ) {
const idVec3& upVector = evalState.axis[ 2 ];
const idVec3& surfaceUp = evalState.surfaceAxis[ 2 ];
// calculate the future velocity that will be created by the forces that are applied
idVec3 futureVelocity = evalState.linVelocity
+ ( evalState.hoverForce + evalState.drivingForce ) * evalState.timeStep / evalState.mass
/*+ evalState.gravity * evalState.timeStep*/;
// project it to the plane
futureVelocity -= ( futureVelocity * surfaceUp ) * surfaceUp;
float futureSpeed = futureVelocity.Normalize();
// apply friction to it
float futureVelDamp = -fwdSpeedDampCoeff * SIGNEDPOWER( futureSpeed, fwdSpeedDampPower );
futureVelDamp = idMath::ClampFloat( -fwdSpeedDampMax, fwdSpeedDampMax, futureVelDamp );
if ( fabs( input.GetForce() ) > 0.0f ) {
futureVelDamp /= input.GetForce();
} else {
futureVelDamp = 0.0f;
}
// if the speed is quite small or the player is giving no input
// then increase the magnitude of the friction
// TODO: Move these tuning parameters to vscript defined thingies
float frictionScaleCutoff = 128.0f;
float frictionScaleMax = 15.0f;
float frictionScalePower = 0.5f;
float frictionScale = 0.8f;
float parkTimeRemaining = parkTime;
if ( startParkTime != 0 ) {
parkTimeRemaining = parkTime - MS2SEC( gameLocal.time - startParkTime );
} else if ( endParkTime != 0 ) {
parkTimeRemaining = MS2SEC( gameLocal.time - endParkTime );
}
parkTimeRemaining /= parkTime;
parkTimeRemaining = idMath::ClampFloat( 0.0f, 1.0f, parkTimeRemaining );
float parkScale = parkTimeRemaining;
if ( parkTimeRemaining < 1.0f || ( input.GetForward() == 0.0f && input.GetCollective() == 0.0f ) ) {
if ( futureSpeed < frictionScaleCutoff ) {
frictionScale = ( frictionScaleCutoff - futureSpeed ) / frictionScaleCutoff;
frictionScale = idMath::Pow( frictionScale, frictionScalePower );
frictionScale = frictionScale * frictionScaleMax + 1.0f;
} else {
// its going too fast to be acted on by the really strong power
// hack a different friction that will slow it down to the friction cutoff over time
lastFrictionScale = 1.0f;
// calculate the acceleration needed to nullify the velocity
futureVelDamp = -( futureSpeed / evalState.timeStep ) / 5.0f;
}
} else {
// the player is applying input, so snap to zero friction scale
lastFrictionScale = 1.0f;
}
// blend the friction scale with the last frame's one
frictionScale = lastFrictionScale * 0.7f + frictionScale * 0.3f;
frictionScale *= parkScale;
lastFrictionScale = frictionScale;
// apply the friction
evalState.frictionForce = futureVelDamp * evalState.mass * frictionScale * futureVelocity;
}
/*
================
sdVehicleRigidBodyPseudoHover::CalculateTilting
================
*/
void sdVehicleRigidBodyPseudoHover::CalculateTilting( const sdVehicleInput& input ) {
evalState.surfaceMatchingQuat = evalState.surfaceQuat;
if ( startParkTime == 0 ) {
idVec3 inputMove( -input.GetForward(), -( input.GetCollective() + 2.0f*input.GetSteerAngle() ) * 0.5f, 0.0f );
inputMove *= 2.5f * input.GetForce();
// modify the surface quat using the movement keys
idRotation pitchRotation( vec3_origin, evalState.axis[ 1 ], inputMove.x );
idRotation yawRotation( vec3_origin, evalState.axis[ 0 ], inputMove.y );
evalState.surfaceMatchingQuat *= pitchRotation.ToQuat();
evalState.surfaceMatchingQuat *= yawRotation.ToQuat();
}
targetQuat = evalState.surfaceMatchingQuat;
}
/*
================
sdVehicleRigidBodyPseudoHover::CalculateYaw
================
*/
void sdVehicleRigidBodyPseudoHover::CalculateYaw( const sdVehicleInput& input ) {
// calculate the desired angular velocity for the yaw
float yawVel = -yawCoeff * input.GetSteerAngle() * input.GetForce() * 0.04f;
if ( input.GetForward() < 0.0f ) {
yawVel = -yawVel;
}
evalState.steeringAngVel = evalState.axis[ 2 ] * yawVel;
// ramp the steering based on park mode
float parkTimeRemaining = 1.0f;
if ( startParkTime != 0 ) {
parkTimeRemaining = parkTime - MS2SEC( gameLocal.time - startParkTime );
} else if ( endParkTime != 0 ) {
parkTimeRemaining = MS2SEC( gameLocal.time - endParkTime );
}
parkTimeRemaining /= parkTime;
parkTimeRemaining = idMath::ClampFloat( 0.0f, 1.0f, parkTimeRemaining );
evalState.steeringAngVel *= parkTimeRemaining;
float rotation = RAD2DEG( -yawVel * parkTimeRemaining * evalState.timeStep * 3.0f );
idRotation rot( vec3_origin, evalState.axis[ 2 ], rotation );
idQuat rotQuat = rot.ToQuat();
targetQuat = targetQuat * rotQuat;
}
/*
================
sdVehicleRigidBodyPseudoHover::ChooseParkPosition
================
*/
void sdVehicleRigidBodyPseudoHover::ChooseParkPosition() {
// The method here: - Cast down from all the corners of the bounds, & the main bounds
// - Use those to estimate an angle to park at
// - Cast the bounds down using the average normal to find the ground
// - Find a position offset above the ground to park at
grounded = false;
// do the downward casts
float midHeight = ( mainBounds[ 0 ].z + mainBounds[ 1 ].z ) * 0.5f;
idVec3 castOffset = midHeight * evalState.axis[ 2 ];
const idVec3& upVector = evalState.axis[ 2 ];
idVec3 traceVector( 0.0f, 0.0f, -1024.0f );
idVec3 start = evalState.origin;
idVec3 end = start + traceVector;
memset( &groundTrace, 0, sizeof( groundTrace ) );
evalState.clipLocale.Translation( CLIP_DEBUG_PARMS groundTrace, start, end, mainClipModel, evalState.axis, MASK_PSEUDOHOVERCLIP );
grounded |= groundTrace.fraction < 1.0f;
idVec3 mainBodyEndPoint = groundTrace.endpos;
idVec3 mainBodyNormal = groundTrace.c.normal;
if ( g_debugVehiclePseudoHover.GetBool() ) {
gameRenderWorld->DebugBounds( colorGreen, mainBounds, evalState.origin, evalState.axis, 10000 );
}
// front
memset( &groundTrace, 0, sizeof( groundTrace ) );
start = evalState.origin + mainBounds[ 1 ].x * evalState.axis[ 0 ] + castOffset;
end = start + traceVector;
evalState.clipLocale.TracePoint( CLIP_DEBUG_PARMS groundTrace, start, end , MASK_PSEUDOHOVERCLIP );
grounded |= groundTrace.fraction < 1.0f;
idVec3 frontEndPoint = groundTrace.endpos;
idVec3 frontNormal = groundTrace.c.normal;
if ( g_debugVehiclePseudoHover.GetBool() ) {
gameRenderWorld->DebugArrow( colorGreen, start, frontEndPoint, 4, 10000 );
}
// back
memset( &groundTrace, 0, sizeof( groundTrace ) );
start = evalState.origin + mainBounds[ 0 ].x * evalState.axis[ 0 ] + castOffset;
end = start + traceVector;
evalState.clipLocale.TracePoint( CLIP_DEBUG_PARMS groundTrace, start, end , MASK_PSEUDOHOVERCLIP );
grounded |= groundTrace.fraction < 1.0f;
idVec3 backEndPoint = groundTrace.endpos;
idVec3 backNormal = groundTrace.c.normal;
if ( g_debugVehiclePseudoHover.GetBool() ) {
gameRenderWorld->DebugArrow( colorGreen, start, backEndPoint, 4, 10000 );
}
// left
memset( &groundTrace, 0, sizeof( groundTrace ) );
start = evalState.origin + mainBounds[ 1 ].y * evalState.axis[ 1 ] + castOffset;
end = start + traceVector;
evalState.clipLocale.TracePoint( CLIP_DEBUG_PARMS groundTrace, start, end , MASK_PSEUDOHOVERCLIP );
grounded |= groundTrace.fraction < 1.0f;
idVec3 leftEndPoint = groundTrace.endpos;
idVec3 leftNormal = groundTrace.c.normal;
if ( g_debugVehiclePseudoHover.GetBool() ) {
gameRenderWorld->DebugArrow( colorGreen, start, leftEndPoint, 4, 10000 );
}
// right
memset( &groundTrace, 0, sizeof( groundTrace ) );
start = evalState.origin + mainBounds[ 0 ].y * evalState.axis[ 1 ] + castOffset;
end = start + traceVector;
evalState.clipLocale.TracePoint( CLIP_DEBUG_PARMS groundTrace, start, end , MASK_PSEUDOHOVERCLIP );
grounded |= groundTrace.fraction < 1.0f;
idVec3 rightEndPoint = groundTrace.endpos;
idVec3 rightNormal = groundTrace.c.normal;
if ( g_debugVehiclePseudoHover.GetBool() ) {
gameRenderWorld->DebugArrow( colorGreen, start, rightEndPoint, 4, 10000 );
}
if ( !grounded ) {
evalState.surfaceNormal = vec3_origin;
return;
}
//
// Use the info gleaned to estimate a surface normal
//
// Calculate the pitch angle of the surface
idVec3 frontBack = frontEndPoint - backEndPoint;
idVec3 frontBackDir = frontBack;
frontBackDir.Normalize();
float pitchAngle = idMath::ACos( idVec3( 0.0f, 0.0f, 1.0f ) * frontBackDir ) * idMath::M_RAD2DEG - 90.0f;
// Calculate the roll angle of the surface
idVec3 leftRight = rightEndPoint - leftEndPoint;
idVec3 leftRightDir = leftRight;
leftRightDir.Normalize();
float rollAngle = idMath::ACos( idVec3( 0.0f, 0.0f, 1.0f ) * leftRightDir ) * idMath::M_RAD2DEG - 90.0f;
float yawAngle = evalState.axis.ToAngles().yaw;
idAngles newAngles( pitchAngle, yawAngle, rollAngle );
idMat3 chosenAxis = newAngles.ToMat3();
chosenParkAxis = chosenAxis;
// now cast the main body down with that normal as its
start = evalState.origin;
end = start - 1024.0f * chosenParkAxis[ 2 ];
memset( &groundTrace, 0, sizeof( groundTrace ) );
evalState.clipLocale.Translation( CLIP_DEBUG_PARMS groundTrace, start, end, mainClipModel, chosenAxis, MASK_PSEUDOHOVERCLIP );
chosenParkOrigin = groundTrace.endpos + chosenParkAxis[ 2 ] * parkHeight;
foundPark = true;
if ( g_debugVehiclePseudoHover.GetBool() ) {
gameRenderWorld->DebugArrow( colorRed, chosenParkOrigin, chosenParkOrigin + chosenAxis[ 0 ] * 128.0f, 4, 10000 );
gameRenderWorld->DebugArrow( colorGreen, chosenParkOrigin, chosenParkOrigin + chosenAxis[ 1 ] * 128.0f, 4, 10000 );
gameRenderWorld->DebugArrow( colorBlue, chosenParkOrigin, chosenParkOrigin + chosenAxis[ 2 ] * 128.0f, 4, 10000 );
}
}
/*
================
sdVehicleRigidBodyPseudoHover::DoParkRepulsors
================
*/
void sdVehicleRigidBodyPseudoHover::DoParkRepulsors() {
//
// Park mode hovering
//
float timeRemaining = parkTime - MS2SEC( gameLocal.time - startParkTime );
if ( timeRemaining < evalState.timeStep ) {
timeRemaining = evalState.timeStep;
}
if ( !foundPark ) {
chosenParkAxis = evalState.axis;
chosenParkOrigin = evalState.origin;
}
evalState.surfaceNormal = chosenParkAxis[ 2 ];
idVec3 distToMove = chosenParkOrigin - evalState.origin;
assert( chosenParkAxis[ 2 ] != vec3_origin );
if ( chosenParkAxis[ 2 ] == vec3_origin ) {
return;
}
float distToMoveLength = distToMove.Length();
if ( distToMoveLength < 25.0f ) {
lockedPark = true;
}
if ( distToMoveLength > 512.0f ) {
lockedPark = false;
}
idVec3 neededAcceleration;
if ( distToMoveLength < 10.0f ) {
// close enough! just figure out how to cancel out our existing velocity
neededAcceleration = -0.2f * evalState.linVelocity / evalState.timeStep;
} else {
// figure out a velocity to reach that point in the time remaining
idVec3 neededVelocity = distToMove / timeRemaining;
float neededVelocityLength = neededVelocity.Length();
if ( neededVelocityLength > 100.0f ) {
neededVelocity *= 100.0f / neededVelocityLength;
}
// figure out what acceleration is needed to get to that velocity in the next frame
idVec3 vDelta = neededVelocity - evalState.linVelocity;
neededAcceleration = vDelta / evalState.timeStep;
}
// make sure nothing ridiculous is happening with the acceleration
float accelLength = neededAcceleration.Length();
if ( accelLength > 500.0f ) {
neededAcceleration *= 500.0f / accelLength;
}
neededAcceleration -= evalState.gravity;
evalState.hoverForce = neededAcceleration * evalState.mass;
}
/*
================
sdVehicleRigidBodyPseudoHover::UpdatePrePhysics
================
*/
void sdVehicleRigidBodyPseudoHover::UpdatePrePhysics( const sdVehicleInput& input ) {
evalState.physics = parent->GetPhysics()->Cast< sdPhysics_RigidBodyMultiple >();
if ( evalState.physics == NULL ) {
return;
}
if ( mainClipModel == NULL ) {
mainBounds.Clear();
for ( int i = 0; i < evalState.physics->GetNumClipModels(); i++ ) {
int contents = evalState.physics->GetContents( i );
if ( contents && contents != MASK_HURTZONE ) {
idBounds bounds = evalState.physics->GetBounds( i );
bounds.TranslateSelf( evalState.physics->GetBodyOffset( i ) );
mainBounds.AddBounds( bounds );
}
}
mainClipModel = new idClipModel( idTraceModel( mainBounds ), false );
}
evalState.timeStep = MS2SEC( gameLocal.msec );
evalState.driver = parent->GetPositionManager().FindDriver();
// get the set of clip models to trace against
idBounds localeBounds = mainBounds;
localeBounds[ 0 ].z -= hoverHeight;
evalState.clipLocale.Update( localeBounds, evalState.physics->GetOrigin(), evalState.physics->GetAxis(),
evalState.physics->GetLinearVelocity(), evalState.physics->GetAngularVelocity(),
MASK_PSEUDOHOVERCLIP, parent );
evalState.clipLocale.RemoveEntitiesOfCollection( "vehicles" );
evalState.clipLocale.RemoveEntitiesOfCollection( "deployables" );
// see if its too steep to go into siege mode
bool wantsHandBrake = input.GetHandBraking();
if ( wantsHandBrake ) {
float angleBetween = idMath::ACos( idVec3( 0.0f, 0.0f, 1.0f ) * evalState.surfaceNormal ) * idMath::M_RAD2DEG;
if ( angleBetween > maxSlope ) {
wantsHandBrake = false;
parent->GetVehicleControl()->CancelSiegeMode();
}
}
if ( parkMode && ( !wantsHandBrake || !grounded ) && !parent->IsEMPed() ) {
parkMode = false;
foundPark = false;
lockedPark = false;
startParkTime = 0;
endParkTime = gameLocal.time;
if ( gameLocal.time - lastUnparkEffectTime > 500 ) {
parent->PlayEffect( "fx_park_disengage", colorWhite.ToVec3(), NULL, effectJoint );
if ( oldDriver != NULL ) {
parent->StartSound( "snd_park_disengage", SND_VEHICLE_MISC, SND_VEHICLE_MISC, 0, NULL );
}
lastUnparkEffectTime = gameLocal.time;
}
} else if ( ( parent->IsEMPed() || wantsHandBrake ) && !parkMode && grounded ) {
parkMode = true;
foundPark = false;
lockedPark = false;
startParkTime = gameLocal.time;
lastParkUpdateTime = 0;
endParkTime = 0;
if ( gameLocal.time - lastParkEffectTime > 500 ) {
parent->PlayEffect( "fx_park_engage", colorWhite.ToVec3(), NULL, effectJoint );
if ( evalState.driver != NULL ) {
parent->StartSound( "snd_park_engage", SND_VEHICLE_MISC, SND_VEHICLE_MISC, 0, NULL );
}
lastParkEffectTime = gameLocal.time;
}
}
oldDriver = evalState.driver;
// inform the vehicle control if we've finished parking or not
sdVehicleControlBase* control = parent->GetVehicleControl();
if ( control != NULL ) {
control->SetSiegeMode( lockedPark );
}
//
// Harvest data
//
evalState.origin = evalState.physics->GetOrigin();
evalState.axis = evalState.physics->GetAxis();
evalState.mass = evalState.physics->GetMass( -1 );
evalState.gravity = evalState.physics->GetGravity();
evalState.linVelocity = evalState.physics->GetLinearVelocity() + evalState.gravity * evalState.timeStep;
evalState.angVelocity = evalState.physics->GetAngularVelocity();
evalState.inertiaTensor = evalState.physics->GetInertiaTensor();
//
// Handle park updating
//
if ( !gameLocal.isClient ) {
// HACK: seek new parking places when in contact with an MCP
if ( parkMode && lockedPark ) {
for ( int i = 0; i < evalState.physics->GetNumContacts(); i++ ) {
const contactInfo_t& contact = evalState.physics->GetContact( i );
idEntity* contactEnt = gameLocal.entities[ contact.entityNum ];
if ( contactEnt != NULL && contactEnt->Cast< sdTransport >() != NULL ) {
if ( !contactEnt->IsCollisionPushable() ) {
lockedPark = false;
foundPark = false;
}
}
}
}
// try picking a place to park
if ( parkMode && ( !foundPark || gameLocal.time > lastParkUpdateTime + 750 ) && !lockedPark ) {
foundPark = false;
ChooseParkPosition();
lastParkUpdateTime = gameLocal.time;
}
} else {
// clients do a simplified form of what the server does, just so they can
// predict the initial park location and start the process - otherwise they'll
// continually override what the server tells them to do
if ( parkMode && !foundPark && gameLocal.isNewFrame ) {
ChooseParkPosition();
}
}
//
// Hovering
//
if ( !parkMode ) {
DoRepulsors();
} else if ( !lockedPark ) {
DoParkRepulsors();
}
if ( !lockedPark ) {
CalculateSurfaceAxis();
CalculateDrivingForce( input );
CalculateFrictionForce( input );
CalculateTilting( input );
CalculateYaw( input );
idVec3 force = evalState.drivingForce + evalState.frictionForce + evalState.hoverForce;
targetVelocity = evalState.linVelocity + evalState.timeStep * force / evalState.mass;
evalState.physics->Activate();
}
}
/*
================
sdVehicleRigidBodyPseudoHover::UpdatePostPhysics
================
*/
void sdVehicleRigidBodyPseudoHover::UpdatePostPhysics( const sdVehicleInput& input ) {
}
/*
================
sdVehicleRigidBodyPseudoHover::AddCustomConstraints
================
*/
const float CONTACT_LCP_EPSILON = 1e-8f;
int sdVehicleRigidBodyPseudoHover::AddCustomConstraints( constraintInfo_t* list, int max ) {
if ( !grounded ) {
return 0;
}
idPhysics* parentPhysics = parent->GetPhysics();
idVec3 targVel = vec3_origin;
idVec3 targAngVel = vec3_origin;
idQuat toQuat;
float angVelDamp = 0.05f;
if ( !lockedPark ) {
targVel = targetVelocity;
toQuat = targetQuat;
} else {
idVec3 delta = 0.2f * ( chosenParkOrigin - parentPhysics->GetOrigin() );
delta /= MS2SEC( gameLocal.msec );
delta *= 0.5f;
targVel = delta;
toQuat = chosenParkAxis.ToQuat();
angVelDamp = 0.1f;
}
idQuat fromQuat = parentPhysics->GetAxis().ToQuat();
idQuat diffQuat = toQuat.Inverse() * fromQuat;
targAngVel = ( diffQuat.ToAngularVelocity() - parentPhysics->GetAngularVelocity() * angVelDamp ) / MS2SEC( gameLocal.msec );
//
// LINEAR VELOCITY
//
idVec3 comWorld = parentPhysics->GetCenterOfMass() * parentPhysics->GetAxis() + parentPhysics->GetOrigin();
// add the position matching constraints
constraintInfo_t& vx = list[ 0 ];
constraintInfo_t& vy = list[ 1 ];
constraintInfo_t& vz = list[ 2 ];
vx.j.SubVec3( 0 ).Set( 1.0f, 0.0f, 0.0f );
vy.j.SubVec3( 0 ).Set( 0.0f, 1.0f, 0.0f );
vz.j.SubVec3( 0 ).Set( 0.0f, 0.0f, 1.0f );
vx.j.SubVec3( 1 ).Zero();
vy.j.SubVec3( 1 ).Zero();
vz.j.SubVec3( 1 ).Zero();
vx.boxIndex = vy.boxIndex = vz.boxIndex = -1;
vx.error = vy.error = vz.error = CONTACT_LCP_EPSILON;
vx.pos = vy.pos = vz.pos = comWorld;
vx.lm = vy.lm = vz.lm = 0.0f;
vx.c = -targVel.x;
vy.c = -targVel.y;
vz.c = -targVel.z;
// calculate the force needed to make it in one frame
idVec3 force = ( targVel - parentPhysics->GetLinearVelocity() ) / MS2SEC( gameLocal.msec );
// limit the max acceleration
float forceLength = force.Normalize();
if ( forceLength > 4000.0f ) {
forceLength = 4000.0f;
}
force = force * forceLength;
force -= parentPhysics->GetGravity();
force *= parentPhysics->GetMass();
if ( force.x < 0.0f ) {
vx.lo = force.x;
vx.hi = 0.0f;
} else {
vx.hi = force.x;
vx.lo = 0.0f;
}
if ( force.y < 0.0f ) {
vy.lo = force.y;
vy.hi = 0.0f;
} else {
vy.hi = force.y;
vy.lo = 0.0f;
}
if ( force.z < 0.0f ) {
vz.lo = force.z;
vz.hi = 0.0f;
} else {
vz.hi = force.z;
vz.lo = 0.0f;
}
//
// ANGULAR VELOCITY
//
constraintInfo_t& wx = list[ 3 ];
constraintInfo_t& wy = list[ 4 ];
constraintInfo_t& wz = list[ 5 ];
wx.j.SubVec3( 0 ).Zero();
wy.j.SubVec3( 0 ).Zero();
wz.j.SubVec3( 0 ).Zero();
wx.j.SubVec3( 1 ).Set( 1.0f, 0.0f, 0.0f );
wy.j.SubVec3( 1 ).Set( 0.0f, 1.0f, 0.0f );
wz.j.SubVec3( 1 ).Set( 0.0f, 0.0f, 1.0f );
wx.boxIndex = wy.boxIndex = wz.boxIndex = -1;
wx.error = wy.error = wz.error = CONTACT_LCP_EPSILON;
wx.pos = wy.pos = wz.pos = comWorld;
wx.lm = wy.lm = wz.lm = 0.0f;
wx.c = -targAngVel.x;
wy.c = -targAngVel.y;
wz.c = -targAngVel.z;
// calculate the alpha needed to achieve the desired angular movements
idVec3 alpha = ( targAngVel - parentPhysics->GetAngularVelocity() ) / MS2SEC( gameLocal.msec );
// limit the max acceleration
float alphaLength = alpha.Normalize();
if ( alphaLength > 400.0f ) {
alphaLength = 400.0f;
}
alpha = alpha * alphaLength;
alpha *= parentPhysics->GetInertiaTensor();
if ( alpha.x < 0.0f ) {
wx.lo = alpha.x;
wx.hi = 0.0f;
} else {
wx.hi = alpha.x;
wx.lo = 0.0f;
}
if ( alpha.y < 0.0f ) {
wy.lo = alpha.y;
wy.hi = 0.0f;
} else {
wy.hi = alpha.y;
wy.lo = 0.0f;
}
if ( alpha.z < 0.0f ) {
wz.lo = alpha.z;
wz.hi = 0.0f;
} else {
wz.hi = alpha.z;
wz.lo = 0.0f;
}
return 6;
}
/*
================
sdVehicleRigidBodyPseudoHover::CreateNetworkStructure
================
*/
sdEntityStateNetworkData* sdVehicleRigidBodyPseudoHover::CreateNetworkStructure( networkStateMode_t mode ) const {
if ( mode == NSM_BROADCAST ) {
return new sdPseudoHoverBroadcastData;
}
if ( mode == NSM_VISIBLE ) {
return new sdPseudoHoverNetworkData;
}
return NULL;
}
/*
================
sdVehicleRigidBodyPseudoHover::CheckNetworkStateChanges
================
*/
bool sdVehicleRigidBodyPseudoHover::CheckNetworkStateChanges( networkStateMode_t mode, const sdEntityStateNetworkData& baseState ) const {
if ( mode == NSM_BROADCAST ) {
NET_GET_BASE( sdPseudoHoverBroadcastData );
NET_CHECK_FIELD( parkMode, parkMode );
NET_CHECK_FIELD( foundPark, foundPark );
NET_CHECK_FIELD( lockedPark, lockedPark );
NET_CHECK_FIELD( startParkTime, startParkTime );
NET_CHECK_FIELD( endParkTime, endParkTime );
NET_CHECK_FIELD( lastParkUpdateTime, lastParkUpdateTime );
NET_CHECK_FIELD( chosenParkOrigin, chosenParkOrigin );
NET_CHECK_FIELD( chosenParkAxis, chosenParkAxis );
return false;
}
if ( mode == NSM_VISIBLE ) {
NET_GET_BASE( sdPseudoHoverNetworkData );
NET_CHECK_FIELD( lastFrictionScale, lastFrictionScale );
return false;
}
return false;
}
/*
================
sdVehicleRigidBodyPseudoHover::WriteNetworkState
================
*/
void sdVehicleRigidBodyPseudoHover::WriteNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, idBitMsg& msg ) const {
if ( mode == NSM_BROADCAST ) {
NET_GET_STATES( sdPseudoHoverBroadcastData );
newData.parkMode = parkMode;
newData.foundPark = foundPark;
newData.lockedPark = lockedPark;
newData.startParkTime = startParkTime;
newData.endParkTime = endParkTime;
newData.lastParkUpdateTime = lastParkUpdateTime;
newData.chosenParkOrigin = chosenParkOrigin;
newData.chosenParkAxis = chosenParkAxis;
msg.WriteBool( newData.parkMode );
msg.WriteBool( newData.foundPark );
msg.WriteBool( newData.lockedPark );
msg.WriteDeltaLong( baseData.startParkTime, newData.startParkTime );
msg.WriteDeltaLong( baseData.endParkTime, newData.endParkTime );
msg.WriteDeltaLong( baseData.lastParkUpdateTime, newData.lastParkUpdateTime );
msg.WriteDeltaVector( baseData.chosenParkOrigin, newData.chosenParkOrigin );
msg.WriteDeltaCQuat( baseData.chosenParkAxis.ToCQuat(), newData.chosenParkAxis.ToCQuat() );
return;
}
if ( mode == NSM_VISIBLE ) {
NET_GET_STATES( sdPseudoHoverNetworkData );
newData.lastFrictionScale = lastFrictionScale;
msg.WriteDeltaFloat( baseData.lastFrictionScale, newData.lastFrictionScale );
return;
}
}
/*
================
sdVehicleRigidBodyPseudoHover::ReadNetworkState
================
*/
void sdVehicleRigidBodyPseudoHover::ReadNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, const idBitMsg& msg ) const {
if ( mode == NSM_VISIBLE ) {
NET_GET_STATES( sdPseudoHoverNetworkData );
newData.lastFrictionScale = msg.ReadDeltaFloat( baseData.lastFrictionScale );
return;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_STATES( sdPseudoHoverBroadcastData );
newData.parkMode = msg.ReadBool();
newData.foundPark = msg.ReadBool();
newData.lockedPark = msg.ReadBool();
newData.startParkTime = msg.ReadDeltaLong( baseData.startParkTime );
newData.endParkTime = msg.ReadDeltaLong( baseData.endParkTime );
newData.lastParkUpdateTime = msg.ReadDeltaLong( baseData.lastParkUpdateTime );
newData.chosenParkOrigin = msg.ReadDeltaVector( baseData.chosenParkOrigin );
idCQuat readQuat = msg.ReadDeltaCQuat( baseData.chosenParkAxis.ToCQuat() );
newData.chosenParkAxis = readQuat.ToMat3();
return;
}
}
/*
================
sdVehicleRigidBodyPseudoHover::ApplyNetworkState
================
*/
void sdVehicleRigidBodyPseudoHover::ApplyNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& newState ) {
if ( mode == NSM_VISIBLE ) {
NET_GET_NEW( sdPseudoHoverNetworkData );
lastFrictionScale = newData.lastFrictionScale;
return;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_NEW( sdPseudoHoverBroadcastData );
parkMode = newData.parkMode;
foundPark = newData.foundPark;
lockedPark = newData.lockedPark;
startParkTime = newData.startParkTime;
endParkTime = newData.endParkTime;
lastParkUpdateTime = newData.lastParkUpdateTime;
chosenParkOrigin = newData.chosenParkOrigin;
chosenParkAxis = newData.chosenParkAxis;
return;
}
}
/*
================
sdPseudoHoverNetworkData::MakeDefault
================
*/
void sdPseudoHoverNetworkData::MakeDefault( void ) {
lastFrictionScale = 1.0f;
}
/*
================
sdPseudoHoverNetworkData::Write
================
*/
void sdPseudoHoverNetworkData::Write( idFile* file ) const {
file->WriteFloat( lastFrictionScale );
}
/*
================
sdPseudoHoverNetworkData::Read
================
*/
void sdPseudoHoverNetworkData::Read( idFile* file ) {
file->ReadFloat( lastFrictionScale );
}
/*
================
sdPseudoHoverBroadcastData::MakeDefault
================
*/
void sdPseudoHoverBroadcastData::MakeDefault( void ) {
parkMode = false;
foundPark = false;
lockedPark = false;
startParkTime = 0;
endParkTime = gameLocal.time;
lastParkUpdateTime = 0;
chosenParkOrigin.Zero();
chosenParkAxis.Identity();
}
/*
================
sdPseudoHoverBroadcastData::Write
================
*/
void sdPseudoHoverBroadcastData::Write( idFile* file ) const {
file->WriteBool( parkMode );
file->WriteBool( foundPark );
file->WriteBool( lockedPark );
file->WriteInt( startParkTime );
file->WriteInt( endParkTime );
file->WriteInt( lastParkUpdateTime );
file->WriteVec3( chosenParkOrigin );
file->WriteMat3( chosenParkAxis );
}
/*
================
sdPseudoHoverBroadcastData::Read
================
*/
void sdPseudoHoverBroadcastData::Read( idFile* file ) {
file->ReadBool( parkMode );
file->ReadBool( foundPark );
file->ReadBool( lockedPark );
file->ReadInt( startParkTime );
file->ReadInt( endParkTime );
file->ReadInt( lastParkUpdateTime );
file->ReadVec3( chosenParkOrigin );
file->ReadMat3( chosenParkAxis );
}
/*
===============================================================================
sdVehicleRigidBodyDragPlane
===============================================================================
*/
CLASS_DECLARATION( sdVehiclePart, sdVehicleRigidBodyDragPlane )
END_CLASS
/*
================
sdVehicleRigidBodyDragPlane::sdVehicleRigidBodyDragPlane
================
*/
sdVehicleRigidBodyDragPlane::sdVehicleRigidBodyDragPlane( void ) {
}
/*
================
sdVehicleRigidBodyDragPlane::~sdVehicleRigidBodyDragPlane
================
*/
sdVehicleRigidBodyDragPlane::~sdVehicleRigidBodyDragPlane( void ) {
}
/*
================
sdVehicleRigidBodyDragPlane::Init
================
*/
void sdVehicleRigidBodyDragPlane::Init( const sdDeclVehiclePart& part, sdTransport_RB* _parent ) {
sdVehiclePart::Init( part );
parent = _parent;
coefficient = part.data.GetFloat( "coefficient" );
maxForce = part.data.GetFloat( "max_force" );
minForce = part.data.GetFloat( "min_force" );
origin = part.data.GetVector( "origin" );
normal = part.data.GetVector( "normal" );
normal.Normalize();
doubleSided = part.data.GetBool( "double_sided" );
useAngleScale = part.data.GetBool( "use_angle_scale" );
}
/*
================
sdVehicleRigidBodyDragPlane::UpdatePrePhysics
================
*/
void sdVehicleRigidBodyDragPlane::UpdatePrePhysics( const sdVehicleInput& input ) {
idPhysics* physics = parent->GetPhysics();
idVec3 parentOrigin = physics->GetOrigin();
idVec3 velocity = physics->GetLinearVelocity();
idMat3 axis = physics->GetAxis();
idVec3 worldNormal = normal * axis;
idVec3 worldOrigin = parentOrigin + ( origin * axis );
idVec3 dragForce = vec3_zero;
// calculate the component of the velocity in the normal direction
float normalVel = velocity * worldNormal;
// only velocity INTO the surface is considered relevant
if ( !doubleSided && normalVel < 0.0f ) {
return;
}
float angleScale = 1.0f;
if ( useAngleScale ) {
// simulate the plane only being in contact for some of the time (ie, boat rising out of the water)
idAngles angles = axis.ToAngles();
angleScale = 7.5f - fabs( angles.pitch );
angleScale = idMath::ClampFloat( 0.0f, 7.5f, angleScale ) / 7.5f;
const idVec3& gravity = physics->GetGravityNormal();
if ( axis[ 2 ] * gravity < -0.7f ) {
idVec3 temp = velocity - ( ( velocity * gravity ) * gravity );
if ( temp.LengthSqr() > Square( 10.f ) ) {
// do a HORRIBLE HACK to keep the rear end of the boat out of the water
const idBounds& bounds = parent->GetPhysics()->GetBounds();
float rearDist = bounds[ 0 ].x;
idVec3 rear( rearDist, 0.0f, 24.0f );
idVec3 worldRear = parentOrigin + rear * axis;
// check if it is in the water
int cont = gameLocal.clip.Contents( CLIP_DEBUG_PARMS worldRear, NULL, mat3_identity, CONTENTS_WATER, parent );
if ( cont ) {
// find the top of the water
// TWTODO (Post-E3): Calculate this without using a trace!
trace_t trace;
idVec3 traceStart = worldRear;
traceStart.z = parentOrigin.z + 24.0f;
gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS trace, traceStart, worldRear, CONTENTS_WATER, parent );
idVec3 waterSurfacePoint = trace.endpos;
// End TWTODO
//gameRenderWorld->DebugArrow( colorRed, worldRear, waterSurfacePoint, 2.0f );
float dropDist = trace.endpos.z - worldRear.z;
// figure out what velocity it needs to push itself out of the water enough
float timeStep = MS2SEC( gameLocal.msec );
float pushOutVel = 0.5f * dropDist / timeStep;
if ( pushOutVel > velocity.z ) {
// ok so its not scaling it by the force, but it just wants a slight nudge up
float accelToPushOut = 0.5f * ( pushOutVel - velocity.z ) / timeStep;
parent->GetPhysics()->AddForce( 0, worldRear, idVec3( 0.0f, 0.0f, accelToPushOut ) );
}
}
}
}
}
// calculate the amount of drag caused by this
float drag = coefficient * normalVel * normalVel;
if ( drag > maxForce ) {
drag = maxForce;
} else if ( drag <= minForce ) {
return;
}
// calculate the drag force
dragForce = -drag * angleScale * worldNormal;
// apply the drag force
parent->GetPhysics()->AddForce( 0, worldOrigin, dragForce );
// gameRenderWorld->DebugLine( colorGreen, worldOrigin, worldOrigin + dragForce * 0.0001f );
}
/*
================
sdVehicleRigidBodyDragPlane::UpdatePostPhysics
================
*/
void sdVehicleRigidBodyDragPlane::UpdatePostPhysics( const sdVehicleInput& input ) {
}
/*
===============================================================================
sdVehicleRigidBodyRudder
===============================================================================
*/
CLASS_DECLARATION( sdVehicleRigidBodyDragPlane, sdVehicleRigidBodyRudder )
END_CLASS
/*
================
sdVehicleRigidBodyRudder::Init
================
*/
void sdVehicleRigidBodyRudder::Init( const sdDeclVehiclePart& part, sdTransport_RB* _parent ) {
sdVehicleRigidBodyDragPlane::Init( part, _parent );
}
/*
================
sdVehicleRigidBodyRudder::UpdatePrePhysics
================
*/
void sdVehicleRigidBodyRudder::UpdatePrePhysics( const sdVehicleInput& input ) {
float oldCoefficient = coefficient;
idVec3 oldOrigin = origin;
float right = input.GetRight();
if ( right != 0.f ) {
origin.y += right * -600.0f;
coefficient *= fabs( right );
normal.x = 1.0f;
normal.y = 0.0f;
normal.z = 0.0f;
sdVehicleRigidBodyDragPlane::UpdatePrePhysics( input );
}
coefficient = oldCoefficient;
origin = oldOrigin;
}
/*
===============================================================================
sdVehicleRigidBodyHurtZone
===============================================================================
*/
idCVar g_debugVehicleHurtZones( "g_debugVehicleHurtZones", "0", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "show info about the hurtZone component" );
CLASS_DECLARATION( sdVehiclePart, sdVehicleRigidBodyHurtZone )
END_CLASS
/*
================
sdVehicleRigidBodyHurtZone::Init
================
*/
void sdVehicleRigidBodyHurtZone::Init( const sdDeclVehiclePart& part, sdTransport_RB* _parent ) {
sdVehicleRigidBodyPart::Init( part, _parent );
maxHealth = health = -1.0f;
sdPhysics_RigidBodyMultiple& rigidBody = *_parent->GetRBPhysics();
rigidBody.SetContactFriction( bodyId, vec3_origin );
rigidBody.SetMass( 1.0f, bodyId );
rigidBody.SetBodyBuoyancy( bodyId, 0.0f );
rigidBody.SetClipMask( MASK_HURTZONE, bodyId );
rigidBody.SetContents( MASK_HURTZONE, bodyId );
}
/*
===============================================================================
sdVehicleAntiRoll
===============================================================================
*/
CLASS_DECLARATION( sdVehiclePart, sdVehicleAntiRoll )
END_CLASS
/*
================
sdVehicleAntiRoll::sdVehicleAntiRoll
================
*/
sdVehicleAntiRoll::sdVehicleAntiRoll( void ) {
}
/*
================
sdVehicleAntiRoll::~sdVehicleAntiRoll
================
*/
sdVehicleAntiRoll::~sdVehicleAntiRoll( void ) {
}
/*
================
sdVehicleAntiRoll::Init
================
*/
void sdVehicleAntiRoll::Init( const sdDeclVehiclePart& part, sdTransport_RB* _parent ) {
sdVehiclePart::Init( part );
parent = _parent;
currentStrength = 0.0f;
active = false;
startAngle = part.data.GetFloat( "angle_start", "15" );
endAngle = part.data.GetFloat( "angle_end", "45" );
strength = part.data.GetFloat( "strength", "1" );
needsGround = part.data.GetBool( "needs_ground", "1" );
if ( endAngle <= startAngle + 0.1f ) {
endAngle = startAngle + 0.1f;
}
}
/*
================
sdVehicleAntiRoll::UpdatePrePhysics
================
*/
void sdVehicleAntiRoll::UpdatePrePhysics( const sdVehicleInput& input ) {
assert( parent != NULL );
active = false;
if ( parent->GetPositionManager().FindDriver() == NULL ) {
return;
}
// use the ground contacts & water level to figure out if we should apply
// cos flipping whilst in the air is kinda cool! ;)
sdPhysics_RigidBodyMultiple* parentPhysics = parent->GetRBPhysics();
if ( !needsGround || parentPhysics->HasGroundContacts() || parentPhysics->InWater() ) {
idAngles myAngles = parentPhysics->GetAxis().ToAngles();
myAngles.Normalize180();
idAngles levelAngles( myAngles.pitch, myAngles.yaw, 0.0f );
idMat3 levelAxis = levelAngles.ToMat3();
// find the angle from the vertical
idVec3 currentUp = parentPhysics->GetAxis()[ 2 ];
float angleBetween = idMath::Fabs( idMath::ACos( levelAxis[ 2 ] * currentUp ) ) * idMath::M_RAD2DEG;
if ( angleBetween < startAngle ) {
return;
}
active = true;
if ( angleBetween > endAngle ) {
angleBetween = endAngle;
}
currentStrength = strength * ( angleBetween - startAngle ) / ( endAngle - startAngle );
}
}
/*
================
sdVehicleAntiRoll::UpdatePostPhysics
================
*/
void sdVehicleAntiRoll::UpdatePostPhysics( const sdVehicleInput& input ) {
}
/*
================
sdVehicleAntiRoll::AddCustomConstraints
================
*/
int sdVehicleAntiRoll::AddCustomConstraints( constraintInfo_t* list, int max ) {
if ( !active ) {
return 0;
}
//
// LIMIT ROLLING
// unidirectional rotation constraint away from current direction we exceed allowed in
//
idPhysics* parentPhysics = parent->GetPhysics();
const idMat3& axis = parentPhysics->GetAxis();
idVec3 comWorld = parentPhysics->GetCenterOfMass() * axis + parentPhysics->GetOrigin();
idAngles angles = axis.ToAngles();
angles.Normalize180();
// figure out the velocity needed to get back to the allowed roll range
idAngles clampedAngles = angles;
clampedAngles.roll = idMath::ClampFloat( -endAngle, endAngle, clampedAngles.roll );
float angVelDamp = 0.05f;
idAngles diffAngles = clampedAngles - angles;
diffAngles.Normalize180();
// clamp the diff angles so that it doesn't try to achieve something totally insane
diffAngles.roll = idMath::ClampFloat( -5.0f, 5.0f, diffAngles.roll );
float rollVelocity = parentPhysics->GetAngularVelocity() * axis[ 0 ];
float neededVelocity = DEG2RAD( diffAngles.roll / MS2SEC( gameLocal.msec ) );
float targetRollVel = neededVelocity - rollVelocity * angVelDamp;
// figure out angular impulse needed
float rollImpulse = currentStrength * ( targetRollVel - rollVelocity ) / MS2SEC( gameLocal.msec );
rollImpulse *= ( parentPhysics->GetInertiaTensor() * axis[ 0 ] ) * axis[ 0 ];
if ( idMath::Fabs( rollImpulse ) < idMath::FLT_EPSILON ) {
return 0;
}
// set up the constraint
constraintInfo_t& wx = list[ 0 ];
wx.j.SubVec3( 0 ).Zero();
wx.j.SubVec3( 1 ) = parentPhysics->GetAxis()[ 0 ];
wx.boxIndex = -1;
wx.error = CONTACT_LCP_EPSILON;
wx.pos = comWorld;
wx.lm = 0.0f;
wx.c = -targetRollVel;
if ( rollImpulse < 0.0f ) {
wx.lo = rollImpulse;
wx.hi = 0.0f;
} else {
wx.hi = rollImpulse;
wx.lo = 0.0f;
}
return 1;
}
/*
===============================================================================
sdVehicleAntiPitch
===============================================================================
*/
CLASS_DECLARATION( sdVehiclePart, sdVehicleAntiPitch )
END_CLASS
/*
================
sdVehicleAntiPitch::sdVehicleAntiPitch
================
*/
sdVehicleAntiPitch::sdVehicleAntiPitch( void ) {
}
/*
================
sdVehicleAntiPitch::~sdVehicleAntiPitch
================
*/
sdVehicleAntiPitch::~sdVehicleAntiPitch( void ) {
}
/*
================
sdVehicleAntiPitch::Init
================
*/
void sdVehicleAntiPitch::Init( const sdDeclVehiclePart& part, sdTransport_RB* _parent ) {
sdVehiclePart::Init( part );
parent = _parent;
currentStrength = 0.0f;
active = false;
startAngle = part.data.GetFloat( "angle_start", "15" );
endAngle = part.data.GetFloat( "angle_end", "45" );
strength = part.data.GetFloat( "strength", "1" );
needsGround = part.data.GetBool( "needs_ground", "1" );
if ( endAngle <= startAngle + 0.1f ) {
endAngle = startAngle + 0.1f;
}
}
/*
================
sdVehicleAntiPitch::UpdatePrePhysics
================
*/
void sdVehicleAntiPitch::UpdatePrePhysics( const sdVehicleInput& input ) {
assert( parent != NULL );
active = false;
if ( parent->GetPositionManager().FindDriver() == NULL ) {
return;
}
// use the ground contacts & water level to figure out if we should apply
// cos flipping whilst in the air is kinda cool! ;)
sdPhysics_RigidBodyMultiple* parentPhysics = parent->GetRBPhysics();
if ( !needsGround || parentPhysics->HasGroundContacts() || parentPhysics->InWater() ) {
idAngles myAngles = parentPhysics->GetAxis().ToAngles();
myAngles.Normalize180();
idAngles levelAngles( 0.0f, myAngles.yaw, myAngles.roll );
idMat3 levelAxis = levelAngles.ToMat3();
// find the angle from the vertical
idVec3 currentUp = parentPhysics->GetAxis()[ 2 ];
float angleBetween = idMath::Fabs( idMath::ACos( levelAxis[ 2 ] * currentUp ) ) * idMath::M_RAD2DEG;
if ( angleBetween < startAngle ) {
return;
}
active = true;
if ( angleBetween > endAngle ) {
angleBetween = endAngle;
}
currentStrength = strength * ( angleBetween - startAngle ) / ( endAngle - startAngle );
}
}
/*
================
sdVehicleAntiPitch::UpdatePostPhysics
================
*/
void sdVehicleAntiPitch::UpdatePostPhysics( const sdVehicleInput& input ) {
}
/*
================
sdVehicleAntiPitch::AddCustomConstraints
================
*/
int sdVehicleAntiPitch::AddCustomConstraints( constraintInfo_t* list, int max ) {
if ( !active ) {
return 0;
}
//
// LIMIT PITCHING
// unidirectional rotation constraint away from current direction we exceed allowed in
//
idPhysics* parentPhysics = parent->GetPhysics();
const idMat3& axis = parentPhysics->GetAxis();
idVec3 comWorld = parentPhysics->GetCenterOfMass() * axis + parentPhysics->GetOrigin();
idAngles angles = axis.ToAngles();
angles.Normalize180();
// figure out the velocity needed to get back to the allowed roll range
idAngles clampedAngles = angles;
clampedAngles.pitch = idMath::ClampFloat( -endAngle, endAngle, clampedAngles.pitch );
float angVelDamp = 0.05f;
idAngles diffAngles = clampedAngles - angles;
diffAngles.Normalize180();
// clamp the diff angles so that it doesn't try to achieve something totally insane
diffAngles.pitch = idMath::ClampFloat( -5.0f, 5.0f, diffAngles.pitch );
float pitchVelocity = parentPhysics->GetAngularVelocity() * axis[ 1 ];
float neededVelocity = DEG2RAD( diffAngles.pitch / MS2SEC( gameLocal.msec ) );
float targetPitchVel = neededVelocity - pitchVelocity * angVelDamp;
// figure out angular impulse needed
float pitchImpulse = currentStrength * ( targetPitchVel - pitchVelocity ) / MS2SEC( gameLocal.msec );
pitchImpulse *= ( parentPhysics->GetInertiaTensor() * axis[ 1 ] ) * axis[ 1 ];
if ( idMath::Fabs( pitchImpulse ) < idMath::FLT_EPSILON ) {
return 0;
}
// set up the constraint
constraintInfo_t& wx = list[ 0 ];
wx.j.SubVec3( 0 ).Zero();
wx.j.SubVec3( 1 ) = parentPhysics->GetAxis()[ 1 ];
wx.boxIndex = -1;
wx.error = CONTACT_LCP_EPSILON;
wx.pos = comWorld;
wx.lm = 0.0f;
wx.c = -targetPitchVel;
if ( pitchImpulse < 0.0f ) {
wx.lo = pitchImpulse;
wx.hi = 0.0f;
} else {
wx.hi = pitchImpulse;
wx.lo = 0.0f;
}
return 1;
}