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

1486 lines
40 KiB
C++

// Copyright (C) 2007 Id Software, Inc.
//
#include "../precompiled.h"
#pragma hdrstop
#if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE )
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#include "Physics_SimpleRigidBody.h"
#include "../Entity.h"
#include "../Player.h"
#include "../ContentMask.h"
#include "../script/Script_Helper.h"
#include "../script/Script_ScriptObject.h"
CLASS_DECLARATION( idPhysics_Base, sdPhysics_SimpleRigidBody )
END_CLASS
const float STOP_SPEED = 10.0f;
/*
================
sdSimpleRigidBodyNetworkState::MakeDefault
================
*/
void sdSimpleRigidBodyNetworkState::MakeDefault( void ) {
position = vec3_origin;
orientation.x = 0.f;
orientation.y = 0.f;
orientation.z = 0.f;
linearVelocity = vec3_zero;
angularVelocity = vec3_zero;
}
/*
================
sdSimpleRigidBodyNetworkState::Write
================
*/
void sdSimpleRigidBodyNetworkState::Write( idFile* file ) const {
file->WriteVec3( position );
file->WriteCQuat( orientation );
file->WriteVec3( linearVelocity );
file->WriteVec3( angularVelocity );
}
/*
================
sdSimpleRigidBodyNetworkState::Read
================
*/
void sdSimpleRigidBodyNetworkState::Read( idFile* file ) {
file->ReadVec3( position );
file->ReadCQuat( orientation );
file->ReadVec3( linearVelocity );
file->ReadVec3( angularVelocity );
position.FixDenormals();
orientation.FixDenormals();
angularVelocity.FixDenormals();
linearVelocity.FixDenormals();
}
/*
================
sdSimpleRigidBodyBroadcastState::MakeDefault
================
*/
void sdSimpleRigidBodyBroadcastState::MakeDefault( void ) {
localPosition = vec3_origin;
localOrientation.x = 0.f;
localOrientation.y = 0.f;
localOrientation.z = 0.f;
atRest = -1;
orientedClip = false;
}
/*
================
sdSimpleRigidBodyBroadcastState::Write
================
*/
void sdSimpleRigidBodyBroadcastState::Write( idFile* file ) const {
file->WriteVec3( localPosition );
file->WriteCQuat( localOrientation );
file->WriteInt( atRest );
file->WriteBool( orientedClip );
}
/*
================
sdSimpleRigidBodyBroadcastState::Read
================
*/
void sdSimpleRigidBodyBroadcastState::Read( idFile* file ) {
file->ReadVec3( localPosition );
file->ReadCQuat( localOrientation );
file->ReadInt( atRest );
file->ReadBool( orientedClip );
}
/*
================
sdPhysics_SimpleRigidBody::Integrate
Very coarse & naive
================
*/
void sdPhysics_SimpleRigidBody::Integrate( float deltaTime, simpleRigidBodyPState_t &next ) {
next = current;
// apply gravity
next.linearVelocity += deltaTime * gravityVector;
// remove any contribution towards the last normal
float velocityTowardsLast = -lastCollideNormal * next.linearVelocity;
if ( velocityTowardsLast > 0.0f ) {
next.linearVelocity += lastCollideNormal * velocityTowardsLast;
}
next.position = next.position + deltaTime * next.linearVelocity;
// rotation
idVec3 rotAxis = current.angularVelocity;
float angle = RAD2DEG( rotAxis.Normalize() );
if ( angle > 0.00001f ) {
idRotation rotation( vec3_origin, rotAxis, -angle * deltaTime );
next.orientation = current.orientation * rotation.ToMat3();
next.orientation.OrthoNormalizeSelf();
}
}
/*
================
sdPhysics_SimpleRigidBody::CollisionResponse
================
*/
bool sdPhysics_SimpleRigidBody::CollisionResponse( const trace_t &collision, idVec3 &impulse ) {
idVec3& velocity = current.linearVelocity;
idVec3 hitVelocity = velocity;
float normalVel = velocity * collision.c.normal;
idVec3 tangent = velocity - normalVel * collision.c.normal;
float tangentVel = tangent.Normalize();
if ( tangentVel < idMath::FLT_EPSILON ) {
tangent.Set( 0.0f, 0.0f, 1.0f );
tangentVel = 0.0f;
}
// HACK - scale the bouncyness so that things can't stop on steep walls
float bounceScale = 1.0f;
if ( idMath::Fabs( collision.c.normal.z ) < 0.7071f ) {
float bounceNeeded = Min( 1.0f, -90.0f / normalVel );
if ( normalBouncyness > idMath::FLT_EPSILON ) {
bounceScale = Max( 1.0f, bounceNeeded / normalBouncyness );
}
}
// bounce the velocity, always bounce off of noplant surfaces.
if ( collision.c.material != NULL && ( collision.c.material->GetSurfaceFlags() & SURF_NOPLANT ) ) {
normalVel = -normalVel;
} else {
normalVel *= -normalBouncyness * bounceScale;
tangentVel *= tangentialBouncyness * bounceScale;
}
velocity = normalVel*collision.c.normal + tangentVel*tangent;
// check for stopping
if ( collision.c.normal.z > 0.2f && velocity.LengthSqr() < Square( stopSpeed ) ) {
velocity.Zero();
current.angularVelocity.Zero();
} else {
if ( collision.c.normal.z > 0.2f ) {
// small velocity -> zero
velocity.FixDenormals( 0.1f );
} else {
velocity.FixDenormals( 0.000001f );
}
// push out from collision surface
current.position += collision.c.normal * 0.01f;
// reflect the angular velocity about the normal too
current.angularVelocity -= ( ( 1.0f - angularBouncyness ) * current.angularVelocity*collision.c.normal )*collision.c.normal;
current.angularVelocity.FixDenormals( 0.000001f );
}
idEntity* ent = gameLocal.entities[ collision.c.entityNum ];
ent->Hit( collision, hitVelocity, self );
return self->Collide( collision, hitVelocity, -1 );
}
/*
================
sdPhysics_SimpleRigidBody::CheckForCollisions
Check for collisions between the current and next state.
If there is a collision the next state is set to the state at the moment of impact.
================
*/
bool sdPhysics_SimpleRigidBody::CheckForCollisions( const float deltaTime, simpleRigidBodyPState_t &next, trace_t &collision ) {
collision.fraction = 1.0f;
// calculate the position of the center of mass at current and next
idVec3 CoMstart = current.position + centerOfMass;
idVec3 CoMend = next.position + centerOfMass;
// if there was a collision
if ( gameLocal.clip.Translation( CLIP_DEBUG_PARMS_ENTINFO( self ) collision, CoMstart, CoMend, centeredClipModel, mat3_identity, clipMask, self ) ) {
// set the next state to the state at the moment of impact
next.position = collision.endpos - centerOfMass;
next.orientation = current.orientation;
next.linearVelocity = current.linearVelocity;
next.angularVelocity = current.angularVelocity;
return true;
}
return false;
}
/*
================
sdPhysics_SimpleRigidBody::TestIfAtRest
Returns true if the body is considered at rest.
Does not catch all cases where the body is at rest but is generally good enough.
================
*/
bool sdPhysics_SimpleRigidBody::TestIfAtRest( void ) const {
if ( current.atRest >= 0 ) {
return true;
}
// when its finished bouncing the collision impulse code manually sets the
// velocities to zero
if ( current.linearVelocity != vec3_origin || current.angularVelocity != vec3_origin ) {
return false;
}
// needs to be touching the ground
if ( contacts.Num() < 1 ) {
return false;
}
return true;
}
/*
================
sdPhysics_SimpleRigidBody::DropToFloorAndRest
Drops the object straight down to the floor and verifies if the object is at rest on the floor.
================
*/
void sdPhysics_SimpleRigidBody::DropToFloorAndRest( void ) {
idVec3 down;
trace_t tr;
if ( testSolid ) {
testSolid = false;
if ( gameLocal.clip.Contents( CLIP_DEBUG_PARMS_ENTINFO( self ) current.position, clipModel, mat3_identity, clipMask, self ) ) {
gameLocal.DWarning( "rigid body in solid for entity '%s' type '%s' at (%s)",
self->name.c_str(), self->GetType()->classname, current.position.ToString(0) );
Rest();
dropToFloor = false;
return;
}
}
// put the body on the floor
down = current.position + gravityNormal * 128.0f;
gameLocal.clip.Translation( CLIP_DEBUG_PARMS_ENTINFO( self ) tr, current.position, down, clipModel, mat3_identity, clipMask, self );
current.position = tr.endpos;
if ( !orientedClip ) {
clipModel->Link( gameLocal.clip, self, clipModel->GetId(), tr.endpos, mat3_identity );
} else {
clipModel->Link( gameLocal.clip, self, clipModel->GetId(), tr.endpos, current.orientation );
}
// if on the floor already
if ( tr.fraction == 0.0f ) {
// test if we are really at rest
EvaluateContacts( CLIP_DEBUG_PARMS_ENTINFO_ONLY( self ) );
if ( !TestIfAtRest() ) {
gameLocal.DWarning( "rigid body not at rest for entity '%s' type '%s' at (%s)",
self->name.c_str(), self->GetType()->classname, current.position.ToString(0) );
}
Rest();
dropToFloor = false;
} else if ( IsOutsideWorld() ) {
// gameLocal.Warning( "rigid body outside world bounds for entity '%s' type '%s' at (%s)",
// self->name.c_str(), self->GetType()->classname, current.position.ToString(0) );
Rest();
dropToFloor = false;
}
}
/*
================
sdPhysics_SimpleRigidBody::DebugDraw
================
*/
void sdPhysics_SimpleRigidBody::DebugDraw( void ) {
if ( rb_showBodies.GetBool() || ( rb_showActive.GetBool() && current.atRest < 0 ) ) {
clipModel->Draw();
}
if ( rb_showMass.GetBool() ) {
gameRenderWorld->DrawText( va( "\n%1.2f", mass ), current.position, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 );
}
if ( rb_showVelocity.GetBool() ) {
DrawVelocity( clipModel->GetId(), 0.1f, 4.0f );
}
}
/*
================
sdPhysics_SimpleRigidBody::sdPhysics_SimpleRigidBody
================
*/
sdPhysics_SimpleRigidBody::sdPhysics_SimpleRigidBody( void ) {
// set default rigid body properties
SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_SLIDEMOVER );
SetBouncyness( 0.6f );
SetStopSpeed( 40.0f );
SetFriction( 0.6f, 0.6f, 0.0f );
SetWaterFriction( 1.f, 1.f );
clipModel = NULL;
centeredClipModel = new idClipModel();
memset( &current, 0, sizeof( current ) );
current.atRest = -1;
current.lastTimeStep = MS2SEC( gameLocal.msec );
current.position.Zero();
current.orientation.Identity();
current.linearVelocity.Zero();
current.angularVelocity.Zero();
lastCollideNormal.Zero();
saved = current;
mass = 1.0f;
inverseMass = 1.0f;
centerOfMass.Zero();
inertiaTensor.Identity();
inverseInertiaTensor.Identity();
SetBuoyancy( 1.f );
waterLevel = 0.0f;
dropToFloor = false;
noImpact = false;
noContact = false;
hasMaster = false;
isOrientated = false;
orientedClip = false;
restFunc = NULL;
}
/*
================
sdPhysics_SimpleRigidBody::~sdPhysics_SimpleRigidBody
================
*/
sdPhysics_SimpleRigidBody::~sdPhysics_SimpleRigidBody( void ) {
gameLocal.clip.DeleteClipModel( clipModel );
gameLocal.clip.DeleteClipModel( centeredClipModel );
}
/*
================
sdPhysics_SimpleRigidBody::SetClipModel
================
*/
#define MAX_INERTIA_SCALE 10.0f
void sdPhysics_SimpleRigidBody::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) {
idMat3 inertiaScale;
assert( self );
assert( model ); // we need a clip model
assert( model->IsTraceModel() ); // and it should be a trace model
assert( density > 0.0f ); // density should be valid
if ( clipModel != NULL && clipModel != model && freeOld ) {
gameLocal.clip.DeleteClipModel( clipModel );
}
clipModel = model;
if ( !orientedClip ) {
clipModel->Link( gameLocal.clip, self, 0, current.position, mat3_identity );
} else {
clipModel->Link( gameLocal.clip, self, 0, current.position, current.orientation );
}
// get mass properties from the trace model
clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor );
// check whether or not the clip model has valid mass properties
if ( mass < idMath::FLT_EPSILON || FLOAT_IS_NAN( mass ) ) {
gameLocal.Warning( "sdPhysics_SimpleRigidBody::SetClipModel: invalid mass for entity '%s' type '%s'",
self->name.c_str(), self->GetType()->classname );
mass = 1.0f;
centerOfMass.Zero();
}
// check whether or not the inertia tensor is balanced
int minIndex = Min3Index( inertiaTensor[0][0], inertiaTensor[1][1], inertiaTensor[2][2] );
inertiaScale.Identity();
inertiaScale[0][0] = inertiaTensor[0][0] / inertiaTensor[minIndex][minIndex];
inertiaScale[1][1] = inertiaTensor[1][1] / inertiaTensor[minIndex][minIndex];
inertiaScale[2][2] = inertiaTensor[2][2] / inertiaTensor[minIndex][minIndex];
if ( inertiaScale[0][0] > MAX_INERTIA_SCALE || inertiaScale[1][1] > MAX_INERTIA_SCALE || inertiaScale[2][2] > MAX_INERTIA_SCALE ) {
gameLocal.Warning( "idPhysics_RigidBody::SetClipModel: unbalanced inertia tensor for entity '%s' type '%s'",
self->name.c_str(), self->GetType()->classname );
// correct the inertia tensor by replacing it with that of a box of the same bounds as this
idTraceModel trm( clipModel->GetBounds() );
trm.GetMassProperties( density, mass, centerOfMass, inertiaTensor );
}
inverseMass = 1.0f / mass;
inverseInertiaTensor = inertiaTensor.Inverse() * ( 1.0f / 6.0f );
current.linearVelocity.Zero();
current.angularVelocity.Zero();
// set up the centered clip model
idTraceModel tempTrace = *clipModel->GetTraceModel();
tempTrace.Translate( -centerOfMass );
centeredClipModel->LoadTraceModel( tempTrace, false );
}
/*
================
sdPhysics_SimpleRigidBody::GetClipModel
================
*/
idClipModel *sdPhysics_SimpleRigidBody::GetClipModel( int id ) const {
return clipModel;
}
/*
================
sdPhysics_SimpleRigidBody::GetNumClipModels
================
*/
int sdPhysics_SimpleRigidBody::GetNumClipModels( void ) const {
return 1;
}
/*
================
sdPhysics_SimpleRigidBody::SetMass
================
*/
void sdPhysics_SimpleRigidBody::SetMass( float mass, int id ) {
assert( mass > 0.0f );
this->mass = mass;
inverseMass = 1.0f / mass;
}
/*
================
sdPhysics_SimpleRigidBody::GetMass
================
*/
float sdPhysics_SimpleRigidBody::GetMass( int id ) const {
return mass;
}
/*
================
sdPhysics_SimpleRigidBody::SetWaterFriction
================
*/
void sdPhysics_SimpleRigidBody::SetWaterFriction( const float linear, const float angular ) {
if ( linear < 0.0f || linear > 1.0f ||
angular < 0.0f || angular > 1.0f ) {
return;
}
linearFrictionWater = linear;
angularFrictionWater = angular;
}
/*
================
sdPhysics_SimpleRigidBody::SetBuoyancy
================
*/
void sdPhysics_SimpleRigidBody::SetBuoyancy( float b ) {
buoyancy = b;
}
/*
================
sdPhysics_SimpleRigidBody::SetBouncyness
================
*/
void sdPhysics_SimpleRigidBody::SetBouncyness( const float b ) {
if ( b < 0.0f || b > 1.0f ) {
return;
}
normalBouncyness = b;
tangentialBouncyness = b;
angularBouncyness = -1.0f;
}
/*
================
sdPhysics_SimpleRigidBody::SetBouncyness
================
*/
void sdPhysics_SimpleRigidBody::SetBouncyness( float normal, float tangential, float angular ) {
normalBouncyness = normal;
tangentialBouncyness = tangential;
angularBouncyness = angular;
}
/*
================
sdPhysics_SimpleRigidBody::SetStopSpeed
================
*/
void sdPhysics_SimpleRigidBody::SetStopSpeed( float _stopSpeed ) {
stopSpeed = _stopSpeed;
}
/*
================
sdPhysics_SimpleRigidBody::Rest
================
*/
void sdPhysics_SimpleRigidBody::Rest( void ) {
int restTime = current.atRest;
current.atRest = gameLocal.time;
current.linearVelocity.Zero();
current.angularVelocity.Zero();
self->BecomeInactive( TH_PHYSICS );
if ( current.atRest != restTime ) {
sdScriptHelper h1;
self->GetScriptObject()->CallNonBlockingScriptEvent( self->GetScriptObject()->GetFunction( "OnRest" ), h1 );
}
}
/*
================
sdPhysics_SimpleRigidBody::DropToFloor
================
*/
void sdPhysics_SimpleRigidBody::DropToFloor( void ) {
dropToFloor = true;
testSolid = true;
}
/*
================
sdPhysics_SimpleRigidBody::NoContact
================
*/
void sdPhysics_SimpleRigidBody::NoContact( void ) {
noContact = true;
}
/*
================
sdPhysics_SimpleRigidBody::Activate
================
*/
void sdPhysics_SimpleRigidBody::Activate( void ) {
current.atRest = -1;
self->BecomeActive( TH_PHYSICS );
}
/*
================
sdPhysics_SimpleRigidBody::PutToRest
put to rest untill something collides with this physics object
================
*/
void sdPhysics_SimpleRigidBody::PutToRest( void ) {
Rest();
}
/*
================
sdPhysics_SimpleRigidBody::EnableImpact
================
*/
void sdPhysics_SimpleRigidBody::EnableImpact( void ) {
noImpact = false;
}
/*
================
sdPhysics_SimpleRigidBody::DisableImpact
================
*/
void sdPhysics_SimpleRigidBody::DisableImpact( void ) {
noImpact = true;
}
/*
================
sdPhysics_SimpleRigidBody::SetContents
================
*/
void sdPhysics_SimpleRigidBody::SetContents( int contents, int id ) {
if ( clipModel ) {
clipModel->SetContents( contents );
}
}
/*
================
sdPhysics_SimpleRigidBody::GetContents
================
*/
int sdPhysics_SimpleRigidBody::GetContents( int id ) const {
return clipModel->GetContents();
}
/*
================
sdPhysics_SimpleRigidBody::GetBounds
================
*/
const idBounds &sdPhysics_SimpleRigidBody::GetBounds( int id ) const {
return clipModel->GetBounds();
}
/*
================
sdPhysics_SimpleRigidBody::GetAbsBounds
================
*/
const idBounds &sdPhysics_SimpleRigidBody::GetAbsBounds( int id ) const {
return clipModel->GetAbsBounds();
}
/*
================
sdPhysics_SimpleRigidBody::Evaluate
Evaluate the impulse based rigid body physics.
When a collision occurs an impulse is applied at the moment of impact but
the remaining time after the collision is ignored.
================
*/
bool sdPhysics_SimpleRigidBody::Evaluate( int timeStepMSec, int endTimeMSec ) {
simpleRigidBodyPState_t next;
idAngles angles;
trace_t collision;
idVec3 impulse;
idVec3 oldOrigin, masterOrigin;
idMat3 oldAxis, masterAxis;
float timeStep, minTimeStep;
bool collided, cameToRest = false;
current.angularVelocity.FixDenormals();
current.linearVelocity.FixDenormals();
current.orientation.FixDenormals();
current.position.FixDenormals();
timeStep = MS2SEC( timeStepMSec );
minTimeStep = MS2SEC( 1 );
current.lastTimeStep = timeStep;
if ( hasMaster ) {
if ( timeStepMSec <= 0 ) {
return true;
}
oldOrigin = current.position;
oldAxis = current.orientation;
self->GetMasterPosition( masterOrigin, masterAxis );
current.position = masterOrigin + localOrigin * masterAxis;
if ( isOrientated ) {
current.orientation = localAxis * masterAxis;
} else {
current.orientation = localAxis;
}
LinkClip();
current.linearVelocity = ( current.position - oldOrigin ) / timeStep;
current.angularVelocity = ( current.orientation * oldAxis.Transpose() ).ToAngularVelocity() / timeStep;
return ( current.position != oldOrigin || current.orientation != oldAxis );
}
// if the body is at rest
if ( current.atRest >= 0 || timeStep <= 0.0f ) {
DebugDraw();
return false;
}
// move the rigid body velocity into the frame of a pusher
current.linearVelocity -= current.pushVelocity.SubVec3( 0 );
current.angularVelocity -= current.pushVelocity.SubVec3( 1 );
clipModel->Unlink( gameLocal.clip );
int count = 0;
int maxRepetitions = 2;
do {
CheckWater();
// calculate next position and orientation
Integrate( timeStep, next );
// check for collisions from the current to the next state
collided = CheckForCollisions( timeStep, next, collision );
float timeStepUsed = collision.fraction * timeStep;
// set the new state
current = next;
if ( collided ) {
// apply collision impulse
if ( CollisionResponse( collision, impulse ) ) {
current.atRest = gameLocal.time;
}
lastCollideNormal = collision.c.normal;
} else {
lastCollideNormal.Zero();
}
// update the position of the clip model
LinkClip();
DebugDraw();
if ( !noContact ) {
// get contacts
EvaluateContacts( CLIP_DEBUG_PARMS_ENTINFO_ONLY( self ) );
// check if the body has come to rest
if ( TestIfAtRest() ) {
// put to rest
Rest();
cameToRest = true;
}
}
if ( current.atRest < 0 ) {
ActivateContactEntities();
}
timeStep -= timeStepUsed;
count++;
} while( count < maxRepetitions && timeStep >= minTimeStep );
// move the rigid body velocity back into the world frame
current.linearVelocity += current.pushVelocity.SubVec3( 0 );
current.angularVelocity += current.pushVelocity.SubVec3( 1 );
current.pushVelocity.Zero();
if ( IsOutsideWorld() ) {
// gameLocal.Warning( "rigid body moved outside world bounds for entity '%s' type '%s' at (%s)",
// self->name.c_str(), self->GetType()->classname, current.position.ToString(0) );
Rest();
}
return true;
}
/*
================
sdPhysics_SimpleRigidBody::UpdateTime
================
*/
void sdPhysics_SimpleRigidBody::UpdateTime( int endTimeMSec ) {
}
/*
================
sdPhysics_SimpleRigidBody::GetTime
================
*/
int sdPhysics_SimpleRigidBody::GetTime( void ) const {
return gameLocal.time;
}
/*
================
sdPhysics_SimpleRigidBody::GetImpactInfo
================
*/
void sdPhysics_SimpleRigidBody::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const {
idVec3 linearVelocity, angularVelocity;
idMat3 inverseWorldInertiaTensor;
linearVelocity = current.linearVelocity;
angularVelocity = current.angularVelocity;
info->invMass = inverseMass;
info->invInertiaTensor.Zero();
info->position = point - ( current.position + centerOfMass * current.orientation );
info->velocity = linearVelocity + angularVelocity.Cross( info->position );
}
/*
================
sdPhysics_SimpleRigidBody::ApplyImpulse
================
*/
void sdPhysics_SimpleRigidBody::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) {
if ( noImpact ) {
return;
}
if ( hasMaster ) {
self->GetMaster()->GetPhysics()->ApplyImpulse( 0, point, impulse );
return;
}
current.linearVelocity += impulse * inverseMass;
const idVec3 momentumDelta = ( point - ( current.position + centerOfMass * current.orientation ) ).Cross( impulse );
const idMat3 inverseWorldInertiaTensor = current.orientation.Transpose() * inverseInertiaTensor * current.orientation;
current.angularVelocity += RAD2DEG( inverseWorldInertiaTensor * momentumDelta );
Activate();
}
/*
================
sdPhysics_SimpleRigidBody::AddForce
================
*/
void sdPhysics_SimpleRigidBody::AddForce( const int id, const idVec3 &point, const idVec3 &force ) {
if ( noImpact ) {
return;
}
if ( hasMaster ) {
self->GetMaster()->GetPhysics()->AddForce( 0, point, force );
return;
}
idVec3 impulse = force * current.lastTimeStep;
ApplyImpulse( id, point, impulse );
}
/*
================
sdPhysics_SimpleRigidBody::IsAtRest
================
*/
bool sdPhysics_SimpleRigidBody::IsAtRest( void ) const {
return current.atRest >= 0;
}
/*
================
sdPhysics_SimpleRigidBody::GetRestStartTime
================
*/
int sdPhysics_SimpleRigidBody::GetRestStartTime( void ) const {
return current.atRest;
}
/*
================
sdPhysics_SimpleRigidBody::IsPushable
================
*/
bool sdPhysics_SimpleRigidBody::IsPushable( void ) const {
return ( !noImpact && !hasMaster );
}
/*
================
sdPhysics_SimpleRigidBody::SaveState
================
*/
void sdPhysics_SimpleRigidBody::SaveState( void ) {
saved = current;
}
/*
================
sdPhysics_SimpleRigidBody::RestoreState
================
*/
void sdPhysics_SimpleRigidBody::RestoreState( void ) {
current = saved;
LinkClip();
EvaluateContacts( CLIP_DEBUG_PARMS_ENTINFO_ONLY( self ) );
}
/*
================
idPhysics::SetOrigin
================
*/
void sdPhysics_SimpleRigidBody::SetOrigin( const idVec3 &newOrigin, int id ) {
idVec3 masterOrigin;
idMat3 masterAxis;
if ( hasMaster ) {
self->GetMasterPosition( masterOrigin, masterAxis );
current.position = masterOrigin + newOrigin * masterAxis;
localOrigin = newOrigin;
} else {
current.position = newOrigin;
}
LinkClip();
Activate();
}
/*
================
idPhysics::SetAxis
================
*/
void sdPhysics_SimpleRigidBody::SetAxis( const idMat3 &newAxis, int id ) {
idVec3 masterOrigin;
idMat3 masterAxis;
if ( hasMaster ) {
if ( isOrientated ) {
self->GetMasterPosition( masterOrigin, masterAxis );
current.orientation = newAxis * masterAxis;
}
localAxis = newAxis;
} else {
current.orientation = newAxis;
}
LinkClip();
Activate();
}
/*
================
idPhysics::Move
================
*/
void sdPhysics_SimpleRigidBody::Translate( const idVec3 &translation, int id ) {
if ( hasMaster ) {
localOrigin += translation;
}
current.position += translation;
LinkClip();
Activate();
}
/*
================
idPhysics::Rotate
================
*/
void sdPhysics_SimpleRigidBody::Rotate( const idRotation &rotation, int id ) {
idVec3 masterOrigin;
idMat3 masterAxis;
current.orientation *= rotation.ToMat3();
current.position *= rotation;
if ( hasMaster ) {
self->GetMasterPosition( masterOrigin, masterAxis );
localAxis *= rotation.ToMat3();
localOrigin = ( current.position - masterOrigin ) * masterAxis.Transpose();
}
LinkClip();
Activate();
}
/*
================
sdPhysics_SimpleRigidBody::GetOrigin
================
*/
const idVec3 &sdPhysics_SimpleRigidBody::GetOrigin( int id ) const {
return current.position;
}
/*
================
sdPhysics_SimpleRigidBody::GetAxis
================
*/
const idMat3 &sdPhysics_SimpleRigidBody::GetAxis( int id ) const {
return current.orientation;
}
/*
================
sdPhysics_SimpleRigidBody::SetLinearVelocity
================
*/
void sdPhysics_SimpleRigidBody::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) {
current.linearVelocity = newLinearVelocity;
Activate();
}
/*
================
sdPhysics_SimpleRigidBody::SetAngularVelocity
================
*/
void sdPhysics_SimpleRigidBody::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) {
current.angularVelocity = newAngularVelocity;
Activate();
}
/*
================
sdPhysics_SimpleRigidBody::GetLinearVelocity
================
*/
const idVec3 &sdPhysics_SimpleRigidBody::GetLinearVelocity( int id ) const {
return current.linearVelocity;
}
/*
================
sdPhysics_SimpleRigidBody::GetAngularVelocity
================
*/
const idVec3 &sdPhysics_SimpleRigidBody::GetAngularVelocity( int id ) const {
return current.angularVelocity;
}
/*
================
sdPhysics_SimpleRigidBody::ClipTranslation
================
*/
void sdPhysics_SimpleRigidBody::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const {
if ( model ) {
gameLocal.clip.TranslationModel( CLIP_DEBUG_PARMS_ENTINFO( self ) results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation,
clipModel, mat3_identity, clipMask,
model, model->GetOrigin(), model->GetAxis() );
}
else {
gameLocal.clip.Translation( CLIP_DEBUG_PARMS_ENTINFO( self ) results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation,
clipModel, mat3_identity, clipMask, self );
}
}
/*
================
sdPhysics_SimpleRigidBody::ClipRotation
================
*/
void sdPhysics_SimpleRigidBody::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const {
// physical presence not affected by rotation
}
/*
================
sdPhysics_SimpleRigidBody::ClipContents
================
*/
int sdPhysics_SimpleRigidBody::ClipContents( const idClipModel *model ) const {
if ( model ) {
return gameLocal.clip.ContentsModel( CLIP_DEBUG_PARMS_ENTINFO( self ) clipModel->GetOrigin(), clipModel, mat3_identity, -1,
model, model->GetOrigin(), model->GetAxis() );
}
else {
return gameLocal.clip.Contents( CLIP_DEBUG_PARMS_ENTINFO( self ) clipModel->GetOrigin(), clipModel, mat3_identity, -1, NULL );
}
}
/*
================
sdPhysics_SimpleRigidBody::UnlinkClip
================
*/
void sdPhysics_SimpleRigidBody::UnlinkClip( void ) {
clipModel->Unlink( gameLocal.clip );
}
/*
================
sdPhysics_SimpleRigidBody::LinkClip
================
*/
void sdPhysics_SimpleRigidBody::LinkClip( void ) {
if ( !orientedClip ) {
clipModel->Link( gameLocal.clip, self, clipModel->GetId(), current.position, mat3_identity );
} else {
clipModel->Link( gameLocal.clip, self, clipModel->GetId(), current.position, current.orientation );
}
}
/*
================
sdPhysics_SimpleRigidBody::DisableClip
================
*/
void sdPhysics_SimpleRigidBody::DisableClip( bool activateContacting ) {
if ( activateContacting ) {
WakeEntitiesContacting( self, clipModel );
}
clipModel->Disable();
}
/*
================
sdPhysics_SimpleRigidBody::EnableClip
================
*/
void sdPhysics_SimpleRigidBody::EnableClip( void ) {
clipModel->Enable();
}
/*
================
sdPhysics_SimpleRigidBody::EvaluateContacts
================
*/
bool sdPhysics_SimpleRigidBody::EvaluateContacts( CLIP_DEBUG_PARMS_DECLARATION_ONLY ) {
idVec3 dir;
int num;
ClearContacts();
contacts.SetNum( 10, false );
dir = current.linearVelocity + current.lastTimeStep * gravityVector;
dir.Normalize();
num = gameLocal.clip.Contacts( CLIP_DEBUG_PARMS_ENTINFO( self ) &contacts[0], 10, clipModel->GetOrigin(),
&dir, CONTACT_EPSILON, clipModel, mat3_identity, clipMask, self );
contacts.SetNum( num, false );
AddContactEntitiesForContacts();
return ( contacts.Num() != 0 );
}
/*
================
sdPhysics_SimpleRigidBody::SetPushed
================
*/
void sdPhysics_SimpleRigidBody::SetPushed( int deltaTime ) {
idRotation rotation;
rotation = ( saved.orientation * current.orientation ).ToRotation();
// velocity with which the af is pushed
current.pushVelocity.SubVec3(0) += ( current.position - saved.position ) / ( deltaTime * idMath::M_MS2SEC );
current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC );
}
/*
================
sdPhysics_SimpleRigidBody::GetPushedLinearVelocity
================
*/
const idVec3 &sdPhysics_SimpleRigidBody::GetPushedLinearVelocity( const int id ) const {
return current.pushVelocity.SubVec3(0);
}
/*
================
sdPhysics_SimpleRigidBody::GetPushedAngularVelocity
================
*/
const idVec3 &sdPhysics_SimpleRigidBody::GetPushedAngularVelocity( const int id ) const {
return current.pushVelocity.SubVec3(1);
}
/*
================
sdPhysics_SimpleRigidBody::SetMaster
================
*/
void sdPhysics_SimpleRigidBody::SetMaster( idEntity *master, const bool orientated ) {
idVec3 masterOrigin;
idMat3 masterAxis;
if ( master ) {
if ( !hasMaster ) {
// transform from world space to master space
self->GetMasterPosition( masterOrigin, masterAxis );
localOrigin = ( current.position - masterOrigin ) * masterAxis.Transpose();
if ( orientated ) {
localAxis = current.orientation * masterAxis.Transpose();
} else {
localAxis = current.orientation;
}
hasMaster = true;
isOrientated = orientated;
ClearContacts();
}
}
else {
localOrigin = vec3_origin;
localAxis = mat3_identity;
if ( hasMaster ) {
hasMaster = false;
Activate();
}
}
}
/*
================
sdPhysics_SimpleRigidBody::CheckWater
================
*/
void sdPhysics_SimpleRigidBody::CheckWater( void ) {
waterLevel = WATERLEVEL_NONE;
const idBounds& absBounds = GetAbsBounds( -1 );
const idClipModel* waterModel;
idCollisionModel* model;
int count = gameLocal.clip.ClipModelsTouchingBounds( CLIP_DEBUG_PARMS_ENTINFO( self ) absBounds, CONTENTS_WATER, &waterModel, 1, NULL );
if ( !count ) {
self->CheckWaterEffectsOnly();
return;
}
if ( !waterModel->GetNumCollisionModels() ) {
self->CheckWaterEffectsOnly();
return;
}
model = waterModel->GetCollisionModel( 0 );
int numPlanes = model->GetNumBrushPlanes();
if ( !numPlanes ) {
self->CheckWaterEffectsOnly();
return;
}
const idBounds& modelBounds = model->GetBounds();
idBounds worldbb;
worldbb.FromTransformedBounds( GetBounds(), GetOrigin(), GetAxis() );
bool submerged = worldbb.GetMaxs()[2] < (modelBounds.GetMaxs()[2] + waterModel->GetOrigin().z);
if ( submerged ) {
waterLevel = WATERLEVEL_HEAD;
}
self->CheckWater( waterModel->GetOrigin(), waterModel->GetAxis(), model );
}
const float RB_ORIGIN_MAX = 32767;
const int RB_ORIGIN_TOTAL_BITS = 24;
const int RB_ORIGIN_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_ORIGIN_MAX ) ) + 1;
const int RB_ORIGIN_MANTISSA_BITS = RB_ORIGIN_TOTAL_BITS - 1 - RB_ORIGIN_EXPONENT_BITS;
const float RB_LINEAR_VELOCITY_MIN = 0.05f;
const float RB_LINEAR_VELOCITY_MAX = 8192.0f;
const int RB_LINEAR_VELOCITY_TOTAL_BITS = 20;
const int RB_LINEAR_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_LINEAR_VELOCITY_MAX, RB_LINEAR_VELOCITY_MIN ) ) + 1;
const int RB_LINEAR_VELOCITY_MANTISSA_BITS = RB_LINEAR_VELOCITY_TOTAL_BITS - 1 - RB_LINEAR_VELOCITY_EXPONENT_BITS;
const float RB_ANGULAR_VELOCITY_MIN = 0.00001f;
const float RB_ANGULAR_VELOCITY_MAX = idMath::PI * 8.0f;
const int RB_ANGULAR_VELOCITY_TOTAL_BITS = 20;
const int RB_ANGULAR_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_ANGULAR_VELOCITY_MAX, RB_ANGULAR_VELOCITY_MIN ) ) + 1;
const int RB_ANGULAR_VELOCITY_MANTISSA_BITS = RB_ANGULAR_VELOCITY_TOTAL_BITS - 1 - RB_ANGULAR_VELOCITY_EXPONENT_BITS;
/*
================
sdPhysics_SimpleRigidBody::CheckNetworkStateChanges
================
*/
bool sdPhysics_SimpleRigidBody::CheckNetworkStateChanges( networkStateMode_t mode, const sdEntityStateNetworkData& baseState ) const {
if ( mode == NSM_VISIBLE ) {
NET_GET_BASE( sdSimpleRigidBodyNetworkState );
if ( baseData.angularVelocity != GetAngularVelocity() ) {
return true;
}
if ( baseData.linearVelocity != GetLinearVelocity() ) {
return true;
}
if ( baseData.orientation != current.orientation.ToCQuat() ) {
return true;
}
if ( baseData.position != current.position ) {
return true;
}
return false;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_BASE( sdSimpleRigidBodyBroadcastState );
if ( baseData.localPosition != localOrigin ) {
return true;
}
if ( baseData.localOrientation != localAxis.ToCQuat() ) {
return true;
}
if ( baseData.atRest != current.atRest ) {
return true;
}
if ( baseData.orientedClip != orientedClip ) {
return true;
}
return false;
}
return false;
}
/*
================
sdPhysics_SimpleRigidBody::WriteNetworkState
================
*/
void sdPhysics_SimpleRigidBody::WriteNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, idBitMsg& msg ) const {
if ( mode == NSM_VISIBLE ) {
NET_GET_STATES( sdSimpleRigidBodyNetworkState );
// update state
newData.position = current.position;
newData.orientation = current.orientation.ToCQuat();
newData.linearVelocity = GetLinearVelocity();
newData.angularVelocity = GetAngularVelocity();
// write state
msg.WriteDeltaVector( baseData.position, newData.position, RB_ORIGIN_EXPONENT_BITS, RB_ORIGIN_MANTISSA_BITS );
msg.WriteDeltaCQuat( baseData.orientation, newData.orientation );
msg.WriteDeltaVector( baseData.linearVelocity, newData.linearVelocity, RB_LINEAR_VELOCITY_EXPONENT_BITS, RB_LINEAR_VELOCITY_MANTISSA_BITS );
msg.WriteDeltaVector( baseData.angularVelocity, newData.angularVelocity, RB_ANGULAR_VELOCITY_EXPONENT_BITS, RB_ANGULAR_VELOCITY_MANTISSA_BITS );
return;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_STATES( sdSimpleRigidBodyBroadcastState );
// update state
newData.localPosition = localOrigin;
newData.localOrientation = localAxis.ToCQuat();
newData.atRest = current.atRest;
newData.orientedClip = orientedClip;
// write state
msg.WriteDeltaVector( baseData.localPosition, newData.localPosition );
msg.WriteDeltaCQuat( baseData.localOrientation, newData.localOrientation );
msg.WriteDeltaLong( baseData.atRest, newData.atRest );
msg.WriteBool( newData.orientedClip );
return;
}
}
/*
================
sdPhysics_SimpleRigidBody::ApplyNetworkState
================
*/
void sdPhysics_SimpleRigidBody::ApplyNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& newState ) {
traceCollection.ForceNextUpdate();
if ( mode == NSM_VISIBLE ) {
NET_GET_NEW( sdSimpleRigidBodyNetworkState );
// update state
current.position = newData.position;
current.orientation = newData.orientation.ToMat3();
if ( !newData.linearVelocity.Compare( current.linearVelocity, idMath::FLT_EPSILON ) ) {
SetLinearVelocity( newData.linearVelocity );
}
if ( !newData.angularVelocity.Compare( current.angularVelocity, idMath::FLT_EPSILON ) ) {
SetAngularVelocity( newData.angularVelocity );
}
if ( clipModel ) {
LinkClip();
}
self->UpdateVisuals();
CheckWater();
return;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_NEW( sdSimpleRigidBodyBroadcastState );
// update state
localOrigin = newData.localPosition;
localAxis = newData.localOrientation.ToMat3();
current.atRest = newData.atRest;
if ( orientedClip != newData.orientedClip ) {
orientedClip = newData.orientedClip;
LinkClip();
}
self->UpdateVisuals();
CheckWater();
return;
}
}
/*
================
sdPhysics_SimpleRigidBody::ReadNetworkState
================
*/
void sdPhysics_SimpleRigidBody::ReadNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, const idBitMsg& msg ) const {
if ( mode == NSM_VISIBLE ) {
NET_GET_STATES( sdSimpleRigidBodyNetworkState );
// read state
newData.position = msg.ReadDeltaVector( baseData.position, RB_ORIGIN_EXPONENT_BITS, RB_ORIGIN_MANTISSA_BITS );
newData.orientation = msg.ReadDeltaCQuat( baseData.orientation );
newData.linearVelocity = msg.ReadDeltaVector( baseData.linearVelocity, RB_LINEAR_VELOCITY_EXPONENT_BITS, RB_LINEAR_VELOCITY_MANTISSA_BITS );
newData.angularVelocity = msg.ReadDeltaVector( baseData.angularVelocity, RB_ANGULAR_VELOCITY_EXPONENT_BITS, RB_ANGULAR_VELOCITY_MANTISSA_BITS );
newData.position.FixDenormals();
newData.orientation.FixDenormals();
newData.angularVelocity.FixDenormals();
newData.linearVelocity.FixDenormals();
self->OnNewOriginRead( newData.position );
self->OnNewAxesRead( newData.orientation.ToMat3() );
return;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_STATES( sdSimpleRigidBodyBroadcastState );
// read state
newData.localPosition = msg.ReadDeltaVector( baseData.localPosition );
newData.localOrientation = msg.ReadDeltaCQuat( baseData.localOrientation );
newData.atRest = msg.ReadDeltaLong( baseData.atRest );
newData.orientedClip = msg.ReadBool();
return;
}
}
/*
================
sdPhysics_SimpleRigidBody::CreateNetworkStructure
================
*/
sdEntityStateNetworkData* sdPhysics_SimpleRigidBody::CreateNetworkStructure( networkStateMode_t mode ) const {
if ( mode == NSM_VISIBLE ) {
return new sdSimpleRigidBodyNetworkState();
}
if ( mode == NSM_BROADCAST ) {
return new sdSimpleRigidBodyBroadcastState();
}
return NULL;
}