278 lines
7.7 KiB
C++
278 lines
7.7 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 "Parachute.h"
|
||
|
#include "../PredictionErrorDecay.h"
|
||
|
|
||
|
/*
|
||
|
===============================================================================
|
||
|
|
||
|
sdParachute
|
||
|
|
||
|
===============================================================================
|
||
|
*/
|
||
|
extern const idEventDef EV_SetOwner;
|
||
|
|
||
|
const idEventDef EV_SetDeployStart( "setDeployStart", '\0', DOC_TEXT( "Sets the game time at which the parachute should start applying force." ), 1, NULL, "f", "time", "Game time in seconds." );
|
||
|
const idEventDef EV_IsMovingTooSlow( "isMovingTooSlow", 'b', DOC_TEXT( "Returns whether the owner is moving too slow to be considered parachuting any more." ), 0, NULL );
|
||
|
|
||
|
CLASS_DECLARATION( sdScriptEntity, sdParachute )
|
||
|
EVENT( EV_SetOwner, sdParachute::Event_SetOwner )
|
||
|
EVENT( EV_SetDeployStart, sdParachute::Event_SetDeployStart )
|
||
|
EVENT( EV_IsMovingTooSlow, sdParachute::Event_IsMovingTooSlow )
|
||
|
END_CLASS
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
sdParachute::Spawn
|
||
|
================
|
||
|
*/
|
||
|
void sdParachute::Spawn( void ) {
|
||
|
maxSpeed = spawnArgs.GetFloat( "min_speed" );
|
||
|
deployStartTime = -1;
|
||
|
deployTime = SEC2MS( spawnArgs.GetFloat( "deploy_time", "1" ) );
|
||
|
tooSlowTime = 0;
|
||
|
|
||
|
radius = InchesToMetres( spawnArgs.GetFloat( "chute_diameter" ) / 2 );
|
||
|
height = InchesToMetres( spawnArgs.GetFloat( "chute_height" ) / 2 );
|
||
|
forceHeight = spawnArgs.GetFloat( "force_height" );
|
||
|
|
||
|
Cd_up = spawnArgs.GetFloat( "drag_coefficient_up" );
|
||
|
Cd_side = spawnArgs.GetFloat( "drag_coefficient_side" );
|
||
|
Cl_side = spawnArgs.GetFloat( "lift_coefficient_side" );
|
||
|
rho = spawnArgs.GetFloat( "air_density" );
|
||
|
|
||
|
maxSideDrag = spawnArgs.GetFloat( "drag_side_max", "600" );
|
||
|
|
||
|
scale = 1.0f;
|
||
|
|
||
|
ownerOffset.Zero();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
sdParachute::Think
|
||
|
================
|
||
|
*/
|
||
|
void sdParachute::Think( void ) {
|
||
|
if ( owner.IsValid() ) {
|
||
|
// convert velocity to metric
|
||
|
idVec3 velocity = InchesToMetres( owner->GetPhysics()->GetLinearVelocity() );
|
||
|
|
||
|
float speedSquared = velocity.LengthSqr();
|
||
|
if ( velocity.z > -maxSpeed ) {
|
||
|
if ( !gameLocal.isClient ) {
|
||
|
if ( !tooSlowTime ) {
|
||
|
tooSlowTime = gameLocal.time;
|
||
|
}
|
||
|
}
|
||
|
} else if ( speedSquared > 0.01f ) {
|
||
|
tooSlowTime = 0;
|
||
|
|
||
|
if ( deployStartTime > 0 ) {
|
||
|
float canopyScale = 1.0f;
|
||
|
float time = gameLocal.time - deployStartTime;
|
||
|
if ( time < deployTime ) {
|
||
|
canopyScale = time / deployTime;
|
||
|
}
|
||
|
|
||
|
ApplyParachute( owner, canopyScale );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sdScriptEntity::Think();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
sdParachute::ApplyParachute
|
||
|
================
|
||
|
*/
|
||
|
void sdParachute::ApplyParachute( idEntity* owner, float canopyScale ) {
|
||
|
// calculate how much to scale the parachute size by based on the owner's mass
|
||
|
if ( owner->GetPhysics()->GetMass() > 0.0f ) {
|
||
|
scale = idMath::Sqrt( owner->GetPhysics()->GetMass() / 100.0f );
|
||
|
} else {
|
||
|
scale = 1.0f;
|
||
|
}
|
||
|
|
||
|
canopyScale *= scale;
|
||
|
|
||
|
const idMat3& axis = GetPhysics()->GetAxis();
|
||
|
const idVec3& trueUp = axis[ 2 ];
|
||
|
idVec3 forceUp( 0, 0, 1.0f );
|
||
|
|
||
|
idVec3 velocity = InchesToMetres( owner->GetPhysics()->GetLinearVelocity() );
|
||
|
// take angular velocity into account
|
||
|
idVec3 angVel = owner->GetPhysics()->GetAngularVelocity();
|
||
|
angVel.z = 0.0f; // ignore yaw
|
||
|
// calculate axis
|
||
|
idVec3 angVelAxis = angVel;
|
||
|
float angVelMagnitude = -angVelAxis.Normalize();
|
||
|
// calculate direction along circle of rotation
|
||
|
idVec3 tangent = trueUp.Cross( angVelAxis );
|
||
|
|
||
|
idVec3 angVelResult = angVelMagnitude * InchesToMetres( forceHeight ) * canopyScale * tangent * 2.0f;
|
||
|
angVelResult = angVelResult * axis;
|
||
|
|
||
|
velocity += angVelResult;
|
||
|
|
||
|
float upSpeed = velocity * forceUp;
|
||
|
idVec3 sideVel = velocity - upSpeed * forceUp;
|
||
|
float sideSpeedSq = sideVel.LengthSqr();
|
||
|
|
||
|
|
||
|
idVec3 upDragDirection = forceUp;
|
||
|
idVec3 sideDragDirection = velocity; //velocity.Normalize();
|
||
|
sideDragDirection.Normalize();
|
||
|
float upComponent = -( upDragDirection * sideDragDirection );
|
||
|
|
||
|
sideDragDirection += upComponent * upDragDirection;
|
||
|
|
||
|
//
|
||
|
// Up axis
|
||
|
//
|
||
|
|
||
|
// don't react if in the opposite direction
|
||
|
if ( upComponent < 0 ) {
|
||
|
upComponent = 0;
|
||
|
}
|
||
|
float currentRadius = radius * canopyScale;
|
||
|
float canopyArea = idMath::PI * currentRadius * currentRadius * upComponent;
|
||
|
|
||
|
// now do drag on this axis
|
||
|
float upDragMagnitude = 0.5 * Cd_up * canopyArea * rho * upSpeed * upSpeed;
|
||
|
idVec3 dragForce = upDragMagnitude * upDragDirection;
|
||
|
|
||
|
//
|
||
|
// Side axis
|
||
|
//
|
||
|
|
||
|
// calculate the cross-sectional area of the side profile of the canopy
|
||
|
float sideArea = 2 * currentRadius * height * canopyScale * canopyScale;
|
||
|
|
||
|
// now do drag on this axis
|
||
|
float sideDragMagnitude = 0.5 * Cd_side * sideArea * rho * sideSpeedSq;
|
||
|
if ( sideDragMagnitude > maxSideDrag ) {
|
||
|
sideDragMagnitude = maxSideDrag;
|
||
|
} else if ( sideDragMagnitude < -maxSideDrag ) {
|
||
|
sideDragMagnitude = -maxSideDrag;
|
||
|
}
|
||
|
|
||
|
dragForce = dragForce + sideDragMagnitude * sideDragDirection;
|
||
|
|
||
|
|
||
|
//
|
||
|
// Apply the forces
|
||
|
//
|
||
|
|
||
|
// convert this to an impulse
|
||
|
idVec3 dragImpulse = dragForce * MS2SEC( gameLocal.msec );
|
||
|
|
||
|
// this impulse is in kg.m/s - convert to game units (kg.in/s)
|
||
|
dragImpulse = MetresToInches( dragImpulse );
|
||
|
|
||
|
// apply it to the object
|
||
|
owner->GetPhysics()->ApplyImpulse( 0, GetPhysics()->GetOrigin() + trueUp * forceHeight, dragImpulse );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
sdParachute::UpdateModelTransform
|
||
|
================
|
||
|
*/
|
||
|
void sdParachute::UpdateModelTransform( void ) {
|
||
|
if ( IsBound() ) {
|
||
|
// HACK: get the offset & angles just like the script does
|
||
|
idVec3 origin;
|
||
|
idMat3 axis;
|
||
|
|
||
|
idEntity* master = GetBindMaster();
|
||
|
idPlayer* playerMaster = master->Cast< idPlayer >();
|
||
|
if ( playerMaster != NULL ) {
|
||
|
origin = GetBindMaster()->GetLastPushedOrigin();
|
||
|
origin.z += 80.0f;
|
||
|
idAngles angles( 0.0f, 0.0f, 0.0f );
|
||
|
angles.yaw = GetBindMaster()->GetLastPushedAxis().ToAngles().yaw;
|
||
|
axis = angles.ToMat3();
|
||
|
} else {
|
||
|
origin = master->GetPhysics()->GetCenterOfMass();
|
||
|
origin = origin * GetBindMaster()->GetLastPushedAxis() + GetBindMaster()->GetLastPushedOrigin();
|
||
|
axis = GetBindMaster()->GetLastPushedAxis();
|
||
|
}
|
||
|
|
||
|
origin += axis * ownerOffset;
|
||
|
|
||
|
// get the offset from the bind master
|
||
|
renderEntity.origin = origin;
|
||
|
renderEntity.axis = axis;
|
||
|
} else {
|
||
|
renderEntity.origin = GetPhysics()->GetOrigin();
|
||
|
renderEntity.axis = GetPhysics()->GetAxis();
|
||
|
}
|
||
|
|
||
|
DoPredictionErrorDecay();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
sdParachute::Present
|
||
|
================
|
||
|
*/
|
||
|
void sdParachute::Present( void ) {
|
||
|
// HACK: ensure its in the right position relative to the owner before pushing to renderer
|
||
|
UpdateModelTransform();
|
||
|
sdScriptEntity::Present();
|
||
|
|
||
|
// make sure this entity is being interpolated after the entity it is bound to
|
||
|
if ( !gameLocal.unlock.unlockedDraw && IsBound() ) {
|
||
|
idEntity* master = GetBindMaster();
|
||
|
if ( master->interpolateNode.InList() ) {
|
||
|
interpolateNode.InsertAfter( master->interpolateNode );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
sdParachute::Event_SetOwner
|
||
|
================
|
||
|
*/
|
||
|
void sdParachute::Event_SetOwner( idEntity* _owner ) {
|
||
|
owner = _owner;
|
||
|
|
||
|
if( owner == NULL ) {
|
||
|
ownerOffset.Zero();
|
||
|
} else {
|
||
|
ownerOffset = owner->spawnArgs.GetVector( "parachute_offset" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
sdParachute::Event_SetDeployStart
|
||
|
================
|
||
|
*/
|
||
|
void sdParachute::Event_SetDeployStart( float time ) {
|
||
|
deployStartTime = SEC2MS( time );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
sdParachute::Event_IsMovingTooSlow
|
||
|
================
|
||
|
*/
|
||
|
void sdParachute::Event_IsMovingTooSlow( void ) {
|
||
|
sdProgram::ReturnBoolean( tooSlowTime != 0 && ( gameLocal.time - tooSlowTime > SEC2MS( 0.1f ) ) );
|
||
|
}
|