doom3-bfg/neo/d3xp/physics/Push.cpp
2012-11-26 12:58:24 -06:00

1448 lines
39 KiB
C++

/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition 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 BFG Edition 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 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition 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 BFG Edition 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.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
#include "../Game_local.h"
/*
============
idPush::InitSavingPushedEntityPositions
============
*/
void idPush::InitSavingPushedEntityPositions() {
numPushed = 0;
}
/*
============
idPush::SaveEntityPosition
============
*/
void idPush::SaveEntityPosition( idEntity *ent ) {
int i;
// if already saved the physics state for this entity
for ( i = 0; i < numPushed; i++ ) {
if ( pushed[i].ent == ent ) {
return;
}
}
// don't overflow
if ( numPushed >= MAX_GENTITIES ) {
gameLocal.Error( "more than MAX_GENTITIES pushed entities" );
return;
}
pushed[numPushed].ent = ent;
// if the entity is an actor
if ( ent->IsType( idActor::Type ) ) {
// save the delta view angles
pushed[numPushed].deltaViewAngles = static_cast<idActor *>(ent)->GetDeltaViewAngles();
}
// save the physics state
ent->GetPhysics()->SaveState();
numPushed++;
}
/*
============
idPush::RestorePushedEntityPositions
============
*/
void idPush::RestorePushedEntityPositions() {
int i;
for ( i = 0; i < numPushed; i++ ) {
// if the entity is an actor
if ( pushed[i].ent->IsType( idActor::Type ) ) {
// set back the delta view angles
static_cast<idActor *>(pushed[i].ent)->SetDeltaViewAngles( pushed[i].deltaViewAngles );
}
// restore the physics state
pushed[i].ent->GetPhysics()->RestoreState();
}
}
/*
============
idPush::RotateEntityToAxial
============
*/
bool idPush::RotateEntityToAxial( idEntity *ent, idVec3 rotationPoint ) {
int i;
trace_t trace;
idRotation rotation;
idMat3 axis;
idPhysics *physics;
physics = ent->GetPhysics();
axis = physics->GetAxis();
if ( !axis.IsRotated() ) {
return true;
}
// try to rotate the bbox back to axial with at most four rotations
for ( i = 0; i < 4; i++ ) {
axis = physics->GetAxis();
rotation = axis.ToRotation();
rotation.Scale( -1 );
rotation.SetOrigin( rotationPoint );
// tiny float numbers in the clip axis, this can get the entity stuck
if ( rotation.GetAngle() == 0.0f ) {
physics->SetAxis( mat3_identity );
return true;
}
//
ent->GetPhysics()->ClipRotation( trace, rotation, NULL );
// if the full rotation is possible
if ( trace.fraction >= 1.0f ) {
// set bbox in final axial position
physics->SetOrigin( trace.endpos );
physics->SetAxis( mat3_identity );
return true;
}
// if partial rotation was possible
else if ( trace.fraction > 0.0f ) {
// partial rotation
physics->SetOrigin( trace.endpos );
physics->SetAxis( trace.endAxis );
}
// next rotate around collision point
rotationPoint = trace.c.point;
}
return false;
}
#ifdef NEW_PUSH
/*
============
idPush::CanPushEntity
============
*/
bool idPush::CanPushEntity( idEntity *ent, idEntity *pusher, idEntity *initialPusher, const int flags ) {
// if the physics object is not pushable
if ( !ent->GetPhysics()->IsPushable() ) {
return false;
}
// if the entity doesn't clip with this pusher
if ( !( ent->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) {
return false;
}
// don't push players in noclip mode
if ( ent->client && ent->client->noclip ) {
return false;
}
// if we should only push idMoveable entities
if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !ent->IsType( idMoveable::Type ) ) {
return false;
}
// if we shouldn't push entities the original pusher rests upon
if ( flags & PUSHFL_NOGROUNDENTITIES ) {
if ( initialPusher->GetPhysics()->IsGroundEntity( ent->entityNumber ) ) {
return false;
}
}
return true;
}
/*
============
idPush::AddEntityToPushedGroup
============
*/
void idPush::AddEntityToPushedGroup( idEntity *ent, float fraction, bool groundContact ) {
int i, j;
for ( i = 0; i < pushedGroupSize; i++ ) {
if ( ent == pushedGroup[i].ent ) {
if ( fraction > pushedGroup[i].fraction ) {
pushedGroup[i].fraction = fraction;
pushedGroup[i].groundContact &= groundContact;
pushedGroup[i].test = true;
}
return;
}
else if ( fraction > pushedGroup[i].fraction ) {
for ( j = pushedGroupSize; j > i; j-- ) {
pushedGroup[j] = pushedGroup[j-1];
}
break;
}
}
// put the entity in the group
pushedGroupSize++;
pushedGroup[i].ent = ent;
pushedGroup[i].fraction = fraction;
pushedGroup[i].groundContact = groundContact;
pushedGroup[i].test = true;
// remove any further occurances of the same entity in the group
for ( i++; i < pushedGroupSize; i++ ) {
if ( ent == pushedGroup[i].ent ) {
for ( j = i+1; j < pushedGroupSize; j++ ) {
pushedGroup[j-1] = pushedGroup[j];
}
pushedGroupSize--;
break;
}
}
}
/*
============
idPush::IsFullyPushed
============
*/
bool idPush::IsFullyPushed( idEntity *ent ) {
int i;
for ( i = 0; i < pushedGroupSize; i++ ) {
if ( pushedGroup[i].fraction < 1.0f ) {
return false;
}
if ( ent == pushedGroup[i].ent ) {
return true;
}
}
return false;
}
/*
============
idPush::ClipTranslationAgainstPusher
============
*/
bool idPush::ClipTranslationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idVec3 &translation ) {
int i, n;
trace_t t;
results.fraction = 1.0f;
n = pusher->GetPhysics()->GetNumClipModels();
for ( i = 0; i < n; i++ ) {
ent->GetPhysics()->ClipTranslation( t, translation, pusher->GetPhysics()->GetClipModel( i ) );
if ( t.fraction < results.fraction ) {
results = t;
}
}
return ( results.fraction < 1.0f );
}
/*
============
idPush::GetPushableEntitiesForTranslation
============
*/
int idPush::GetPushableEntitiesForTranslation( idEntity *pusher, idEntity *initialPusher, const int flags,
const idVec3 &translation, idEntity *entityList[], int maxEntities ) {
int i, n, l;
idBounds bounds, pushBounds;
idPhysics *physics;
// get bounds for the whole movement
physics = pusher->GetPhysics();
bounds = physics->GetBounds();
pushBounds.FromBoundsTranslation( bounds, physics->GetOrigin(), physics->GetAxis(), translation );
pushBounds.ExpandSelf( 2.0f );
// get all entities within the push bounds
n = gameLocal.clip.EntitiesTouchingBounds( pushBounds, -1, entityList, MAX_GENTITIES );
for ( l = i = 0; i < n; i++ ) {
if ( entityList[i] == pusher || entityList[i] == initialPusher ) {
continue;
}
if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) {
entityList[l++] = entityList[i];
}
}
return l;
}
/*
============
idPush::ClipTranslationalPush
Try to push other entities by translating the given entity.
============
*/
float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags,
const idVec3 &newOrigin, const idVec3 &translation ) {
int i, j, numListedEntities;
idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ];
float fraction;
bool groundContact, blocked = false;
float totalMass;
trace_t trace;
idVec3 realTranslation, partialTranslation;
totalMass = 0.0f;
results.fraction = 1.0f;
results.endpos = newOrigin;
results.endAxis = pusher->GetPhysics()->GetAxis();
memset( results.c, 0, sizeof( results.c ) );
if ( translation == vec3_origin ) {
return totalMass;
}
// clip against all non-pushable physics objects
if ( flags & PUSHFL_CLIP ) {
numListedEntities = GetPushableEntitiesForTranslation( pusher, pusher, flags, translation, entityList, MAX_GENTITIES );
// disable pushable entities for collision detection
for ( i = 0; i < numListedEntities; i++ ) {
entityList[i]->GetPhysics()->DisableClip();
}
// clip translation
pusher->GetPhysics()->ClipTranslation( results, translation, NULL );
// enable pushable entities
for ( i = 0; i < numListedEntities; i++ ) {
entityList[i]->GetPhysics()->EnableClip();
}
if ( results.fraction == 0.0f ) {
return totalMass;
}
realTranslation = results.fraction * translation;
}
else {
realTranslation = translation;
}
// put the pusher in the group of pushed physics objects
pushedGroup[0].ent = pusher;
pushedGroup[0].fraction = 1.0f;
pushedGroup[0].groundContact = true;
pushedGroup[0].test = true;
pushedGroupSize = 1;
// get all physics objects that need to be pushed
for ( i = 0; i < pushedGroupSize; ) {
if ( !pushedGroup[i].test ) {
i++;
continue;
}
pushedGroup[i].test = false;
curPusher = pushedGroup[i].ent;
fraction = pushedGroup[i].fraction;
groundContact = pushedGroup[i].groundContact;
i = 0;
numListedEntities = GetPushableEntitiesForTranslation( curPusher, pusher, flags, realTranslation, entityList, MAX_GENTITIES );
for ( j = 0; j < numListedEntities; j++ ) {
ent = entityList[ j ];
if ( IsFullyPushed( ent ) ) {
continue;
}
if ( !CanPushEntity( ent, curPusher, pusher, flags ) ) {
continue;
}
if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) {
AddEntityToPushedGroup( ent, 1.0f * fraction, false );
}
else if ( ClipTranslationAgainstPusher( trace, ent, curPusher, -fraction * realTranslation ) ) {
AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact );
}
}
}
// save physics states and disable physics objects for collision detection
for ( i = 0; i < pushedGroupSize; i++ ) {
SaveEntityPosition( pushedGroup[i].ent );
pushedGroup[i].ent->GetPhysics()->DisableClip();
}
// clip all pushed physics objects
for ( i = 1; i < pushedGroupSize; i++ ) {
partialTranslation = realTranslation * pushedGroup[i].fraction;
pushedGroup[i].ent->GetPhysics()->ClipTranslation( trace, partialTranslation, NULL );
if ( trace.fraction < 1.0f ) {
blocked = true;
break;
}
}
// enable all physics objects for collision detection
for ( i = 1; i < pushedGroupSize; i++ ) {
pushedGroup[i].ent->GetPhysics()->EnableClip();
}
// push all or nothing
if ( blocked ) {
if ( flags & PUSHFL_CLIP ) {
pusher->GetPhysics()->ClipTranslation( results, realTranslation, NULL );
}
else {
results.fraction = 0.0f;
results.endpos = pusher->GetPhysics()->GetOrigin();
results.endAxis = pusher->GetPhysics()->GetAxis();
}
}
else {
// translate all pushed physics objects
for ( i = 1; i < pushedGroupSize; i++ ) {
partialTranslation = realTranslation * pushedGroup[i].fraction;
pushedGroup[i].ent->GetPhysics()->Translate( partialTranslation );
totalMass += pushedGroup[i].ent->GetPhysics()->GetMass();
}
// translate the clip models of the pusher
for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) {
pusher->GetPhysics()->GetClipModel(i)->Translate( results.fraction * realTranslation );
pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip );
}
}
return totalMass;
}
/*
============
idPush::ClipRotationAgainstPusher
============
*/
bool idPush::ClipRotationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idRotation &rotation ) {
int i, n;
trace_t t;
results.fraction = 1.0f;
n = pusher->GetPhysics()->GetNumClipModels();
for ( i = 0; i < n; i++ ) {
ent->GetPhysics()->ClipRotation( t, rotation, pusher->GetPhysics()->GetClipModel( i ) );
if ( t.fraction < results.fraction ) {
results = t;
}
}
return ( results.fraction < 1.0f );
}
/*
============
idPush::GetPushableEntitiesForRotation
============
*/
int idPush::GetPushableEntitiesForRotation( idEntity *pusher, idEntity *initialPusher, const int flags,
const idRotation &rotation, idEntity *entityList[], int maxEntities ) {
int i, n, l;
idBounds bounds, pushBounds;
idPhysics *physics;
// get bounds for the whole movement
physics = pusher->GetPhysics();
bounds = physics->GetBounds();
pushBounds.FromBoundsRotation( bounds, physics->GetOrigin(), physics->GetAxis(), rotation );
pushBounds.ExpandSelf( 2.0f );
// get all entities within the push bounds
n = gameLocal.clip.EntitiesTouchingBounds( pushBounds, -1, entityList, MAX_GENTITIES );
for ( l = i = 0; i < n; i++ ) {
if ( entityList[i] == pusher || entityList[i] == initialPusher ) {
continue;
}
if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) {
entityList[l++] = entityList[i];
}
}
return l;
}
/*
============
idPush::ClipRotationalPush
Try to push other entities by rotating the given entity.
============
*/
float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags,
const idMat3 &newAxis, const idRotation &rotation ) {
int i, j, numListedEntities;
idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ];
float fraction;
bool groundContact, blocked = false;
float totalMass;
trace_t trace;
idRotation realRotation, partialRotation;
idMat3 oldAxis;
totalMass = 0.0f;
results.fraction = 1.0f;
results.endpos = pusher->GetPhysics()->GetOrigin();
results.endAxis = newAxis;
memset( results.c, 0, sizeof( results.c ) );
if ( !rotation.GetAngle() ) {
return totalMass;
}
// clip against all non-pushable physics objects
if ( flags & PUSHFL_CLIP ) {
numListedEntities = GetPushableEntitiesForRotation( pusher, pusher, flags, rotation, entityList, MAX_GENTITIES );
// disable pushable entities for collision detection
for ( i = 0; i < numListedEntities; i++ ) {
entityList[i]->GetPhysics()->DisableClip();
}
// clip rotation
pusher->GetPhysics()->ClipRotation( results, rotation, NULL );
// enable pushable entities
for ( i = 0; i < numListedEntities; i++ ) {
entityList[i]->GetPhysics()->EnableClip();
}
if ( results.fraction == 0.0f ) {
return totalMass;
}
realRotation = results.fraction * rotation;
}
else {
realRotation = rotation;
}
// put the pusher in the group of pushed physics objects
pushedGroup[0].ent = pusher;
pushedGroup[0].fraction = 1.0f;
pushedGroup[0].groundContact = true;
pushedGroup[0].test = true;
pushedGroupSize = 1;
// get all physics objects that need to be pushed
for ( i = 0; i < pushedGroupSize; ) {
if ( !pushedGroup[i].test ) {
i++;
continue;
}
pushedGroup[i].test = false;
curPusher = pushedGroup[i].ent;
fraction = pushedGroup[i].fraction;
groundContact = pushedGroup[i].groundContact;
i = 0;
numListedEntities = GetPushableEntitiesForRotation( curPusher, pusher, flags, realRotation, entityList, MAX_GENTITIES );
for ( j = 0; j < numListedEntities; j++ ) {
ent = entityList[ j ];
if ( IsFullyPushed( ent ) ) {
continue;
}
if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) {
AddEntityToPushedGroup( ent, 1.0f * fraction, false );
}
else if ( ClipRotationAgainstPusher( trace, ent, curPusher, -fraction * realRotation ) ) {
AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact );
}
}
}
// save physics states and disable physics objects for collision detection
for ( i = 1; i < pushedGroupSize; i++ ) {
SaveEntityPosition( pushedGroup[i].ent );
pushedGroup[i].ent->GetPhysics()->DisableClip();
}
// clip all pushed physics objects
for ( i = 1; i < pushedGroupSize; i++ ) {
partialRotation = realRotation * pushedGroup[i].fraction;
pushedGroup[i].ent->GetPhysics()->ClipRotation( trace, partialRotation, NULL );
if ( trace.fraction < 1.0f ) {
blocked = true;
break;
}
}
// enable all physics objects for collision detection
for ( i = 1; i < pushedGroupSize; i++ ) {
pushedGroup[i].ent->GetPhysics()->EnableClip();
}
// push all or nothing
if ( blocked ) {
if ( flags & PUSHFL_CLIP ) {
pusher->GetPhysics()->ClipRotation( results, realRotation, NULL );
}
else {
results.fraction = 0.0f;
results.endpos = pusher->GetPhysics()->GetOrigin();
results.endAxis = pusher->GetPhysics()->GetAxis();
}
}
else {
// rotate all pushed physics objects
for ( i = 1; i < pushedGroupSize; i++ ) {
partialRotation = realRotation * pushedGroup[i].fraction;
pushedGroup[i].ent->GetPhysics()->Rotate( partialRotation );
totalMass += pushedGroup[i].ent->GetPhysics()->GetMass();
}
// rotate the clip models of the pusher
for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) {
pusher->GetPhysics()->GetClipModel(i)->Rotate( realRotation );
pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip );
pusher->GetPhysics()->GetClipModel(i)->Enable();
}
// rotate any actors back to axial
for ( i = 1; i < pushedGroupSize; i++ ) {
// if the entity is using actor physics
if ( pushedGroup[i].ent->GetPhysics()->IsType( idPhysics_Actor::Type ) ) {
// rotate the collision model back to axial
if ( !RotateEntityToAxial( pushedGroup[i].ent, pushedGroup[i].ent->GetPhysics()->GetOrigin() ) ) {
// don't allow rotation if the bbox is no longer axial
results.fraction = 0.0f;
results.endpos = pusher->GetPhysics()->GetOrigin();
results.endAxis = pusher->GetPhysics()->GetAxis();
}
}
}
}
return totalMass;
}
#else /* !NEW_PUSH */
enum {
PUSH_NO, // not pushed
PUSH_OK, // pushed ok
PUSH_BLOCKED // blocked
};
/*
============
idPush::ClipEntityRotation
============
*/
void idPush::ClipEntityRotation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idRotation &rotation ) {
if ( skip ) {
skip->Disable();
}
ent->GetPhysics()->ClipRotation( trace, rotation, clipModel );
if ( skip ) {
skip->Enable();
}
}
/*
============
idPush::ClipEntityTranslation
============
*/
void idPush::ClipEntityTranslation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idVec3 &translation ) {
if ( skip ) {
skip->Disable();
}
ent->GetPhysics()->ClipTranslation( trace, translation, clipModel );
if ( skip ) {
skip->Enable();
}
}
/*
============
idPush::TryRotatePushEntity
============
*/
#ifdef _DEBUG
// #define ROTATIONAL_PUSH_DEBUG
#endif
int idPush::TryRotatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags,
const idMat3 &newAxis, const idRotation &rotation ) {
trace_t trace;
idVec3 rotationPoint;
idRotation newRotation;
float checkAngle;
idPhysics *physics;
physics = check->GetPhysics();
#ifdef ROTATIONAL_PUSH_DEBUG
bool startsolid = false;
if ( physics->ClipContents( clipModel ) ) {
startsolid = true;
}
#endif
results.fraction = 1.0f;
results.endpos = clipModel->GetOrigin();
results.endAxis = newAxis;
memset( &results.c, 0, sizeof( results.c ) );
// always pushed when standing on the pusher
if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) {
// rotate the entity colliding with all other entities except the pusher itself
ClipEntityRotation( trace, check, NULL, clipModel, rotation );
// if there is a collision
if ( trace.fraction < 1.0f ) {
// angle along which the entity is pushed
checkAngle = rotation.GetAngle() * trace.fraction;
// test if the entity can stay at it's partly pushed position by rotating
// the entity in reverse only colliding with pusher
newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), -(rotation.GetAngle() - checkAngle) );
ClipEntityRotation( results, check, clipModel, NULL, newRotation );
// if there is a collision
if ( results.fraction < 1.0f ) {
// FIXME: try to push the blocking entity as well or try to slide along collision plane(s)?
results.c.normal = -results.c.normal;
results.c.dist = -results.c.dist;
// the entity will be crushed between the pusher and some other entity
return PUSH_BLOCKED;
}
}
else {
// angle along which the entity is pushed
checkAngle = rotation.GetAngle();
}
// point to rotate entity bbox around back to axial
rotationPoint = physics->GetOrigin();
}
else {
// rotate entity in reverse only colliding with pusher
newRotation = rotation;
newRotation.Scale( -1 );
//
ClipEntityRotation( results, check, clipModel, NULL, newRotation );
// if no collision with the pusher then the entity is not pushed by the pusher
if ( results.fraction >= 1.0f ) {
#ifdef ROTATIONAL_PUSH_DEBUG
// set pusher into final position
clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis );
if ( physics->ClipContents( clipModel ) ) {
if ( !startsolid ) {
int bah = 1;
}
}
#endif
return PUSH_NO;
}
// get point to rotate bbox around back to axial
rotationPoint = results.c.point;
// angle along which the entity will be pushed
checkAngle = rotation.GetAngle() * (1.0f - results.fraction);
// rotate the entity colliding with all other entities except the pusher itself
newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle );
ClipEntityRotation( trace, check, NULL, clipModel, newRotation );
// if there is a collision
if ( trace.fraction < 1.0f ) {
// FIXME: try to push the blocking entity as well or try to slide along collision plane(s)?
results.c.normal = -results.c.normal;
results.c.dist = -results.c.dist;
// the entity will be crushed between the pusher and some other entity
return PUSH_BLOCKED;
}
}
SaveEntityPosition( check );
newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle );
// NOTE: this code prevents msvc 6.0 & 7.0 from screwing up the above code in
// release builds moving less floats than it should
static float shit = checkAngle;
newRotation.RotatePoint( rotationPoint );
// rotate the entity
physics->Rotate( newRotation );
// set pusher into final position
clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis );
#ifdef ROTATIONAL_PUSH_DEBUG
if ( physics->ClipContents( clipModel ) ) {
if ( !startsolid ) {
int bah = 1;
}
}
#endif
// if the entity uses actor physics
if ( physics->IsType( idPhysics_Actor::Type ) ) {
// rotate the collision model back to axial
if ( !RotateEntityToAxial( check, rotationPoint ) ) {
// don't allow rotation if the bbox is no longer axial
return PUSH_BLOCKED;
}
}
#ifdef ROTATIONAL_PUSH_DEBUG
if ( physics->ClipContents( clipModel ) ) {
if ( !startsolid ) {
int bah = 1;
}
}
#endif
// if the entity is an actor using actor physics
if ( check->IsType( idActor::Type ) && physics->IsType( idPhysics_Actor::Type ) ) {
// if the entity is standing ontop of the pusher
if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) {
// rotate actor view
idActor *actor = static_cast<idActor *>(check);
idAngles delta = actor->GetDeltaViewAngles();
delta.yaw += newRotation.ToMat3()[0].ToYaw();
actor->SetDeltaViewAngles( delta );
}
}
return PUSH_OK;
}
/*
============
idPush::TryTranslatePushEntity
============
*/
#ifdef _DEBUG
// #define TRANSLATIONAL_PUSH_DEBUG
#endif
int idPush::TryTranslatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags,
const idVec3 &newOrigin, const idVec3 &move ) {
trace_t trace;
idVec3 checkMove;
idVec3 oldOrigin;
idPhysics *physics;
physics = check->GetPhysics();
#ifdef TRANSLATIONAL_PUSH_DEBUG
bool startsolid = false;
if ( physics->ClipContents( clipModel ) ) {
startsolid = true;
}
#endif
results.fraction = 1.0f;
results.endpos = newOrigin;
results.endAxis = clipModel->GetAxis();
memset( &results.c, 0, sizeof( results.c ) );
// always pushed when standing on the pusher
if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) {
// move the entity colliding with all other entities except the pusher itself
ClipEntityTranslation( trace, check, NULL, clipModel, move );
// if there is a collision
if ( trace.fraction < 1.0f ) {
// vector along which the entity is pushed
checkMove = move * trace.fraction;
// test if the entity can stay at it's partly pushed position by moving the entity in reverse only colliding with pusher
ClipEntityTranslation( results, check, clipModel, NULL, -(move - checkMove) );
// if there is a collision
if ( results.fraction < 1.0f ) {
// FIXME: try to push the blocking entity as well or try to slide along collision plane(s)?
results.c.normal = -results.c.normal;
results.c.dist = -results.c.dist;
// the entity will be crushed between the pusher and some other entity
return PUSH_BLOCKED;
}
}
else {
// vector along which the entity is pushed
checkMove = move;
}
}
else {
// move entity in reverse only colliding with pusher
ClipEntityTranslation( results, check, clipModel, NULL, -move );
// if no collision with the pusher then the entity is not pushed by the pusher
if ( results.fraction >= 1.0f ) {
return PUSH_NO;
}
// vector along which the entity is pushed
checkMove = move * (1.0f - results.fraction);
// move the entity colliding with all other entities except the pusher itself
ClipEntityTranslation( trace, check, NULL, clipModel, checkMove );
// if there is a collisions
if ( trace.fraction < 1.0f ) {
results.c.normal = -results.c.normal;
results.c.dist = -results.c.dist;
// FIXME: try to push the blocking entity as well ?
// FIXME: handle sliding along more than one collision plane ?
// FIXME: this code has issues, player pushing box into corner in "maps/mre/aaron/test.map"
/*
oldOrigin = physics->GetOrigin();
// movement still remaining
checkMove *= (1.0f - trace.fraction);
// project the movement along the collision plane
if ( !checkMove.ProjectAlongPlane( trace.c.normal, 0.1f, 1.001f ) ) {
return PUSH_BLOCKED;
}
checkMove *= 1.001f;
// move entity from collision point along the collision plane
physics->SetOrigin( trace.endpos );
ClipEntityTranslation( trace, check, NULL, NULL, checkMove );
if ( trace.fraction < 1.0f ) {
physics->SetOrigin( oldOrigin );
return PUSH_BLOCKED;
}
checkMove = trace.endpos - oldOrigin;
// move entity in reverse only colliding with pusher
physics->SetOrigin( trace.endpos );
ClipEntityTranslation( trace, check, clipModel, NULL, -move );
physics->SetOrigin( oldOrigin );
*/
if ( trace.fraction < 1.0f ) {
return PUSH_BLOCKED;
}
}
}
SaveEntityPosition( check );
// translate the entity
physics->Translate( checkMove );
#ifdef TRANSLATIONAL_PUSH_DEBUG
// set the pusher in the translated position
clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() );
if ( physics->ClipContents( clipModel ) ) {
if ( !startsolid ) {
int bah = 1;
}
}
#endif
return PUSH_OK;
}
/*
============
idPush::DiscardEntities
============
*/
int idPush::DiscardEntities( idEntity *entityList[], int numEntities, int flags, idEntity *pusher ) {
int i, num;
idEntity *check;
// remove all entities we cannot or should not push from the list
for ( num = i = 0; i < numEntities; i++ ) {
check = entityList[ i ];
// if the physics object is not pushable
if ( !check->GetPhysics()->IsPushable() ) {
continue;
}
// if the entity doesn't clip with this pusher
if ( !( check->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) {
continue;
}
// don't push players in noclip mode
if ( check->IsType( idPlayer::Type ) && static_cast<idPlayer *>(check)->noclip ) {
continue;
}
// if we should only push idMoveable entities
if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !check->IsType( idMoveable::Type ) ) {
continue;
}
// if we shouldn't push entities the clip model rests upon
if ( flags & PUSHFL_NOGROUNDENTITIES ) {
if ( pusher->GetPhysics()->IsGroundEntity( check->entityNumber ) ) {
continue;
}
}
// keep entity in list
entityList[ num++ ] = entityList[i];
}
return num;
}
/*
============
idPush::ClipTranslationalPush
Try to push other entities by moving the given entity.
============
*/
float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags,
const idVec3 &newOrigin, const idVec3 &translation ) {
int i, listedEntities, res;
idEntity *check, *entityList[ MAX_GENTITIES ];
idBounds bounds, pushBounds;
idVec3 clipMove, clipOrigin, oldOrigin, dir, impulse;
trace_t pushResults;
bool wasEnabled;
float totalMass;
idClipModel *clipModel;
clipModel = pusher->GetPhysics()->GetClipModel();
totalMass = 0.0f;
results.fraction = 1.0f;
results.endpos = newOrigin;
results.endAxis = clipModel->GetAxis();
memset( &results.c, 0, sizeof( results.c ) );
if ( translation == vec3_origin ) {
return totalMass;
}
dir = translation;
dir.Normalize();
dir.z += 1.0f;
dir *= 10.0f;
// get bounds for the whole movement
bounds = clipModel->GetBounds();
if ( bounds[0].x >= bounds[1].x ) {
return totalMass;
}
pushBounds.FromBoundsTranslation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), translation );
wasEnabled = clipModel->IsEnabled();
// make sure we don't get the pushing clip model in the list
clipModel->Disable();
listedEntities = gameLocal.clip.EntitiesTouchingBounds( pushBounds, -1, entityList, MAX_GENTITIES );
// discard entities we cannot or should not push
listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher );
if ( flags & PUSHFL_CLIP ) {
// can only clip movement of a trace model
assert( clipModel->IsTraceModel() );
// disable to be pushed entities for collision detection
for ( i = 0; i < listedEntities; i++ ) {
entityList[i]->GetPhysics()->DisableClip();
}
gameLocal.clip.Translation( results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL );
// enable to be pushed entities for collision detection
for ( i = 0; i < listedEntities; i++ ) {
entityList[i]->GetPhysics()->EnableClip();
}
if ( results.fraction == 0.0f ) {
if ( wasEnabled ) {
clipModel->Enable();
}
return totalMass;
}
clipMove = results.endpos - clipModel->GetOrigin();
clipOrigin = results.endpos;
}
else {
clipMove = translation;
clipOrigin = newOrigin;
}
// we have to enable the clip model because we use it during pushing
clipModel->Enable();
// save pusher old position
oldOrigin = clipModel->GetOrigin();
// try to push the entities
for ( i = 0; i < listedEntities; i++ ) {
check = entityList[ i ];
idPhysics *physics = check->GetPhysics();
// disable the entity for collision detection
physics->DisableClip();
res = TryTranslatePushEntity( pushResults, check, clipModel, flags, clipOrigin, clipMove );
// enable the entity for collision detection
physics->EnableClip();
// if the entity is pushed
if ( res == PUSH_OK ) {
// set the pusher in the translated position
clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() );
// the entity might be pushed off the ground
physics->EvaluateContacts();
// put pusher back in old position
clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), oldOrigin, clipModel->GetAxis() );
// wake up this object
if ( flags & PUSHFL_APPLYIMPULSE ) {
impulse = physics->GetMass() * dir;
} else {
impulse.Zero();
}
check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), impulse );
// add mass of pushed entity
totalMass += physics->GetMass();
}
// if the entity is not blocking
if ( res != PUSH_BLOCKED ) {
continue;
}
// if the blocking entity is a projectile
if ( check->IsType( idProjectile::Type ) ) {
check->ProcessEvent( &EV_Explode );
continue;
}
// if blocking entities should be crushed
if ( flags & PUSHFL_CRUSH ) {
check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) );
continue;
}
// if the entity is an active articulated figure and gibs
if ( check->IsType( idAFEntity_Base::Type ) && check->spawnArgs.GetBool( "gib" ) ) {
if ( static_cast<idAFEntity_Base *>(check)->IsActiveAF() ) {
check->ProcessEvent( &EV_Gib, "damage_Gib" );
}
}
// if the entity is a moveable item and gibs
if ( check->IsType( idMoveableItem::Type ) && check->spawnArgs.GetBool( "gib" ) ) {
check->ProcessEvent( &EV_Gib, "damage_Gib" );
}
// blocked
results = pushResults;
results.fraction = 0.0f;
results.endAxis = clipModel->GetAxis();
results.endpos = clipModel->GetOrigin();
results.c.entityNum = check->entityNumber;
results.c.id = 0;
if ( !wasEnabled ) {
clipModel->Disable();
}
return totalMass;
}
if ( !wasEnabled ) {
clipModel->Disable();
}
return totalMass;
}
/*
============
idPush::ClipRotationalPush
Try to push other entities by moving the given entity.
============
*/
float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags,
const idMat3 &newAxis, const idRotation &rotation ) {
int i, listedEntities, res;
idEntity *check, *entityList[ MAX_GENTITIES ];
idBounds bounds, pushBounds;
idRotation clipRotation;
idMat3 clipAxis, oldAxis;
trace_t pushResults;
bool wasEnabled;
float totalMass;
idClipModel *clipModel;
clipModel = pusher->GetPhysics()->GetClipModel();
totalMass = 0.0f;
results.fraction = 1.0f;
results.endpos = clipModel->GetOrigin();
results.endAxis = newAxis;
memset( &results.c, 0, sizeof( results.c ) );
if ( !rotation.GetAngle() ) {
return totalMass;
}
// get bounds for the whole movement
bounds = clipModel->GetBounds();
if ( bounds[0].x >= bounds[1].x ) {
return totalMass;
}
pushBounds.FromBoundsRotation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), rotation );
wasEnabled = clipModel->IsEnabled();
// make sure we don't get the pushing clip model in the list
clipModel->Disable();
listedEntities = gameLocal.clip.EntitiesTouchingBounds( pushBounds, -1, entityList, MAX_GENTITIES );
// discard entities we cannot or should not push
listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher );
if ( flags & PUSHFL_CLIP ) {
// can only clip movement of a trace model
assert( clipModel->IsTraceModel() );
// disable to be pushed entities for collision detection
for ( i = 0; i < listedEntities; i++ ) {
entityList[i]->GetPhysics()->DisableClip();
}
gameLocal.clip.Rotation( results, clipModel->GetOrigin(), rotation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL );
// enable to be pushed entities for collision detection
for ( i = 0; i < listedEntities; i++ ) {
entityList[i]->GetPhysics()->EnableClip();
}
if ( results.fraction == 0.0f ) {
if ( wasEnabled ) {
clipModel->Enable();
}
return totalMass;
}
clipRotation = rotation * results.fraction;
clipAxis = results.endAxis;
}
else {
clipRotation = rotation;
clipAxis = newAxis;
}
// we have to enable the clip model because we use it during pushing
clipModel->Enable();
// save pusher old position
oldAxis = clipModel->GetAxis();
// try to push all the entities
for ( i = 0; i < listedEntities; i++ ) {
check = entityList[ i ];
idPhysics *physics = check->GetPhysics();
// disable the entity for collision detection
physics->DisableClip();
res = TryRotatePushEntity( pushResults, check, clipModel, flags, clipAxis, clipRotation );
// enable the entity for collision detection
physics->EnableClip();
// if the entity is pushed
if ( res == PUSH_OK ) {
// set the pusher in the rotated position
clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis );
// the entity might be pushed off the ground
physics->EvaluateContacts();
// put pusher back in old position
clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), oldAxis );
// wake up this object
check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), vec3_origin );
// add mass of pushed entity
totalMass += physics->GetMass();
}
// if the entity is not blocking
if ( res != PUSH_BLOCKED ) {
continue;
}
// if the blocking entity is a projectile
if ( check->IsType( idProjectile::Type ) ) {
check->ProcessEvent( &EV_Explode );
continue;
}
// if blocking entities should be crushed
if ( flags & PUSHFL_CRUSH ) {
check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) );
continue;
}
// if the entity is an active articulated figure and gibs
if ( check->IsType( idAFEntity_Base::Type ) && check->spawnArgs.GetBool( "gib" ) ) {
if ( static_cast<idAFEntity_Base *>(check)->IsActiveAF() ) {
check->ProcessEvent( &EV_Gib, "damage_Gib" );
}
}
// blocked
results = pushResults;
results.fraction = 0.0f;
results.endAxis = clipModel->GetAxis();
results.endpos = clipModel->GetOrigin();
results.c.entityNum = check->entityNumber;
results.c.id = 0;
if ( !wasEnabled ) {
clipModel->Disable();
}
return totalMass;
}
if ( !wasEnabled ) {
clipModel->Disable();
}
return totalMass;
}
#endif /* !NEW_PUSH */
/*
============
idPush::ClipPush
Try to push other entities by moving the given entity.
============
*/
float idPush::ClipPush( trace_t &results, idEntity *pusher, const int flags,
const idVec3 &oldOrigin, const idMat3 &oldAxis,
idVec3 &newOrigin, idMat3 &newAxis ) {
idVec3 translation;
idRotation rotation;
float mass;
mass = 0.0f;
results.fraction = 1.0f;
results.endpos = newOrigin;
results.endAxis = newAxis;
memset( &results.c, 0, sizeof( results.c ) );
// translational push
translation = newOrigin - oldOrigin;
// if the pusher translates
if ( translation != vec3_origin ) {
mass += ClipTranslationalPush( results, pusher, flags, newOrigin, translation );
if ( results.fraction < 1.0f ) {
newOrigin = oldOrigin;
newAxis = oldAxis;
return mass;
}
} else {
newOrigin = oldOrigin;
}
// rotational push
rotation = ( oldAxis.Transpose() * newAxis ).ToRotation();
rotation.SetOrigin( newOrigin );
rotation.Normalize180();
rotation.ReCalculateMatrix(); // recalculate the rotation matrix to avoid accumulating rounding errors
// if the pusher rotates
if ( rotation.GetAngle() != 0.0f ) {
// recalculate new axis to avoid floating point rounding problems
newAxis = oldAxis * rotation.ToMat3();
newAxis.OrthoNormalizeSelf();
newAxis.FixDenormals();
newAxis.FixDegeneracies();
pusher->GetPhysics()->GetClipModel()->SetPosition( newOrigin, oldAxis );
mass += ClipRotationalPush( results, pusher, flags, newAxis, rotation );
if ( results.fraction < 1.0f ) {
newOrigin = oldOrigin;
newAxis = oldAxis;
return mass;
}
} else {
newAxis = oldAxis;
}
return mass;
}