mirror of
https://github.com/dhewm/dhewm3-sdk.git
synced 2024-11-22 20:51:40 +00:00
8013 lines
197 KiB
C++
8013 lines
197 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
#include "idlib/math/Quat.h"
|
|
#include "idlib/Timer.h"
|
|
|
|
#include "gamesys/SysCvar.h"
|
|
#include "Entity.h"
|
|
#include "Player.h"
|
|
#include "WorldSpawn.h"
|
|
|
|
#include "physics/Physics_AF.h"
|
|
|
|
CLASS_DECLARATION( idPhysics_Base, idPhysics_AF )
|
|
END_CLASS
|
|
|
|
const float ERROR_REDUCTION = 0.5f;
|
|
const float ERROR_REDUCTION_MAX = 256.0f;
|
|
const float LIMIT_ERROR_REDUCTION = 0.3f;
|
|
const float LCP_EPSILON = 1e-7f;
|
|
const float LIMIT_LCP_EPSILON = 1e-4f;
|
|
const float CONTACT_LCP_EPSILON = 1e-6f;
|
|
const float CENTER_OF_MASS_EPSILON = 1e-4f;
|
|
const float NO_MOVE_TIME = 1.0f;
|
|
const float NO_MOVE_TRANSLATION_TOLERANCE = 10.0f;
|
|
const float NO_MOVE_ROTATION_TOLERANCE = 10.0f;
|
|
const float MIN_MOVE_TIME = -1.0f;
|
|
const float MAX_MOVE_TIME = -1.0f;
|
|
const float IMPULSE_THRESHOLD = 500.0f;
|
|
const float SUSPEND_LINEAR_VELOCITY = 10.0f;
|
|
const float SUSPEND_ANGULAR_VELOCITY = 15.0f;
|
|
const float SUSPEND_LINEAR_ACCELERATION = 20.0f;
|
|
const float SUSPEND_ANGULAR_ACCELERATION = 30.0f;
|
|
const idVec6 vec6_lcp_epsilon = idVec6( LCP_EPSILON, LCP_EPSILON, LCP_EPSILON,
|
|
LCP_EPSILON, LCP_EPSILON, LCP_EPSILON );
|
|
|
|
#define AF_TIMINGS
|
|
|
|
#ifdef AF_TIMINGS
|
|
static int lastTimerReset = 0;
|
|
static int numArticulatedFigures = 0;
|
|
static idTimer timer_total, timer_pc, timer_ac, timer_collision, timer_lcp;
|
|
#endif
|
|
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::idAFConstraint
|
|
================
|
|
*/
|
|
idAFConstraint::idAFConstraint( void ) {
|
|
type = CONSTRAINT_INVALID;
|
|
name = "noname";
|
|
body1 = NULL;
|
|
body2 = NULL;
|
|
physics = NULL;
|
|
|
|
lo.Zero( 6 );
|
|
lo.SubVec6(0) = -vec6_infinity;
|
|
hi.Zero( 6 );
|
|
hi.SubVec6(0) = vec6_infinity;
|
|
e.SetSize( 6 );
|
|
e.SubVec6(0) = vec6_lcp_epsilon;
|
|
|
|
boxConstraint = NULL;
|
|
boxIndex[0] = -1;
|
|
boxIndex[1] = -1;
|
|
boxIndex[2] = -1;
|
|
boxIndex[3] = -1;
|
|
boxIndex[4] = -1;
|
|
boxIndex[5] = -1;
|
|
|
|
firstIndex = 0;
|
|
|
|
memset( &fl, 0, sizeof( fl ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::~idAFConstraint
|
|
================
|
|
*/
|
|
idAFConstraint::~idAFConstraint( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::SetBody1
|
|
================
|
|
*/
|
|
void idAFConstraint::SetBody1( idAFBody *body ) {
|
|
if ( body1 != body) {
|
|
body1 = body;
|
|
if ( physics ) {
|
|
physics->SetChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::SetBody2
|
|
================
|
|
*/
|
|
void idAFConstraint::SetBody2( idAFBody *body ) {
|
|
if ( body2 != body ) {
|
|
body2 = body;
|
|
if ( physics ) {
|
|
physics->SetChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::GetMultiplier
|
|
================
|
|
*/
|
|
const idVecX &idAFConstraint::GetMultiplier( void ) {
|
|
return lm;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint::Evaluate( float invTimeStep ) {
|
|
assert( 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint::ApplyFriction( float invTimeStep ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::GetForce
|
|
================
|
|
*/
|
|
void idAFConstraint::GetForce( idAFBody *body, idVec6 &force ) {
|
|
idVecX v;
|
|
|
|
v.SetData( 6, VECX_ALLOCA( 6 ) );
|
|
if ( body == body1 ) {
|
|
J1.TransposeMultiply( v, lm );
|
|
}
|
|
else if ( body == body2 ) {
|
|
J2.TransposeMultiply( v, lm );
|
|
}
|
|
else {
|
|
v.Zero();
|
|
}
|
|
force[0] = v[0]; force[1] = v[1]; force[2] = v[2]; force[3] = v[3]; force[4] = v[4]; force[5] = v[5];
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint::Translate( const idVec3 &translation ) {
|
|
assert( 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint::Rotate( const idRotation &rotation ) {
|
|
assert( 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::GetCenter
|
|
================
|
|
*/
|
|
void idAFConstraint::GetCenter( idVec3 ¢er ) {
|
|
center.Zero();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint::DebugDraw( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::InitSize
|
|
================
|
|
*/
|
|
void idAFConstraint::InitSize( int size ) {
|
|
J1.Zero( size, 6 );
|
|
J2.Zero( size, 6 );
|
|
c1.Zero( size );
|
|
c2.Zero( size );
|
|
s.Zero( size );
|
|
lm.Zero( size );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::Save
|
|
================
|
|
*/
|
|
void idAFConstraint::Save( idSaveGame *saveFile ) const {
|
|
saveFile->WriteInt( type );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint::Restore
|
|
================
|
|
*/
|
|
void idAFConstraint::Restore( idRestoreGame *saveFile ) {
|
|
constraintType_t t;
|
|
saveFile->ReadInt( (int &)t );
|
|
assert( t == type );
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_Fixed
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::idAFConstraint_Fixed
|
|
================
|
|
*/
|
|
idAFConstraint_Fixed::idAFConstraint_Fixed( const idStr &name, idAFBody *body1, idAFBody *body2 ) {
|
|
assert( body1 );
|
|
type = CONSTRAINT_FIXED;
|
|
this->name = name;
|
|
this->body1 = body1;
|
|
this->body2 = body2;
|
|
InitSize( 6 );
|
|
fl.allowPrimary = true;
|
|
fl.noCollision = true;
|
|
|
|
InitOffset();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::InitOffset
|
|
================
|
|
*/
|
|
void idAFConstraint_Fixed::InitOffset( void ) {
|
|
if ( body2 ) {
|
|
offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose();
|
|
relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose();
|
|
}
|
|
else {
|
|
offset = body1->GetWorldOrigin();
|
|
relAxis = body1->GetWorldAxis();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::SetBody1
|
|
================
|
|
*/
|
|
void idAFConstraint_Fixed::SetBody1( idAFBody *body ) {
|
|
if ( body1 != body) {
|
|
body1 = body;
|
|
InitOffset();
|
|
if ( physics ) {
|
|
physics->SetChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::SetBody2
|
|
================
|
|
*/
|
|
void idAFConstraint_Fixed::SetBody2( idAFBody *body ) {
|
|
if ( body2 != body ) {
|
|
body2 = body;
|
|
InitOffset();
|
|
if ( physics ) {
|
|
physics->SetChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_Fixed::Evaluate( float invTimeStep ) {
|
|
idVec3 ofs, a2;
|
|
idMat3 ax;
|
|
idRotation r;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
if ( master ) {
|
|
a2 = offset * master->GetWorldAxis();
|
|
ofs = a2 + master->GetWorldOrigin();
|
|
ax = relAxis * master->GetWorldAxis();
|
|
}
|
|
else {
|
|
a2.Zero();
|
|
ofs = offset;
|
|
ax = relAxis;
|
|
}
|
|
|
|
J1.Set( mat3_identity, mat3_zero,
|
|
mat3_zero, mat3_identity );
|
|
|
|
if ( body2 ) {
|
|
J2.Set( -mat3_identity, SkewSymmetric( a2 ),
|
|
mat3_zero, -mat3_identity );
|
|
}
|
|
else {
|
|
J2.Zero( 6, 6 );
|
|
}
|
|
|
|
c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( ofs - body1->GetWorldOrigin() );
|
|
r = ( body1->GetWorldAxis().Transpose() * ax ).ToRotation();
|
|
c1.SubVec3(1) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * -(float) DEG2RAD( r.GetAngle() ) );
|
|
|
|
c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_Fixed::ApplyFriction( float invTimeStep ) {
|
|
// no friction
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_Fixed::Translate( const idVec3 &translation ) {
|
|
if ( !body2 ) {
|
|
offset += translation;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_Fixed::Rotate( const idRotation &rotation ) {
|
|
if ( !body2 ) {
|
|
offset *= rotation;
|
|
relAxis *= rotation.ToMat3();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::GetCenter
|
|
================
|
|
*/
|
|
void idAFConstraint_Fixed::GetCenter( idVec3 ¢er ) {
|
|
center = body1->GetWorldOrigin();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_Fixed::DebugDraw( void ) {
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
if ( master ) {
|
|
gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), master->GetWorldOrigin() );
|
|
}
|
|
else {
|
|
gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), vec3_origin );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::Save
|
|
================
|
|
*/
|
|
void idAFConstraint_Fixed::Save( idSaveGame *saveFile ) const {
|
|
idAFConstraint::Save( saveFile );
|
|
saveFile->WriteVec3( offset );
|
|
saveFile->WriteMat3( relAxis );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Fixed::Restore
|
|
================
|
|
*/
|
|
void idAFConstraint_Fixed::Restore( idRestoreGame *saveFile ) {
|
|
idAFConstraint::Restore( saveFile );
|
|
saveFile->ReadVec3( offset );
|
|
saveFile->ReadMat3( relAxis );
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_BallAndSocketJoint
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint
|
|
================
|
|
*/
|
|
idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) {
|
|
assert( body1 );
|
|
type = CONSTRAINT_BALLANDSOCKETJOINT;
|
|
this->name = name;
|
|
this->body1 = body1;
|
|
this->body2 = body2;
|
|
InitSize( 3 );
|
|
coneLimit = NULL;
|
|
pyramidLimit = NULL;
|
|
friction = 0.0f;
|
|
fc = NULL;
|
|
fl.allowPrimary = true;
|
|
fl.noCollision = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint
|
|
================
|
|
*/
|
|
idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint( void ) {
|
|
if ( coneLimit ) {
|
|
delete coneLimit;
|
|
}
|
|
if ( pyramidLimit ) {
|
|
delete pyramidLimit;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::SetAnchor
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::SetAnchor( const idVec3 &worldPosition ) {
|
|
|
|
// get anchor relative to center of mass of body1
|
|
anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose();
|
|
if ( body2 ) {
|
|
// get anchor relative to center of mass of body2
|
|
anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose();
|
|
}
|
|
else {
|
|
anchor2 = worldPosition;
|
|
}
|
|
|
|
if ( coneLimit ) {
|
|
coneLimit->SetAnchor( anchor2 );
|
|
}
|
|
if ( pyramidLimit ) {
|
|
pyramidLimit->SetAnchor( anchor2 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::GetAnchor
|
|
================
|
|
*/
|
|
idVec3 idAFConstraint_BallAndSocketJoint::GetAnchor( void ) const {
|
|
if ( body2 ) {
|
|
return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2;
|
|
}
|
|
return anchor2;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::SetNoLimit
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::SetNoLimit( void ) {
|
|
if ( coneLimit ) {
|
|
delete coneLimit;
|
|
coneLimit = NULL;
|
|
}
|
|
if ( pyramidLimit ) {
|
|
delete pyramidLimit;
|
|
pyramidLimit = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::SetConeLimit
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) {
|
|
if ( pyramidLimit ) {
|
|
delete pyramidLimit;
|
|
pyramidLimit = NULL;
|
|
}
|
|
if ( !coneLimit ) {
|
|
coneLimit = new idAFConstraint_ConeLimit;
|
|
coneLimit->SetPhysics( physics );
|
|
}
|
|
if ( body2 ) {
|
|
coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, body1Axis * body1->GetWorldAxis().Transpose() );
|
|
}
|
|
else {
|
|
coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, body1Axis * body1->GetWorldAxis().Transpose() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::SetPyramidLimit
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis,
|
|
const float angle1, const float angle2, const idVec3 &body1Axis ) {
|
|
if ( coneLimit ) {
|
|
delete coneLimit;
|
|
coneLimit = NULL;
|
|
}
|
|
if ( !pyramidLimit ) {
|
|
pyramidLimit = new idAFConstraint_PyramidLimit;
|
|
pyramidLimit->SetPhysics( physics );
|
|
}
|
|
if ( body2 ) {
|
|
pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(),
|
|
baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2,
|
|
body1Axis * body1->GetWorldAxis().Transpose() );
|
|
}
|
|
else {
|
|
pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2,
|
|
body1Axis * body1->GetWorldAxis().Transpose() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::SetLimitEpsilon
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::SetLimitEpsilon( const float e ) {
|
|
if ( coneLimit ) {
|
|
coneLimit->SetEpsilon( e );
|
|
}
|
|
if ( pyramidLimit ) {
|
|
pyramidLimit->SetEpsilon( e );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::GetFriction
|
|
================
|
|
*/
|
|
float idAFConstraint_BallAndSocketJoint::GetFriction( void ) const {
|
|
if ( af_forceFriction.GetFloat() > 0.0f ) {
|
|
return af_forceFriction.GetFloat();
|
|
}
|
|
return friction * physics->GetJointFrictionScale();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::Evaluate( float invTimeStep ) {
|
|
idVec3 a1, a2;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
a1 = anchor1 * body1->GetWorldAxis();
|
|
|
|
if ( master ) {
|
|
a2 = anchor2 * master->GetWorldAxis();
|
|
c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) );
|
|
}
|
|
else {
|
|
a2.Zero();
|
|
c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( anchor2 - ( a1 + body1->GetWorldOrigin() ) );
|
|
}
|
|
|
|
c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX );
|
|
|
|
J1.Set( mat3_identity, -SkewSymmetric( a1 ) );
|
|
|
|
if ( body2 ) {
|
|
J2.Set( -mat3_identity, SkewSymmetric( a2 ) );
|
|
}
|
|
else {
|
|
J2.Zero( 3, 6 );
|
|
}
|
|
|
|
if ( coneLimit ) {
|
|
coneLimit->Add( physics, invTimeStep );
|
|
}
|
|
else if ( pyramidLimit ) {
|
|
pyramidLimit->Add( physics, invTimeStep );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::ApplyFriction( float invTimeStep ) {
|
|
idVec3 angular;
|
|
float invMass, currentFriction;
|
|
|
|
currentFriction = GetFriction();
|
|
|
|
if ( currentFriction <= 0.0f ) {
|
|
return;
|
|
}
|
|
|
|
if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) {
|
|
|
|
angular = body1->GetAngularVelocity();
|
|
invMass = body1->GetInverseMass();
|
|
if ( body2 ) {
|
|
angular -= body2->GetAngularVelocity();
|
|
invMass += body2->GetInverseMass();
|
|
}
|
|
|
|
angular *= currentFriction / invMass;
|
|
|
|
body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() );
|
|
if ( body2 ) {
|
|
body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() );
|
|
}
|
|
}
|
|
else {
|
|
if ( !fc ) {
|
|
fc = new idAFConstraint_BallAndSocketJointFriction;
|
|
fc->Setup( this );
|
|
}
|
|
|
|
fc->Add( physics, invTimeStep );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::GetForce
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::GetForce( idAFBody *body, idVec6 &force ) {
|
|
idAFConstraint::GetForce( body, force );
|
|
// FIXME: add limit force
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::Translate( const idVec3 &translation ) {
|
|
if ( !body2 ) {
|
|
anchor2 += translation;
|
|
}
|
|
if ( coneLimit ) {
|
|
coneLimit->Translate( translation );
|
|
}
|
|
else if ( pyramidLimit ) {
|
|
pyramidLimit->Translate( translation );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::Rotate( const idRotation &rotation ) {
|
|
if ( !body2 ) {
|
|
anchor2 *= rotation;
|
|
}
|
|
if ( coneLimit ) {
|
|
coneLimit->Rotate( rotation );
|
|
}
|
|
else if ( pyramidLimit ) {
|
|
pyramidLimit->Rotate( rotation );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::GetCenter
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::GetCenter( idVec3 ¢er ) {
|
|
center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::DebugDraw( void ) {
|
|
idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis();
|
|
gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 5, 0, 0 ), a1 + idVec3( 5, 0, 0 ) );
|
|
gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 5, 0 ), a1 + idVec3( 0, 5, 0 ) );
|
|
gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 0, 5 ), a1 + idVec3( 0, 0, 5 ) );
|
|
|
|
if ( af_showLimits.GetBool() ) {
|
|
if ( coneLimit ) {
|
|
coneLimit->DebugDraw();
|
|
}
|
|
if ( pyramidLimit ) {
|
|
pyramidLimit->DebugDraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::Save
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::Save( idSaveGame *saveFile ) const {
|
|
idAFConstraint::Save( saveFile );
|
|
saveFile->WriteVec3( anchor1 );
|
|
saveFile->WriteVec3( anchor2 );
|
|
saveFile->WriteFloat( friction );
|
|
if ( coneLimit ) {
|
|
coneLimit->Save( saveFile );
|
|
}
|
|
if ( pyramidLimit ) {
|
|
pyramidLimit->Save( saveFile );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJoint::Restore
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJoint::Restore( idRestoreGame *saveFile ) {
|
|
idAFConstraint::Restore( saveFile );
|
|
saveFile->ReadVec3( anchor1 );
|
|
saveFile->ReadVec3( anchor2 );
|
|
saveFile->ReadFloat( friction );
|
|
if ( coneLimit ) {
|
|
coneLimit->Restore( saveFile );
|
|
}
|
|
if ( pyramidLimit ) {
|
|
pyramidLimit->Restore( saveFile );
|
|
}
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_BallAndSocketJointFriction
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction
|
|
================
|
|
*/
|
|
idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction( void ) {
|
|
type = CONSTRAINT_FRICTION;
|
|
name = "ballAndSocketJointFriction";
|
|
InitSize( 3 );
|
|
joint = NULL;
|
|
fl.allowPrimary = false;
|
|
fl.frameConstraint = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJointFriction::Setup
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJointFriction::Setup( idAFConstraint_BallAndSocketJoint *bsj ) {
|
|
this->joint = bsj;
|
|
body1 = bsj->GetBody1();
|
|
body2 = bsj->GetBody2();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJointFriction::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJointFriction::Evaluate( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJointFriction::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJointFriction::ApplyFriction( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJointFriction::Add
|
|
================
|
|
*/
|
|
bool idAFConstraint_BallAndSocketJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) {
|
|
float f;
|
|
|
|
physics = phys;
|
|
|
|
f = joint->GetFriction() * joint->GetMultiplier().Length();
|
|
if ( f == 0.0f ) {
|
|
return false;
|
|
}
|
|
|
|
lo[0] = lo[1] = lo[2] = -f;
|
|
hi[0] = hi[1] = hi[2] = f;
|
|
|
|
J1.Zero( 3, 6 );
|
|
J1[0][3] = J1[1][4] = J1[2][5] = 1.0f;
|
|
|
|
if ( body2 ) {
|
|
|
|
J2.Zero( 3, 6 );
|
|
J2[0][3] = J2[1][4] = J2[2][5] = 1.0f;
|
|
}
|
|
|
|
physics->AddFrameConstraint( this );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJointFriction::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJointFriction::Translate( const idVec3 &translation ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_BallAndSocketJointFriction::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_BallAndSocketJointFriction::Rotate( const idRotation &rotation ) {
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_UniversalJoint
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint
|
|
================
|
|
*/
|
|
idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) {
|
|
assert( body1 );
|
|
type = CONSTRAINT_UNIVERSALJOINT;
|
|
this->name = name;
|
|
this->body1 = body1;
|
|
this->body2 = body2;
|
|
InitSize( 4 );
|
|
coneLimit = NULL;
|
|
pyramidLimit = NULL;
|
|
friction = 0.0f;
|
|
fc = NULL;
|
|
fl.allowPrimary = true;
|
|
fl.noCollision = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint
|
|
================
|
|
*/
|
|
idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint( void ) {
|
|
if ( coneLimit ) {
|
|
delete coneLimit;
|
|
}
|
|
if ( pyramidLimit ) {
|
|
delete pyramidLimit;
|
|
}
|
|
if ( fc ) {
|
|
delete fc;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::SetAnchor
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::SetAnchor( const idVec3 &worldPosition ) {
|
|
|
|
// get anchor relative to center of mass of body1
|
|
anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose();
|
|
if ( body2 ) {
|
|
// get anchor relative to center of mass of body2
|
|
anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose();
|
|
}
|
|
else {
|
|
anchor2 = worldPosition;
|
|
}
|
|
|
|
if ( coneLimit ) {
|
|
coneLimit->SetAnchor( anchor2 );
|
|
}
|
|
if ( pyramidLimit ) {
|
|
pyramidLimit->SetAnchor( anchor2 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::GetAnchor
|
|
================
|
|
*/
|
|
idVec3 idAFConstraint_UniversalJoint::GetAnchor( void ) const {
|
|
if ( body2 ) {
|
|
return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2;
|
|
}
|
|
return anchor2;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::SetShafts
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::SetShafts( const idVec3 &cardanShaft1, const idVec3 &cardanShaft2 ) {
|
|
idVec3 cardanAxis;
|
|
float l id_attribute((unused));
|
|
|
|
shaft1 = cardanShaft1;
|
|
l = shaft1.Normalize();
|
|
assert( l != 0.0f );
|
|
shaft2 = cardanShaft2;
|
|
l = shaft2.Normalize();
|
|
assert( l != 0.0f );
|
|
|
|
// the cardan axis is a vector orthogonal to both cardan shafts
|
|
cardanAxis = shaft1.Cross( shaft2 );
|
|
if ( cardanAxis.Normalize() == 0.0f ) {
|
|
idVec3 vecY;
|
|
shaft1.OrthogonalBasis( cardanAxis, vecY );
|
|
cardanAxis.Normalize();
|
|
}
|
|
|
|
shaft1 *= body1->GetWorldAxis().Transpose();
|
|
axis1 = cardanAxis * body1->GetWorldAxis().Transpose();
|
|
if ( body2 ) {
|
|
shaft2 *= body2->GetWorldAxis().Transpose();
|
|
axis2 = cardanAxis * body2->GetWorldAxis().Transpose();
|
|
}
|
|
else {
|
|
axis2 = cardanAxis;
|
|
}
|
|
|
|
if ( coneLimit ) {
|
|
coneLimit->SetBody1Axis( shaft1 );
|
|
}
|
|
if ( pyramidLimit ) {
|
|
pyramidLimit->SetBody1Axis( shaft1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::SetNoLimit
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::SetNoLimit( void ) {
|
|
if ( coneLimit ) {
|
|
delete coneLimit;
|
|
coneLimit = NULL;
|
|
}
|
|
if ( pyramidLimit ) {
|
|
delete pyramidLimit;
|
|
pyramidLimit = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::SetConeLimit
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle ) {
|
|
if ( pyramidLimit ) {
|
|
delete pyramidLimit;
|
|
pyramidLimit = NULL;
|
|
}
|
|
if ( !coneLimit ) {
|
|
coneLimit = new idAFConstraint_ConeLimit;
|
|
coneLimit->SetPhysics( physics );
|
|
}
|
|
if ( body2 ) {
|
|
coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, shaft1 );
|
|
}
|
|
else {
|
|
coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, shaft1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::SetPyramidLimit
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis,
|
|
const float angle1, const float angle2 ) {
|
|
if ( coneLimit ) {
|
|
delete coneLimit;
|
|
coneLimit = NULL;
|
|
}
|
|
if ( !pyramidLimit ) {
|
|
pyramidLimit = new idAFConstraint_PyramidLimit;
|
|
pyramidLimit->SetPhysics( physics );
|
|
}
|
|
if ( body2 ) {
|
|
pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(),
|
|
baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2, shaft1 );
|
|
}
|
|
else {
|
|
pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2, shaft1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::SetLimitEpsilon
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::SetLimitEpsilon( const float e ) {
|
|
if ( coneLimit ) {
|
|
coneLimit->SetEpsilon( e );
|
|
}
|
|
if ( pyramidLimit ) {
|
|
pyramidLimit->SetEpsilon( e );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::GetFriction
|
|
================
|
|
*/
|
|
float idAFConstraint_UniversalJoint::GetFriction( void ) const {
|
|
if ( af_forceFriction.GetFloat() > 0.0f ) {
|
|
return af_forceFriction.GetFloat();
|
|
}
|
|
return friction * physics->GetJointFrictionScale();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::Evaluate
|
|
|
|
NOTE: this joint is homokinetic
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::Evaluate( float invTimeStep ) {
|
|
idVec3 a1, a2, s1, s2, d1, d2, v;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
a1 = anchor1 * body1->GetWorldAxis();
|
|
s1 = shaft1 * body1->GetWorldAxis();
|
|
d1 = s1.Cross( axis1 * body1->GetWorldAxis() );
|
|
|
|
if ( master ) {
|
|
a2 = anchor2 * master->GetWorldAxis();
|
|
s2 = shaft2 * master->GetWorldAxis();
|
|
d2 = axis2 * master->GetWorldAxis();
|
|
c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) );
|
|
}
|
|
else {
|
|
a2 = anchor2;
|
|
s2 = shaft2;
|
|
d2 = axis2;
|
|
c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) );
|
|
}
|
|
|
|
J1.Set( mat3_identity, -SkewSymmetric( a1 ),
|
|
mat3_zero, idMat3( s1[0], s1[1], s1[2],
|
|
0.0f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 0.0f ) );
|
|
J1.SetSize( 4, 6 );
|
|
|
|
if ( body2 ) {
|
|
J2.Set( -mat3_identity, SkewSymmetric( a2 ),
|
|
mat3_zero, idMat3( s2[0], s2[1], s2[2],
|
|
0.0f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 0.0f ) );
|
|
J2.SetSize( 4, 6 );
|
|
}
|
|
else {
|
|
J2.Zero( 4, 6 );
|
|
}
|
|
|
|
v = s1.Cross( s2 );
|
|
if ( v.Normalize() != 0.0f ) {
|
|
idMat3 m1, m2;
|
|
|
|
m1[0] = s1;
|
|
m1[1] = v;
|
|
m1[2] = v.Cross( m1[0] );
|
|
|
|
m2[0] = -s2;
|
|
m2[1] = v;
|
|
m2[2] = v.Cross( m2[0] );
|
|
|
|
d2 *= m2.Transpose() * m1;
|
|
}
|
|
|
|
c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( d1 * d2 );
|
|
|
|
c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX );
|
|
|
|
if ( coneLimit ) {
|
|
coneLimit->Add( physics, invTimeStep );
|
|
}
|
|
else if ( pyramidLimit ) {
|
|
pyramidLimit->Add( physics, invTimeStep );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::ApplyFriction( float invTimeStep ) {
|
|
idVec3 angular;
|
|
float invMass, currentFriction;
|
|
|
|
currentFriction = GetFriction();
|
|
|
|
if ( currentFriction <= 0.0f ) {
|
|
return;
|
|
}
|
|
|
|
if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) {
|
|
|
|
angular = body1->GetAngularVelocity();
|
|
invMass = body1->GetInverseMass();
|
|
if ( body2 ) {
|
|
angular -= body2->GetAngularVelocity();
|
|
invMass += body2->GetInverseMass();
|
|
}
|
|
|
|
angular *= currentFriction / invMass;
|
|
|
|
body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() );
|
|
if ( body2 ) {
|
|
body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() );
|
|
}
|
|
}
|
|
else {
|
|
if ( !fc ) {
|
|
fc = new idAFConstraint_UniversalJointFriction;
|
|
fc->Setup( this );
|
|
}
|
|
|
|
fc->Add( physics, invTimeStep );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::GetForce
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::GetForce( idAFBody *body, idVec6 &force ) {
|
|
idAFConstraint::GetForce( body, force );
|
|
// FIXME: add limit force
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::Translate( const idVec3 &translation ) {
|
|
if ( !body2 ) {
|
|
anchor2 += translation;
|
|
}
|
|
if ( coneLimit ) {
|
|
coneLimit->Translate( translation );
|
|
}
|
|
else if ( pyramidLimit ) {
|
|
pyramidLimit->Translate( translation );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::Rotate( const idRotation &rotation ) {
|
|
if ( !body2 ) {
|
|
anchor2 *= rotation;
|
|
shaft2 *= rotation.ToMat3();
|
|
axis2 *= rotation.ToMat3();
|
|
}
|
|
if ( coneLimit ) {
|
|
coneLimit->Rotate( rotation );
|
|
}
|
|
else if ( pyramidLimit ) {
|
|
pyramidLimit->Rotate( rotation );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::GetCenter
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::GetCenter( idVec3 ¢er ) {
|
|
center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::DebugDraw( void ) {
|
|
idVec3 a1, a2, s1, s2, d1, d2, v;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis();
|
|
s1 = shaft1 * body1->GetWorldAxis();
|
|
d1 = axis1 * body1->GetWorldAxis();
|
|
|
|
if ( master ) {
|
|
a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis();
|
|
s2 = shaft2 * master->GetWorldAxis();
|
|
d2 = axis2 * master->GetWorldAxis();
|
|
}
|
|
else {
|
|
a2 = anchor2;
|
|
s2 = shaft2;
|
|
d2 = axis2;
|
|
}
|
|
|
|
v = s1.Cross( s2 );
|
|
if ( v.Normalize() != 0.0f ) {
|
|
idMat3 m1, m2;
|
|
|
|
m1[0] = s1;
|
|
m1[1] = v;
|
|
m1[2] = v.Cross( m1[0] );
|
|
|
|
m2[0] = -s2;
|
|
m2[1] = v;
|
|
m2[2] = v.Cross( m2[0] );
|
|
|
|
d2 *= m2.Transpose() * m1;
|
|
}
|
|
|
|
gameRenderWorld->DebugArrow( colorCyan, a1, a1 + s1 * 5.0f, 1.0f );
|
|
gameRenderWorld->DebugArrow( colorBlue, a2, a2 + s2 * 5.0f, 1.0f );
|
|
gameRenderWorld->DebugLine( colorGreen, a1, a1 + d1 * 5.0f );
|
|
gameRenderWorld->DebugLine( colorGreen, a2, a2 + d2 * 5.0f );
|
|
|
|
if ( af_showLimits.GetBool() ) {
|
|
if ( coneLimit ) {
|
|
coneLimit->DebugDraw();
|
|
}
|
|
if ( pyramidLimit ) {
|
|
pyramidLimit->DebugDraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::Save
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::Save( idSaveGame *saveFile ) const {
|
|
idAFConstraint::Save( saveFile );
|
|
saveFile->WriteVec3( anchor1 );
|
|
saveFile->WriteVec3( anchor2 );
|
|
saveFile->WriteVec3( shaft1 );
|
|
saveFile->WriteVec3( shaft2 );
|
|
saveFile->WriteVec3( axis1 );
|
|
saveFile->WriteVec3( axis2 );
|
|
saveFile->WriteFloat( friction );
|
|
if ( coneLimit ) {
|
|
coneLimit->Save( saveFile );
|
|
}
|
|
if ( pyramidLimit ) {
|
|
pyramidLimit->Save( saveFile );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJoint::Restore
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJoint::Restore( idRestoreGame *saveFile ) {
|
|
idAFConstraint::Restore( saveFile );
|
|
saveFile->ReadVec3( anchor1 );
|
|
saveFile->ReadVec3( anchor2 );
|
|
saveFile->ReadVec3( shaft1 );
|
|
saveFile->ReadVec3( shaft2 );
|
|
saveFile->ReadVec3( axis1 );
|
|
saveFile->ReadVec3( axis2 );
|
|
saveFile->ReadFloat( friction );
|
|
if ( coneLimit ) {
|
|
coneLimit->Restore( saveFile );
|
|
}
|
|
if ( pyramidLimit ) {
|
|
pyramidLimit->Restore( saveFile );
|
|
}
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_UniversalJointFriction
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction
|
|
================
|
|
*/
|
|
idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction( void ) {
|
|
type = CONSTRAINT_FRICTION;
|
|
name = "universalJointFriction";
|
|
InitSize( 2 );
|
|
joint = NULL;
|
|
fl.allowPrimary = false;
|
|
fl.frameConstraint = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJointFriction::Setup
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJointFriction::Setup( idAFConstraint_UniversalJoint *uj ) {
|
|
this->joint = uj;
|
|
body1 = uj->GetBody1();
|
|
body2 = uj->GetBody2();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJointFriction::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJointFriction::Evaluate( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJointFriction::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJointFriction::ApplyFriction( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJointFriction::Add
|
|
================
|
|
*/
|
|
bool idAFConstraint_UniversalJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) {
|
|
idVec3 s1, s2, dir1, dir2;
|
|
float f;
|
|
|
|
physics = phys;
|
|
|
|
f = joint->GetFriction() * joint->GetMultiplier().Length();
|
|
if ( f == 0.0f ) {
|
|
return false;
|
|
}
|
|
|
|
lo[0] = lo[1] = -f;
|
|
hi[0] = hi[1] = f;
|
|
|
|
joint->GetShafts( s1, s2 );
|
|
|
|
s1 *= body1->GetWorldAxis();
|
|
s1.NormalVectors( dir1, dir2 );
|
|
|
|
J1.SetSize( 2, 6 );
|
|
J1.SubVec6(0).SubVec3(0).Zero();
|
|
J1.SubVec6(0).SubVec3(1) = dir1;
|
|
J1.SubVec6(1).SubVec3(0).Zero();
|
|
J1.SubVec6(1).SubVec3(1) = dir2;
|
|
|
|
if ( body2 ) {
|
|
|
|
J2.SetSize( 2, 6 );
|
|
J2.SubVec6(0).SubVec3(0).Zero();
|
|
J2.SubVec6(0).SubVec3(1) = -dir1;
|
|
J2.SubVec6(1).SubVec3(0).Zero();
|
|
J2.SubVec6(1).SubVec3(1) = -dir2;
|
|
}
|
|
|
|
physics->AddFrameConstraint( this );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJointFriction::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJointFriction::Translate( const idVec3 &translation ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_UniversalJointFriction::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_UniversalJointFriction::Rotate( const idRotation &rotation ) {
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_CylindricalJoint
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint
|
|
================
|
|
*/
|
|
idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_CylindricalJoint::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_CylindricalJoint::Evaluate( float invTimeStep ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_CylindricalJoint::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_CylindricalJoint::ApplyFriction( float invTimeStep ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_CylindricalJoint::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_CylindricalJoint::Translate( const idVec3 &translation ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_CylindricalJoint::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_CylindricalJoint::Rotate( const idRotation &rotation ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_CylindricalJoint::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_CylindricalJoint::DebugDraw( void ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_Hinge
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::idAFConstraint_Hinge
|
|
================
|
|
*/
|
|
idAFConstraint_Hinge::idAFConstraint_Hinge( const idStr &name, idAFBody *body1, idAFBody *body2 ) {
|
|
assert( body1 );
|
|
type = CONSTRAINT_HINGE;
|
|
this->name = name;
|
|
this->body1 = body1;
|
|
this->body2 = body2;
|
|
InitSize( 5 );
|
|
coneLimit = NULL;
|
|
steering = NULL;
|
|
friction = 0.0f;
|
|
fc = NULL;
|
|
fl.allowPrimary = true;
|
|
fl.noCollision = true;
|
|
initialAxis = body1->GetWorldAxis();
|
|
if ( body2 ) {
|
|
initialAxis *= body2->GetWorldAxis().Transpose();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::~idAFConstraint_Hinge
|
|
================
|
|
*/
|
|
idAFConstraint_Hinge::~idAFConstraint_Hinge( void ) {
|
|
if ( coneLimit ) {
|
|
delete coneLimit;
|
|
}
|
|
if ( fc ) {
|
|
delete fc;
|
|
}
|
|
if ( steering ) {
|
|
delete steering;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::SetAnchor
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::SetAnchor( const idVec3 &worldPosition ) {
|
|
// get anchor relative to center of mass of body1
|
|
anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose();
|
|
if ( body2 ) {
|
|
// get anchor relative to center of mass of body2
|
|
anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose();
|
|
}
|
|
else {
|
|
anchor2 = worldPosition;
|
|
}
|
|
|
|
if ( coneLimit ) {
|
|
coneLimit->SetAnchor( anchor2 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::GetAnchor
|
|
================
|
|
*/
|
|
idVec3 idAFConstraint_Hinge::GetAnchor( void ) const {
|
|
if ( body2 ) {
|
|
return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2;
|
|
}
|
|
return anchor2;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::SetAxis
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::SetAxis( const idVec3 &axis ) {
|
|
idVec3 normAxis;
|
|
|
|
normAxis = axis;
|
|
normAxis.Normalize();
|
|
|
|
// get axis relative to body1
|
|
axis1 = normAxis * body1->GetWorldAxis().Transpose();
|
|
if ( body2 ) {
|
|
// get axis relative to body2
|
|
axis2 = normAxis * body2->GetWorldAxis().Transpose();
|
|
}
|
|
else {
|
|
axis2 = normAxis;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::GetAxis
|
|
================
|
|
*/
|
|
idVec3 idAFConstraint_Hinge::GetAxis( void ) const {
|
|
if ( body2 ) {
|
|
return axis2 * body2->GetWorldAxis();
|
|
}
|
|
return axis2;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::SetNoLimit
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::SetNoLimit( void ) {
|
|
if ( coneLimit ) {
|
|
delete coneLimit;
|
|
coneLimit = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::SetLimit
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::SetLimit( const idVec3 &axis, const float angle, const idVec3 &body1Axis ) {
|
|
if ( !coneLimit ) {
|
|
coneLimit = new idAFConstraint_ConeLimit;
|
|
coneLimit->SetPhysics( physics );
|
|
}
|
|
if ( body2 ) {
|
|
coneLimit->Setup( body1, body2, anchor2, axis * body2->GetWorldAxis().Transpose(), angle, body1Axis * body1->GetWorldAxis().Transpose() );
|
|
}
|
|
else {
|
|
coneLimit->Setup( body1, body2, anchor2, axis, angle, body1Axis * body1->GetWorldAxis().Transpose() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::SetLimitEpsilon
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::SetLimitEpsilon( const float e ) {
|
|
if ( coneLimit ) {
|
|
coneLimit->SetEpsilon( e );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::GetFriction
|
|
================
|
|
*/
|
|
float idAFConstraint_Hinge::GetFriction( void ) const {
|
|
if ( af_forceFriction.GetFloat() > 0.0f ) {
|
|
return af_forceFriction.GetFloat();
|
|
}
|
|
return friction * physics->GetJointFrictionScale();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::GetAngle
|
|
================
|
|
*/
|
|
float idAFConstraint_Hinge::GetAngle( void ) const {
|
|
idMat3 axis;
|
|
idRotation rotation;
|
|
float angle;
|
|
|
|
axis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose() * initialAxis.Transpose();
|
|
rotation = axis.ToRotation();
|
|
angle = rotation.GetAngle();
|
|
if ( rotation.GetVec() * axis1 < 0.0f ) {
|
|
return -angle;
|
|
}
|
|
return angle;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::SetSteerAngle
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::SetSteerAngle( const float degrees ) {
|
|
if ( coneLimit ) {
|
|
delete coneLimit;
|
|
coneLimit = NULL;
|
|
}
|
|
if ( !steering ) {
|
|
steering = new idAFConstraint_HingeSteering();
|
|
steering->Setup( this );
|
|
}
|
|
steering->SetSteerAngle( degrees );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::SetSteerSpeed
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::SetSteerSpeed( const float speed ) {
|
|
if ( steering ) {
|
|
steering->SetSteerSpeed( speed );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::Evaluate( float invTimeStep ) {
|
|
idVec3 a1, a2;
|
|
idVec3 x1, x2, cross;
|
|
idVec3 vecX, vecY;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
x1 = axis1 * body1->GetWorldAxis(); // axis in body1 space
|
|
x1.OrthogonalBasis( vecX, vecY ); // basis for axis in body1 space
|
|
|
|
a1 = anchor1 * body1->GetWorldAxis(); // anchor in body1 space
|
|
|
|
if ( master ) {
|
|
a2 = anchor2 * master->GetWorldAxis(); // anchor in master space
|
|
x2 = axis2 * master->GetWorldAxis();
|
|
c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) );
|
|
}
|
|
else {
|
|
a2 = anchor2;
|
|
x2 = axis2;
|
|
c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) );
|
|
}
|
|
|
|
J1.Set( mat3_identity, -SkewSymmetric( a1 ),
|
|
mat3_zero, idMat3( vecX[0], vecX[1], vecX[2],
|
|
vecY[0], vecY[1], vecY[2],
|
|
0.0f, 0.0f, 0.0f ) );
|
|
J1.SetSize( 5, 6 );
|
|
|
|
if ( body2 ) {
|
|
J2.Set( -mat3_identity, SkewSymmetric( a2 ),
|
|
mat3_zero, idMat3( -vecX[0], -vecX[1], -vecX[2],
|
|
-vecY[0], -vecY[1], -vecY[2],
|
|
0.0f, 0.0f, 0.0f ) );
|
|
J2.SetSize( 5, 6 );
|
|
}
|
|
else {
|
|
J2.Zero( 5, 6 );
|
|
}
|
|
|
|
cross = x1.Cross( x2 );
|
|
|
|
c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecX );
|
|
c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecY );
|
|
|
|
c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX );
|
|
|
|
if ( steering ) {
|
|
steering->Add( physics, invTimeStep );
|
|
}
|
|
else if ( coneLimit ) {
|
|
coneLimit->Add( physics, invTimeStep );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::ApplyFriction( float invTimeStep ) {
|
|
idVec3 angular;
|
|
float invMass, currentFriction;
|
|
|
|
currentFriction = GetFriction();
|
|
|
|
if ( currentFriction <= 0.0f ) {
|
|
return;
|
|
}
|
|
|
|
if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) {
|
|
|
|
angular = body1->GetAngularVelocity();
|
|
invMass = body1->GetInverseMass();
|
|
if ( body2 ) {
|
|
angular -= body2->GetAngularVelocity();
|
|
invMass += body2->GetInverseMass();
|
|
}
|
|
|
|
angular *= currentFriction / invMass;
|
|
|
|
body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() );
|
|
if ( body2 ) {
|
|
body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() );
|
|
}
|
|
}
|
|
else {
|
|
if ( !fc ) {
|
|
fc = new idAFConstraint_HingeFriction;
|
|
fc->Setup( this );
|
|
}
|
|
|
|
fc->Add( physics, invTimeStep );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::GetForce
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::GetForce( idAFBody *body, idVec6 &force ) {
|
|
idAFConstraint::GetForce( body, force );
|
|
// FIXME: add limit force
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::Translate( const idVec3 &translation ) {
|
|
if ( !body2 ) {
|
|
anchor2 += translation;
|
|
}
|
|
if ( coneLimit ) {
|
|
coneLimit->Translate( translation );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::Rotate( const idRotation &rotation ) {
|
|
if ( !body2 ) {
|
|
anchor2 *= rotation;
|
|
axis2 *= rotation.ToMat3();
|
|
}
|
|
if ( coneLimit ) {
|
|
coneLimit->Rotate( rotation );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::GetCenter
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::GetCenter( idVec3 ¢er ) {
|
|
center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::DebugDraw( void ) {
|
|
idVec3 vecX, vecY;
|
|
idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis();
|
|
idVec3 x1 = axis1 * body1->GetWorldAxis();
|
|
x1.OrthogonalBasis( vecX, vecY );
|
|
|
|
gameRenderWorld->DebugArrow( colorBlue, a1 - 4.0f * x1, a1 + 4.0f * x1, 1 );
|
|
gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecX, a1 + 2.0f * vecX );
|
|
gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecY, a1 + 2.0f * vecY );
|
|
|
|
if ( af_showLimits.GetBool() ) {
|
|
if ( coneLimit ) {
|
|
coneLimit->DebugDraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::Save
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::Save( idSaveGame *saveFile ) const {
|
|
idAFConstraint::Save( saveFile );
|
|
saveFile->WriteVec3( anchor1 );
|
|
saveFile->WriteVec3( anchor2 );
|
|
saveFile->WriteVec3( axis1 );
|
|
saveFile->WriteVec3( axis2 );
|
|
saveFile->WriteMat3( initialAxis );
|
|
saveFile->WriteFloat( friction );
|
|
if ( coneLimit ) {
|
|
saveFile->WriteBool( true );
|
|
coneLimit->Save( saveFile );
|
|
} else {
|
|
saveFile->WriteBool( false );
|
|
}
|
|
if ( steering ) {
|
|
saveFile->WriteBool( true );
|
|
steering->Save( saveFile );
|
|
} else {
|
|
saveFile->WriteBool( false );
|
|
}
|
|
if ( fc ) {
|
|
saveFile->WriteBool( true );
|
|
fc->Save( saveFile );
|
|
} else {
|
|
saveFile->WriteBool( false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Hinge::Restore
|
|
================
|
|
*/
|
|
void idAFConstraint_Hinge::Restore( idRestoreGame *saveFile ) {
|
|
bool b;
|
|
idAFConstraint::Restore( saveFile );
|
|
saveFile->ReadVec3( anchor1 );
|
|
saveFile->ReadVec3( anchor2 );
|
|
saveFile->ReadVec3( axis1 );
|
|
saveFile->ReadVec3( axis2 );
|
|
saveFile->ReadMat3( initialAxis );
|
|
saveFile->ReadFloat( friction );
|
|
|
|
saveFile->ReadBool( b );
|
|
if ( b ) {
|
|
if ( !coneLimit ) {
|
|
coneLimit = new idAFConstraint_ConeLimit;
|
|
}
|
|
coneLimit->SetPhysics( physics );
|
|
coneLimit->Restore( saveFile );
|
|
}
|
|
saveFile->ReadBool( b );
|
|
if ( b ) {
|
|
if ( !steering ) {
|
|
steering = new idAFConstraint_HingeSteering;
|
|
}
|
|
steering->Setup( this );
|
|
steering->Restore( saveFile );
|
|
}
|
|
saveFile->ReadBool( b );
|
|
if ( b ) {
|
|
if ( !fc ) {
|
|
fc = new idAFConstraint_HingeFriction;
|
|
}
|
|
fc->Setup( this );
|
|
fc->Restore( saveFile );
|
|
}
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_HingeFriction
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeFriction::idAFConstraint_HingeFriction
|
|
================
|
|
*/
|
|
idAFConstraint_HingeFriction::idAFConstraint_HingeFriction( void ) {
|
|
type = CONSTRAINT_FRICTION;
|
|
name = "hingeFriction";
|
|
InitSize( 1 );
|
|
hinge = NULL;
|
|
fl.allowPrimary = false;
|
|
fl.frameConstraint = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeFriction::Setup
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeFriction::Setup( idAFConstraint_Hinge *h ) {
|
|
this->hinge = h;
|
|
body1 = h->GetBody1();
|
|
body2 = h->GetBody2();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeFriction::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeFriction::Evaluate( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeFriction::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeFriction::ApplyFriction( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeFriction::Add
|
|
================
|
|
*/
|
|
bool idAFConstraint_HingeFriction::Add( idPhysics_AF *phys, float invTimeStep ) {
|
|
idVec3 a1, a2;
|
|
float f;
|
|
|
|
physics = phys;
|
|
|
|
f = hinge->GetFriction() * hinge->GetMultiplier().Length();
|
|
if ( f == 0.0f ) {
|
|
return false;
|
|
}
|
|
|
|
lo[0] = -f;
|
|
hi[0] = f;
|
|
|
|
hinge->GetAxis( a1, a2 );
|
|
|
|
a1 *= body1->GetWorldAxis();
|
|
|
|
J1.SetSize( 1, 6 );
|
|
J1.SubVec6(0).SubVec3(0).Zero();
|
|
J1.SubVec6(0).SubVec3(1) = a1;
|
|
|
|
if ( body2 ) {
|
|
a2 *= body2->GetWorldAxis();
|
|
|
|
J2.SetSize( 1, 6 );
|
|
J2.SubVec6(0).SubVec3(0).Zero();
|
|
J2.SubVec6(0).SubVec3(1) = -a2;
|
|
}
|
|
|
|
physics->AddFrameConstraint( this );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeFriction::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeFriction::Translate( const idVec3 &translation ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeFriction::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeFriction::Rotate( const idRotation &rotation ) {
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_HingeSteering
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeSteering::idAFConstraint_HingeSteering
|
|
================
|
|
*/
|
|
idAFConstraint_HingeSteering::idAFConstraint_HingeSteering( void ) {
|
|
type = CONSTRAINT_HINGESTEERING;
|
|
name = "hingeFriction";
|
|
InitSize( 1 );
|
|
hinge = NULL;
|
|
fl.allowPrimary = false;
|
|
fl.frameConstraint = true;
|
|
steerSpeed = 0.0f;
|
|
epsilon = LCP_EPSILON;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeSteering::Save
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeSteering::Save( idSaveGame *saveFile ) const {
|
|
saveFile->WriteFloat(steerAngle);
|
|
saveFile->WriteFloat(steerSpeed);
|
|
saveFile->WriteFloat(epsilon);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeSteering::Restore
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeSteering::Restore( idRestoreGame *saveFile ) {
|
|
saveFile->ReadFloat(steerAngle);
|
|
saveFile->ReadFloat(steerSpeed);
|
|
saveFile->ReadFloat(epsilon);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeSteering::Setup
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeSteering::Setup( idAFConstraint_Hinge *h ) {
|
|
this->hinge = h;
|
|
body1 = h->GetBody1();
|
|
body2 = h->GetBody2();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeSteering::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeSteering::Evaluate( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeSteering::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeSteering::ApplyFriction( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeSteering::Add
|
|
================
|
|
*/
|
|
bool idAFConstraint_HingeSteering::Add( idPhysics_AF *phys, float invTimeStep ) {
|
|
float angle, speed;
|
|
idVec3 a1, a2;
|
|
|
|
physics = phys;
|
|
|
|
hinge->GetAxis( a1, a2 );
|
|
angle = hinge->GetAngle();
|
|
|
|
a1 *= body1->GetWorldAxis();
|
|
|
|
J1.SetSize( 1, 6 );
|
|
J1.SubVec6(0).SubVec3(0).Zero();
|
|
J1.SubVec6(0).SubVec3(1) = a1;
|
|
|
|
if ( body2 ) {
|
|
a2 *= body2->GetWorldAxis();
|
|
|
|
J2.SetSize( 1, 6 );
|
|
J2.SubVec6(0).SubVec3(0).Zero();
|
|
J2.SubVec6(0).SubVec3(1) = -a2;
|
|
}
|
|
|
|
speed = steerAngle - angle;
|
|
if ( steerSpeed != 0.0f ) {
|
|
if ( speed > steerSpeed ) {
|
|
speed = steerSpeed;
|
|
}
|
|
else if ( speed < -steerSpeed ) {
|
|
speed = -steerSpeed;
|
|
}
|
|
}
|
|
|
|
c1[0] = DEG2RAD( speed ) * invTimeStep;
|
|
|
|
physics->AddFrameConstraint( this );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeSteering::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeSteering::Translate( const idVec3 &translation ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_HingeSteering::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_HingeSteering::Rotate( const idRotation &rotation ) {
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_Slider
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Slider::idAFConstraint_Slider
|
|
================
|
|
*/
|
|
idAFConstraint_Slider::idAFConstraint_Slider( const idStr &name, idAFBody *body1, idAFBody *body2 ) {
|
|
assert( body1 );
|
|
type = CONSTRAINT_SLIDER;
|
|
this->name = name;
|
|
this->body1 = body1;
|
|
this->body2 = body2;
|
|
InitSize( 5 );
|
|
fl.allowPrimary = true;
|
|
fl.noCollision = true;
|
|
|
|
if ( body2 ) {
|
|
offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose();
|
|
relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose();
|
|
}
|
|
else {
|
|
offset = body1->GetWorldOrigin();
|
|
relAxis = body1->GetWorldAxis();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Slider::SetAxis
|
|
================
|
|
*/
|
|
void idAFConstraint_Slider::SetAxis( const idVec3 &ax ) {
|
|
idVec3 normAxis;
|
|
|
|
// get normalized axis relative to body1
|
|
normAxis = ax;
|
|
normAxis.Normalize();
|
|
if ( body2 ) {
|
|
axis = normAxis * body2->GetWorldAxis().Transpose();
|
|
}
|
|
else {
|
|
axis = normAxis;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Slider::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_Slider::Evaluate( float invTimeStep ) {
|
|
idVec3 vecX, vecY, ofs;
|
|
idRotation r;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
if ( master ) {
|
|
(axis * master->GetWorldAxis()).OrthogonalBasis( vecX, vecY );
|
|
ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin();
|
|
r = ( body1->GetWorldAxis().Transpose() * (relAxis * master->GetWorldAxis()) ).ToRotation();
|
|
}
|
|
else {
|
|
axis.OrthogonalBasis( vecX, vecY );
|
|
ofs = offset - body1->GetWorldOrigin();
|
|
r = ( body1->GetWorldAxis().Transpose() * relAxis ).ToRotation();
|
|
}
|
|
|
|
J1.Set( mat3_zero, mat3_identity,
|
|
idMat3( vecX, vecY, vec3_origin ), mat3_zero );
|
|
J1.SetSize( 5, 6 );
|
|
|
|
if ( body2 ) {
|
|
|
|
J2.Set( mat3_zero, -mat3_identity,
|
|
idMat3( -vecX, -vecY, vec3_origin ), mat3_zero );
|
|
J2.SetSize( 5, 6 );
|
|
}
|
|
else {
|
|
J2.Zero( 5, 6 );
|
|
}
|
|
|
|
c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * - (float) DEG2RAD( r.GetAngle() ) );
|
|
|
|
c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( vecX * ofs );
|
|
c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( vecY * ofs );
|
|
|
|
c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Slider::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_Slider::ApplyFriction( float invTimeStep ) {
|
|
// no friction
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Slider::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_Slider::Translate( const idVec3 &translation ) {
|
|
if ( !body2 ) {
|
|
offset += translation;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Slider::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_Slider::Rotate( const idRotation &rotation ) {
|
|
if ( !body2 ) {
|
|
offset *= rotation;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Slider::GetCenter
|
|
================
|
|
*/
|
|
void idAFConstraint_Slider::GetCenter( idVec3 ¢er ) {
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
if ( master ) {
|
|
center = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin();
|
|
}
|
|
else {
|
|
center = offset - body1->GetWorldOrigin();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Slider::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_Slider::DebugDraw( void ) {
|
|
idVec3 ofs;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
if ( master ) {
|
|
ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin();
|
|
}
|
|
else {
|
|
ofs = offset - body1->GetWorldOrigin();
|
|
}
|
|
gameRenderWorld->DebugLine( colorGreen, ofs, ofs + axis * body1->GetWorldAxis() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Slider::Save
|
|
================
|
|
*/
|
|
void idAFConstraint_Slider::Save( idSaveGame *saveFile ) const {
|
|
idAFConstraint::Save( saveFile );
|
|
saveFile->WriteVec3( axis );
|
|
saveFile->WriteVec3( offset );
|
|
saveFile->WriteMat3( relAxis );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Slider::Restore
|
|
================
|
|
*/
|
|
void idAFConstraint_Slider::Restore( idRestoreGame *saveFile ) {
|
|
idAFConstraint::Restore( saveFile );
|
|
saveFile->ReadVec3( axis );
|
|
saveFile->ReadVec3( offset );
|
|
saveFile->ReadMat3( relAxis );
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_Line
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Line::idAFConstraint_Line
|
|
================
|
|
*/
|
|
idAFConstraint_Line::idAFConstraint_Line( const idStr &name, idAFBody *body1, idAFBody *body2 ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Line::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_Line::Evaluate( float invTimeStep ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Line::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_Line::ApplyFriction( float invTimeStep ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Line::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_Line::Translate( const idVec3 &translation ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Line::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_Line::Rotate( const idRotation &rotation ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Line::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_Line::DebugDraw( void ) {
|
|
assert( 0 ); // FIXME: implement
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_Plane
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Plane::idAFConstraint_Plane
|
|
================
|
|
*/
|
|
idAFConstraint_Plane::idAFConstraint_Plane( const idStr &name, idAFBody *body1, idAFBody *body2 ) {
|
|
assert( body1 );
|
|
type = CONSTRAINT_PLANE;
|
|
this->name = name;
|
|
this->body1 = body1;
|
|
this->body2 = body2;
|
|
InitSize( 1 );
|
|
fl.allowPrimary = true;
|
|
fl.noCollision = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Plane::SetPlane
|
|
================
|
|
*/
|
|
void idAFConstraint_Plane::SetPlane( const idVec3 &normal, const idVec3 &anchor ) {
|
|
// get anchor relative to center of mass of body1
|
|
anchor1 = ( anchor - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose();
|
|
if ( body2 ) {
|
|
// get anchor relative to center of mass of body2
|
|
anchor2 = ( anchor - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose();
|
|
planeNormal = normal * body2->GetWorldAxis().Transpose();
|
|
}
|
|
else {
|
|
anchor2 = anchor;
|
|
planeNormal = normal;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Plane::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_Plane::Evaluate( float invTimeStep ) {
|
|
idVec3 a1, a2, normal, p;
|
|
idVec6 v;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis();
|
|
if ( master ) {
|
|
a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis();
|
|
normal = planeNormal * master->GetWorldAxis();
|
|
}
|
|
else {
|
|
a2 = anchor2;
|
|
normal = planeNormal;
|
|
}
|
|
|
|
p = a1 - body1->GetWorldOrigin();
|
|
v.SubVec3(0) = normal;
|
|
v.SubVec3(1) = p.Cross( normal );
|
|
J1.Set( 1, 6, v.ToFloatPtr() );
|
|
|
|
if ( body2 ) {
|
|
p = a1 - body2->GetWorldOrigin();
|
|
v.SubVec3(0) = -normal;
|
|
v.SubVec3(1) = p.Cross( -normal );
|
|
J2.Set( 1, 6, v.ToFloatPtr() );
|
|
}
|
|
|
|
c1[0] = -( invTimeStep * ERROR_REDUCTION ) * (a1 * normal - a2 * normal);
|
|
|
|
c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Plane::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_Plane::ApplyFriction( float invTimeStep ) {
|
|
// no friction
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Plane::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_Plane::Translate( const idVec3 &translation ) {
|
|
if ( !body2 ) {
|
|
anchor2 += translation;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Plane::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_Plane::Rotate( const idRotation &rotation ) {
|
|
if ( !body2 ) {
|
|
anchor2 *= rotation;
|
|
planeNormal *= rotation.ToMat3();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Plane::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_Plane::DebugDraw( void ) {
|
|
idVec3 a1, normal, right, up;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis();
|
|
if ( master ) {
|
|
normal = planeNormal * master->GetWorldAxis();
|
|
}
|
|
else {
|
|
normal = planeNormal;
|
|
}
|
|
normal.NormalVectors( right, up );
|
|
normal *= 4.0f;
|
|
right *= 4.0f;
|
|
up *= 4.0f;
|
|
|
|
gameRenderWorld->DebugLine( colorCyan, a1 - right, a1 + right );
|
|
gameRenderWorld->DebugLine( colorCyan, a1 - up, a1 + up );
|
|
gameRenderWorld->DebugArrow( colorCyan, a1, a1 + normal, 1 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Plane::Save
|
|
================
|
|
*/
|
|
void idAFConstraint_Plane::Save( idSaveGame *saveFile ) const {
|
|
idAFConstraint::Save( saveFile );
|
|
saveFile->WriteVec3( anchor1 );
|
|
saveFile->WriteVec3( anchor2 );
|
|
saveFile->WriteVec3( planeNormal );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Plane::Restore
|
|
================
|
|
*/
|
|
void idAFConstraint_Plane::Restore( idRestoreGame *saveFile ) {
|
|
idAFConstraint::Restore( saveFile );
|
|
saveFile->ReadVec3( anchor1 );
|
|
saveFile->ReadVec3( anchor2 );
|
|
saveFile->ReadVec3( planeNormal );
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_Spring
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::idAFConstraint_Spring
|
|
================
|
|
*/
|
|
idAFConstraint_Spring::idAFConstraint_Spring( const idStr &name, idAFBody *body1, idAFBody *body2 ) {
|
|
assert( body1 );
|
|
type = CONSTRAINT_SPRING;
|
|
this->name = name;
|
|
this->body1 = body1;
|
|
this->body2 = body2;
|
|
InitSize( 1 );
|
|
fl.allowPrimary = false;
|
|
kstretch = kcompress = damping = 1.0f;
|
|
minLength = maxLength = restLength = 0.0f;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::SetAnchor
|
|
================
|
|
*/
|
|
void idAFConstraint_Spring::SetAnchor( const idVec3 &worldAnchor1, const idVec3 &worldAnchor2 ) {
|
|
// get anchor relative to center of mass of body1
|
|
anchor1 = ( worldAnchor1 - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose();
|
|
if ( body2 ) {
|
|
// get anchor relative to center of mass of body2
|
|
anchor2 = ( worldAnchor2 - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose();
|
|
}
|
|
else {
|
|
anchor2 = worldAnchor2;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::SetSpring
|
|
================
|
|
*/
|
|
void idAFConstraint_Spring::SetSpring( const float stretch, const float compress, const float damping, const float restLength ) {
|
|
assert( stretch >= 0.0f && compress >= 0.0f && restLength >= 0.0f );
|
|
this->kstretch = stretch;
|
|
this->kcompress = compress;
|
|
this->damping = damping;
|
|
this->restLength = restLength;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::SetLimit
|
|
================
|
|
*/
|
|
void idAFConstraint_Spring::SetLimit( const float minLength, const float maxLength ) {
|
|
assert( minLength >= 0.0f && maxLength >= 0.0f && maxLength >= minLength );
|
|
this->minLength = minLength;
|
|
this->maxLength = maxLength;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_Spring::Evaluate( float invTimeStep ) {
|
|
idVec3 a1, a2, velocity1, velocity2, force;
|
|
idVec6 v1, v2;
|
|
float d, dampingForce, length, error;
|
|
bool limit;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis();
|
|
velocity1 = body1->GetPointVelocity( a1 );
|
|
|
|
if ( master ) {
|
|
a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis();
|
|
velocity2 = master->GetPointVelocity( a2 );
|
|
}
|
|
else {
|
|
a2 = anchor2;
|
|
velocity2.Zero();
|
|
}
|
|
|
|
force = a2 - a1;
|
|
d = force * force;
|
|
if ( d != 0.0f ) {
|
|
dampingForce = damping * idMath::Fabs( (velocity2 - velocity1) * force ) / d;
|
|
}
|
|
else {
|
|
dampingForce = 0.0f;
|
|
}
|
|
length = force.Normalize();
|
|
|
|
if ( length > restLength ) {
|
|
if ( kstretch > 0.0f ) {
|
|
idVec3 springForce = force * ( Square( length - restLength ) * kstretch - dampingForce );
|
|
body1->AddForce( a1, springForce );
|
|
if ( master ) {
|
|
master->AddForce( a2, -springForce );
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if ( kcompress > 0.0f ) {
|
|
idVec3 springForce = force * -( Square( restLength - length ) * kcompress - dampingForce );
|
|
body1->AddForce( a1, springForce );
|
|
if ( master ) {
|
|
master->AddForce( a2, -springForce );
|
|
}
|
|
}
|
|
}
|
|
|
|
// check for spring limits
|
|
if ( length < minLength ) {
|
|
force = -force;
|
|
error = minLength - length;
|
|
limit = true;
|
|
}
|
|
else if ( maxLength > 0.0f && length > maxLength ) {
|
|
error = length - maxLength;
|
|
limit = true;
|
|
}
|
|
else {
|
|
error = 0.0f;
|
|
limit = false;
|
|
}
|
|
|
|
if ( limit ) {
|
|
a1 -= body1->GetWorldOrigin();
|
|
v1.SubVec3(0) = force;
|
|
v1.SubVec3(1) = a1.Cross( force );
|
|
J1.Set( 1, 6, v1.ToFloatPtr() );
|
|
if ( body2 ) {
|
|
a2 -= body2->GetWorldOrigin();
|
|
v2.SubVec3(0) = -force;
|
|
v2.SubVec3(1) = a2.Cross( -force );
|
|
J2.Set( 1, 6, v2.ToFloatPtr() );
|
|
}
|
|
c1[0] = -( invTimeStep * ERROR_REDUCTION ) * error;
|
|
lo[0] = 0.0f;
|
|
}
|
|
else {
|
|
J1.Zero( 0, 0 );
|
|
J2.Zero( 0, 0 );
|
|
}
|
|
|
|
c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_Spring::ApplyFriction( float invTimeStep ) {
|
|
// no friction
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_Spring::Translate( const idVec3 &translation ) {
|
|
if ( !body2 ) {
|
|
anchor2 += translation;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_Spring::Rotate( const idRotation &rotation ) {
|
|
if ( !body2 ) {
|
|
anchor2 *= rotation;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::GetCenter
|
|
================
|
|
*/
|
|
void idAFConstraint_Spring::GetCenter( idVec3 ¢er ) {
|
|
idAFBody *master;
|
|
idVec3 a1, a2;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis();
|
|
if ( master ) {
|
|
a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis();
|
|
}
|
|
else {
|
|
a2 = anchor2;
|
|
}
|
|
center = ( a1 + a2 ) * 0.5f;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_Spring::DebugDraw( void ) {
|
|
idAFBody *master;
|
|
float length;
|
|
idVec3 a1, a2, dir, mid, p;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis();
|
|
if ( master ) {
|
|
a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis();
|
|
}
|
|
else {
|
|
a2 = anchor2;
|
|
}
|
|
dir = a2 - a1;
|
|
mid = a1 + 0.5f * dir;
|
|
length = dir.Normalize();
|
|
|
|
// draw spring
|
|
gameRenderWorld->DebugLine( colorGreen, a1, a2 );
|
|
|
|
// draw rest length
|
|
p = restLength * 0.5f * dir;
|
|
gameRenderWorld->DebugCircle( colorWhite, mid + p, dir, 1.0f, 10 );
|
|
gameRenderWorld->DebugCircle( colorWhite, mid - p, dir, 1.0f, 10 );
|
|
if ( restLength > length ) {
|
|
gameRenderWorld->DebugLine( colorWhite, a2, mid + p );
|
|
gameRenderWorld->DebugLine( colorWhite, a1, mid - p );
|
|
}
|
|
|
|
if ( minLength > 0.0f ) {
|
|
// draw min length
|
|
gameRenderWorld->DebugCircle( colorBlue, mid + minLength * 0.5f * dir, dir, 2.0f, 10 );
|
|
gameRenderWorld->DebugCircle( colorBlue, mid - minLength * 0.5f * dir, dir, 2.0f, 10 );
|
|
}
|
|
|
|
if ( maxLength > 0.0f ) {
|
|
// draw max length
|
|
gameRenderWorld->DebugCircle( colorRed, mid + maxLength * 0.5f * dir, dir, 2.0f, 10 );
|
|
gameRenderWorld->DebugCircle( colorRed, mid - maxLength * 0.5f * dir, dir, 2.0f, 10 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::Save
|
|
================
|
|
*/
|
|
void idAFConstraint_Spring::Save( idSaveGame *saveFile ) const {
|
|
idAFConstraint::Save( saveFile );
|
|
saveFile->WriteVec3( anchor1 );
|
|
saveFile->WriteVec3( anchor2 );
|
|
saveFile->WriteFloat( kstretch );
|
|
saveFile->WriteFloat( kcompress );
|
|
saveFile->WriteFloat( damping );
|
|
saveFile->WriteFloat( restLength );
|
|
saveFile->WriteFloat( minLength );
|
|
saveFile->WriteFloat( maxLength );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Spring::Restore
|
|
================
|
|
*/
|
|
void idAFConstraint_Spring::Restore( idRestoreGame *saveFile ) {
|
|
idAFConstraint::Restore( saveFile );
|
|
saveFile->ReadVec3( anchor1 );
|
|
saveFile->ReadVec3( anchor2 );
|
|
saveFile->ReadFloat( kstretch );
|
|
saveFile->ReadFloat( kcompress );
|
|
saveFile->ReadFloat( damping );
|
|
saveFile->ReadFloat( restLength );
|
|
saveFile->ReadFloat( minLength );
|
|
saveFile->ReadFloat( maxLength );
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_Contact
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Contact::idAFConstraint_Contact
|
|
================
|
|
*/
|
|
idAFConstraint_Contact::idAFConstraint_Contact( void ) {
|
|
name = "contact";
|
|
type = CONSTRAINT_CONTACT;
|
|
InitSize( 1 );
|
|
fc = NULL;
|
|
fl.allowPrimary = false;
|
|
fl.frameConstraint = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Contact::~idAFConstraint_Contact
|
|
================
|
|
*/
|
|
idAFConstraint_Contact::~idAFConstraint_Contact( void ) {
|
|
if ( fc ) {
|
|
delete fc;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Contact::Setup
|
|
================
|
|
*/
|
|
void idAFConstraint_Contact::Setup( idAFBody *b1, idAFBody *b2, contactInfo_t &c ) {
|
|
idVec3 p;
|
|
idVec6 v;
|
|
float vel;
|
|
float minBounceVelocity = 2.0f;
|
|
|
|
assert( b1 );
|
|
|
|
body1 = b1;
|
|
body2 = b2;
|
|
contact = c;
|
|
|
|
p = c.point - body1->GetWorldOrigin();
|
|
v.SubVec3(0) = c.normal;
|
|
v.SubVec3(1) = p.Cross( c.normal );
|
|
J1.Set( 1, 6, v.ToFloatPtr() );
|
|
vel = v.SubVec3(0) * body1->GetLinearVelocity() + v.SubVec3(1) * body1->GetAngularVelocity();
|
|
|
|
if ( body2 ) {
|
|
p = c.point - body2->GetWorldOrigin();
|
|
v.SubVec3(0) = -c.normal;
|
|
v.SubVec3(1) = p.Cross( -c.normal );
|
|
J2.Set( 1, 6, v.ToFloatPtr() );
|
|
vel += v.SubVec3(0) * body2->GetLinearVelocity() + v.SubVec3(1) * body2->GetAngularVelocity();
|
|
c2[0] = 0.0f;
|
|
}
|
|
|
|
if ( body1->GetBouncyness() > 0.0f && -vel > minBounceVelocity ) {
|
|
c1[0] = body1->GetBouncyness() * vel;
|
|
} else {
|
|
c1[0] = 0.0f;
|
|
}
|
|
|
|
e[0] = CONTACT_LCP_EPSILON;
|
|
lo[0] = 0.0f;
|
|
hi[0] = idMath::INFINITY;
|
|
boxConstraint = NULL;
|
|
boxIndex[0] = -1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Contact::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_Contact::Evaluate( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Contact::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_Contact::ApplyFriction( float invTimeStep ) {
|
|
idVec3 r, velocity, normal, dir1, dir2;
|
|
float friction, magnitude, forceNumerator, forceDenominator;
|
|
idVecX impulse, dv;
|
|
|
|
friction = body1->GetContactFriction();
|
|
if ( body2 && body2->GetContactFriction() < friction ) {
|
|
friction = body2->GetContactFriction();
|
|
}
|
|
|
|
friction *= physics->GetContactFrictionScale();
|
|
|
|
if ( friction <= 0.0f ) {
|
|
return;
|
|
}
|
|
|
|
// separate friction per contact is silly but it's fast and often looks close enough
|
|
if ( af_useImpulseFriction.GetBool() ) {
|
|
|
|
impulse.SetData( 6, VECX_ALLOCA( 6 ) );
|
|
dv.SetData( 6, VECX_ALLOCA( 6 ) );
|
|
|
|
// calculate velocity in the contact plane
|
|
r = contact.point - body1->GetWorldOrigin();
|
|
velocity = body1->GetLinearVelocity() + body1->GetAngularVelocity().Cross( r );
|
|
velocity -= contact.normal * velocity * contact.normal;
|
|
|
|
// get normalized direction of friction and magnitude of velocity
|
|
normal = -velocity;
|
|
magnitude = normal.Normalize();
|
|
|
|
forceNumerator = friction * magnitude;
|
|
forceDenominator = body1->GetInverseMass() + ( ( body1->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal );
|
|
impulse.SubVec3(0) = (forceNumerator / forceDenominator) * normal;
|
|
impulse.SubVec3(1) = r.Cross( impulse.SubVec3(0) );
|
|
body1->InverseWorldSpatialInertiaMultiply( dv, impulse.ToFloatPtr() );
|
|
|
|
// modify velocity with friction force
|
|
body1->SetLinearVelocity( body1->GetLinearVelocity() + dv.SubVec3(0) );
|
|
body1->SetAngularVelocity( body1->GetAngularVelocity() + dv.SubVec3(1) );
|
|
}
|
|
else {
|
|
|
|
if ( !fc ) {
|
|
fc = new idAFConstraint_ContactFriction;
|
|
}
|
|
// call setup each frame because contact constraints are re-used for different bodies
|
|
fc->Setup( this );
|
|
fc->Add( physics, invTimeStep );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Contact::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_Contact::Translate( const idVec3 &translation ) {
|
|
assert( 0 ); // contact should never be translated
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Contact::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_Contact::Rotate( const idRotation &rotation ) {
|
|
assert( 0 ); // contact should never be rotated
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Contact::GetCenter
|
|
================
|
|
*/
|
|
void idAFConstraint_Contact::GetCenter( idVec3 ¢er ) {
|
|
center = contact.point;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Contact::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_Contact::DebugDraw( void ) {
|
|
idVec3 x, y;
|
|
contact.normal.NormalVectors( x, y );
|
|
gameRenderWorld->DebugLine( colorWhite, contact.point, contact.point + 6.0f * contact.normal );
|
|
gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * x, contact.point + 2.0f * x );
|
|
gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * y, contact.point + 2.0f * y );
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_ContactFriction
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ContactFriction::idAFConstraint_ContactFriction
|
|
================
|
|
*/
|
|
idAFConstraint_ContactFriction::idAFConstraint_ContactFriction( void ) {
|
|
type = CONSTRAINT_FRICTION;
|
|
name = "contactFriction";
|
|
InitSize( 2 );
|
|
cc = NULL;
|
|
fl.allowPrimary = false;
|
|
fl.frameConstraint = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ContactFriction::Setup
|
|
================
|
|
*/
|
|
void idAFConstraint_ContactFriction::Setup( idAFConstraint_Contact *cc ) {
|
|
this->cc = cc;
|
|
body1 = cc->GetBody1();
|
|
body2 = cc->GetBody2();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ContactFriction::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_ContactFriction::Evaluate( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ContactFriction::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_ContactFriction::ApplyFriction( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ContactFriction::Add
|
|
================
|
|
*/
|
|
bool idAFConstraint_ContactFriction::Add( idPhysics_AF *phys, float invTimeStep ) {
|
|
idVec3 r, dir1, dir2;
|
|
float friction;
|
|
int newRow;
|
|
|
|
physics = phys;
|
|
|
|
friction = body1->GetContactFriction() * physics->GetContactFrictionScale();
|
|
|
|
// if the body only has friction in one direction
|
|
if ( body1->GetFrictionDirection( dir1 ) ) {
|
|
// project the friction direction into the contact plane
|
|
dir1 -= dir1 * cc->GetContact().normal * dir1;
|
|
dir1.Normalize();
|
|
|
|
r = cc->GetContact().point - body1->GetWorldOrigin();
|
|
|
|
J1.SetSize( 1, 6 );
|
|
J1.SubVec6(0).SubVec3(0) = dir1;
|
|
J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 );
|
|
c1.SetSize( 1 );
|
|
c1[0] = 0.0f;
|
|
|
|
if ( body2 ) {
|
|
r = cc->GetContact().point - body2->GetWorldOrigin();
|
|
|
|
J2.SetSize( 1, 6 );
|
|
J2.SubVec6(0).SubVec3(0) = -dir1;
|
|
J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 );
|
|
c2.SetSize( 1 );
|
|
c2[0] = 0.0f;
|
|
}
|
|
|
|
lo[0] = -friction;
|
|
hi[0] = friction;
|
|
boxConstraint = cc;
|
|
boxIndex[0] = 0;
|
|
}
|
|
else {
|
|
// get two friction directions orthogonal to contact normal
|
|
cc->GetContact().normal.NormalVectors( dir1, dir2 );
|
|
|
|
r = cc->GetContact().point - body1->GetWorldOrigin();
|
|
|
|
J1.SetSize( 2, 6 );
|
|
J1.SubVec6(0).SubVec3(0) = dir1;
|
|
J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 );
|
|
J1.SubVec6(1).SubVec3(0) = dir2;
|
|
J1.SubVec6(1).SubVec3(1) = r.Cross( dir2 );
|
|
c1.SetSize( 2 );
|
|
c1[0] = c1[1] = 0.0f;
|
|
|
|
if ( body2 ) {
|
|
r = cc->GetContact().point - body2->GetWorldOrigin();
|
|
|
|
J2.SetSize( 2, 6 );
|
|
J2.SubVec6(0).SubVec3(0) = -dir1;
|
|
J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 );
|
|
J2.SubVec6(1).SubVec3(0) = -dir2;
|
|
J2.SubVec6(1).SubVec3(1) = r.Cross( -dir2 );
|
|
c2.SetSize( 2 );
|
|
c2[0] = c2[1] = 0.0f;
|
|
|
|
if ( body2->GetContactFriction() < friction ) {
|
|
friction = body2->GetContactFriction();
|
|
}
|
|
}
|
|
|
|
lo[0] = -friction;
|
|
hi[0] = friction;
|
|
boxConstraint = cc;
|
|
boxIndex[0] = 0;
|
|
lo[1] = -friction;
|
|
hi[1] = friction;
|
|
boxIndex[1] = 0;
|
|
}
|
|
|
|
if ( body1->GetContactMotorDirection( dir1 ) && body1->GetContactMotorForce() > 0.0f ) {
|
|
// project the motor force direction into the contact plane
|
|
dir1 -= dir1 * cc->GetContact().normal * dir1;
|
|
dir1.Normalize();
|
|
|
|
r = cc->GetContact().point - body1->GetWorldOrigin();
|
|
|
|
newRow = J1.GetNumRows();
|
|
J1.ChangeSize( newRow+1, J1.GetNumColumns() );
|
|
J1.SubVec6(newRow).SubVec3(0) = -dir1;
|
|
J1.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 );
|
|
c1.ChangeSize( newRow+1 );
|
|
c1[newRow] = body1->GetContactMotorVelocity();
|
|
|
|
if ( body2 ) {
|
|
r = cc->GetContact().point - body2->GetWorldOrigin();
|
|
|
|
J2.ChangeSize( newRow+1, J2.GetNumColumns() );
|
|
J2.SubVec6(newRow).SubVec3(0) = -dir1;
|
|
J2.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 );
|
|
c2.ChangeSize( newRow+1 );
|
|
c2[newRow] = 0.0f;
|
|
}
|
|
|
|
lo[newRow] = -body1->GetContactMotorForce();
|
|
hi[newRow] = body1->GetContactMotorForce();
|
|
boxIndex[newRow] = -1;
|
|
}
|
|
|
|
physics->AddFrameConstraint( this );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ContactFriction::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_ContactFriction::Translate( const idVec3 &translation ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ContactFriction::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_ContactFriction::Rotate( const idRotation &rotation ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ContactFriction::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_ContactFriction::DebugDraw( void ) {
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_ConeLimit
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::idAFConstraint_ConeLimit
|
|
================
|
|
*/
|
|
idAFConstraint_ConeLimit::idAFConstraint_ConeLimit( void ) {
|
|
type = CONSTRAINT_CONELIMIT;
|
|
name = "coneLimit";
|
|
InitSize( 1 );
|
|
fl.allowPrimary = false;
|
|
fl.frameConstraint = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::Setup
|
|
|
|
the coneAnchor is the top of the cone in body2 space
|
|
the coneAxis is the axis of the cone in body2 space
|
|
the coneAngle is the angle the cone hull makes at the top
|
|
the body1Axis is the axis in body1 space that should stay within the cone
|
|
================
|
|
*/
|
|
void idAFConstraint_ConeLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &coneAnchor, const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) {
|
|
this->body1 = b1;
|
|
this->body2 = b2;
|
|
this->coneAxis = coneAxis;
|
|
this->coneAxis.Normalize();
|
|
this->coneAnchor = coneAnchor;
|
|
this->body1Axis = body1Axis;
|
|
this->body1Axis.Normalize();
|
|
this->cosAngle = (float) cos( DEG2RAD( coneAngle * 0.5f ) );
|
|
this->sinHalfAngle = (float) sin( DEG2RAD( coneAngle * 0.25f ) );
|
|
this->cosHalfAngle = (float) cos( DEG2RAD( coneAngle * 0.25f ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::SetAnchor
|
|
================
|
|
*/
|
|
void idAFConstraint_ConeLimit::SetAnchor( const idVec3 &coneAnchor ) {
|
|
this->coneAnchor = coneAnchor;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::SetBody1Axis
|
|
================
|
|
*/
|
|
void idAFConstraint_ConeLimit::SetBody1Axis( const idVec3 &body1Axis ) {
|
|
this->body1Axis = body1Axis;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_ConeLimit::Evaluate( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_ConeLimit::ApplyFriction( float invTimeStep ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::Add
|
|
================
|
|
*/
|
|
bool idAFConstraint_ConeLimit::Add( idPhysics_AF *phys, float invTimeStep ) {
|
|
float a;
|
|
idVec6 J1row, J2row;
|
|
idVec3 ax, anchor, body1ax, normal, coneVector, p1, p2;
|
|
idQuat q;
|
|
idAFBody *master;
|
|
|
|
if ( af_skipLimits.GetBool() ) {
|
|
lm.Zero(); // constraint exerts no force
|
|
return false;
|
|
}
|
|
|
|
physics = phys;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
if ( master ) {
|
|
ax = coneAxis * master->GetWorldAxis();
|
|
anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis();
|
|
}
|
|
else {
|
|
ax = coneAxis;
|
|
anchor = coneAnchor;
|
|
}
|
|
|
|
body1ax = body1Axis * body1->GetWorldAxis();
|
|
|
|
a = ax * body1ax;
|
|
|
|
// if the body1 axis is inside the cone
|
|
if ( a > cosAngle ) {
|
|
lm.Zero(); // constraint exerts no force
|
|
return false;
|
|
}
|
|
|
|
// calculate the inward cone normal for the position the body1 axis went outside the cone
|
|
normal = body1ax.Cross( ax );
|
|
normal.Normalize();
|
|
q.x = normal.x * sinHalfAngle;
|
|
q.y = normal.y * sinHalfAngle;
|
|
q.z = normal.z * sinHalfAngle;
|
|
q.w = cosHalfAngle;
|
|
coneVector = ax * q.ToMat3();
|
|
normal = coneVector.Cross( ax ).Cross( coneVector );
|
|
normal.Normalize();
|
|
|
|
p1 = anchor + 32.0f * coneVector - body1->GetWorldOrigin();
|
|
|
|
J1row.SubVec3(0) = normal;
|
|
J1row.SubVec3(1) = p1.Cross( normal );
|
|
J1.Set( 1, 6, J1row.ToFloatPtr() );
|
|
|
|
c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) );
|
|
|
|
if ( body2 ) {
|
|
|
|
p2 = anchor + 32.0f * coneVector - master->GetWorldOrigin();
|
|
|
|
J2row.SubVec3(0) = -normal;
|
|
J2row.SubVec3(1) = p2.Cross( -normal );
|
|
J2.Set( 1, 6, J2row.ToFloatPtr() );
|
|
|
|
c2[0] = 0.0f;
|
|
}
|
|
|
|
lo[0] = 0.0f;
|
|
e[0] = LIMIT_LCP_EPSILON;
|
|
|
|
physics->AddFrameConstraint( this );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_ConeLimit::Translate( const idVec3 &translation ) {
|
|
if ( !body2 ) {
|
|
coneAnchor += translation;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_ConeLimit::Rotate( const idRotation &rotation ) {
|
|
if ( !body2 ) {
|
|
coneAnchor *= rotation;
|
|
coneAxis *= rotation.ToMat3();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_ConeLimit::DebugDraw( void ) {
|
|
idVec3 ax, anchor, x, y, z, start, end;
|
|
float sinAngle, a, size = 10.0f;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
if ( master ) {
|
|
ax = coneAxis * master->GetWorldAxis();
|
|
anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis();
|
|
}
|
|
else {
|
|
ax = coneAxis;
|
|
anchor = coneAnchor;
|
|
}
|
|
|
|
// draw body1 axis
|
|
gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) );
|
|
|
|
// draw cone
|
|
ax.NormalVectors( x, y );
|
|
sinAngle = idMath::Sqrt( 1.0f - cosAngle * cosAngle );
|
|
x *= size * sinAngle;
|
|
y *= size * sinAngle;
|
|
z = anchor + ax * size * cosAngle;
|
|
start = x + z;
|
|
for ( a = 0.0f; a < 360.0f; a += 45.0f ) {
|
|
end = x * (float) cos( DEG2RAD(a + 45.0f) ) + y * (float) sin( DEG2RAD(a + 45.0f) ) + z;
|
|
gameRenderWorld->DebugLine( colorMagenta, anchor, start );
|
|
gameRenderWorld->DebugLine( colorMagenta, start, end );
|
|
start = end;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::Save
|
|
================
|
|
*/
|
|
void idAFConstraint_ConeLimit::Save( idSaveGame *saveFile ) const {
|
|
idAFConstraint::Save( saveFile );
|
|
saveFile->WriteVec3( coneAnchor );
|
|
saveFile->WriteVec3( coneAxis );
|
|
saveFile->WriteVec3( body1Axis );
|
|
saveFile->WriteFloat( cosAngle );
|
|
saveFile->WriteFloat( sinHalfAngle );
|
|
saveFile->WriteFloat( cosHalfAngle );
|
|
saveFile->WriteFloat( epsilon );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_ConeLimit::Restore
|
|
================
|
|
*/
|
|
void idAFConstraint_ConeLimit::Restore( idRestoreGame *saveFile ) {
|
|
idAFConstraint::Restore( saveFile );
|
|
saveFile->ReadVec3( coneAnchor );
|
|
saveFile->ReadVec3( coneAxis );
|
|
saveFile->ReadVec3( body1Axis );
|
|
saveFile->ReadFloat( cosAngle );
|
|
saveFile->ReadFloat( sinHalfAngle );
|
|
saveFile->ReadFloat( cosHalfAngle );
|
|
saveFile->ReadFloat( epsilon );
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_PyramidLimit
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit
|
|
================
|
|
*/
|
|
idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit( void ) {
|
|
type = CONSTRAINT_PYRAMIDLIMIT;
|
|
name = "pyramidLimit";
|
|
InitSize( 1 );
|
|
fl.allowPrimary = false;
|
|
fl.frameConstraint = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::Setup
|
|
================
|
|
*/
|
|
void idAFConstraint_PyramidLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &pyramidAnchor,
|
|
const idVec3 &pyramidAxis, const idVec3 &baseAxis,
|
|
const float pyramidAngle1, const float pyramidAngle2, const idVec3 &body1Axis ) {
|
|
body1 = b1;
|
|
body2 = b2;
|
|
// setup the base and make sure the basis is orthonormal
|
|
pyramidBasis[2] = pyramidAxis;
|
|
pyramidBasis[2].Normalize();
|
|
pyramidBasis[0] = baseAxis;
|
|
pyramidBasis[0] -= pyramidBasis[2] * baseAxis * pyramidBasis[2];
|
|
pyramidBasis[0].Normalize();
|
|
pyramidBasis[1] = pyramidBasis[0].Cross( pyramidBasis[2] );
|
|
// pyramid top
|
|
this->pyramidAnchor = pyramidAnchor;
|
|
// angles
|
|
cosAngle[0] = (float) cos( DEG2RAD( pyramidAngle1 * 0.5f ) );
|
|
cosAngle[1] = (float) cos( DEG2RAD( pyramidAngle2 * 0.5f ) );
|
|
sinHalfAngle[0] = (float) sin( DEG2RAD( pyramidAngle1 * 0.25f ) );
|
|
sinHalfAngle[1] = (float) sin( DEG2RAD( pyramidAngle2 * 0.25f ) );
|
|
cosHalfAngle[0] = (float) cos( DEG2RAD( pyramidAngle1 * 0.25f ) );
|
|
cosHalfAngle[1] = (float) cos( DEG2RAD( pyramidAngle2 * 0.25f ) );
|
|
|
|
this->body1Axis = body1Axis;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::SetAnchor
|
|
================
|
|
*/
|
|
void idAFConstraint_PyramidLimit::SetAnchor( const idVec3 &pyramidAnchor ) {
|
|
this->pyramidAnchor = pyramidAnchor;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::SetBody1Axis
|
|
================
|
|
*/
|
|
void idAFConstraint_PyramidLimit::SetBody1Axis( const idVec3 &body1Axis ) {
|
|
this->body1Axis = body1Axis;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_PyramidLimit::Evaluate( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_PyramidLimit::ApplyFriction( float invTimeStep ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::Add
|
|
================
|
|
*/
|
|
bool idAFConstraint_PyramidLimit::Add( idPhysics_AF *phys, float invTimeStep ) {
|
|
int i;
|
|
float a[2];
|
|
idVec6 J1row, J2row;
|
|
idMat3 worldBase;
|
|
idVec3 anchor, body1ax, ax[2], v, normal, pyramidVector, p1, p2;
|
|
idQuat q;
|
|
idAFBody *master;
|
|
|
|
if ( af_skipLimits.GetBool() ) {
|
|
lm.Zero(); // constraint exerts no force
|
|
return false;
|
|
}
|
|
|
|
physics = phys;
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
if ( master ) {
|
|
worldBase[0] = pyramidBasis[0] * master->GetWorldAxis();
|
|
worldBase[1] = pyramidBasis[1] * master->GetWorldAxis();
|
|
worldBase[2] = pyramidBasis[2] * master->GetWorldAxis();
|
|
anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis();
|
|
}
|
|
else {
|
|
worldBase = pyramidBasis;
|
|
anchor = pyramidAnchor;
|
|
}
|
|
|
|
body1ax = body1Axis * body1->GetWorldAxis();
|
|
|
|
for ( i = 0; i < 2; i++ ) {
|
|
ax[i] = body1ax - worldBase[!i] * body1ax * worldBase[!i];
|
|
ax[i].Normalize();
|
|
a[i] = worldBase[2] * ax[i];
|
|
}
|
|
|
|
// if the body1 axis is inside the pyramid
|
|
if ( a[0] > cosAngle[0] && a[1] > cosAngle[1] ) {
|
|
lm.Zero(); // constraint exerts no force
|
|
return false;
|
|
}
|
|
|
|
// calculate the inward pyramid normal for the position the body1 axis went outside the pyramid
|
|
pyramidVector = worldBase[2];
|
|
for ( i = 0; i < 2; i++ ) {
|
|
if ( a[i] <= cosAngle[i] ) {
|
|
v = ax[i].Cross( worldBase[2] );
|
|
v.Normalize();
|
|
q.x = v.x * sinHalfAngle[i];
|
|
q.y = v.y * sinHalfAngle[i];
|
|
q.z = v.z * sinHalfAngle[i];
|
|
q.w = cosHalfAngle[i];
|
|
pyramidVector *= q.ToMat3();
|
|
}
|
|
}
|
|
normal = pyramidVector.Cross( worldBase[2] ).Cross( pyramidVector );
|
|
normal.Normalize();
|
|
|
|
p1 = anchor + 32.0f * pyramidVector - body1->GetWorldOrigin();
|
|
|
|
J1row.SubVec3(0) = normal;
|
|
J1row.SubVec3(1) = p1.Cross( normal );
|
|
J1.Set( 1, 6, J1row.ToFloatPtr() );
|
|
|
|
c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) );
|
|
|
|
if ( body2 ) {
|
|
|
|
p2 = anchor + 32.0f * pyramidVector - master->GetWorldOrigin();
|
|
|
|
J2row.SubVec3(0) = -normal;
|
|
J2row.SubVec3(1) = p2.Cross( -normal );
|
|
J2.Set( 1, 6, J2row.ToFloatPtr() );
|
|
|
|
c2[0] = 0.0f;
|
|
}
|
|
|
|
lo[0] = 0.0f;
|
|
e[0] = LIMIT_LCP_EPSILON;
|
|
|
|
physics->AddFrameConstraint( this );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_PyramidLimit::Translate( const idVec3 &translation ) {
|
|
if ( !body2 ) {
|
|
pyramidAnchor += translation;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_PyramidLimit::Rotate( const idRotation &rotation ) {
|
|
if ( !body2 ) {
|
|
pyramidAnchor *= rotation;
|
|
pyramidBasis[0] *= rotation.ToMat3();
|
|
pyramidBasis[1] *= rotation.ToMat3();
|
|
pyramidBasis[2] *= rotation.ToMat3();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_PyramidLimit::DebugDraw( void ) {
|
|
int i;
|
|
float size = 10.0f;
|
|
idVec3 anchor, dir, p[4];
|
|
idMat3 worldBase, m[2];
|
|
idQuat q;
|
|
idAFBody *master;
|
|
|
|
master = body2 ? body2 : physics->GetMasterBody();
|
|
|
|
if ( master ) {
|
|
worldBase[0] = pyramidBasis[0] * master->GetWorldAxis();
|
|
worldBase[1] = pyramidBasis[1] * master->GetWorldAxis();
|
|
worldBase[2] = pyramidBasis[2] * master->GetWorldAxis();
|
|
anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis();
|
|
}
|
|
else {
|
|
worldBase = pyramidBasis;
|
|
anchor = pyramidAnchor;
|
|
}
|
|
|
|
// draw body1 axis
|
|
gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) );
|
|
|
|
// draw the pyramid
|
|
for ( i = 0; i < 2; i++ ) {
|
|
q.x = worldBase[!i].x * sinHalfAngle[i];
|
|
q.y = worldBase[!i].y * sinHalfAngle[i];
|
|
q.z = worldBase[!i].z * sinHalfAngle[i];
|
|
q.w = cosHalfAngle[i];
|
|
m[i] = q.ToMat3();
|
|
}
|
|
|
|
dir = worldBase[2] * size;
|
|
p[0] = anchor + m[0] * (m[1] * dir);
|
|
p[1] = anchor + m[0] * (m[1].Transpose() * dir);
|
|
p[2] = anchor + m[0].Transpose() * (m[1].Transpose() * dir);
|
|
p[3] = anchor + m[0].Transpose() * (m[1] * dir);
|
|
|
|
for ( i = 0; i < 4; i++ ) {
|
|
gameRenderWorld->DebugLine( colorMagenta, anchor, p[i] );
|
|
gameRenderWorld->DebugLine( colorMagenta, p[i], p[(i+1)&3] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::Save
|
|
================
|
|
*/
|
|
void idAFConstraint_PyramidLimit::Save( idSaveGame *saveFile ) const {
|
|
idAFConstraint::Save( saveFile );
|
|
saveFile->WriteVec3( pyramidAnchor );
|
|
saveFile->WriteMat3( pyramidBasis );
|
|
saveFile->WriteVec3( body1Axis );
|
|
saveFile->WriteFloat( cosAngle[0] );
|
|
saveFile->WriteFloat( cosAngle[1] );
|
|
saveFile->WriteFloat( sinHalfAngle[0] );
|
|
saveFile->WriteFloat( sinHalfAngle[1] );
|
|
saveFile->WriteFloat( cosHalfAngle[0] );
|
|
saveFile->WriteFloat( cosHalfAngle[1] );
|
|
saveFile->WriteFloat( epsilon );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_PyramidLimit::Restore
|
|
================
|
|
*/
|
|
void idAFConstraint_PyramidLimit::Restore( idRestoreGame *saveFile ) {
|
|
idAFConstraint::Restore( saveFile );
|
|
saveFile->ReadVec3( pyramidAnchor );
|
|
saveFile->ReadMat3( pyramidBasis );
|
|
saveFile->ReadVec3( body1Axis );
|
|
saveFile->ReadFloat( cosAngle[0] );
|
|
saveFile->ReadFloat( cosAngle[1] );
|
|
saveFile->ReadFloat( sinHalfAngle[0] );
|
|
saveFile->ReadFloat( sinHalfAngle[1] );
|
|
saveFile->ReadFloat( cosHalfAngle[0] );
|
|
saveFile->ReadFloat( cosHalfAngle[1] );
|
|
saveFile->ReadFloat( epsilon );
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFConstraint_Suspension
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Suspension::idAFConstraint_Suspension
|
|
================
|
|
*/
|
|
idAFConstraint_Suspension::idAFConstraint_Suspension( void ) {
|
|
type = CONSTRAINT_SUSPENSION;
|
|
name = "suspension";
|
|
InitSize( 3 );
|
|
fl.allowPrimary = false;
|
|
fl.frameConstraint = true;
|
|
|
|
localOrigin.Zero();
|
|
localAxis.Identity();
|
|
suspensionUp = 0.0f;
|
|
suspensionDown = 0.0f;
|
|
suspensionKCompress = 0.0f;
|
|
suspensionDamping = 0.0f;
|
|
steerAngle = 0.0f;
|
|
friction = 2.0f;
|
|
motorEnabled = false;
|
|
motorForce = 0.0f;
|
|
motorVelocity = 0.0f;
|
|
wheelModel = NULL;
|
|
memset( &trace, 0, sizeof( trace ) );
|
|
epsilon = LCP_EPSILON;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Suspension::Setup
|
|
================
|
|
*/
|
|
void idAFConstraint_Suspension::Setup( const char *name, idAFBody *body, const idVec3 &origin, const idMat3 &axis, idClipModel *clipModel ) {
|
|
this->name = name;
|
|
body1 = body;
|
|
body2 = NULL;
|
|
localOrigin = ( origin - body->GetWorldOrigin() ) * body->GetWorldAxis().Transpose();
|
|
localAxis = axis * body->GetWorldAxis().Transpose();
|
|
wheelModel = clipModel;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Suspension::SetSuspension
|
|
================
|
|
*/
|
|
void idAFConstraint_Suspension::SetSuspension( const float up, const float down, const float k, const float d, const float f ) {
|
|
suspensionUp = up;
|
|
suspensionDown = down;
|
|
suspensionKCompress = k;
|
|
suspensionDamping = d;
|
|
friction = f;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Suspension::GetWheelOrigin
|
|
================
|
|
*/
|
|
const idVec3 idAFConstraint_Suspension::GetWheelOrigin( void ) const {
|
|
return body1->GetWorldOrigin() + wheelOffset * body1->GetWorldAxis();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Suspension::Evaluate
|
|
================
|
|
*/
|
|
void idAFConstraint_Suspension::Evaluate( float invTimeStep ) {
|
|
float velocity, suspensionLength, springLength, compression, dampingForce, springForce;
|
|
idVec3 origin, start, end, vel1, vel2, springDir, r, frictionDir, motorDir;
|
|
idMat3 axis;
|
|
idRotation rotation;
|
|
|
|
axis = localAxis * body1->GetWorldAxis();
|
|
origin = body1->GetWorldOrigin() + localOrigin * body1->GetWorldAxis();
|
|
start = origin + suspensionUp * axis[2];
|
|
end = origin - suspensionDown * axis[2];
|
|
|
|
rotation.SetVec( axis[2] );
|
|
rotation.SetAngle( steerAngle );
|
|
|
|
axis *= rotation.ToMat3();
|
|
|
|
gameLocal.clip.Translation( trace, start, end, wheelModel, axis, MASK_SOLID, NULL );
|
|
|
|
wheelOffset = ( trace.endpos - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose();
|
|
|
|
if ( trace.fraction >= 1.0f ) {
|
|
J1.SetSize( 0, 6 );
|
|
if ( body2 ) {
|
|
J2.SetSize( 0, 6 );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// calculate and add spring force
|
|
vel1 = body1->GetPointVelocity( start );
|
|
if ( body2 ) {
|
|
vel2 = body2->GetPointVelocity( trace.c.point );
|
|
} else {
|
|
vel2.Zero();
|
|
}
|
|
|
|
suspensionLength = suspensionUp + suspensionDown;
|
|
springDir = trace.endpos - start;
|
|
springLength = trace.fraction * suspensionLength;
|
|
dampingForce = suspensionDamping * idMath::Fabs( ( vel2 - vel1 ) * springDir ) / ( 1.0f + springLength * springLength );
|
|
compression = suspensionLength - springLength;
|
|
springForce = compression * compression * suspensionKCompress - dampingForce;
|
|
|
|
r = trace.c.point - body1->GetWorldOrigin();
|
|
J1.SetSize( 2, 6 );
|
|
J1.SubVec6(0).SubVec3(0) = trace.c.normal;
|
|
J1.SubVec6(0).SubVec3(1) = r.Cross( trace.c.normal );
|
|
c1.SetSize( 2 );
|
|
c1[0] = 0.0f;
|
|
velocity = J1.SubVec6(0).SubVec3(0) * body1->GetLinearVelocity() + J1.SubVec6(0).SubVec3(1) * body1->GetAngularVelocity();
|
|
|
|
if ( body2 ) {
|
|
r = trace.c.point - body2->GetWorldOrigin();
|
|
J2.SetSize( 2, 6 );
|
|
J2.SubVec6(0).SubVec3(0) = -trace.c.normal;
|
|
J2.SubVec6(0).SubVec3(1) = r.Cross( -trace.c.normal );
|
|
c2.SetSize( 2 );
|
|
c2[0] = 0.0f;
|
|
velocity += J2.SubVec6(0).SubVec3(0) * body2->GetLinearVelocity() + J2.SubVec6(0).SubVec3(1) * body2->GetAngularVelocity();
|
|
}
|
|
|
|
c1[0] = -compression; // + 0.5f * -velocity;
|
|
|
|
e[0] = 1e-4f;
|
|
lo[0] = 0.0f;
|
|
hi[0] = springForce;
|
|
boxConstraint = NULL;
|
|
boxIndex[0] = -1;
|
|
|
|
// project the friction direction into the contact plane
|
|
frictionDir = axis[1] - axis[1] * trace.c.normal * axis[1];
|
|
frictionDir.Normalize();
|
|
|
|
r = trace.c.point - body1->GetWorldOrigin();
|
|
|
|
J1.SubVec6(1).SubVec3(0) = frictionDir;
|
|
J1.SubVec6(1).SubVec3(1) = r.Cross( frictionDir );
|
|
c1[1] = 0.0f;
|
|
|
|
if ( body2 ) {
|
|
r = trace.c.point - body2->GetWorldOrigin();
|
|
|
|
J2.SubVec6(1).SubVec3(0) = -frictionDir;
|
|
J2.SubVec6(1).SubVec3(1) = r.Cross( -frictionDir );
|
|
c2[1] = 0.0f;
|
|
}
|
|
|
|
lo[1] = -friction * physics->GetContactFrictionScale();
|
|
hi[1] = friction * physics->GetContactFrictionScale();
|
|
|
|
boxConstraint = this;
|
|
boxIndex[1] = 0;
|
|
|
|
|
|
if ( motorEnabled ) {
|
|
// project the motor force direction into the contact plane
|
|
motorDir = axis[0] - axis[0] * trace.c.normal * axis[0];
|
|
motorDir.Normalize();
|
|
|
|
r = trace.c.point - body1->GetWorldOrigin();
|
|
|
|
J1.ChangeSize( 3, J1.GetNumColumns() );
|
|
J1.SubVec6(2).SubVec3(0) = -motorDir;
|
|
J1.SubVec6(2).SubVec3(1) = r.Cross( -motorDir );
|
|
c1.ChangeSize( 3 );
|
|
c1[2] = motorVelocity;
|
|
|
|
if ( body2 ) {
|
|
r = trace.c.point - body2->GetWorldOrigin();
|
|
|
|
J2.ChangeSize( 3, J2.GetNumColumns() );
|
|
J2.SubVec6(2).SubVec3(0) = -motorDir;
|
|
J2.SubVec6(2).SubVec3(1) = r.Cross( -motorDir );
|
|
c2.ChangeSize( 3 );
|
|
c2[2] = 0.0f;
|
|
}
|
|
|
|
lo[2] = -motorForce;
|
|
hi[2] = motorForce;
|
|
boxIndex[2] = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Suspension::ApplyFriction
|
|
================
|
|
*/
|
|
void idAFConstraint_Suspension::ApplyFriction( float invTimeStep ) {
|
|
// do nothing
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Suspension::Translate
|
|
================
|
|
*/
|
|
void idAFConstraint_Suspension::Translate( const idVec3 &translation ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Suspension::Rotate
|
|
================
|
|
*/
|
|
void idAFConstraint_Suspension::Rotate( const idRotation &rotation ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFConstraint_Suspension::DebugDraw
|
|
================
|
|
*/
|
|
void idAFConstraint_Suspension::DebugDraw( void ) {
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
idRotation rotation;
|
|
|
|
axis = localAxis * body1->GetWorldAxis();
|
|
|
|
rotation.SetVec( axis[2] );
|
|
rotation.SetAngle( steerAngle );
|
|
|
|
axis *= rotation.ToMat3();
|
|
|
|
if ( trace.fraction < 1.0f ) {
|
|
origin = trace.c.point;
|
|
|
|
gameRenderWorld->DebugLine( colorWhite, origin, origin + 6.0f * axis[2] );
|
|
gameRenderWorld->DebugLine( colorWhite, origin - 4.0f * axis[0], origin + 4.0f * axis[0] );
|
|
gameRenderWorld->DebugLine( colorWhite, origin - 2.0f * axis[1], origin + 2.0f * axis[1] );
|
|
}
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idAFBody
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFBody::idAFBody
|
|
================
|
|
*/
|
|
idAFBody::idAFBody( void ) {
|
|
Init();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::idAFBody
|
|
================
|
|
*/
|
|
idAFBody::idAFBody( const idStr &name, idClipModel *clipModel, float density ) {
|
|
|
|
assert( clipModel );
|
|
assert( clipModel->IsTraceModel() );
|
|
|
|
Init();
|
|
|
|
this->name = name;
|
|
this->clipModel = NULL;
|
|
|
|
SetClipModel( clipModel );
|
|
SetDensity( density );
|
|
|
|
current->worldOrigin = clipModel->GetOrigin();
|
|
current->worldAxis = clipModel->GetAxis();
|
|
*next = *current;
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::~idAFBody
|
|
================
|
|
*/
|
|
idAFBody::~idAFBody( void ) {
|
|
delete clipModel;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::Init
|
|
================
|
|
*/
|
|
void idAFBody::Init( void ) {
|
|
name = "noname";
|
|
parent = NULL;
|
|
clipModel = NULL;
|
|
primaryConstraint = NULL;
|
|
tree = NULL;
|
|
|
|
linearFriction = -1.0f;
|
|
angularFriction = -1.0f;
|
|
contactFriction = -1.0f;
|
|
bouncyness = -1.0f;
|
|
clipMask = 0;
|
|
|
|
frictionDir = vec3_zero;
|
|
contactMotorDir = vec3_zero;
|
|
contactMotorVelocity = 0.0f;
|
|
contactMotorForce = 0.0f;
|
|
|
|
mass = 1.0f;
|
|
invMass = 1.0f;
|
|
centerOfMass = vec3_zero;
|
|
inertiaTensor = mat3_identity;
|
|
inverseInertiaTensor = mat3_identity;
|
|
|
|
current = &state[0];
|
|
next = &state[1];
|
|
current->worldOrigin = vec3_zero;
|
|
current->worldAxis = mat3_identity;
|
|
current->spatialVelocity = vec6_zero;
|
|
current->externalForce = vec6_zero;
|
|
*next = *current;
|
|
saved = *current;
|
|
atRestOrigin = vec3_zero;
|
|
atRestAxis = mat3_identity;
|
|
|
|
s.Zero( 6 );
|
|
totalForce.Zero( 6 );
|
|
auxForce.Zero( 6 );
|
|
acceleration.Zero( 6 );
|
|
|
|
response = NULL;
|
|
responseIndex = NULL;
|
|
numResponses = 0;
|
|
maxAuxiliaryIndex = 0;
|
|
maxSubTreeAuxiliaryIndex = 0;
|
|
|
|
memset( &fl, 0, sizeof( fl ) );
|
|
|
|
fl.selfCollision = true;
|
|
fl.isZero = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::SetClipModel
|
|
================
|
|
*/
|
|
void idAFBody::SetClipModel( idClipModel *clipModel ) {
|
|
if ( this->clipModel && this->clipModel != clipModel ) {
|
|
delete this->clipModel;
|
|
}
|
|
this->clipModel = clipModel;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::SetFriction
|
|
================
|
|
*/
|
|
void idAFBody::SetFriction( float linear, float angular, float contact ) {
|
|
if ( linear < 0.0f || linear > 1.0f ||
|
|
angular < 0.0f || angular > 1.0f ||
|
|
contact < 0.0f ) {
|
|
gameLocal.Warning( "idAFBody::SetFriction: friction out of range, linear = %.1f, angular = %.1f, contact = %.1f", linear, angular, contact );
|
|
return;
|
|
}
|
|
linearFriction = linear;
|
|
angularFriction = angular;
|
|
contactFriction = contact;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::SetBouncyness
|
|
================
|
|
*/
|
|
void idAFBody::SetBouncyness( float bounce ) {
|
|
if ( bounce < 0.0f || bounce > 1.0f ) {
|
|
gameLocal.Warning( "idAFBody::SetBouncyness: bouncyness out of range, bounce = %.1f", bounce );
|
|
return;
|
|
}
|
|
bouncyness = bounce;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::SetDensity
|
|
================
|
|
*/
|
|
void idAFBody::SetDensity( float density, const idMat3 &inertiaScale ) {
|
|
|
|
// get the body mass properties
|
|
clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor );
|
|
|
|
// make sure we have a valid mass
|
|
if ( mass <= 0.0f || FLOAT_IS_NAN( mass ) ) {
|
|
gameLocal.Warning( "idAFBody::SetDensity: invalid mass for body '%s'", name.c_str() );
|
|
mass = 1.0f;
|
|
centerOfMass.Zero();
|
|
inertiaTensor.Identity();
|
|
}
|
|
|
|
// make sure the center of mass is at the body origin
|
|
if ( !centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) {
|
|
gameLocal.Warning( "idAFBody::SetDentity: center of mass not at origin for body '%s'", name.c_str() );
|
|
}
|
|
centerOfMass.Zero();
|
|
|
|
// calculate the inverse mass and inverse inertia tensor
|
|
invMass = 1.0f / mass;
|
|
if ( inertiaScale != mat3_identity ) {
|
|
inertiaTensor *= inertiaScale;
|
|
}
|
|
if ( inertiaTensor.IsDiagonal( 1e-3f ) ) {
|
|
inertiaTensor[0][1] = inertiaTensor[0][2] = 0.0f;
|
|
inertiaTensor[1][0] = inertiaTensor[1][2] = 0.0f;
|
|
inertiaTensor[2][0] = inertiaTensor[2][1] = 0.0f;
|
|
inverseInertiaTensor.Identity();
|
|
inverseInertiaTensor[0][0] = 1.0f / inertiaTensor[0][0];
|
|
inverseInertiaTensor[1][1] = 1.0f / inertiaTensor[1][1];
|
|
inverseInertiaTensor[2][2] = 1.0f / inertiaTensor[2][2];
|
|
}
|
|
else {
|
|
inverseInertiaTensor = inertiaTensor.Inverse();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::SetFrictionDirection
|
|
================
|
|
*/
|
|
void idAFBody::SetFrictionDirection( const idVec3 &dir ) {
|
|
frictionDir = dir * current->worldAxis.Transpose();
|
|
fl.useFrictionDir = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::GetFrictionDirection
|
|
================
|
|
*/
|
|
bool idAFBody::GetFrictionDirection( idVec3 &dir ) const {
|
|
if ( fl.useFrictionDir ) {
|
|
dir = frictionDir * current->worldAxis;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::SetContactMotorDirection
|
|
================
|
|
*/
|
|
void idAFBody::SetContactMotorDirection( const idVec3 &dir ) {
|
|
contactMotorDir = dir * current->worldAxis.Transpose();
|
|
fl.useContactMotorDir = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::GetContactMotorDirection
|
|
================
|
|
*/
|
|
bool idAFBody::GetContactMotorDirection( idVec3 &dir ) const {
|
|
if ( fl.useContactMotorDir ) {
|
|
dir = contactMotorDir * current->worldAxis;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::GetPointVelocity
|
|
================
|
|
*/
|
|
idVec3 idAFBody::GetPointVelocity( const idVec3 &point ) const {
|
|
idVec3 r = point - current->worldOrigin;
|
|
return current->spatialVelocity.SubVec3(0) + current->spatialVelocity.SubVec3(1).Cross( r );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::AddForce
|
|
================
|
|
*/
|
|
void idAFBody::AddForce( const idVec3 &point, const idVec3 &force ) {
|
|
current->externalForce.SubVec3(0) += force;
|
|
current->externalForce.SubVec3(1) += (point - current->worldOrigin).Cross( force );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::InverseWorldSpatialInertiaMultiply
|
|
|
|
dst = this->inverseWorldSpatialInertia * v;
|
|
================
|
|
*/
|
|
ID_INLINE void idAFBody::InverseWorldSpatialInertiaMultiply( idVecX &dst, const float *v ) const {
|
|
const float *mPtr = inverseWorldSpatialInertia.ToFloatPtr();
|
|
const float *vPtr = v;
|
|
float *dstPtr = dst.ToFloatPtr();
|
|
|
|
if ( fl.spatialInertiaSparse ) {
|
|
dstPtr[0] = mPtr[0*6+0] * vPtr[0];
|
|
dstPtr[1] = mPtr[1*6+1] * vPtr[1];
|
|
dstPtr[2] = mPtr[2*6+2] * vPtr[2];
|
|
dstPtr[3] = mPtr[3*6+3] * vPtr[3] + mPtr[3*6+4] * vPtr[4] + mPtr[3*6+5] * vPtr[5];
|
|
dstPtr[4] = mPtr[4*6+3] * vPtr[3] + mPtr[4*6+4] * vPtr[4] + mPtr[4*6+5] * vPtr[5];
|
|
dstPtr[5] = mPtr[5*6+3] * vPtr[3] + mPtr[5*6+4] * vPtr[4] + mPtr[5*6+5] * vPtr[5];
|
|
} else {
|
|
gameLocal.Warning( "spatial inertia is not sparse for body %s", name.c_str() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::Save
|
|
================
|
|
*/
|
|
void idAFBody::Save( idSaveGame *saveFile ) {
|
|
saveFile->WriteFloat( linearFriction );
|
|
saveFile->WriteFloat( angularFriction );
|
|
saveFile->WriteFloat( contactFriction );
|
|
saveFile->WriteFloat( bouncyness );
|
|
saveFile->WriteInt( clipMask );
|
|
saveFile->WriteVec3( frictionDir );
|
|
saveFile->WriteVec3( contactMotorDir );
|
|
saveFile->WriteFloat( contactMotorVelocity );
|
|
saveFile->WriteFloat( contactMotorForce );
|
|
|
|
saveFile->WriteFloat( mass );
|
|
saveFile->WriteFloat( invMass );
|
|
saveFile->WriteVec3( centerOfMass );
|
|
saveFile->WriteMat3( inertiaTensor );
|
|
saveFile->WriteMat3( inverseInertiaTensor );
|
|
|
|
saveFile->WriteVec3( current->worldOrigin );
|
|
saveFile->WriteMat3( current->worldAxis );
|
|
saveFile->WriteVec6( current->spatialVelocity );
|
|
saveFile->WriteVec6( current->externalForce );
|
|
saveFile->WriteVec3( atRestOrigin );
|
|
saveFile->WriteMat3( atRestAxis );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFBody::Restore
|
|
================
|
|
*/
|
|
void idAFBody::Restore( idRestoreGame *saveFile ) {
|
|
saveFile->ReadFloat( linearFriction );
|
|
saveFile->ReadFloat( angularFriction );
|
|
saveFile->ReadFloat( contactFriction );
|
|
saveFile->ReadFloat( bouncyness );
|
|
saveFile->ReadInt( clipMask );
|
|
saveFile->ReadVec3( frictionDir );
|
|
saveFile->ReadVec3( contactMotorDir );
|
|
saveFile->ReadFloat( contactMotorVelocity );
|
|
saveFile->ReadFloat( contactMotorForce );
|
|
|
|
saveFile->ReadFloat( mass );
|
|
saveFile->ReadFloat( invMass );
|
|
saveFile->ReadVec3( centerOfMass );
|
|
saveFile->ReadMat3( inertiaTensor );
|
|
saveFile->ReadMat3( inverseInertiaTensor );
|
|
|
|
saveFile->ReadVec3( current->worldOrigin );
|
|
saveFile->ReadMat3( current->worldAxis );
|
|
saveFile->ReadVec6( current->spatialVelocity );
|
|
saveFile->ReadVec6( current->externalForce );
|
|
saveFile->ReadVec3( atRestOrigin );
|
|
saveFile->ReadMat3( atRestAxis );
|
|
}
|
|
|
|
|
|
|
|
//===============================================================
|
|
// M
|
|
// idAFTree MrE
|
|
// E
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idAFTree::Factor
|
|
|
|
factor matrix for the primary constraints in the tree
|
|
================
|
|
*/
|
|
void idAFTree::Factor( void ) const {
|
|
int i, j;
|
|
idAFBody *body;
|
|
idAFConstraint *child = NULL;
|
|
idMatX childI;
|
|
|
|
childI.SetData( 6, 6, MATX_ALLOCA( 6 * 6 ) );
|
|
|
|
// from the leaves up towards the root
|
|
for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) {
|
|
body = sortedBodies[i];
|
|
|
|
if ( body->children.Num() ) {
|
|
|
|
for ( j = 0; j < body->children.Num(); j++ ) {
|
|
|
|
child = body->children[j]->primaryConstraint;
|
|
|
|
// child->I = - child->body1->J.Transpose() * child->body1->I * child->body1->J;
|
|
childI.SetSize( child->J1.GetNumRows(), child->J1.GetNumRows() );
|
|
child->body1->J.TransposeMultiply( child->body1->I ).Multiply( childI, child->body1->J );
|
|
childI.Negate();
|
|
|
|
child->invI = childI;
|
|
if ( !child->invI.InverseFastSelf() ) {
|
|
gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for constraint '%s'",
|
|
child->invI.GetNumRows(), child->invI.GetNumColumns(), child->GetName().c_str() );
|
|
}
|
|
child->J = child->invI * child->J;
|
|
|
|
body->I -= child->J.TransposeMultiply( childI ) * child->J;
|
|
}
|
|
|
|
body->invI = body->I;
|
|
if ( !body->invI.InverseFastSelf() ) {
|
|
gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for body %s",
|
|
child->invI.GetNumRows(), child->invI.GetNumColumns(), body->GetName().c_str() );
|
|
}
|
|
if ( body->primaryConstraint ) {
|
|
body->J = body->invI * body->J;
|
|
}
|
|
}
|
|
else if ( body->primaryConstraint ) {
|
|
body->J = body->inverseWorldSpatialInertia * body->J;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFTree::Solve
|
|
|
|
solve for primary constraints in the tree
|
|
================
|
|
*/
|
|
void idAFTree::Solve( int auxiliaryIndex ) const {
|
|
int i, j;
|
|
idAFBody *body, *child;
|
|
idAFConstraint *primaryConstraint;
|
|
|
|
// from the leaves up towards the root
|
|
for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) {
|
|
body = sortedBodies[i];
|
|
|
|
for ( j = 0; j < body->children.Num(); j++ ) {
|
|
child = body->children[j];
|
|
primaryConstraint = child->primaryConstraint;
|
|
|
|
if ( !child->fl.isZero ) {
|
|
child->J.TransposeMultiplySub( primaryConstraint->s, child->s );
|
|
primaryConstraint->fl.isZero = false;
|
|
}
|
|
if ( !primaryConstraint->fl.isZero ) {
|
|
primaryConstraint->J.TransposeMultiplySub( body->s, primaryConstraint->s );
|
|
body->fl.isZero = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool useSymmetry = af_useSymmetry.GetBool();
|
|
|
|
// from the root down towards the leaves
|
|
for ( i = 0; i < sortedBodies.Num(); i++ ) {
|
|
body = sortedBodies[i];
|
|
primaryConstraint = body->primaryConstraint;
|
|
|
|
if ( primaryConstraint ) {
|
|
|
|
if ( useSymmetry && body->parent->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !primaryConstraint->fl.isZero ) {
|
|
primaryConstraint->s = primaryConstraint->invI * primaryConstraint->s;
|
|
}
|
|
primaryConstraint->J.MultiplySub( primaryConstraint->s, primaryConstraint->body2->s );
|
|
|
|
primaryConstraint->lm = primaryConstraint->s;
|
|
|
|
if ( useSymmetry && body->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) {
|
|
continue;
|
|
}
|
|
|
|
if ( body->children.Num() ) {
|
|
if ( !body->fl.isZero ) {
|
|
body->s = body->invI * body->s;
|
|
}
|
|
body->J.MultiplySub( body->s, primaryConstraint->s );
|
|
}
|
|
} else if ( body->children.Num() ) {
|
|
body->s = body->invI * body->s;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFTree::Response
|
|
|
|
calculate body forces in the tree in response to a constraint force
|
|
================
|
|
*/
|
|
void idAFTree::Response( const idAFConstraint *constraint, int row, int auxiliaryIndex ) const {
|
|
int i, j;
|
|
idAFBody *body;
|
|
idAFConstraint *child, *primaryConstraint;
|
|
idVecX v;
|
|
|
|
// if a single body don't waste time because there aren't any primary constraints
|
|
if ( sortedBodies.Num() == 1 ) {
|
|
body = constraint->body1;
|
|
if ( body->tree == this ) {
|
|
body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row );
|
|
body->responseIndex[body->numResponses++] = auxiliaryIndex;
|
|
}
|
|
else {
|
|
body = constraint->body2;
|
|
body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row );
|
|
body->responseIndex[body->numResponses++] = auxiliaryIndex;
|
|
}
|
|
return;
|
|
}
|
|
|
|
v.SetData( 6, VECX_ALLOCA( 6 ) );
|
|
|
|
// initialize right hand side to zero
|
|
for ( i = 0; i < sortedBodies.Num(); i++ ) {
|
|
body = sortedBodies[i];
|
|
primaryConstraint = body->primaryConstraint;
|
|
if ( primaryConstraint ) {
|
|
primaryConstraint->s.Zero();
|
|
primaryConstraint->fl.isZero = true;
|
|
}
|
|
body->s.Zero();
|
|
body->fl.isZero = true;
|
|
body->GetResponseForce( body->numResponses ).Zero();
|
|
}
|
|
|
|
// set right hand side for first constrained body
|
|
body = constraint->body1;
|
|
if ( body->tree == this ) {
|
|
body->InverseWorldSpatialInertiaMultiply( v, constraint->J1[row] );
|
|
primaryConstraint = body->primaryConstraint;
|
|
if ( primaryConstraint ) {
|
|
primaryConstraint->J1.Multiply( primaryConstraint->s, v );
|
|
primaryConstraint->fl.isZero = false;
|
|
}
|
|
for ( i = 0; i < body->children.Num(); i++ ) {
|
|
child = body->children[i]->primaryConstraint;
|
|
child->J2.Multiply( child->s, v );
|
|
child->fl.isZero = false;
|
|
}
|
|
body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row );
|
|
}
|
|
|
|
// set right hand side for second constrained body
|
|
body = constraint->body2;
|
|
if ( body && body->tree == this ) {
|
|
body->InverseWorldSpatialInertiaMultiply( v, constraint->J2[row] );
|
|
primaryConstraint = body->primaryConstraint;
|
|
if ( primaryConstraint ) {
|
|
primaryConstraint->J1.MultiplyAdd( primaryConstraint->s, v );
|
|
primaryConstraint->fl.isZero = false;
|
|
}
|
|
for ( i = 0; i < body->children.Num(); i++ ) {
|
|
child = body->children[i]->primaryConstraint;
|
|
child->J2.MultiplyAdd( child->s, v );
|
|
child->fl.isZero = false;
|
|
}
|
|
body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row );
|
|
}
|
|
|
|
|
|
// solve for primary constraints
|
|
Solve( auxiliaryIndex );
|
|
|
|
bool useSymmetry = af_useSymmetry.GetBool();
|
|
|
|
// store body forces in response to the constraint force
|
|
idVecX force;
|
|
for ( i = 0; i < sortedBodies.Num(); i++ ) {
|
|
body = sortedBodies[i];
|
|
|
|
if ( useSymmetry && body->maxAuxiliaryIndex < auxiliaryIndex ) {
|
|
continue;
|
|
}
|
|
|
|
force.SetData( 6, body->response + body->numResponses * 8 );
|
|
|
|
// add forces of all primary constraints acting on this body
|
|
primaryConstraint = body->primaryConstraint;
|
|
if ( primaryConstraint ) {
|
|
primaryConstraint->J1.TransposeMultiplyAdd( force, primaryConstraint->lm );
|
|
}
|
|
for ( j = 0; j < body->children.Num(); j++ ) {
|
|
child = body->children[j]->primaryConstraint;
|
|
child->J2.TransposeMultiplyAdd( force, child->lm );
|
|
}
|
|
|
|
body->responseIndex[body->numResponses++] = auxiliaryIndex;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFTree::CalculateForces
|
|
|
|
calculate forces on the bodies in the tree
|
|
================
|
|
*/
|
|
void idAFTree::CalculateForces( float timeStep ) const {
|
|
int i, j;
|
|
float invStep;
|
|
idAFBody *body;
|
|
idAFConstraint *child, *c, *primaryConstraint;
|
|
|
|
// forces on bodies
|
|
for ( i = 0; i < sortedBodies.Num(); i++ ) {
|
|
body = sortedBodies[i];
|
|
|
|
body->totalForce.SubVec6(0) = body->current->externalForce + body->auxForce.SubVec6(0);
|
|
}
|
|
|
|
// if a single body don't waste time because there aren't any primary constraints
|
|
if ( sortedBodies.Num() == 1 ) {
|
|
return;
|
|
}
|
|
|
|
invStep = 1.0f / timeStep;
|
|
|
|
// initialize right hand side
|
|
for ( i = 0; i < sortedBodies.Num(); i++ ) {
|
|
body = sortedBodies[i];
|
|
|
|
body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() );
|
|
body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep;
|
|
primaryConstraint = body->primaryConstraint;
|
|
if ( primaryConstraint ) {
|
|
// b = ( J * acc + c )
|
|
c = primaryConstraint;
|
|
c->s = c->J1 * c->body1->acceleration + c->J2 * c->body2->acceleration + invStep * ( c->c1 + c->c2 );
|
|
c->fl.isZero = false;
|
|
}
|
|
body->s.Zero();
|
|
body->fl.isZero = true;
|
|
}
|
|
|
|
// solve for primary constraints
|
|
Solve();
|
|
|
|
// calculate forces on bodies after applying primary constraints
|
|
for ( i = 0; i < sortedBodies.Num(); i++ ) {
|
|
body = sortedBodies[i];
|
|
|
|
// add forces of all primary constraints acting on this body
|
|
primaryConstraint = body->primaryConstraint;
|
|
if ( primaryConstraint ) {
|
|
primaryConstraint->J1.TransposeMultiplyAdd( body->totalForce, primaryConstraint->lm );
|
|
}
|
|
for ( j = 0; j < body->children.Num(); j++ ) {
|
|
child = body->children[j]->primaryConstraint;
|
|
child->J2.TransposeMultiplyAdd( body->totalForce, child->lm );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFTree::SetMaxSubTreeAuxiliaryIndex
|
|
================
|
|
*/
|
|
void idAFTree::SetMaxSubTreeAuxiliaryIndex( void ) {
|
|
int i, j;
|
|
idAFBody *body, *child;
|
|
|
|
// from the leaves up towards the root
|
|
for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) {
|
|
body = sortedBodies[i];
|
|
|
|
body->maxSubTreeAuxiliaryIndex = body->maxAuxiliaryIndex;
|
|
for ( j = 0; j < body->children.Num(); j++ ) {
|
|
child = body->children[j];
|
|
if ( child->maxSubTreeAuxiliaryIndex > body->maxSubTreeAuxiliaryIndex ) {
|
|
body->maxSubTreeAuxiliaryIndex = child->maxSubTreeAuxiliaryIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFTree::SortBodies_r
|
|
================
|
|
*/
|
|
void idAFTree::SortBodies_r( idList<idAFBody*>&sortedList, idAFBody *body ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < body->children.Num(); i++ ) {
|
|
sortedList.Append( body->children[i] );
|
|
}
|
|
for ( i = 0; i < body->children.Num(); i++ ) {
|
|
SortBodies_r( sortedList, body->children[i] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFTree::SortBodies
|
|
|
|
sort body list to make sure parents come first
|
|
================
|
|
*/
|
|
void idAFTree::SortBodies( void ) {
|
|
int i;
|
|
idAFBody *body;
|
|
|
|
// find the root
|
|
for ( i = 0; i < sortedBodies.Num(); i++ ) {
|
|
if ( !sortedBodies[i]->parent ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i >= sortedBodies.Num() ) {
|
|
gameLocal.Error( "Articulated figure tree has no root." );
|
|
}
|
|
|
|
body = sortedBodies[i];
|
|
sortedBodies.Clear();
|
|
sortedBodies.Append( body );
|
|
SortBodies_r( sortedBodies, body );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAFTree::DebugDraw
|
|
================
|
|
*/
|
|
void idAFTree::DebugDraw( const idVec4 &color ) const {
|
|
int i;
|
|
idAFBody *body;
|
|
|
|
for ( i = 1; i < sortedBodies.Num(); i++ ) {
|
|
body = sortedBodies[i];
|
|
gameRenderWorld->DebugArrow( color, body->parent->current->worldOrigin, body->current->worldOrigin, 1 );
|
|
}
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
// M
|
|
// idPhysics_AF MrE
|
|
// E
|
|
//===============================================================
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::EvaluateConstraints
|
|
================
|
|
*/
|
|
void idPhysics_AF::EvaluateConstraints( float timeStep ) {
|
|
int i;
|
|
float invTimeStep;
|
|
idAFBody *body;
|
|
idAFConstraint *c;
|
|
|
|
invTimeStep = 1.0f / timeStep;
|
|
|
|
// setup the constraint equations for the current position and orientation of the bodies
|
|
for ( i = 0; i < primaryConstraints.Num(); i++ ) {
|
|
c = primaryConstraints[i];
|
|
c->Evaluate( invTimeStep );
|
|
c->J = c->J2;
|
|
}
|
|
for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
auxiliaryConstraints[i]->Evaluate( invTimeStep );
|
|
}
|
|
|
|
// add contact constraints to the list with frame constraints
|
|
for ( i = 0; i < contactConstraints.Num(); i++ ) {
|
|
AddFrameConstraint( contactConstraints[i] );
|
|
}
|
|
|
|
// setup body primary constraint matrix
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
if ( body->primaryConstraint ) {
|
|
body->J = body->primaryConstraint->J1.Transpose();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::EvaluateBodies
|
|
================
|
|
*/
|
|
void idPhysics_AF::EvaluateBodies( float timeStep ) {
|
|
int i;
|
|
idAFBody *body;
|
|
idMat3 axis;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
// we transpose the axis before using it because idMat3 is column-major
|
|
axis = body->current->worldAxis.Transpose();
|
|
|
|
// if the center of mass is at the body point of reference
|
|
if ( body->centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) {
|
|
|
|
// spatial inertia in world space
|
|
body->I.Set( body->mass * mat3_identity, mat3_zero,
|
|
mat3_zero, axis * body->inertiaTensor * axis.Transpose() );
|
|
|
|
// inverse spatial inertia in world space
|
|
body->inverseWorldSpatialInertia.Set( body->invMass * mat3_identity, mat3_zero,
|
|
mat3_zero, axis * body->inverseInertiaTensor * axis.Transpose() );
|
|
|
|
body->fl.spatialInertiaSparse = true;
|
|
}
|
|
else {
|
|
idMat3 massMoment = body->mass * SkewSymmetric( body->centerOfMass );
|
|
|
|
// spatial inertia in world space
|
|
body->I.Set( body->mass * mat3_identity, massMoment,
|
|
massMoment.Transpose(), axis * body->inertiaTensor * axis.Transpose() );
|
|
|
|
// inverse spatial inertia in world space
|
|
body->inverseWorldSpatialInertia = body->I.InverseFast();
|
|
|
|
body->fl.spatialInertiaSparse = false;
|
|
}
|
|
|
|
// initialize auxiliary constraint force to zero
|
|
body->auxForce.Zero();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::AddFrameConstraints
|
|
================
|
|
*/
|
|
void idPhysics_AF::AddFrameConstraints( void ) {
|
|
int i;
|
|
|
|
// add frame constraints to auxiliary constraints
|
|
for ( i = 0; i < frameConstraints.Num(); i++ ) {
|
|
auxiliaryConstraints.Append( frameConstraints[i] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::RemoveFrameConstraints
|
|
================
|
|
*/
|
|
void idPhysics_AF::RemoveFrameConstraints( void ) {
|
|
// remove all the frame constraints from the auxiliary constraints
|
|
auxiliaryConstraints.SetNum( auxiliaryConstraints.Num() - frameConstraints.Num(), false );
|
|
frameConstraints.SetNum( 0, false );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::ApplyFriction
|
|
================
|
|
*/
|
|
void idPhysics_AF::ApplyFriction( float timeStep, float endTimeMSec ) {
|
|
int i;
|
|
float invTimeStep;
|
|
|
|
if ( af_skipFriction.GetBool() ) {
|
|
return;
|
|
}
|
|
|
|
if ( jointFrictionDentStart < MS2SEC( endTimeMSec ) && jointFrictionDentEnd > MS2SEC( endTimeMSec ) ) {
|
|
float halfTime = ( jointFrictionDentEnd - jointFrictionDentStart ) * 0.5f;
|
|
if ( jointFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) {
|
|
jointFrictionDentScale = 1.0f - ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart ) / halfTime;
|
|
} else {
|
|
jointFrictionDentScale = jointFrictionDent + ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart - halfTime ) / halfTime;
|
|
}
|
|
} else {
|
|
jointFrictionDentScale = 0.0f;
|
|
}
|
|
|
|
if ( contactFrictionDentStart < MS2SEC( endTimeMSec ) && contactFrictionDentEnd > MS2SEC( endTimeMSec ) ) {
|
|
float halfTime = ( contactFrictionDentEnd - contactFrictionDentStart ) * 0.5f;
|
|
if ( contactFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) {
|
|
contactFrictionDentScale = 1.0f - ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart ) / halfTime;
|
|
} else {
|
|
contactFrictionDentScale = contactFrictionDent + ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart - halfTime ) / halfTime;
|
|
}
|
|
} else {
|
|
contactFrictionDentScale = 0.0f;
|
|
}
|
|
|
|
invTimeStep = 1.0f / timeStep;
|
|
|
|
for ( i = 0; i < primaryConstraints.Num(); i++ ) {
|
|
primaryConstraints[i]->ApplyFriction( invTimeStep );
|
|
}
|
|
for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
auxiliaryConstraints[i]->ApplyFriction( invTimeStep );
|
|
}
|
|
for ( i = 0; i < frameConstraints.Num(); i++ ) {
|
|
frameConstraints[i]->ApplyFriction( invTimeStep );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::PrimaryFactor
|
|
================
|
|
*/
|
|
void idPhysics_AF::PrimaryFactor( void ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < trees.Num(); i++ ) {
|
|
trees[i]->Factor();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::PrimaryForces
|
|
================
|
|
*/
|
|
void idPhysics_AF::PrimaryForces( float timeStep ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < trees.Num(); i++ ) {
|
|
trees[i]->CalculateForces( timeStep );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::AuxiliaryForces
|
|
================
|
|
*/
|
|
void idPhysics_AF::AuxiliaryForces( float timeStep ) {
|
|
int i, j, k, l, n, m, s, numAuxConstraints, *index, *boxIndex;
|
|
float *ptr, *j1, *j2, *dstPtr, *forcePtr;
|
|
float invStep, u;
|
|
idAFBody *body;
|
|
idAFConstraint *constraint;
|
|
idVecX tmp;
|
|
idMatX jmk;
|
|
idVecX rhs, w, lm, lo, hi;
|
|
|
|
// get the number of one dimensional auxiliary constraints
|
|
for ( numAuxConstraints = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
numAuxConstraints += auxiliaryConstraints[i]->J1.GetNumRows();
|
|
}
|
|
|
|
if ( numAuxConstraints == 0 ) {
|
|
return;
|
|
}
|
|
|
|
// allocate memory to store the body response to auxiliary constraint forces
|
|
forcePtr = (float *) _alloca16( bodies.Num() * numAuxConstraints * 8 * sizeof( float ) );
|
|
index = (int *) _alloca16( bodies.Num() * numAuxConstraints * sizeof( int ) );
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
body->response = forcePtr;
|
|
body->responseIndex = index;
|
|
body->numResponses = 0;
|
|
body->maxAuxiliaryIndex = 0;
|
|
forcePtr += numAuxConstraints * 8;
|
|
index += numAuxConstraints;
|
|
}
|
|
|
|
// set on each body the largest index of an auxiliary constraint constraining the body
|
|
if ( af_useSymmetry.GetBool() ) {
|
|
for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
constraint = auxiliaryConstraints[i];
|
|
for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) {
|
|
if ( k > constraint->body1->maxAuxiliaryIndex ) {
|
|
constraint->body1->maxAuxiliaryIndex = k;
|
|
}
|
|
if ( constraint->body2 && k > constraint->body2->maxAuxiliaryIndex ) {
|
|
constraint->body2->maxAuxiliaryIndex = k;
|
|
}
|
|
}
|
|
}
|
|
for ( i = 0; i < trees.Num(); i++ ) {
|
|
trees[i]->SetMaxSubTreeAuxiliaryIndex();
|
|
}
|
|
}
|
|
|
|
// calculate forces of primary constraints in response to the auxiliary constraint forces
|
|
for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
constraint = auxiliaryConstraints[i];
|
|
|
|
for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) {
|
|
|
|
// calculate body forces in the tree in response to the constraint force
|
|
constraint->body1->tree->Response( constraint, j, k );
|
|
// if there is a second body which is part of a different tree
|
|
if ( constraint->body2 && constraint->body2->tree != constraint->body1->tree ) {
|
|
// calculate body forces in the second tree in response to the constraint force
|
|
constraint->body2->tree->Response( constraint, j, k );
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: the rows are 16 byte padded
|
|
jmk.SetData( numAuxConstraints, ((numAuxConstraints+3)&~3), MATX_ALLOCA( numAuxConstraints * ((numAuxConstraints+3)&~3) ) );
|
|
tmp.SetData( 6, VECX_ALLOCA( 6 ) );
|
|
|
|
// create constraint matrix for auxiliary constraints using a mass matrix adjusted for the primary constraints
|
|
for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
constraint = auxiliaryConstraints[i];
|
|
|
|
for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) {
|
|
constraint->body1->InverseWorldSpatialInertiaMultiply( tmp, constraint->J1[j] );
|
|
j1 = tmp.ToFloatPtr();
|
|
ptr = constraint->body1->response;
|
|
index = constraint->body1->responseIndex;
|
|
dstPtr = jmk[k];
|
|
s = af_useSymmetry.GetBool() ? k + 1 : numAuxConstraints;
|
|
for ( l = n = 0, m = index[n]; n < constraint->body1->numResponses && m < s; n++, m = index[n] ) {
|
|
while( l < m ) {
|
|
dstPtr[l++] = 0.0f;
|
|
}
|
|
dstPtr[l++] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] +
|
|
j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5];
|
|
ptr += 8;
|
|
}
|
|
|
|
while( l < s ) {
|
|
dstPtr[l++] = 0.0f;
|
|
}
|
|
|
|
if ( constraint->body2 ) {
|
|
constraint->body2->InverseWorldSpatialInertiaMultiply( tmp, constraint->J2[j] );
|
|
j2 = tmp.ToFloatPtr();
|
|
ptr = constraint->body2->response;
|
|
index = constraint->body2->responseIndex;
|
|
for ( n = 0, m = index[n]; n < constraint->body2->numResponses && m < s; n++, m = index[n] ) {
|
|
dstPtr[m] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] +
|
|
j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5];
|
|
ptr += 8;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( af_useSymmetry.GetBool() ) {
|
|
n = jmk.GetNumColumns();
|
|
for ( i = 0; i < numAuxConstraints; i++ ) {
|
|
ptr = jmk.ToFloatPtr() + ( i + 1 ) * n + i;
|
|
dstPtr = jmk.ToFloatPtr() + i * n + i + 1;
|
|
for ( j = i+1; j < numAuxConstraints; j++ ) {
|
|
*dstPtr++ = *ptr;
|
|
ptr += n;
|
|
}
|
|
}
|
|
}
|
|
|
|
invStep = 1.0f / timeStep;
|
|
|
|
// calculate body acceleration
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() );
|
|
body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep;
|
|
}
|
|
|
|
rhs.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) );
|
|
lo.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) );
|
|
hi.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) );
|
|
lm.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) );
|
|
boxIndex = (int *) _alloca16( numAuxConstraints * sizeof( int ) );
|
|
|
|
// set first index for special box constrained variables
|
|
for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
auxiliaryConstraints[i]->firstIndex = k;
|
|
k += auxiliaryConstraints[i]->J1.GetNumRows();
|
|
}
|
|
|
|
// initialize right hand side and low and high bounds for auxiliary constraints
|
|
for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
constraint = auxiliaryConstraints[i];
|
|
n = k;
|
|
|
|
for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) {
|
|
|
|
j1 = constraint->J1[j];
|
|
ptr = constraint->body1->acceleration.ToFloatPtr();
|
|
rhs[k] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] + j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5];
|
|
rhs[k] += constraint->c1[j] * invStep;
|
|
|
|
if ( constraint->body2 ) {
|
|
j2 = constraint->J2[j];
|
|
ptr = constraint->body2->acceleration.ToFloatPtr();
|
|
rhs[k] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] + j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5];
|
|
rhs[k] += constraint->c2[j] * invStep;
|
|
}
|
|
|
|
rhs[k] = -rhs[k];
|
|
lo[k] = constraint->lo[j];
|
|
hi[k] = constraint->hi[j];
|
|
|
|
if ( constraint->boxIndex[j] >= 0 ) {
|
|
if ( constraint->boxConstraint->fl.isPrimary ) {
|
|
gameLocal.Error( "cannot reference primary constraints for the box index" );
|
|
}
|
|
boxIndex[k] = constraint->boxConstraint->firstIndex + constraint->boxIndex[j];
|
|
}
|
|
else {
|
|
boxIndex[k] = -1;
|
|
}
|
|
jmk[k][k] += constraint->e[j] * invStep;
|
|
}
|
|
}
|
|
|
|
#ifdef AF_TIMINGS
|
|
timer_lcp.Start();
|
|
#endif
|
|
|
|
// calculate lagrange multipliers for auxiliary constraints
|
|
if ( !lcp->Solve( jmk, lm, rhs, lo, hi, boxIndex ) ) {
|
|
return; // bad monkey!
|
|
}
|
|
|
|
#ifdef AF_TIMINGS
|
|
timer_lcp.Stop();
|
|
#endif
|
|
|
|
// calculate auxiliary constraint forces
|
|
for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
constraint = auxiliaryConstraints[i];
|
|
|
|
for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) {
|
|
constraint->lm[j] = u = lm[k];
|
|
|
|
j1 = constraint->J1[j];
|
|
ptr = constraint->body1->auxForce.ToFloatPtr();
|
|
ptr[0] += j1[0] * u; ptr[1] += j1[1] * u; ptr[2] += j1[2] * u;
|
|
ptr[3] += j1[3] * u; ptr[4] += j1[4] * u; ptr[5] += j1[5] * u;
|
|
|
|
if ( constraint->body2 ) {
|
|
j2 = constraint->J2[j];
|
|
ptr = constraint->body2->auxForce.ToFloatPtr();
|
|
ptr[0] += j2[0] * u; ptr[1] += j2[1] * u; ptr[2] += j2[2] * u;
|
|
ptr[3] += j2[3] * u; ptr[4] += j2[4] * u; ptr[5] += j2[5] * u;
|
|
}
|
|
}
|
|
}
|
|
|
|
// recalculate primary constraint forces in response to auxiliary constraint forces
|
|
PrimaryForces( timeStep );
|
|
|
|
// clear pointers pointing to stack space so tools don't get confused
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
body->response = NULL;
|
|
body->responseIndex = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::VerifyContactConstraints
|
|
================
|
|
*/
|
|
void idPhysics_AF::VerifyContactConstraints( void ) {
|
|
#if 0
|
|
int i;
|
|
float impulseNumerator, impulseDenominator;
|
|
idVec3 r, velocity, normalVelocity, normal, impulse;
|
|
idAFBody *body;
|
|
|
|
for ( i = 0; i < contactConstraints.Num(); i++ ) {
|
|
body = contactConstraints[i]->body1;
|
|
const contactInfo_t &contact = contactConstraints[i]->GetContact();
|
|
|
|
r = contact.point - body->GetCenterOfMass();
|
|
|
|
// calculate velocity at contact point
|
|
velocity = body->GetLinearVelocity() + body->GetAngularVelocity().Cross( r );
|
|
|
|
// velocity along normal vector
|
|
normalVelocity = ( velocity * contact.normal ) * contact.normal;
|
|
|
|
// if moving towards the surface at the contact point
|
|
if ( normalVelocity * contact.normal < 0.0f ) {
|
|
// calculate impulse
|
|
normal = -normalVelocity;
|
|
impulseNumerator = normal.Normalize();
|
|
impulseDenominator = body->GetInverseMass() + ( ( body->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal );
|
|
impulse = (impulseNumerator / impulseDenominator) * normal * 1.0001f;
|
|
|
|
// apply impulse
|
|
body->SetLinearVelocity( body->GetLinearVelocity() + impulse );
|
|
body->SetAngularVelocity( body->GetAngularVelocity() + r.Cross( impulse ) );
|
|
}
|
|
}
|
|
#else
|
|
int i;
|
|
idAFBody *body;
|
|
idVec3 normal;
|
|
|
|
for ( i = 0; i < contactConstraints.Num(); i++ ) {
|
|
body = contactConstraints[i]->body1;
|
|
normal = contactConstraints[i]->GetContact().normal;
|
|
if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) {
|
|
body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal;
|
|
}
|
|
body = contactConstraints[i]->body2;
|
|
if ( !body ) {
|
|
continue;
|
|
}
|
|
normal = -normal;
|
|
if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) {
|
|
body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::Evolve
|
|
================
|
|
*/
|
|
void idPhysics_AF::Evolve( float timeStep ) {
|
|
int i;
|
|
float angle;
|
|
idVec3 vec;
|
|
idAFBody *body;
|
|
idVec6 force;
|
|
idRotation rotation;
|
|
float vSqr, maxLinearVelocity, maxAngularVelocity;
|
|
|
|
maxLinearVelocity = af_maxLinearVelocity.GetFloat() / timeStep;
|
|
maxAngularVelocity = af_maxAngularVelocity.GetFloat() / timeStep;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
// calculate the spatial velocity for the next physics state
|
|
body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() );
|
|
body->next->spatialVelocity = body->current->spatialVelocity + timeStep * body->acceleration.SubVec6(0);
|
|
|
|
if ( maxLinearVelocity > 0.0f ) {
|
|
// cap the linear velocity
|
|
vSqr = body->next->spatialVelocity.SubVec3(0).LengthSqr();
|
|
if ( vSqr > Square( maxLinearVelocity ) ) {
|
|
body->next->spatialVelocity.SubVec3(0) *= idMath::InvSqrt( vSqr ) * maxLinearVelocity;
|
|
}
|
|
}
|
|
|
|
if ( maxAngularVelocity > 0.0f ) {
|
|
// cap the angular velocity
|
|
vSqr = body->next->spatialVelocity.SubVec3(1).LengthSqr();
|
|
if ( vSqr > Square( maxAngularVelocity ) ) {
|
|
body->next->spatialVelocity.SubVec3(1) *= idMath::InvSqrt( vSqr ) * maxAngularVelocity;
|
|
}
|
|
}
|
|
}
|
|
|
|
// make absolutely sure all contact constraints are satisfied
|
|
VerifyContactConstraints();
|
|
|
|
// calculate the position of the bodies for the next physics state
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
// translate world origin
|
|
body->next->worldOrigin = body->current->worldOrigin + timeStep * body->next->spatialVelocity.SubVec3( 0 );
|
|
|
|
// convert angular velocity to a rotation matrix
|
|
vec = body->next->spatialVelocity.SubVec3( 1 );
|
|
angle = -timeStep * (float) RAD2DEG( vec.Normalize() );
|
|
rotation = idRotation( vec3_origin, vec, angle );
|
|
rotation.Normalize180();
|
|
|
|
// rotate world axis
|
|
body->next->worldAxis = body->current->worldAxis * rotation.ToMat3();
|
|
body->next->worldAxis.OrthoNormalizeSelf();
|
|
|
|
// linear and angular friction
|
|
body->next->spatialVelocity.SubVec3(0) -= body->linearFriction * body->next->spatialVelocity.SubVec3(0);
|
|
body->next->spatialVelocity.SubVec3(1) -= body->angularFriction * body->next->spatialVelocity.SubVec3(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::CollisionImpulse
|
|
|
|
apply impulse to the colliding bodies
|
|
the current state of the body should be set to the moment of impact
|
|
this is silly as it doesn't take the AF structure into account
|
|
================
|
|
*/
|
|
bool idPhysics_AF::CollisionImpulse( float timeStep, idAFBody *body, trace_t &collision ) {
|
|
idVec3 r, velocity, impulse;
|
|
idMat3 inverseWorldInertiaTensor;
|
|
float impulseNumerator, impulseDenominator;
|
|
impactInfo_t info;
|
|
idEntity *ent;
|
|
|
|
ent = gameLocal.entities[collision.c.entityNum];
|
|
if ( ent == self ) {
|
|
return false;
|
|
}
|
|
|
|
// get info from other entity involved
|
|
ent->GetImpactInfo( self, collision.c.id, collision.c.point, &info );
|
|
// collision point relative to the body center of mass
|
|
r = collision.c.point - (body->current->worldOrigin + body->centerOfMass * body->current->worldAxis);
|
|
// the velocity at the collision point
|
|
velocity = body->current->spatialVelocity.SubVec3(0) + body->current->spatialVelocity.SubVec3(1).Cross(r);
|
|
// subtract velocity of other entity
|
|
velocity -= info.velocity;
|
|
// never stick
|
|
if ( velocity * collision.c.normal > 0.0f ) {
|
|
velocity = collision.c.normal;
|
|
}
|
|
inverseWorldInertiaTensor = body->current->worldAxis.Transpose() * body->inverseInertiaTensor * body->current->worldAxis;
|
|
impulseNumerator = -( 1.0f + body->bouncyness ) * ( velocity * collision.c.normal );
|
|
impulseDenominator = body->invMass + ( ( inverseWorldInertiaTensor * r.Cross( collision.c.normal ) ).Cross( r ) * collision.c.normal );
|
|
if ( info.invMass ) {
|
|
impulseDenominator += info.invMass + ( ( info.invInertiaTensor * info.position.Cross( collision.c.normal ) ).Cross( info.position ) * collision.c.normal );
|
|
}
|
|
impulse = (impulseNumerator / impulseDenominator) * collision.c.normal;
|
|
|
|
// apply impact to other entity
|
|
ent->ApplyImpulse( self, collision.c.id, collision.c.point, -impulse );
|
|
|
|
// callback to self to let the entity know about the impact
|
|
return self->Collide( collision, velocity );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::ApplyCollisions
|
|
================
|
|
*/
|
|
bool idPhysics_AF::ApplyCollisions( float timeStep ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < collisions.Num(); i++ ) {
|
|
if ( CollisionImpulse( timeStep, collisions[i].body, collisions[i].trace ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetupCollisionForBody
|
|
================
|
|
*/
|
|
idEntity *idPhysics_AF::SetupCollisionForBody( idAFBody *body ) const {
|
|
int i;
|
|
idAFBody *b;
|
|
idEntity *passEntity;
|
|
|
|
passEntity = NULL;
|
|
|
|
if ( !selfCollision || !body->fl.selfCollision || af_skipSelfCollision.GetBool() ) {
|
|
|
|
// disable all bodies
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
bodies[i]->clipModel->Disable();
|
|
}
|
|
|
|
// don't collide with world collision model if attached to the world
|
|
for ( i = 0; i < body->constraints.Num(); i++ ) {
|
|
if ( !body->constraints[i]->fl.noCollision ) {
|
|
continue;
|
|
}
|
|
// if this constraint attaches the body to the world
|
|
if ( body->constraints[i]->body2 == NULL ) {
|
|
// don't collide with the world collision model
|
|
passEntity = gameLocal.world;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
// enable all bodies that have self collision
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
if ( bodies[i]->fl.selfCollision ) {
|
|
bodies[i]->clipModel->Enable();
|
|
} else {
|
|
bodies[i]->clipModel->Disable();
|
|
}
|
|
}
|
|
|
|
// don't let the body collide with itself
|
|
body->clipModel->Disable();
|
|
|
|
// disable any bodies attached with constraints
|
|
for ( i = 0; i < body->constraints.Num(); i++ ) {
|
|
if ( !body->constraints[i]->fl.noCollision ) {
|
|
continue;
|
|
}
|
|
// if this constraint attaches the body to the world
|
|
if ( body->constraints[i]->body2 == NULL ) {
|
|
// don't collide with the world collision model
|
|
passEntity = gameLocal.world;
|
|
} else {
|
|
if ( body->constraints[i]->body1 == body ) {
|
|
b = body->constraints[i]->body2;
|
|
} else if ( body->constraints[i]->body2 == body ) {
|
|
b = body->constraints[i]->body1;
|
|
} else {
|
|
continue;
|
|
}
|
|
// don't collide with this body
|
|
b->clipModel->Disable();
|
|
}
|
|
}
|
|
}
|
|
|
|
return passEntity;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::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
|
|
assumes all bodies are linked for collision detection and relinks all bodies after moving them
|
|
================
|
|
*/
|
|
void idPhysics_AF::CheckForCollisions( float timeStep ) {
|
|
// #define TEST_COLLISION_DETECTION
|
|
int i, index;
|
|
idAFBody *body;
|
|
idMat3 axis;
|
|
idRotation rotation;
|
|
trace_t collision;
|
|
idEntity *passEntity;
|
|
|
|
// clear list with collisions
|
|
collisions.SetNum( 0, false );
|
|
|
|
if ( !enableCollision ) {
|
|
return;
|
|
}
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
if ( body->clipMask != 0 ) {
|
|
|
|
passEntity = SetupCollisionForBody( body );
|
|
|
|
#ifdef TEST_COLLISION_DETECTION
|
|
bool startsolid = false;
|
|
if ( gameLocal.clip.Contents( body->current->worldOrigin, body->clipModel,
|
|
body->current->worldAxis, body->clipMask, passEntity ) ) {
|
|
startsolid = true;
|
|
}
|
|
#endif
|
|
|
|
TransposeMultiply( body->current->worldAxis, body->next->worldAxis, axis );
|
|
rotation = axis.ToRotation();
|
|
rotation.SetOrigin( body->current->worldOrigin );
|
|
|
|
// if there was a collision
|
|
if ( gameLocal.clip.Motion( collision, body->current->worldOrigin, body->next->worldOrigin, rotation,
|
|
body->clipModel, body->current->worldAxis, body->clipMask, passEntity ) ) {
|
|
|
|
// set the next state to the state at the moment of impact
|
|
body->next->worldOrigin = collision.endpos;
|
|
body->next->worldAxis = collision.endAxis;
|
|
|
|
// add collision to the list
|
|
index = collisions.Num();
|
|
collisions.SetNum( index + 1, false );
|
|
collisions[index].trace = collision;
|
|
collisions[index].body = body;
|
|
}
|
|
|
|
#ifdef TEST_COLLISION_DETECTION
|
|
if ( gameLocal.clip.Contents( body->next->worldOrigin, body->clipModel,
|
|
body->next->worldAxis, body->clipMask, passEntity ) ) {
|
|
if ( !startsolid ) {
|
|
int bah = 1;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
body->clipModel->Link( gameLocal.clip, self, body->clipModel->GetId(), body->next->worldOrigin, body->next->worldAxis );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::EvaluateContacts
|
|
================
|
|
*/
|
|
bool idPhysics_AF::EvaluateContacts( void ) {
|
|
int i, j, k, numContacts, numBodyContacts;
|
|
idAFBody *body;
|
|
contactInfo_t contactInfo[10];
|
|
idEntity *passEntity;
|
|
idVecX dir( 6, VECX_ALLOCA( 6 ) );
|
|
|
|
// evaluate bodies
|
|
EvaluateBodies( current.lastTimeStep );
|
|
|
|
// remove all existing contacts
|
|
ClearContacts();
|
|
|
|
contactBodies.SetNum( 0, false );
|
|
|
|
if ( !enableCollision ) {
|
|
return false;
|
|
}
|
|
|
|
// find all the contacts
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
if ( body->clipMask == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
passEntity = SetupCollisionForBody( body );
|
|
|
|
body->InverseWorldSpatialInertiaMultiply( dir, body->current->externalForce.ToFloatPtr() );
|
|
dir.SubVec6(0) = body->current->spatialVelocity + current.lastTimeStep * dir.SubVec6(0);
|
|
dir.SubVec3(0).Normalize();
|
|
dir.SubVec3(1).Normalize();
|
|
|
|
numContacts = gameLocal.clip.Contacts( contactInfo, 10, body->current->worldOrigin, dir.SubVec6(0), 2.0f, //CONTACT_EPSILON,
|
|
body->clipModel, body->current->worldAxis, body->clipMask, passEntity );
|
|
|
|
#if 1
|
|
// merge nearby contacts between the same bodies
|
|
// and assure there are at most three planar contacts between any pair of bodies
|
|
for ( j = 0; j < numContacts; j++ ) {
|
|
|
|
numBodyContacts = 0;
|
|
for ( k = 0; k < contacts.Num(); k++ ) {
|
|
if ( contacts[k].entityNum == contactInfo[j].entityNum ) {
|
|
if ( ( contacts[k].id == i && contactInfo[j].id == contactBodies[k] ) ||
|
|
( contactBodies[k] == i && contacts[k].id == contactInfo[j].id ) ) {
|
|
|
|
if ( ( contacts[k].point - contactInfo[j].point ).LengthSqr() < Square( 2.0f ) ) {
|
|
break;
|
|
}
|
|
if ( idMath::Fabs( contacts[k].normal * contactInfo[j].normal ) > 0.9f ) {
|
|
numBodyContacts++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( k >= contacts.Num() && numBodyContacts < 3 ) {
|
|
contacts.Append( contactInfo[j] );
|
|
contactBodies.Append( i );
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
for ( j = 0; j < numContacts; j++ ) {
|
|
contacts.Append( contactInfo[j] );
|
|
contactBodies.Append( i );
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
AddContactEntitiesForContacts();
|
|
|
|
return ( contacts.Num() != 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetupContactConstraints
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetupContactConstraints( void ) {
|
|
int i;
|
|
|
|
// make sure enough contact constraints are allocated
|
|
contactConstraints.AssureSizeAlloc( contacts.Num(), idListNewElement<idAFConstraint_Contact> );
|
|
contactConstraints.SetNum( contacts.Num(), false );
|
|
|
|
// setup contact constraints
|
|
for ( i = 0; i < contacts.Num(); i++ ) {
|
|
// add contact constraint
|
|
contactConstraints[i]->physics = this;
|
|
if ( contacts[i].entityNum == self->entityNumber ) {
|
|
contactConstraints[i]->Setup( bodies[contactBodies[i]], bodies[ contacts[i].id ], contacts[i] );
|
|
}
|
|
else {
|
|
contactConstraints[i]->Setup( bodies[contactBodies[i]], NULL, contacts[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::ApplyContactForces
|
|
================
|
|
*/
|
|
void idPhysics_AF::ApplyContactForces( void ) {
|
|
#if 0
|
|
int i;
|
|
idEntity *ent;
|
|
idVec3 force;
|
|
|
|
for ( i = 0; i < contactConstraints.Num(); i++ ) {
|
|
if ( contactConstraints[i]->body2 != NULL ) {
|
|
continue;
|
|
}
|
|
const contactInfo_t &contact = contactConstraints[i]->GetContact();
|
|
ent = gameLocal.entities[contact.entityNum];
|
|
if ( !ent ) {
|
|
continue;
|
|
}
|
|
force.Zero();
|
|
ent->AddForce( self, contact.id, contact.point, force );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::ClearExternalForce
|
|
================
|
|
*/
|
|
void idPhysics_AF::ClearExternalForce( void ) {
|
|
int i;
|
|
idAFBody *body;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
// clear external force
|
|
body->current->externalForce.Zero();
|
|
body->next->externalForce.Zero();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::AddGravity
|
|
================
|
|
*/
|
|
void idPhysics_AF::AddGravity( void ) {
|
|
int i;
|
|
idAFBody *body;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
// add gravitational force
|
|
body->current->externalForce.SubVec3( 0 ) += body->mass * gravityVector;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SwapStates
|
|
================
|
|
*/
|
|
void idPhysics_AF::SwapStates( void ) {
|
|
int i;
|
|
idAFBody *body;
|
|
AFBodyPState_t *swap;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
|
|
body = bodies[i];
|
|
|
|
// swap the current and next state for next simulation step
|
|
swap = body->current;
|
|
body->current = body->next;
|
|
body->next = swap;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::UpdateClipModels
|
|
================
|
|
*/
|
|
void idPhysics_AF::UpdateClipModels( void ) {
|
|
int i;
|
|
idAFBody *body;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
body->clipModel->Link( gameLocal.clip, self, body->clipModel->GetId(), body->current->worldOrigin, body->current->worldAxis );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetSuspendSpeed
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetSuspendSpeed( const idVec2 &velocity, const idVec2 &acceleration ) {
|
|
this->suspendVelocity = velocity;
|
|
this->suspendAcceleration = acceleration;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetSuspendTime
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetSuspendTime( const float minTime, const float maxTime ) {
|
|
this->minMoveTime = minTime;
|
|
this->maxMoveTime = maxTime;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetSuspendTolerance
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetSuspendTolerance( const float noMoveTime, const float noMoveTranslation, const float noMoveRotation ) {
|
|
this->noMoveTime = noMoveTime;
|
|
this->noMoveTranslation = noMoveTranslation;
|
|
this->noMoveRotation = noMoveRotation;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetTimeScaleRamp
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetTimeScaleRamp( const float start, const float end ) {
|
|
timeScaleRampStart = start;
|
|
timeScaleRampEnd = end;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetJointFrictionDent
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetJointFrictionDent( const float dent, const float start, const float end ) {
|
|
jointFrictionDent = dent;
|
|
jointFrictionDentStart = start;
|
|
jointFrictionDentEnd = end;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetJointFrictionScale
|
|
================
|
|
*/
|
|
float idPhysics_AF::GetJointFrictionScale( void ) const {
|
|
if ( jointFrictionDentScale > 0.0f ) {
|
|
return jointFrictionDentScale;
|
|
} else if ( jointFrictionScale > 0.0f ) {
|
|
return jointFrictionScale;
|
|
} else if ( af_jointFrictionScale.GetFloat() > 0.0f ) {
|
|
return af_jointFrictionScale.GetFloat();
|
|
}
|
|
return 1.0f;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetContactFrictionDent
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetContactFrictionDent( const float dent, const float start, const float end ) {
|
|
contactFrictionDent = dent;
|
|
contactFrictionDentStart = start;
|
|
contactFrictionDentEnd = end;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetContactFrictionScale
|
|
================
|
|
*/
|
|
float idPhysics_AF::GetContactFrictionScale( void ) const {
|
|
if ( contactFrictionDentScale > 0.0f ) {
|
|
return contactFrictionDentScale;
|
|
} else if ( contactFrictionScale > 0.0f ) {
|
|
return contactFrictionScale;
|
|
} else if ( af_contactFrictionScale.GetFloat() > 0.0f ) {
|
|
return af_contactFrictionScale.GetFloat();
|
|
}
|
|
return 1.0f;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::TestIfAtRest
|
|
================
|
|
*/
|
|
bool idPhysics_AF::TestIfAtRest( float timeStep ) {
|
|
int i;
|
|
float translationSqr, maxTranslationSqr, rotation, maxRotation;
|
|
idAFBody *body;
|
|
|
|
if ( current.atRest >= 0 ) {
|
|
return true;
|
|
}
|
|
|
|
current.activateTime += timeStep;
|
|
|
|
// if the simulation should never be suspended before a certaint amount of time passed
|
|
if ( minMoveTime > 0.0f && current.activateTime < minMoveTime ) {
|
|
return false;
|
|
}
|
|
|
|
// if the simulation should always be suspended after a certain amount time passed
|
|
if ( maxMoveTime > 0.0f && current.activateTime > maxMoveTime ) {
|
|
return true;
|
|
}
|
|
|
|
// test if all bodies hardly moved over a period of time
|
|
if ( current.noMoveTime == 0.0f ) {
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
body->atRestOrigin = body->current->worldOrigin;
|
|
body->atRestAxis = body->current->worldAxis;
|
|
}
|
|
current.noMoveTime += timeStep;
|
|
}
|
|
else if ( current.noMoveTime > noMoveTime ) {
|
|
current.noMoveTime = 0.0f;
|
|
maxTranslationSqr = 0.0f;
|
|
maxRotation = 0.0f;
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
translationSqr = ( body->current->worldOrigin - body->atRestOrigin ).LengthSqr();
|
|
if ( translationSqr > maxTranslationSqr ) {
|
|
maxTranslationSqr = translationSqr;
|
|
}
|
|
rotation = ( body->atRestAxis.Transpose() * body->current->worldAxis ).ToRotation().GetAngle();
|
|
if ( rotation > maxRotation ) {
|
|
maxRotation = rotation;
|
|
}
|
|
}
|
|
|
|
if ( maxTranslationSqr < Square( noMoveTranslation ) && maxRotation < noMoveRotation ) {
|
|
// hardly moved over a period of time so the articulated figure may come to rest
|
|
return true;
|
|
}
|
|
} else {
|
|
current.noMoveTime += timeStep;
|
|
}
|
|
|
|
// test if the velocity or acceleration of any body is still too large to come to rest
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
if ( body->current->spatialVelocity.SubVec3(0).LengthSqr() > Square( suspendVelocity[0] ) ) {
|
|
return false;
|
|
}
|
|
if ( body->current->spatialVelocity.SubVec3(1).LengthSqr() > Square( suspendVelocity[1] ) ) {
|
|
return false;
|
|
}
|
|
if ( body->acceleration.SubVec3(0).LengthSqr() > Square( suspendAcceleration[0] ) ) {
|
|
return false;
|
|
}
|
|
if ( body->acceleration.SubVec3(1).LengthSqr() > Square( suspendAcceleration[1] ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// all bodies have a velocity and acceleration small enough to come to rest
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::Rest
|
|
================
|
|
*/
|
|
void idPhysics_AF::Rest( void ) {
|
|
int i;
|
|
|
|
current.atRest = gameLocal.time;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
bodies[i]->current->spatialVelocity.Zero();
|
|
bodies[i]->current->externalForce.Zero();
|
|
}
|
|
|
|
self->BecomeInactive( TH_PHYSICS );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::Activate
|
|
================
|
|
*/
|
|
void idPhysics_AF::Activate( void ) {
|
|
// if the articulated figure was at rest
|
|
if ( current.atRest >= 0 ) {
|
|
// normally gravity is added at the end of a simulation frame
|
|
// if the figure was at rest add gravity here so it is applied this simulation frame
|
|
AddGravity();
|
|
// reset the active time for the max move time
|
|
current.activateTime = 0.0f;
|
|
}
|
|
current.atRest = -1;
|
|
current.noMoveTime = 0.0f;
|
|
self->BecomeActive( TH_PHYSICS );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::PutToRest
|
|
|
|
put to rest untill something collides with this physics object
|
|
================
|
|
*/
|
|
void idPhysics_AF::PutToRest( void ) {
|
|
Rest();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::EnableImpact
|
|
================
|
|
*/
|
|
void idPhysics_AF::EnableImpact( void ) {
|
|
noImpact = false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::DisableImpact
|
|
================
|
|
*/
|
|
void idPhysics_AF::DisableImpact( void ) {
|
|
noImpact = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::AddPushVelocity
|
|
================
|
|
*/
|
|
void idPhysics_AF::AddPushVelocity( const idVec6 &pushVelocity ) {
|
|
int i;
|
|
|
|
if ( pushVelocity != vec6_origin ) {
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
bodies[i]->current->spatialVelocity += pushVelocity;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetClipModel
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetClipModel
|
|
================
|
|
*/
|
|
idClipModel *idPhysics_AF::GetClipModel( int id ) const {
|
|
if ( id >= 0 && id < bodies.Num() ) {
|
|
return bodies[id]->GetClipModel();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetNumClipModels
|
|
================
|
|
*/
|
|
int idPhysics_AF::GetNumClipModels( void ) const {
|
|
return bodies.Num();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetMass
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetMass( float mass, int id ) {
|
|
if ( id >= 0 && id < bodies.Num() ) {
|
|
}
|
|
else {
|
|
forceTotalMass = mass;
|
|
}
|
|
SetChanged();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetMass
|
|
================
|
|
*/
|
|
float idPhysics_AF::GetMass( int id ) const {
|
|
if ( id >= 0 && id < bodies.Num() ) {
|
|
return bodies[id]->mass;
|
|
}
|
|
return totalMass;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetContents
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetContents( int contents, int id ) {
|
|
int i;
|
|
|
|
if ( id >= 0 && id < bodies.Num() ) {
|
|
bodies[id]->GetClipModel()->SetContents( contents );
|
|
}
|
|
else {
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
bodies[i]->GetClipModel()->SetContents( contents );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetContents
|
|
================
|
|
*/
|
|
int idPhysics_AF::GetContents( int id ) const {
|
|
int i, contents;
|
|
|
|
if ( id >= 0 && id < bodies.Num() ) {
|
|
return bodies[id]->GetClipModel()->GetContents();
|
|
}
|
|
else {
|
|
contents = 0;
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
contents |= bodies[i]->GetClipModel()->GetContents();
|
|
}
|
|
return contents;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetBounds
|
|
================
|
|
*/
|
|
const idBounds &idPhysics_AF::GetBounds( int id ) const {
|
|
int i;
|
|
static idBounds relBounds;
|
|
|
|
if ( id >= 0 && id < bodies.Num() ) {
|
|
return bodies[id]->GetClipModel()->GetBounds();
|
|
}
|
|
else if ( !bodies.Num() ) {
|
|
relBounds.Zero();
|
|
return relBounds;
|
|
}
|
|
else {
|
|
relBounds = bodies[0]->GetClipModel()->GetBounds();
|
|
for ( i = 1; i < bodies.Num(); i++ ) {
|
|
idBounds bounds;
|
|
idVec3 origin = ( bodies[i]->GetWorldOrigin() - bodies[0]->GetWorldOrigin() ) * bodies[0]->GetWorldAxis().Transpose();
|
|
idMat3 axis = bodies[i]->GetWorldAxis() * bodies[0]->GetWorldAxis().Transpose();
|
|
bounds.FromTransformedBounds( bodies[i]->GetClipModel()->GetBounds(), origin, axis );
|
|
relBounds += bounds;
|
|
}
|
|
return relBounds;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetAbsBounds
|
|
================
|
|
*/
|
|
const idBounds &idPhysics_AF::GetAbsBounds( int id ) const {
|
|
int i;
|
|
static idBounds absBounds;
|
|
|
|
if ( id >= 0 && id < bodies.Num() ) {
|
|
return bodies[id]->GetClipModel()->GetAbsBounds();
|
|
}
|
|
else if ( !bodies.Num() ) {
|
|
absBounds.Zero();
|
|
return absBounds;
|
|
}
|
|
else {
|
|
absBounds = bodies[0]->GetClipModel()->GetAbsBounds();
|
|
for ( i = 1; i < bodies.Num(); i++ ) {
|
|
absBounds += bodies[i]->GetClipModel()->GetAbsBounds();
|
|
}
|
|
return absBounds;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::Evaluate
|
|
================
|
|
*/
|
|
bool idPhysics_AF::Evaluate( int timeStepMSec, int endTimeMSec ) {
|
|
float timeStep;
|
|
|
|
if ( timeScaleRampStart < MS2SEC( endTimeMSec ) && timeScaleRampEnd > MS2SEC( endTimeMSec ) ) {
|
|
timeStep = MS2SEC( timeStepMSec ) * ( MS2SEC( endTimeMSec ) - timeScaleRampStart ) / ( timeScaleRampEnd - timeScaleRampStart );
|
|
} else if ( af_timeScale.GetFloat() != 1.0f ) {
|
|
timeStep = MS2SEC( timeStepMSec ) * af_timeScale.GetFloat();
|
|
} else {
|
|
timeStep = MS2SEC( timeStepMSec ) * timeScale;
|
|
}
|
|
current.lastTimeStep = timeStep;
|
|
|
|
|
|
// if the articulated figure changed
|
|
if ( changedAF || ( linearTime != af_useLinearTime.GetBool() ) ) {
|
|
BuildTrees();
|
|
changedAF = false;
|
|
linearTime = af_useLinearTime.GetBool();
|
|
}
|
|
|
|
// get the new master position
|
|
if ( masterBody ) {
|
|
idVec3 masterOrigin;
|
|
idMat3 masterAxis;
|
|
self->GetMasterPosition( masterOrigin, masterAxis );
|
|
if ( current.atRest >= 0 && ( masterBody->current->worldOrigin != masterOrigin || masterBody->current->worldAxis != masterAxis ) ) {
|
|
Activate();
|
|
}
|
|
masterBody->current->worldOrigin = masterOrigin;
|
|
masterBody->current->worldAxis = masterAxis;
|
|
}
|
|
|
|
// if the simulation is suspended because the figure is at rest
|
|
if ( current.atRest >= 0 || timeStep <= 0.0f ) {
|
|
DebugDraw();
|
|
return false;
|
|
}
|
|
|
|
// move the af velocity into the frame of a pusher
|
|
AddPushVelocity( -current.pushVelocity );
|
|
|
|
#ifdef AF_TIMINGS
|
|
timer_total.Start();
|
|
#endif
|
|
|
|
#ifdef AF_TIMINGS
|
|
timer_collision.Start();
|
|
#endif
|
|
|
|
// evaluate contacts
|
|
EvaluateContacts();
|
|
|
|
// setup contact constraints
|
|
SetupContactConstraints();
|
|
|
|
#ifdef AF_TIMINGS
|
|
timer_collision.Stop();
|
|
#endif
|
|
|
|
// evaluate constraint equations
|
|
EvaluateConstraints( timeStep );
|
|
|
|
// apply friction
|
|
ApplyFriction( timeStep, endTimeMSec );
|
|
|
|
// add frame constraints
|
|
AddFrameConstraints();
|
|
|
|
#ifdef AF_TIMINGS
|
|
int i, numPrimary = 0, numAuxiliary = 0;
|
|
for ( i = 0; i < primaryConstraints.Num(); i++ ) {
|
|
numPrimary += primaryConstraints[i]->J1.GetNumRows();
|
|
}
|
|
for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
numAuxiliary += auxiliaryConstraints[i]->J1.GetNumRows();
|
|
}
|
|
timer_pc.Start();
|
|
#endif
|
|
|
|
// factor matrices for primary constraints
|
|
PrimaryFactor();
|
|
|
|
// calculate forces on bodies after applying primary constraints
|
|
PrimaryForces( timeStep );
|
|
|
|
#ifdef AF_TIMINGS
|
|
timer_pc.Stop();
|
|
timer_ac.Start();
|
|
#endif
|
|
|
|
// calculate and apply auxiliary constraint forces
|
|
AuxiliaryForces( timeStep );
|
|
|
|
#ifdef AF_TIMINGS
|
|
timer_ac.Stop();
|
|
#endif
|
|
|
|
// evolve current state to next state
|
|
Evolve( timeStep );
|
|
|
|
// debug graphics
|
|
DebugDraw();
|
|
|
|
// clear external forces on all bodies
|
|
ClearExternalForce();
|
|
|
|
// apply contact force to other entities
|
|
ApplyContactForces();
|
|
|
|
// remove all frame constraints
|
|
RemoveFrameConstraints();
|
|
|
|
#ifdef AF_TIMINGS
|
|
timer_collision.Start();
|
|
#endif
|
|
|
|
// check for collisions between current and next state
|
|
CheckForCollisions( timeStep );
|
|
|
|
#ifdef AF_TIMINGS
|
|
timer_collision.Stop();
|
|
#endif
|
|
|
|
// swap the current and next state
|
|
SwapStates();
|
|
|
|
// make sure all clip models are disabled in case they were enabled for self collision
|
|
if ( selfCollision && !af_skipSelfCollision.GetBool() ) {
|
|
DisableClip();
|
|
}
|
|
|
|
// apply collision impulses
|
|
if ( ApplyCollisions( timeStep ) ) {
|
|
current.atRest = gameLocal.time;
|
|
comeToRest = true;
|
|
}
|
|
|
|
// test if the simulation can be suspended because the whole figure is at rest
|
|
if ( comeToRest && TestIfAtRest( timeStep ) ) {
|
|
Rest();
|
|
} else {
|
|
ActivateContactEntities();
|
|
}
|
|
|
|
// add gravitational force
|
|
AddGravity();
|
|
|
|
// move the af velocity back into the world frame
|
|
AddPushVelocity( current.pushVelocity );
|
|
current.pushVelocity.Zero();
|
|
|
|
if ( IsOutsideWorld() ) {
|
|
gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' at (%s)",
|
|
self->name.c_str(), self->GetType()->classname, bodies[0]->current->worldOrigin.ToString(0) );
|
|
Rest();
|
|
}
|
|
|
|
#ifdef AF_TIMINGS
|
|
timer_total.Stop();
|
|
|
|
if ( af_showTimings.GetInteger() == 1 ) {
|
|
gameLocal.Printf( "%12s: t %u pc %2d, %u ac %2d %u lcp %u cd %u\n",
|
|
self->name.c_str(),
|
|
timer_total.Milliseconds(),
|
|
numPrimary, timer_pc.Milliseconds(),
|
|
numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(),
|
|
timer_lcp.Milliseconds(), timer_collision.Milliseconds() );
|
|
}
|
|
else if ( af_showTimings.GetInteger() == 2 ) {
|
|
numArticulatedFigures++;
|
|
if ( endTimeMSec > lastTimerReset ) {
|
|
gameLocal.Printf( "af %d: t %u pc %2d, %u ac %2d %u lcp %u cd %u\n",
|
|
numArticulatedFigures,
|
|
timer_total.Milliseconds(),
|
|
numPrimary, timer_pc.Milliseconds(),
|
|
numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(),
|
|
timer_lcp.Milliseconds(), timer_collision.Milliseconds() );
|
|
}
|
|
}
|
|
|
|
if ( endTimeMSec > lastTimerReset ) {
|
|
lastTimerReset = endTimeMSec;
|
|
numArticulatedFigures = 0;
|
|
timer_total.Clear();
|
|
timer_pc.Clear();
|
|
timer_ac.Clear();
|
|
timer_collision.Clear();
|
|
timer_lcp.Clear();
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::UpdateTime
|
|
================
|
|
*/
|
|
void idPhysics_AF::UpdateTime( int endTimeMSec ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetTime
|
|
================
|
|
*/
|
|
int idPhysics_AF::GetTime( void ) const {
|
|
return gameLocal.time;
|
|
}
|
|
|
|
/*
|
|
================
|
|
DrawTraceModelSilhouette
|
|
================
|
|
*/
|
|
void DrawTraceModelSilhouette( const idVec3 &projectionOrigin, const idClipModel *clipModel ) {
|
|
int i, numSilEdges;
|
|
int silEdges[MAX_TRACEMODEL_EDGES];
|
|
idVec3 v1, v2;
|
|
const idTraceModel *trm = clipModel->GetTraceModel();
|
|
const idVec3 &origin = clipModel->GetOrigin();
|
|
const idMat3 &axis = clipModel->GetAxis();
|
|
|
|
numSilEdges = trm->GetProjectionSilhouetteEdges( ( projectionOrigin - origin ) * axis.Transpose(), silEdges );
|
|
for ( i = 0; i < numSilEdges; i++ ) {
|
|
v1 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INTSIGNBITSET( silEdges[i] ) ] ];
|
|
v2 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INTSIGNBITNOTSET( silEdges[i] ) ] ];
|
|
gameRenderWorld->DebugArrow( colorRed, origin + v1 * axis, origin + v2 * axis, 1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::DebugDraw
|
|
================
|
|
*/
|
|
void idPhysics_AF::DebugDraw( void ) {
|
|
int i;
|
|
idAFBody *body, *highlightBody = NULL, *constrainedBody1 = NULL, *constrainedBody2 = NULL;
|
|
idAFConstraint *constraint;
|
|
idVec3 center;
|
|
idMat3 axis;
|
|
|
|
if ( af_highlightConstraint.GetString()[0] ) {
|
|
constraint = GetConstraint( af_highlightConstraint.GetString() );
|
|
if ( constraint ) {
|
|
constraint->GetCenter( center );
|
|
axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3();
|
|
gameRenderWorld->DebugCone( colorYellow, center, (axis[2] - axis[1]) * 4.0f, 0.0f, 1.0f, 0 );
|
|
|
|
if ( af_showConstrainedBodies.GetBool() ) {
|
|
cvarSystem->SetCVarString( "cm_drawColor", colorCyan.ToString( 0 ) );
|
|
constrainedBody1 = constraint->body1;
|
|
if ( constrainedBody1 ) {
|
|
collisionModelManager->DrawModel( constrainedBody1->clipModel->Handle(), constrainedBody1->clipModel->GetOrigin(),
|
|
constrainedBody1->clipModel->GetAxis(), vec3_origin, 0.0f );
|
|
}
|
|
cvarSystem->SetCVarString( "cm_drawColor", colorBlue.ToString( 0 ) );
|
|
constrainedBody2 = constraint->body2;
|
|
if ( constrainedBody2 ) {
|
|
collisionModelManager->DrawModel( constrainedBody2->clipModel->Handle(), constrainedBody2->clipModel->GetOrigin(),
|
|
constrainedBody2->clipModel->GetAxis(), vec3_origin, 0.0f );
|
|
}
|
|
cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( af_highlightBody.GetString()[0] ) {
|
|
highlightBody = GetBody( af_highlightBody.GetString() );
|
|
if ( highlightBody ) {
|
|
cvarSystem->SetCVarString( "cm_drawColor", colorYellow.ToString( 0 ) );
|
|
collisionModelManager->DrawModel( highlightBody->clipModel->Handle(), highlightBody->clipModel->GetOrigin(),
|
|
highlightBody->clipModel->GetAxis(), vec3_origin, 0.0f );
|
|
cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) );
|
|
}
|
|
}
|
|
|
|
if ( af_showBodies.GetBool() ) {
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
if ( body == constrainedBody1 || body == constrainedBody2 ) {
|
|
continue;
|
|
}
|
|
if ( body == highlightBody ) {
|
|
continue;
|
|
}
|
|
collisionModelManager->DrawModel( body->clipModel->Handle(), body->clipModel->GetOrigin(),
|
|
body->clipModel->GetAxis(), vec3_origin, 0.0f );
|
|
//DrawTraceModelSilhouette( gameLocal.GetLocalPlayer()->GetEyePosition(), body->clipModel );
|
|
}
|
|
}
|
|
|
|
if ( af_showBodyNames.GetBool() ) {
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
gameRenderWorld->DrawText( body->GetName().c_str(), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 );
|
|
}
|
|
}
|
|
|
|
if ( af_showMass.GetBool() ) {
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
gameRenderWorld->DrawText( va( "\n%1.2f", 1.0f / body->GetInverseMass() ), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 );
|
|
}
|
|
}
|
|
|
|
if ( af_showTotalMass.GetBool() ) {
|
|
axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3();
|
|
gameRenderWorld->DrawText( va( "\n%1.2f", totalMass ), bodies[0]->GetWorldOrigin() + axis[2] * 8.0f, 0.15f, colorCyan, axis, 1 );
|
|
}
|
|
|
|
if ( af_showInertia.GetBool() ) {
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
idMat3 &I = body->inertiaTensor;
|
|
gameRenderWorld->DrawText( va( "\n\n\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )",
|
|
I[0].x, I[0].y, I[0].z,
|
|
I[1].x, I[1].y, I[1].z,
|
|
I[2].x, I[2].y, I[2].z ),
|
|
body->GetWorldOrigin(), 0.05f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 );
|
|
}
|
|
}
|
|
|
|
if ( af_showVelocity.GetBool() ) {
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
DrawVelocity( bodies[i]->clipModel->GetId(), 0.1f, 4.0f );
|
|
}
|
|
}
|
|
|
|
if ( af_showConstraints.GetBool() ) {
|
|
for ( i = 0; i < primaryConstraints.Num(); i++ ) {
|
|
constraint = primaryConstraints[i];
|
|
constraint->DebugDraw();
|
|
}
|
|
if ( !af_showPrimaryOnly.GetBool() ) {
|
|
for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
constraint = auxiliaryConstraints[i];
|
|
constraint->DebugDraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( af_showConstraintNames.GetBool() ) {
|
|
for ( i = 0; i < primaryConstraints.Num(); i++ ) {
|
|
constraint = primaryConstraints[i];
|
|
constraint->GetCenter( center );
|
|
gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 );
|
|
}
|
|
if ( !af_showPrimaryOnly.GetBool() ) {
|
|
for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) {
|
|
constraint = auxiliaryConstraints[i];
|
|
constraint->GetCenter( center );
|
|
gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( af_showTrees.GetBool() || ( af_showActive.GetBool() && current.atRest < 0 ) ) {
|
|
for ( i = 0; i < trees.Num(); i++ ) {
|
|
trees[i]->DebugDraw( idStr::ColorForIndex( i+3 ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::idPhysics_AF
|
|
================
|
|
*/
|
|
idPhysics_AF::idPhysics_AF( void ) {
|
|
trees.Clear();
|
|
bodies.Clear();
|
|
constraints.Clear();
|
|
primaryConstraints.Clear();
|
|
auxiliaryConstraints.Clear();
|
|
frameConstraints.Clear();
|
|
contacts.Clear();
|
|
collisions.Clear();
|
|
changedAF = true;
|
|
masterBody = NULL;
|
|
|
|
lcp = idLCP::AllocSymmetric();
|
|
|
|
memset( ¤t, 0, sizeof( current ) );
|
|
current.atRest = -1;
|
|
current.lastTimeStep = USERCMD_MSEC;
|
|
saved = current;
|
|
|
|
linearFriction = 0.005f;
|
|
angularFriction = 0.005f;
|
|
contactFriction = 0.8f;
|
|
bouncyness = 0.4f;
|
|
totalMass = 0.0f;
|
|
forceTotalMass = -1.0f;
|
|
|
|
suspendVelocity.Set( SUSPEND_LINEAR_VELOCITY, SUSPEND_ANGULAR_VELOCITY );
|
|
suspendAcceleration.Set( SUSPEND_LINEAR_ACCELERATION, SUSPEND_LINEAR_ACCELERATION );
|
|
noMoveTime = NO_MOVE_TIME;
|
|
noMoveTranslation = NO_MOVE_TRANSLATION_TOLERANCE;
|
|
noMoveRotation = NO_MOVE_ROTATION_TOLERANCE;
|
|
minMoveTime = MIN_MOVE_TIME;
|
|
maxMoveTime = MAX_MOVE_TIME;
|
|
impulseThreshold = IMPULSE_THRESHOLD;
|
|
|
|
timeScale = 1.0f;
|
|
timeScaleRampStart = 0.0f;
|
|
timeScaleRampEnd = 0.0f;
|
|
|
|
jointFrictionScale = 0.0f;
|
|
jointFrictionDent = 0.0f;
|
|
jointFrictionDentStart = 0.0f;
|
|
jointFrictionDentEnd = 0.0f;
|
|
jointFrictionDentScale = 0.0f;
|
|
|
|
contactFrictionScale = 0.0f;
|
|
contactFrictionDent = 0.0f;
|
|
contactFrictionDentStart = 0.0f;
|
|
contactFrictionDentEnd = 0.0f;
|
|
contactFrictionDentScale = 0.0f;
|
|
|
|
enableCollision = true;
|
|
selfCollision = true;
|
|
comeToRest = true;
|
|
linearTime = true;
|
|
noImpact = false;
|
|
worldConstraintsLocked = false;
|
|
forcePushable = false;
|
|
|
|
#ifdef AF_TIMINGS
|
|
lastTimerReset = 0;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::~idPhysics_AF
|
|
================
|
|
*/
|
|
idPhysics_AF::~idPhysics_AF( void ) {
|
|
int i;
|
|
|
|
trees.DeleteContents( true );
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
delete bodies[i];
|
|
}
|
|
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
delete constraints[i];
|
|
}
|
|
|
|
contactConstraints.SetNum( contactConstraints.NumAllocated(), false );
|
|
for ( i = 0; i < contactConstraints.NumAllocated(); i++ ) {
|
|
delete contactConstraints[i];
|
|
}
|
|
|
|
delete lcp;
|
|
|
|
if ( masterBody ) {
|
|
delete masterBody;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF_SavePState
|
|
================
|
|
*/
|
|
void idPhysics_AF_SavePState( idSaveGame *saveFile, const AFPState_t &state ) {
|
|
saveFile->WriteInt( state.atRest );
|
|
saveFile->WriteFloat( state.noMoveTime );
|
|
saveFile->WriteFloat( state.activateTime );
|
|
saveFile->WriteFloat( state.lastTimeStep );
|
|
saveFile->WriteVec6( state.pushVelocity );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF_RestorePState
|
|
================
|
|
*/
|
|
void idPhysics_AF_RestorePState( idRestoreGame *saveFile, AFPState_t &state ) {
|
|
saveFile->ReadInt( state.atRest );
|
|
saveFile->ReadFloat( state.noMoveTime );
|
|
saveFile->ReadFloat( state.activateTime );
|
|
saveFile->ReadFloat( state.lastTimeStep );
|
|
saveFile->ReadVec6( state.pushVelocity );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::Save
|
|
================
|
|
*/
|
|
void idPhysics_AF::Save( idSaveGame *saveFile ) const {
|
|
int i;
|
|
|
|
// the articulated figure structure is handled by the owner
|
|
|
|
idPhysics_AF_SavePState( saveFile, current );
|
|
idPhysics_AF_SavePState( saveFile, saved );
|
|
|
|
saveFile->WriteInt( bodies.Num() );
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
bodies[i]->Save( saveFile );
|
|
}
|
|
if ( masterBody ) {
|
|
saveFile->WriteBool( true );
|
|
masterBody->Save( saveFile );
|
|
} else {
|
|
saveFile->WriteBool( false );
|
|
}
|
|
|
|
saveFile->WriteInt( constraints.Num() );
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
constraints[i]->Save( saveFile );
|
|
}
|
|
|
|
saveFile->WriteBool( changedAF );
|
|
|
|
saveFile->WriteFloat( linearFriction );
|
|
saveFile->WriteFloat( angularFriction );
|
|
saveFile->WriteFloat( contactFriction );
|
|
saveFile->WriteFloat( bouncyness );
|
|
saveFile->WriteFloat( totalMass );
|
|
saveFile->WriteFloat( forceTotalMass );
|
|
|
|
saveFile->WriteVec2( suspendVelocity );
|
|
saveFile->WriteVec2( suspendAcceleration );
|
|
saveFile->WriteFloat( noMoveTime );
|
|
saveFile->WriteFloat( noMoveTranslation );
|
|
saveFile->WriteFloat( noMoveRotation );
|
|
saveFile->WriteFloat( minMoveTime );
|
|
saveFile->WriteFloat( maxMoveTime );
|
|
saveFile->WriteFloat( impulseThreshold );
|
|
|
|
saveFile->WriteFloat( timeScale );
|
|
saveFile->WriteFloat( timeScaleRampStart );
|
|
saveFile->WriteFloat( timeScaleRampEnd );
|
|
|
|
saveFile->WriteFloat( jointFrictionScale );
|
|
saveFile->WriteFloat( jointFrictionDent );
|
|
saveFile->WriteFloat( jointFrictionDentStart );
|
|
saveFile->WriteFloat( jointFrictionDentEnd );
|
|
saveFile->WriteFloat( jointFrictionDentScale );
|
|
|
|
saveFile->WriteFloat( contactFrictionScale );
|
|
saveFile->WriteFloat( contactFrictionDent );
|
|
saveFile->WriteFloat( contactFrictionDentStart );
|
|
saveFile->WriteFloat( contactFrictionDentEnd );
|
|
saveFile->WriteFloat( contactFrictionDentScale );
|
|
|
|
saveFile->WriteBool( enableCollision );
|
|
saveFile->WriteBool( selfCollision );
|
|
saveFile->WriteBool( comeToRest );
|
|
saveFile->WriteBool( linearTime );
|
|
saveFile->WriteBool( noImpact );
|
|
saveFile->WriteBool( worldConstraintsLocked );
|
|
saveFile->WriteBool( forcePushable );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::Restore
|
|
================
|
|
*/
|
|
void idPhysics_AF::Restore( idRestoreGame *saveFile ) {
|
|
int i, num;
|
|
bool hasMaster;
|
|
|
|
// the articulated figure structure should have already been restored
|
|
|
|
idPhysics_AF_RestorePState( saveFile, current );
|
|
idPhysics_AF_RestorePState( saveFile, saved );
|
|
|
|
saveFile->ReadInt( num );
|
|
assert( num == bodies.Num() );
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
bodies[i]->Restore( saveFile );
|
|
}
|
|
saveFile->ReadBool( hasMaster );
|
|
if ( hasMaster ) {
|
|
masterBody = new idAFBody();
|
|
masterBody->Restore( saveFile );
|
|
}
|
|
|
|
saveFile->ReadInt( num );
|
|
assert( num == constraints.Num() );
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
constraints[i]->Restore( saveFile );
|
|
}
|
|
|
|
saveFile->ReadBool( changedAF );
|
|
|
|
saveFile->ReadFloat( linearFriction );
|
|
saveFile->ReadFloat( angularFriction );
|
|
saveFile->ReadFloat( contactFriction );
|
|
saveFile->ReadFloat( bouncyness );
|
|
saveFile->ReadFloat( totalMass );
|
|
saveFile->ReadFloat( forceTotalMass );
|
|
|
|
saveFile->ReadVec2( suspendVelocity );
|
|
saveFile->ReadVec2( suspendAcceleration );
|
|
saveFile->ReadFloat( noMoveTime );
|
|
saveFile->ReadFloat( noMoveTranslation );
|
|
saveFile->ReadFloat( noMoveRotation );
|
|
saveFile->ReadFloat( minMoveTime );
|
|
saveFile->ReadFloat( maxMoveTime );
|
|
saveFile->ReadFloat( impulseThreshold );
|
|
|
|
saveFile->ReadFloat( timeScale );
|
|
saveFile->ReadFloat( timeScaleRampStart );
|
|
saveFile->ReadFloat( timeScaleRampEnd );
|
|
|
|
saveFile->ReadFloat( jointFrictionScale );
|
|
saveFile->ReadFloat( jointFrictionDent );
|
|
saveFile->ReadFloat( jointFrictionDentStart );
|
|
saveFile->ReadFloat( jointFrictionDentEnd );
|
|
saveFile->ReadFloat( jointFrictionDentScale );
|
|
|
|
saveFile->ReadFloat( contactFrictionScale );
|
|
saveFile->ReadFloat( contactFrictionDent );
|
|
saveFile->ReadFloat( contactFrictionDentStart );
|
|
saveFile->ReadFloat( contactFrictionDentEnd );
|
|
saveFile->ReadFloat( contactFrictionDentScale );
|
|
|
|
saveFile->ReadBool( enableCollision );
|
|
saveFile->ReadBool( selfCollision );
|
|
saveFile->ReadBool( comeToRest );
|
|
saveFile->ReadBool( linearTime );
|
|
saveFile->ReadBool( noImpact );
|
|
saveFile->ReadBool( worldConstraintsLocked );
|
|
saveFile->ReadBool( forcePushable );
|
|
|
|
changedAF = true;
|
|
|
|
UpdateClipModels();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::IsClosedLoop
|
|
================
|
|
*/
|
|
bool idPhysics_AF::IsClosedLoop( const idAFBody *body1, const idAFBody *body2 ) const {
|
|
const idAFBody *b1, *b2;
|
|
|
|
for ( b1 = body1; b1->parent; b1 = b1->parent ) {
|
|
}
|
|
for ( b2 = body2; b2->parent; b2 = b2->parent ) {
|
|
}
|
|
return ( b1 == b2 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::BuildTrees
|
|
================
|
|
*/
|
|
void idPhysics_AF::BuildTrees( void ) {
|
|
int i;
|
|
float scale;
|
|
idAFBody *b;
|
|
idAFConstraint *c;
|
|
idAFTree *tree;
|
|
|
|
primaryConstraints.Clear();
|
|
auxiliaryConstraints.Clear();
|
|
trees.DeleteContents( true );
|
|
|
|
totalMass = 0.0f;
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
b = bodies[i];
|
|
b->parent = NULL;
|
|
b->primaryConstraint = NULL;
|
|
b->constraints.SetNum( 0, false );
|
|
b->children.Clear();
|
|
b->tree = NULL;
|
|
totalMass += b->mass;
|
|
}
|
|
|
|
if ( forceTotalMass > 0.0f ) {
|
|
scale = forceTotalMass / totalMass;
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
b = bodies[i];
|
|
b->mass *= scale;
|
|
b->invMass = 1.0f / b->mass;
|
|
b->inertiaTensor *= scale;
|
|
b->inverseInertiaTensor = b->inertiaTensor.Inverse();
|
|
}
|
|
totalMass = forceTotalMass;
|
|
}
|
|
|
|
if ( af_useLinearTime.GetBool() ) {
|
|
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
c = constraints[i];
|
|
|
|
c->body1->constraints.Append( c );
|
|
if ( c->body2 ) {
|
|
c->body2->constraints.Append( c );
|
|
}
|
|
|
|
// only bilateral constraints between two non-world bodies that do not
|
|
// create loops can be used as primary constraints
|
|
if ( !c->body1->primaryConstraint && c->fl.allowPrimary && c->body2 != NULL && !IsClosedLoop( c->body1, c->body2 ) ) {
|
|
c->body1->primaryConstraint = c;
|
|
c->body1->parent = c->body2;
|
|
c->body2->children.Append( c->body1 );
|
|
c->fl.isPrimary = true;
|
|
c->firstIndex = 0;
|
|
primaryConstraints.Append( c );
|
|
} else {
|
|
c->fl.isPrimary = false;
|
|
auxiliaryConstraints.Append( c );
|
|
}
|
|
}
|
|
|
|
// create trees for all parent bodies
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
if ( !bodies[i]->parent ) {
|
|
tree = new idAFTree();
|
|
tree->sortedBodies.Clear();
|
|
tree->sortedBodies.Append( bodies[i] );
|
|
bodies[i]->tree = tree;
|
|
trees.Append( tree );
|
|
}
|
|
}
|
|
|
|
// add each child body to the appropriate tree
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
if ( bodies[i]->parent ) {
|
|
for ( b = bodies[i]->parent; !b->tree; b = b->parent ) {
|
|
}
|
|
b->tree->sortedBodies.Append( bodies[i] );
|
|
bodies[i]->tree = b->tree;
|
|
}
|
|
}
|
|
|
|
if ( trees.Num() > 1 ) {
|
|
gameLocal.Warning( "Articulated figure has multiple separate tree structures for entity '%s' type '%s'.",
|
|
self->name.c_str(), self->GetType()->classname );
|
|
}
|
|
|
|
// sort bodies in each tree to make sure parents come first
|
|
for ( i = 0; i < trees.Num(); i++ ) {
|
|
trees[i]->SortBodies();
|
|
}
|
|
|
|
} else {
|
|
|
|
// create a tree for each body
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
tree = new idAFTree();
|
|
tree->sortedBodies.Clear();
|
|
tree->sortedBodies.Append( bodies[i] );
|
|
bodies[i]->tree = tree;
|
|
trees.Append( tree );
|
|
}
|
|
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
c = constraints[i];
|
|
|
|
c->body1->constraints.Append( c );
|
|
if ( c->body2 ) {
|
|
c->body2->constraints.Append( c );
|
|
}
|
|
|
|
c->fl.isPrimary = false;
|
|
auxiliaryConstraints.Append( c );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::AddBody
|
|
|
|
bodies get an id in the order they are added starting at zero
|
|
as such the first body added will get id zero
|
|
================
|
|
*/
|
|
int idPhysics_AF::AddBody( idAFBody *body ) {
|
|
int id = 0;
|
|
|
|
if ( !body->clipModel ) {
|
|
gameLocal.Error( "idPhysics_AF::AddBody: body '%s' has no clip model.", body->name.c_str() );
|
|
}
|
|
|
|
if ( bodies.Find( body ) ) {
|
|
gameLocal.Error( "idPhysics_AF::AddBody: body '%s' added twice.", body->name.c_str() );
|
|
}
|
|
|
|
if ( GetBody( body->name ) ) {
|
|
gameLocal.Error( "idPhysics_AF::AddBody: a body with the name '%s' already exists.", body->name.c_str() );
|
|
}
|
|
|
|
id = bodies.Num();
|
|
body->clipModel->SetId( id );
|
|
if ( body->linearFriction < 0.0f ) {
|
|
body->linearFriction = linearFriction;
|
|
body->angularFriction = angularFriction;
|
|
body->contactFriction = contactFriction;
|
|
}
|
|
if ( body->bouncyness < 0.0f ) {
|
|
body->bouncyness = bouncyness;
|
|
}
|
|
if ( !body->fl.clipMaskSet ) {
|
|
body->clipMask = clipMask;
|
|
}
|
|
|
|
bodies.Append( body );
|
|
|
|
changedAF = true;
|
|
|
|
return id;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::AddConstraint
|
|
================
|
|
*/
|
|
void idPhysics_AF::AddConstraint( idAFConstraint *constraint ) {
|
|
|
|
if ( constraints.Find( constraint ) ) {
|
|
gameLocal.Error( "idPhysics_AF::AddConstraint: constraint '%s' added twice.", constraint->name.c_str() );
|
|
}
|
|
if ( GetConstraint( constraint->name ) ) {
|
|
gameLocal.Error( "idPhysics_AF::AddConstraint: a constraint with the name '%s' already exists.", constraint->name.c_str() );
|
|
}
|
|
if ( !constraint->body1 ) {
|
|
gameLocal.Error( "idPhysics_AF::AddConstraint: body1 == NULL on constraint '%s'.", constraint->name.c_str() );
|
|
}
|
|
if ( !bodies.Find( constraint->body1 ) ) {
|
|
gameLocal.Error( "idPhysics_AF::AddConstraint: body1 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() );
|
|
}
|
|
if ( constraint->body2 && !bodies.Find( constraint->body2 ) ) {
|
|
gameLocal.Error( "idPhysics_AF::AddConstraint: body2 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() );
|
|
}
|
|
if ( constraint->body1 == constraint->body2 ) {
|
|
gameLocal.Error( "idPhysics_AF::AddConstraint: body1 and body2 of constraint '%s' are the same.", constraint->name.c_str() );
|
|
}
|
|
|
|
constraints.Append( constraint );
|
|
constraint->physics = this;
|
|
|
|
changedAF = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::AddFrameConstraint
|
|
================
|
|
*/
|
|
void idPhysics_AF::AddFrameConstraint( idAFConstraint *constraint ) {
|
|
frameConstraints.Append( constraint );
|
|
constraint->physics = this;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::ForceBodyId
|
|
================
|
|
*/
|
|
void idPhysics_AF::ForceBodyId( idAFBody *body, int newId ) {
|
|
int id;
|
|
|
|
id = bodies.FindIndex( body );
|
|
if ( id == -1 ) {
|
|
gameLocal.Error( "ForceBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() );
|
|
}
|
|
if ( id != newId ) {
|
|
idAFBody *b = bodies[newId];
|
|
bodies[newId] = bodies[id];
|
|
bodies[id] = b;
|
|
changedAF = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetBodyId
|
|
================
|
|
*/
|
|
int idPhysics_AF::GetBodyId( idAFBody *body ) const {
|
|
int id;
|
|
|
|
id = bodies.FindIndex( body );
|
|
if ( id == -1 && body ) {
|
|
gameLocal.Error( "GetBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() );
|
|
}
|
|
return id;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetBodyId
|
|
================
|
|
*/
|
|
int idPhysics_AF::GetBodyId( const char *bodyName ) const {
|
|
int i;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
if ( !bodies[i]->name.Icmp( bodyName ) ) {
|
|
return i;
|
|
}
|
|
}
|
|
gameLocal.Error( "GetBodyId: no body with the name '%s' is not part of the articulated figure.\n", bodyName );
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetConstraintId
|
|
================
|
|
*/
|
|
int idPhysics_AF::GetConstraintId( idAFConstraint *constraint ) const {
|
|
int id;
|
|
|
|
id = constraints.FindIndex( constraint );
|
|
if ( id == -1 && constraint ) {
|
|
gameLocal.Error( "GetConstraintId: constraint '%s' is not part of the articulated figure.\n", constraint->name.c_str() );
|
|
}
|
|
return id;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetConstraintId
|
|
================
|
|
*/
|
|
int idPhysics_AF::GetConstraintId( const char *constraintName ) const {
|
|
int i;
|
|
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
if ( constraints[i]->name.Icmp( constraintName ) == 0 ) {
|
|
return i;
|
|
}
|
|
}
|
|
gameLocal.Error( "GetConstraintId: no constraint with the name '%s' is not part of the articulated figure.\n", constraintName );
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetNumBodies
|
|
================
|
|
*/
|
|
int idPhysics_AF::GetNumBodies( void ) const {
|
|
return bodies.Num();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetNumConstraints
|
|
================
|
|
*/
|
|
int idPhysics_AF::GetNumConstraints( void ) const {
|
|
return constraints.Num();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetBody
|
|
================
|
|
*/
|
|
idAFBody *idPhysics_AF::GetBody( const char *bodyName ) const {
|
|
int i;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
if ( !bodies[i]->name.Icmp( bodyName ) ) {
|
|
return bodies[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetBody
|
|
================
|
|
*/
|
|
idAFBody *idPhysics_AF::GetBody( const int id ) const {
|
|
if ( id < 0 || id >= bodies.Num() ) {
|
|
gameLocal.Error( "GetBody: no body with id %d exists\n", id );
|
|
return NULL;
|
|
}
|
|
return bodies[id];
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetConstraint
|
|
================
|
|
*/
|
|
idAFConstraint *idPhysics_AF::GetConstraint( const char *constraintName ) const {
|
|
int i;
|
|
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
if ( constraints[i]->name.Icmp( constraintName ) == 0 ) {
|
|
return constraints[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetConstraint
|
|
================
|
|
*/
|
|
idAFConstraint *idPhysics_AF::GetConstraint( const int id ) const {
|
|
if ( id < 0 || id >= constraints.Num() ) {
|
|
gameLocal.Error( "GetConstraint: no constraint with id %d exists\n", id );
|
|
return NULL;
|
|
}
|
|
return constraints[id];
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::DeleteBody
|
|
================
|
|
*/
|
|
void idPhysics_AF::DeleteBody( const char *bodyName ) {
|
|
int i;
|
|
|
|
// find the body with the given name
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
if ( !bodies[i]->name.Icmp( bodyName ) ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i >= bodies.Num() ) {
|
|
gameLocal.Warning( "DeleteBody: no body found in the articulated figure with the name '%s' for entity '%s' type '%s'.",
|
|
bodyName, self->name.c_str(), self->GetType()->classname );
|
|
return;
|
|
}
|
|
|
|
DeleteBody( i );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::DeleteBody
|
|
================
|
|
*/
|
|
void idPhysics_AF::DeleteBody( const int id ) {
|
|
int j;
|
|
|
|
if ( id < 0 || id > bodies.Num() ) {
|
|
gameLocal.Error( "DeleteBody: no body with id %d.", id );
|
|
return;
|
|
}
|
|
|
|
// remove any constraints attached to this body
|
|
for ( j = 0; j < constraints.Num(); j++ ) {
|
|
if ( constraints[j]->body1 == bodies[id] || constraints[j]->body2 == bodies[id] ) {
|
|
delete constraints[j];
|
|
constraints.RemoveIndex( j );
|
|
j--;
|
|
}
|
|
}
|
|
|
|
// remove the body
|
|
delete bodies[id];
|
|
bodies.RemoveIndex( id );
|
|
|
|
// set new body ids
|
|
for ( j = 0; j < bodies.Num(); j++ ) {
|
|
bodies[j]->clipModel->SetId( j );
|
|
}
|
|
|
|
changedAF = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::DeleteConstraint
|
|
================
|
|
*/
|
|
void idPhysics_AF::DeleteConstraint( const char *constraintName ) {
|
|
int i;
|
|
|
|
// find the constraint with the given name
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
if ( !constraints[i]->name.Icmp( constraintName ) ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i >= constraints.Num() ) {
|
|
gameLocal.Warning( "DeleteConstraint: no constriant found in the articulated figure with the name '%s' for entity '%s' type '%s'.",
|
|
constraintName, self->name.c_str(), self->GetType()->classname );
|
|
return;
|
|
}
|
|
|
|
DeleteConstraint( i );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::DeleteConstraint
|
|
================
|
|
*/
|
|
void idPhysics_AF::DeleteConstraint( const int id ) {
|
|
|
|
if ( id < 0 || id >= constraints.Num() ) {
|
|
gameLocal.Error( "DeleteConstraint: no constraint with id %d.", id );
|
|
return;
|
|
}
|
|
|
|
// remove the constraint
|
|
delete constraints[id];
|
|
constraints.RemoveIndex( id );
|
|
|
|
changedAF = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetBodyContactConstraints
|
|
================
|
|
*/
|
|
int idPhysics_AF::GetBodyContactConstraints( const int id, idAFConstraint_Contact *contacts[], int maxContacts ) const {
|
|
int i, numContacts;
|
|
idAFBody *body;
|
|
idAFConstraint_Contact *contact;
|
|
|
|
if ( id < 0 || id >= bodies.Num() || maxContacts <= 0 ) {
|
|
return 0;
|
|
}
|
|
|
|
numContacts = 0;
|
|
body = bodies[id];
|
|
for ( i = 0; i < contactConstraints.Num(); i++ ) {
|
|
contact = contactConstraints[i];
|
|
if ( contact->body1 == body || contact->body2 == body ) {
|
|
contacts[numContacts++] = contact;
|
|
if ( numContacts >= maxContacts ) {
|
|
return numContacts;
|
|
}
|
|
}
|
|
}
|
|
return numContacts;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetDefaultFriction
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetDefaultFriction( float linear, float angular, float contact ) {
|
|
if ( linear < 0.0f || linear > 1.0f ||
|
|
angular < 0.0f || angular > 1.0f ||
|
|
contact < 0.0f || contact > 1.0f ) {
|
|
return;
|
|
}
|
|
linearFriction = linear;
|
|
angularFriction = angular;
|
|
contactFriction = contact;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetImpactInfo
|
|
================
|
|
*/
|
|
void idPhysics_AF::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const {
|
|
if ( id < 0 || id >= bodies.Num() ) {
|
|
memset( info, 0, sizeof( *info ) );
|
|
return;
|
|
}
|
|
info->invMass = 1.0f / bodies[id]->mass;
|
|
info->invInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis;
|
|
info->position = point - bodies[id]->current->worldOrigin;
|
|
info->velocity = bodies[id]->current->spatialVelocity.SubVec3(0) + bodies[id]->current->spatialVelocity.SubVec3(1).Cross( info->position );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::ApplyImpulse
|
|
================
|
|
*/
|
|
void idPhysics_AF::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) {
|
|
if ( id < 0 || id >= bodies.Num() ) {
|
|
return;
|
|
}
|
|
if ( noImpact || impulse.LengthSqr() < Square( impulseThreshold ) ) {
|
|
return;
|
|
}
|
|
idMat3 invWorldInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis;
|
|
bodies[id]->current->spatialVelocity.SubVec3(0) += bodies[id]->invMass * impulse;
|
|
bodies[id]->current->spatialVelocity.SubVec3(1) += invWorldInertiaTensor * (point - bodies[id]->current->worldOrigin).Cross( impulse );
|
|
Activate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::AddForce
|
|
================
|
|
*/
|
|
void idPhysics_AF::AddForce( const int id, const idVec3 &point, const idVec3 &force ) {
|
|
if ( noImpact ) {
|
|
return;
|
|
}
|
|
if ( id < 0 || id >= bodies.Num() ) {
|
|
return;
|
|
}
|
|
bodies[id]->current->externalForce.SubVec3( 0 ) += force;
|
|
bodies[id]->current->externalForce.SubVec3( 1 ) += (point - bodies[id]->current->worldOrigin).Cross( force );
|
|
Activate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::IsAtRest
|
|
================
|
|
*/
|
|
bool idPhysics_AF::IsAtRest( void ) const {
|
|
return current.atRest >= 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetRestStartTime
|
|
================
|
|
*/
|
|
int idPhysics_AF::GetRestStartTime( void ) const {
|
|
return current.atRest;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::IsPushable
|
|
================
|
|
*/
|
|
bool idPhysics_AF::IsPushable( void ) const {
|
|
return ( !noImpact && ( masterBody == NULL || forcePushable ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SaveState
|
|
================
|
|
*/
|
|
void idPhysics_AF::SaveState( void ) {
|
|
int i;
|
|
|
|
saved = current;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
memcpy( &bodies[i]->saved, bodies[i]->current, sizeof( AFBodyPState_t ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::RestoreState
|
|
================
|
|
*/
|
|
void idPhysics_AF::RestoreState( void ) {
|
|
int i;
|
|
|
|
current = saved;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
*(bodies[i]->current) = bodies[i]->saved;
|
|
}
|
|
|
|
EvaluateContacts();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetOrigin
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetOrigin( const idVec3 &newOrigin, int id ) {
|
|
if ( masterBody ) {
|
|
Translate( masterBody->current->worldOrigin + masterBody->current->worldAxis * newOrigin - bodies[0]->current->worldOrigin );
|
|
} else {
|
|
Translate( newOrigin - bodies[0]->current->worldOrigin );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetAxis
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetAxis( const idMat3 &newAxis, int id ) {
|
|
idMat3 axis;
|
|
idRotation rotation;
|
|
|
|
if ( masterBody ) {
|
|
axis = bodies[0]->current->worldAxis.Transpose() * ( newAxis * masterBody->current->worldAxis );
|
|
} else {
|
|
axis = bodies[0]->current->worldAxis.Transpose() * newAxis;
|
|
}
|
|
rotation = axis.ToRotation();
|
|
rotation.SetOrigin( bodies[0]->current->worldOrigin );
|
|
|
|
Rotate( rotation );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::Translate
|
|
================
|
|
*/
|
|
void idPhysics_AF::Translate( const idVec3 &translation, int id ) {
|
|
int i;
|
|
idAFBody *body;
|
|
|
|
if ( !worldConstraintsLocked ) {
|
|
// translate constraints attached to the world
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
constraints[i]->Translate( translation );
|
|
}
|
|
}
|
|
|
|
// translate all the bodies
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
|
|
body = bodies[i];
|
|
body->current->worldOrigin += translation;
|
|
}
|
|
|
|
Activate();
|
|
|
|
UpdateClipModels();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::Rotate
|
|
================
|
|
*/
|
|
void idPhysics_AF::Rotate( const idRotation &rotation, int id ) {
|
|
int i;
|
|
idAFBody *body;
|
|
|
|
if ( !worldConstraintsLocked ) {
|
|
// rotate constraints attached to the world
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
constraints[i]->Rotate( rotation );
|
|
}
|
|
}
|
|
|
|
// rotate all the bodies
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
body->current->worldOrigin *= rotation;
|
|
body->current->worldAxis *= rotation.ToMat3();
|
|
}
|
|
|
|
Activate();
|
|
|
|
UpdateClipModels();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetOrigin
|
|
================
|
|
*/
|
|
const idVec3 &idPhysics_AF::GetOrigin( int id ) const {
|
|
if ( id < 0 || id >= bodies.Num() ) {
|
|
return vec3_origin;
|
|
}
|
|
else {
|
|
return bodies[id]->current->worldOrigin;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetAxis
|
|
================
|
|
*/
|
|
const idMat3 &idPhysics_AF::GetAxis( int id ) const {
|
|
if ( id < 0 || id >= bodies.Num() ) {
|
|
return mat3_identity;
|
|
}
|
|
else {
|
|
return bodies[id]->current->worldAxis;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetLinearVelocity
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) {
|
|
if ( id < 0 || id >= bodies.Num() ) {
|
|
return;
|
|
}
|
|
bodies[id]->current->spatialVelocity.SubVec3( 0 ) = newLinearVelocity;
|
|
Activate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetAngularVelocity
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) {
|
|
if ( id < 0 || id >= bodies.Num() ) {
|
|
return;
|
|
}
|
|
bodies[id]->current->spatialVelocity.SubVec3( 1 ) = newAngularVelocity;
|
|
Activate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetLinearVelocity
|
|
================
|
|
*/
|
|
const idVec3 &idPhysics_AF::GetLinearVelocity( int id ) const {
|
|
if ( id < 0 || id >= bodies.Num() ) {
|
|
return vec3_origin;
|
|
}
|
|
else {
|
|
return bodies[id]->current->spatialVelocity.SubVec3( 0 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetAngularVelocity
|
|
================
|
|
*/
|
|
const idVec3 &idPhysics_AF::GetAngularVelocity( int id ) const {
|
|
if ( id < 0 || id >= bodies.Num() ) {
|
|
return vec3_origin;
|
|
}
|
|
else {
|
|
return bodies[id]->current->spatialVelocity.SubVec3( 1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::ClipTranslation
|
|
================
|
|
*/
|
|
void idPhysics_AF::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const {
|
|
int i;
|
|
idAFBody *body;
|
|
trace_t bodyResults;
|
|
|
|
results.fraction = 1.0f;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
if ( body->clipModel->IsTraceModel() ) {
|
|
if ( model ) {
|
|
gameLocal.clip.TranslationModel( bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation,
|
|
body->clipModel, body->current->worldAxis, body->clipMask,
|
|
model->Handle(), model->GetOrigin(), model->GetAxis() );
|
|
}
|
|
else {
|
|
gameLocal.clip.Translation( bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation,
|
|
body->clipModel, body->current->worldAxis, body->clipMask, self );
|
|
}
|
|
if ( bodyResults.fraction < results.fraction ) {
|
|
results = bodyResults;
|
|
}
|
|
}
|
|
}
|
|
|
|
results.endpos = bodies[0]->current->worldOrigin + results.fraction * translation;
|
|
results.endAxis = bodies[0]->current->worldAxis;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::ClipRotation
|
|
================
|
|
*/
|
|
void idPhysics_AF::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const {
|
|
int i;
|
|
idAFBody *body;
|
|
trace_t bodyResults;
|
|
idRotation partialRotation;
|
|
|
|
results.fraction = 1.0f;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
if ( body->clipModel->IsTraceModel() ) {
|
|
if ( model ) {
|
|
gameLocal.clip.RotationModel( bodyResults, body->current->worldOrigin, rotation,
|
|
body->clipModel, body->current->worldAxis, body->clipMask,
|
|
model->Handle(), model->GetOrigin(), model->GetAxis() );
|
|
}
|
|
else {
|
|
gameLocal.clip.Rotation( bodyResults, body->current->worldOrigin, rotation,
|
|
body->clipModel, body->current->worldAxis, body->clipMask, self );
|
|
}
|
|
if ( bodyResults.fraction < results.fraction ) {
|
|
results = bodyResults;
|
|
}
|
|
}
|
|
}
|
|
|
|
partialRotation = rotation * results.fraction;
|
|
results.endpos = bodies[0]->current->worldOrigin * partialRotation;
|
|
results.endAxis = bodies[0]->current->worldAxis * partialRotation.ToMat3();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::ClipContents
|
|
================
|
|
*/
|
|
int idPhysics_AF::ClipContents( const idClipModel *model ) const {
|
|
int i, contents;
|
|
idAFBody *body;
|
|
|
|
contents = 0;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
body = bodies[i];
|
|
|
|
if ( body->clipModel->IsTraceModel() ) {
|
|
if ( model ) {
|
|
contents |= gameLocal.clip.ContentsModel( body->current->worldOrigin,
|
|
body->clipModel, body->current->worldAxis, -1,
|
|
model->Handle(), model->GetOrigin(), model->GetAxis() );
|
|
}
|
|
else {
|
|
contents |= gameLocal.clip.Contents( body->current->worldOrigin,
|
|
body->clipModel, body->current->worldAxis, -1, NULL );
|
|
}
|
|
}
|
|
}
|
|
|
|
return contents;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::DisableClip
|
|
================
|
|
*/
|
|
void idPhysics_AF::DisableClip( void ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
bodies[i]->clipModel->Disable();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::EnableClip
|
|
================
|
|
*/
|
|
void idPhysics_AF::EnableClip( void ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
bodies[i]->clipModel->Enable();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::UnlinkClip
|
|
================
|
|
*/
|
|
void idPhysics_AF::UnlinkClip( void ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
bodies[i]->clipModel->Unlink();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::LinkClip
|
|
================
|
|
*/
|
|
void idPhysics_AF::LinkClip( void ) {
|
|
UpdateClipModels();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetPushed
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetPushed( int deltaTime ) {
|
|
idAFBody *body;
|
|
idRotation rotation;
|
|
|
|
if ( bodies.Num() ) {
|
|
body = bodies[0];
|
|
rotation = ( body->saved.worldAxis.Transpose() * body->current->worldAxis ).ToRotation();
|
|
|
|
// velocity with which the af is pushed
|
|
current.pushVelocity.SubVec3(0) += ( body->current->worldOrigin - body->saved.worldOrigin ) / ( deltaTime * idMath::M_MS2SEC );
|
|
current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetPushedLinearVelocity
|
|
================
|
|
*/
|
|
const idVec3 &idPhysics_AF::GetPushedLinearVelocity( const int id ) const {
|
|
return current.pushVelocity.SubVec3(0);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::GetPushedAngularVelocity
|
|
================
|
|
*/
|
|
const idVec3 &idPhysics_AF::GetPushedAngularVelocity( const int id ) const {
|
|
return current.pushVelocity.SubVec3(1);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::SetMaster
|
|
|
|
the binding is orientated based on the constraints being used
|
|
================
|
|
*/
|
|
void idPhysics_AF::SetMaster( idEntity *master, const bool orientated ) {
|
|
int i;
|
|
idVec3 masterOrigin;
|
|
idMat3 masterAxis;
|
|
idRotation rotation;
|
|
|
|
if ( master ) {
|
|
self->GetMasterPosition( masterOrigin, masterAxis );
|
|
if ( !masterBody ) {
|
|
masterBody = new idAFBody();
|
|
// translate and rotate all the constraints with body2 == NULL from world space to master space
|
|
rotation = masterAxis.Transpose().ToRotation();
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
if ( constraints[i]->GetBody2() == NULL ) {
|
|
constraints[i]->Translate( -masterOrigin );
|
|
constraints[i]->Rotate( rotation );
|
|
}
|
|
}
|
|
Activate();
|
|
}
|
|
masterBody->current->worldOrigin = masterOrigin;
|
|
masterBody->current->worldAxis = masterAxis;
|
|
}
|
|
else {
|
|
if ( masterBody ) {
|
|
// translate and rotate all the constraints with body2 == NULL from master space to world space
|
|
rotation = masterBody->current->worldAxis.ToRotation();
|
|
for ( i = 0; i < constraints.Num(); i++ ) {
|
|
if ( constraints[i]->GetBody2() == NULL ) {
|
|
constraints[i]->Rotate( rotation );
|
|
constraints[i]->Translate( masterBody->current->worldOrigin );
|
|
}
|
|
}
|
|
delete masterBody;
|
|
masterBody = NULL;
|
|
Activate();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
const float AF_VELOCITY_MAX = 16000;
|
|
const int AF_VELOCITY_TOTAL_BITS = 16;
|
|
const int AF_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_VELOCITY_MAX ) ) + 1;
|
|
const int AF_VELOCITY_MANTISSA_BITS = AF_VELOCITY_TOTAL_BITS - 1 - AF_VELOCITY_EXPONENT_BITS;
|
|
//const float AF_FORCE_MAX = 1e20f;
|
|
//const int AF_FORCE_TOTAL_BITS = 16;
|
|
//const int AF_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_FORCE_MAX ) ) + 1;
|
|
//const int AF_FORCE_MANTISSA_BITS = AF_FORCE_TOTAL_BITS - 1 - AF_FORCE_EXPONENT_BITS;
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::WriteToSnapshot
|
|
================
|
|
*/
|
|
void idPhysics_AF::WriteToSnapshot( idBitMsgDelta &msg ) const {
|
|
int i;
|
|
idCQuat quat;
|
|
|
|
msg.WriteInt( current.atRest );
|
|
msg.WriteFloat( current.noMoveTime );
|
|
msg.WriteFloat( current.activateTime );
|
|
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
|
|
msg.WriteByte( bodies.Num() );
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
AFBodyPState_t *state = bodies[i]->current;
|
|
quat = state->worldAxis.ToCQuat();
|
|
|
|
msg.WriteFloat( state->worldOrigin[0] );
|
|
msg.WriteFloat( state->worldOrigin[1] );
|
|
msg.WriteFloat( state->worldOrigin[2] );
|
|
msg.WriteFloat( quat.x );
|
|
msg.WriteFloat( quat.y );
|
|
msg.WriteFloat( quat.z );
|
|
msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
/* msg.WriteDeltaFloat( 0.0f, state->externalForce[0], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, state->externalForce[1], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, state->externalForce[2], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, state->externalForce[3], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, state->externalForce[4], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
msg.WriteDeltaFloat( 0.0f, state->externalForce[5], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
*/
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_AF::ReadFromSnapshot
|
|
================
|
|
*/
|
|
void idPhysics_AF::ReadFromSnapshot( const idBitMsgDelta &msg ) {
|
|
int i, num id_attribute((unused));
|
|
idCQuat quat;
|
|
|
|
current.atRest = msg.ReadInt();
|
|
current.noMoveTime = msg.ReadFloat();
|
|
current.activateTime = msg.ReadFloat();
|
|
current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
current.pushVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
current.pushVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
current.pushVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
|
|
num = msg.ReadByte();
|
|
assert( num == bodies.Num() );
|
|
|
|
for ( i = 0; i < bodies.Num(); i++ ) {
|
|
AFBodyPState_t *state = bodies[i]->current;
|
|
|
|
state->worldOrigin[0] = msg.ReadFloat();
|
|
state->worldOrigin[1] = msg.ReadFloat();
|
|
state->worldOrigin[2] = msg.ReadFloat();
|
|
quat.x = msg.ReadFloat();
|
|
quat.y = msg.ReadFloat();
|
|
quat.z = msg.ReadFloat();
|
|
state->spatialVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
state->spatialVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
state->spatialVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
state->spatialVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
state->spatialVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
state->spatialVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS );
|
|
/* state->externalForce[0] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
state->externalForce[1] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
state->externalForce[2] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
state->externalForce[3] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
state->externalForce[4] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
state->externalForce[5] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS );
|
|
*/
|
|
state->worldAxis = quat.ToMat3();
|
|
}
|
|
|
|
UpdateClipModels();
|
|
}
|