mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-11 14:44:36 +00:00
Engine directory for ticket #1
This commit is contained in:
parent
352279af7a
commit
7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions
513
Engine/source/T3D/physics/bullet/btPlayer.cpp
Normal file
513
Engine/source/T3D/physics/bullet/btPlayer.cpp
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "T3D/physics/bullet/btPlayer.h"
|
||||
|
||||
#include "T3D/physics/physicsPlugin.h"
|
||||
#include "T3D/physics/bullet/btWorld.h"
|
||||
#include "T3D/physics/bullet/btCasts.h"
|
||||
#include "collision/collision.h"
|
||||
|
||||
BtPlayer::BtPlayer()
|
||||
: PhysicsPlayer(),
|
||||
mWorld( NULL ),
|
||||
mObject( NULL ),
|
||||
mGhostObject( NULL ),
|
||||
mColShape( NULL ),
|
||||
mOriginOffset( 0.0f )
|
||||
{
|
||||
}
|
||||
|
||||
BtPlayer::~BtPlayer()
|
||||
{
|
||||
_releaseController();
|
||||
}
|
||||
|
||||
void BtPlayer::_releaseController()
|
||||
{
|
||||
if ( !mGhostObject )
|
||||
return;
|
||||
|
||||
mWorld->getDynamicsWorld()->removeCollisionObject( mGhostObject );
|
||||
|
||||
SAFE_DELETE( mGhostObject );
|
||||
SAFE_DELETE( mColShape );
|
||||
}
|
||||
|
||||
void BtPlayer::init( const char *type,
|
||||
const Point3F &size,
|
||||
F32 runSurfaceCos,
|
||||
F32 stepHeight,
|
||||
SceneObject *obj,
|
||||
PhysicsWorld *world )
|
||||
{
|
||||
AssertFatal( obj, "BtPlayer::init - Got a null scene object!" );
|
||||
AssertFatal( world, "BtPlayer::init - Got a null world!" );
|
||||
AssertFatal( dynamic_cast<BtWorld*>( world ), "BtPlayer::init - The world is the wrong type!" );
|
||||
|
||||
// Cleanup any previous controller.
|
||||
_releaseController();
|
||||
|
||||
mObject = obj;
|
||||
mWorld = (BtWorld*)world;
|
||||
|
||||
mStepHeight = stepHeight;
|
||||
|
||||
//if ( dStricmp( type, "Capsule" ) == 0 )
|
||||
{
|
||||
F32 radius = getMax( size.x, size.y ) * 0.5f;
|
||||
F32 height = size.z - ( radius * 2.0f );
|
||||
mColShape = new btCapsuleShapeZ( radius, height );
|
||||
mColShape->setMargin( 0.05f );
|
||||
mOriginOffset = ( height * 0.5 ) + radius;
|
||||
}
|
||||
//else
|
||||
{
|
||||
//mColShape = new btBoxShape( btVector3( 0.5f, 0.5f, 1.0f ) );
|
||||
//mOriginOffset = 1.0f;
|
||||
}
|
||||
|
||||
mGhostObject = new btPairCachingGhostObject();
|
||||
mGhostObject->setCollisionShape( mColShape );
|
||||
mGhostObject->setCollisionFlags( btCollisionObject::CF_CHARACTER_OBJECT );
|
||||
mWorld->getDynamicsWorld()->addCollisionObject( mGhostObject,
|
||||
btBroadphaseProxy::CharacterFilter,
|
||||
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter );
|
||||
|
||||
mUserData.setObject( obj );
|
||||
mGhostObject->setUserPointer( &mUserData );
|
||||
}
|
||||
|
||||
Point3F BtPlayer::move( const VectorF &disp, CollisionList &outCol )
|
||||
{
|
||||
AssertFatal( mGhostObject, "BtPlayer::move - The controller is null!" );
|
||||
|
||||
// First recover from any penetrations from the previous tick.
|
||||
U32 numPenetrationLoops = 0;
|
||||
bool touchingContact = false;
|
||||
while ( _recoverFromPenetration() )
|
||||
{
|
||||
numPenetrationLoops++;
|
||||
touchingContact = true;
|
||||
if ( numPenetrationLoops > 4 )
|
||||
break;
|
||||
}
|
||||
|
||||
btTransform newTrans = mGhostObject->getWorldTransform();
|
||||
btVector3 newPos = newTrans.getOrigin();
|
||||
|
||||
// The move consists of 3 steps... the up step, the forward
|
||||
// step, and the down step.
|
||||
|
||||
btVector3 forwardSweep( disp.x, disp.y, 0.0f );
|
||||
const bool hasForwardSweep = forwardSweep.length2() > 0.0f;
|
||||
F32 upSweep = 0.0f;
|
||||
F32 downSweep = 0.0f;
|
||||
if ( disp[2] < 0.0f )
|
||||
downSweep = disp[2];
|
||||
else
|
||||
upSweep = disp[2];
|
||||
|
||||
// Only do auto stepping if the character is moving forward.
|
||||
F32 stepOffset = mStepHeight;
|
||||
if ( hasForwardSweep )
|
||||
upSweep += stepOffset;
|
||||
|
||||
// First we do the up step which includes the passed in
|
||||
// upward displacement as well as the auto stepping.
|
||||
if ( upSweep > 0.0f &&
|
||||
_sweep( &newPos, btVector3( 0.0f, 0.0f, upSweep ), NULL ) )
|
||||
{
|
||||
// Keep track of how far we actually swept to make sure
|
||||
// we do not remove too much in the down sweep.
|
||||
F32 delta = newPos[2] - newTrans.getOrigin()[2];
|
||||
if ( delta < stepOffset )
|
||||
stepOffset = delta;
|
||||
}
|
||||
|
||||
// Now do the forward step.
|
||||
_stepForward( &newPos, forwardSweep, &outCol );
|
||||
|
||||
// Now remove what remains of our auto step
|
||||
// from the down sweep.
|
||||
if ( hasForwardSweep )
|
||||
downSweep -= stepOffset;
|
||||
|
||||
// Do the downward sweep.
|
||||
if ( downSweep < 0.0f )
|
||||
_sweep( &newPos, btVector3( 0.0f, 0.0f, downSweep ), &outCol );
|
||||
|
||||
// Finally update the ghost with its new position.
|
||||
newTrans.setOrigin( newPos );
|
||||
mGhostObject->setWorldTransform( newTrans );
|
||||
|
||||
// Return the current position of the ghost.
|
||||
newPos[2] -= mOriginOffset;
|
||||
return btCast<Point3F>( newPos );
|
||||
}
|
||||
|
||||
bool BtPlayer::_recoverFromPenetration()
|
||||
{
|
||||
bool penetration = false;
|
||||
|
||||
btDynamicsWorld *collWorld = mWorld->getDynamicsWorld();
|
||||
|
||||
collWorld->getDispatcher()->dispatchAllCollisionPairs( mGhostObject->getOverlappingPairCache(),
|
||||
collWorld->getDispatchInfo(),
|
||||
collWorld->getDispatcher() );
|
||||
|
||||
btVector3 currPos = mGhostObject->getWorldTransform().getOrigin();
|
||||
btScalar maxPen = 0.0f;
|
||||
btManifoldArray manifoldArray;
|
||||
|
||||
for ( U32 i = 0; i < mGhostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++ )
|
||||
{
|
||||
btBroadphasePair *collisionPair = &mGhostObject->getOverlappingPairCache()->getOverlappingPairArray()[i];
|
||||
|
||||
if ( ((btCollisionObject*)collisionPair->m_pProxy0->m_clientObject)->getCollisionFlags() & btCollisionObject::CF_NO_CONTACT_RESPONSE ||
|
||||
((btCollisionObject*)collisionPair->m_pProxy1->m_clientObject)->getCollisionFlags() & btCollisionObject::CF_NO_CONTACT_RESPONSE )
|
||||
continue;
|
||||
|
||||
manifoldArray.resize(0);
|
||||
if (collisionPair->m_algorithm)
|
||||
collisionPair->m_algorithm->getAllContactManifolds(manifoldArray);
|
||||
|
||||
for ( U32 j=0; j < manifoldArray.size(); j++ )
|
||||
{
|
||||
btPersistentManifold* manifold = manifoldArray[j];
|
||||
btScalar directionSign = manifold->getBody0() == mGhostObject ? -1.0f : 1.0f;
|
||||
|
||||
for ( U32 p=0; p < manifold->getNumContacts(); p++ )
|
||||
{
|
||||
const btManifoldPoint&pt = manifold->getContactPoint(p);
|
||||
|
||||
if ( pt.getDistance() < -mColShape->getMargin() )
|
||||
{
|
||||
if ( pt.getDistance() < maxPen )
|
||||
{
|
||||
maxPen = pt.getDistance();
|
||||
//m_touchingNormal = pt.m_normalWorldOnB * directionSign;//??
|
||||
}
|
||||
|
||||
currPos += pt.m_normalWorldOnB * directionSign * pt.getDistance(); // * 0.25f;
|
||||
penetration = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//printf("touching %f\n", pt.getDistance());
|
||||
}
|
||||
}
|
||||
|
||||
//manifold->clearManifold();
|
||||
}
|
||||
}
|
||||
|
||||
// Update the ghost transform.
|
||||
btTransform newTrans = mGhostObject->getWorldTransform();
|
||||
newTrans.setOrigin( currPos );
|
||||
mGhostObject->setWorldTransform( newTrans );
|
||||
|
||||
return penetration;
|
||||
}
|
||||
|
||||
|
||||
class BtPlayerSweepCallback : public btCollisionWorld::ClosestConvexResultCallback
|
||||
{
|
||||
typedef btCollisionWorld::ClosestConvexResultCallback Parent;
|
||||
|
||||
public:
|
||||
|
||||
BtPlayerSweepCallback( btCollisionObject *me, const btVector3 &moveVec )
|
||||
: Parent( btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0) ),
|
||||
mMe( me ),
|
||||
mMoveVec( moveVec )
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool needsCollision(btBroadphaseProxy* proxy0) const
|
||||
{
|
||||
if ( proxy0->m_clientObject == mMe )
|
||||
return false;
|
||||
|
||||
return Parent::needsCollision( proxy0 );
|
||||
}
|
||||
|
||||
virtual btScalar addSingleResult( btCollisionWorld::LocalConvexResult &convexResult,
|
||||
bool normalInWorldSpace )
|
||||
{
|
||||
// NOTE: I shouldn't have to do any of this, but Bullet
|
||||
// has some weird bugs.
|
||||
//
|
||||
// For one the plane type will return hits on a Z up surface
|
||||
// for sweeps that have no Z sweep component.
|
||||
//
|
||||
// Second the normal returned here is sometimes backwards
|
||||
// to the sweep direction... no clue why.
|
||||
//
|
||||
F32 dotN = mMoveVec.dot( convexResult.m_hitNormalLocal );
|
||||
if ( mFabs( dotN ) < 0.1f )
|
||||
return 1.0f;
|
||||
|
||||
if ( convexResult.m_hitCollisionObject->getCollisionFlags() & btCollisionObject::CF_NO_CONTACT_RESPONSE )
|
||||
return 1.0f;
|
||||
|
||||
return Parent::addSingleResult( convexResult, normalInWorldSpace );
|
||||
}
|
||||
|
||||
protected:
|
||||
btVector3 mMoveVec;
|
||||
btCollisionObject *mMe;
|
||||
};
|
||||
|
||||
bool BtPlayer::_sweep( btVector3 *inOutCurrPos, const btVector3 &disp, CollisionList *outCol )
|
||||
{
|
||||
btTransform start( btTransform::getIdentity() );
|
||||
start.setOrigin ( *inOutCurrPos );
|
||||
|
||||
btTransform end( btTransform::getIdentity() );
|
||||
end.setOrigin ( *inOutCurrPos + disp );
|
||||
|
||||
BtPlayerSweepCallback callback( mGhostObject, disp.normalized() );
|
||||
callback.m_collisionFilterGroup = mGhostObject->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = mGhostObject->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
||||
mGhostObject->convexSweepTest( mColShape, start, end, callback, 0.0f );
|
||||
|
||||
inOutCurrPos->setInterpolate3( start.getOrigin(), end.getOrigin(), callback.m_closestHitFraction );
|
||||
if ( callback.hasHit() )
|
||||
{
|
||||
if ( outCol )
|
||||
{
|
||||
Collision& col = outCol->increment();
|
||||
dMemset( &col, 0, sizeof( col ) );
|
||||
|
||||
col.normal = btCast<Point3F>( callback.m_hitNormalWorld );
|
||||
col.object = PhysicsUserData::getObject( callback.m_hitCollisionObject->getUserPointer() );
|
||||
|
||||
if (disp.z() < 0.0f)
|
||||
{
|
||||
// We're sweeping down as part of the stepping routine. In this
|
||||
// case we want to have the collision normal only point in the opposite direction.
|
||||
// i.e. up If we include the sideways part of the normal then the Player class
|
||||
// velocity calculations using this normal will affect the player's forwards
|
||||
// momentum. This is especially noticable on stairs as the rounded bottom of
|
||||
// the capsule slides up the corner of a stair.
|
||||
col.normal.set(0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BtPlayer::_stepForward( btVector3 *inOutCurrPos, const btVector3 &displacement, CollisionList *outCol )
|
||||
{
|
||||
btTransform start( btTransform::getIdentity() );
|
||||
btTransform end( btTransform::getIdentity() );
|
||||
F32 fraction = 1.0f;
|
||||
S32 maxIter = 10;
|
||||
btVector3 disp = displacement;
|
||||
|
||||
while ( fraction > 0.01f && maxIter-- > 0 )
|
||||
{
|
||||
// Setup the sweep start and end transforms.
|
||||
start.setOrigin( *inOutCurrPos );
|
||||
end.setOrigin( *inOutCurrPos + disp );
|
||||
|
||||
BtPlayerSweepCallback callback( mGhostObject, disp.length2() > 0.0f ? disp.normalized() : disp );
|
||||
callback.m_collisionFilterGroup = mGhostObject->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = mGhostObject->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
||||
mGhostObject->convexSweepTest( mColShape, start, end, callback, 0.0f );
|
||||
|
||||
// Subtract from the travel fraction.
|
||||
fraction -= callback.m_closestHitFraction;
|
||||
|
||||
// Did we get a hit?
|
||||
if ( callback.hasHit() )
|
||||
{
|
||||
/*
|
||||
// Get the real hit normal... Bullet returns the 'seperating normal' and not
|
||||
// the normal of the hit object.
|
||||
btTransform rayStart( btTransform::getIdentity() );
|
||||
rayStart.setOrigin( callback.m_hitPointWorld + callback.m_hitNormalWorld );
|
||||
btTransform rayEnd( btTransform::getIdentity() );
|
||||
rayEnd.setOrigin( callback.m_hitPointWorld - callback.m_hitNormalWorld );
|
||||
|
||||
btCollisionWorld::ClosestRayResultCallback rayHit( rayStart.getOrigin(), rayEnd.getOrigin() );
|
||||
mWorld->getDynamicsWorld()->rayTestSingle( rayStart,
|
||||
rayEnd,
|
||||
callback.m_hitCollisionObject,
|
||||
callback.m_hitCollisionObject->getCollisionShape(),
|
||||
callback.m_hitCollisionObject->getWorldTransform(),
|
||||
rayHit );
|
||||
|
||||
if ( !rayHit.hasHit() )
|
||||
break;
|
||||
*/
|
||||
|
||||
Collision& col = outCol->increment();
|
||||
dMemset( &col, 0, sizeof( col ) );
|
||||
|
||||
col.normal = btCast<Point3F>( callback.m_hitNormalWorld );
|
||||
col.object = PhysicsUserData::getObject( callback.m_hitCollisionObject->getUserPointer() );
|
||||
|
||||
// If the collision direction is sideways then modify the collision normal
|
||||
// to remove any z component. This takes care of any sideways collisions
|
||||
// with the round bottom of the capsule when it comes to the Player class
|
||||
// velocity calculations. We want all sideways collisions to be treated
|
||||
// as if they hit the side of a cylinder.
|
||||
if (col.normal.z > 0.0f)
|
||||
{
|
||||
// This will only remove the z component of the collision normal
|
||||
// for the bottom of the character controller, which would hit during
|
||||
// a step. We'll leave the top hemisphere of the character's capsule
|
||||
// alone as bumping one's head is an entirely different story. This
|
||||
// helps with low doorways.
|
||||
col.normal.z = 0.0f;
|
||||
col.normal.normalizeSafe();
|
||||
}
|
||||
|
||||
// Interpolate to the new position.
|
||||
inOutCurrPos->setInterpolate3( start.getOrigin(), end.getOrigin(), callback.m_closestHitFraction );
|
||||
|
||||
// Subtract out the displacement along the collision normal.
|
||||
F32 bd = -disp.dot( callback.m_hitNormalWorld );
|
||||
btVector3 dv = callback.m_hitNormalWorld * bd;
|
||||
disp += dv;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we moved whole way
|
||||
*inOutCurrPos = end.getOrigin();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BtPlayer::findContact( SceneObject **contactObject,
|
||||
VectorF *contactNormal,
|
||||
Vector<SceneObject*> *outOverlapObjects ) const
|
||||
{
|
||||
AssertFatal( mGhostObject, "BtPlayer::findContact - The controller is null!" );
|
||||
|
||||
VectorF normal;
|
||||
F32 maxDot = -1.0f;
|
||||
|
||||
// Go thru the contact points... get the first contact.
|
||||
btHashedOverlappingPairCache *pairCache = mGhostObject->getOverlappingPairCache();
|
||||
btBroadphasePairArray& pairArray = pairCache->getOverlappingPairArray();
|
||||
U32 numPairs = pairArray.size();
|
||||
btManifoldArray manifoldArray;
|
||||
|
||||
for ( U32 i=0; i < numPairs; i++ )
|
||||
{
|
||||
const btBroadphasePair &pair = pairArray[i];
|
||||
|
||||
btBroadphasePair *collisionPair = pairCache->findPair( pair.m_pProxy0, pair.m_pProxy1 );
|
||||
if ( !collisionPair || !collisionPair->m_algorithm )
|
||||
continue;
|
||||
|
||||
btCollisionObject *other = (btCollisionObject*)pair.m_pProxy0->m_clientObject;
|
||||
if ( other == mGhostObject )
|
||||
other = (btCollisionObject*)pair.m_pProxy1->m_clientObject;
|
||||
|
||||
AssertFatal( !outOverlapObjects->contains( PhysicsUserData::getObject( other->getUserPointer() ) ),
|
||||
"Got multiple pairs of the same object!" );
|
||||
outOverlapObjects->push_back( PhysicsUserData::getObject( other->getUserPointer() ) );
|
||||
|
||||
if ( other->getCollisionFlags() & btCollisionObject::CF_NO_CONTACT_RESPONSE )
|
||||
continue;
|
||||
|
||||
manifoldArray.clear();
|
||||
collisionPair->m_algorithm->getAllContactManifolds( manifoldArray );
|
||||
|
||||
for ( U32 j=0; j < manifoldArray.size(); j++ )
|
||||
{
|
||||
btPersistentManifold *manifold = manifoldArray[j];
|
||||
btScalar directionSign = manifold->getBody0() == mGhostObject ? 1.0f : -1.0f;
|
||||
|
||||
for ( U32 p=0; p < manifold->getNumContacts(); p++ )
|
||||
{
|
||||
const btManifoldPoint &pt = manifold->getContactPoint(p);
|
||||
|
||||
// Test the normal... is it the most vertical one we got?
|
||||
normal = btCast<Point3F>( pt.m_normalWorldOnB * directionSign );
|
||||
F32 dot = mDot( normal, VectorF( 0, 0, 1 ) );
|
||||
if ( dot > maxDot )
|
||||
{
|
||||
maxDot = dot;
|
||||
|
||||
btCollisionObject *colObject = (btCollisionObject*)collisionPair->m_pProxy0->m_clientObject;
|
||||
*contactObject = PhysicsUserData::getObject( colObject->getUserPointer() );
|
||||
*contactNormal = normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BtPlayer::enableCollision()
|
||||
{
|
||||
AssertFatal( mGhostObject, "BtPlayer::enableCollision - The controller is null!" );
|
||||
|
||||
//mController->setCollision( true );
|
||||
}
|
||||
|
||||
void BtPlayer::disableCollision()
|
||||
{
|
||||
AssertFatal( mGhostObject, "BtPlayer::disableCollision - The controller is null!" );
|
||||
|
||||
//mController->setCollision( false );
|
||||
}
|
||||
|
||||
PhysicsWorld* BtPlayer::getWorld()
|
||||
{
|
||||
return mWorld;
|
||||
}
|
||||
|
||||
void BtPlayer::setTransform( const MatrixF &transform )
|
||||
{
|
||||
AssertFatal( mGhostObject, "BtPlayer::setTransform - The ghost object is null!" );
|
||||
|
||||
btTransform xfm = btCast<btTransform>( transform );
|
||||
xfm.getOrigin()[2] += mOriginOffset;
|
||||
|
||||
mGhostObject->setWorldTransform( xfm );
|
||||
}
|
||||
|
||||
MatrixF& BtPlayer::getTransform( MatrixF *outMatrix )
|
||||
{
|
||||
AssertFatal( mGhostObject, "BtPlayer::getTransform - The ghost object is null!" );
|
||||
|
||||
*outMatrix = btCast<MatrixF>( mGhostObject->getWorldTransform() );
|
||||
*outMatrix[11] -= mOriginOffset;
|
||||
|
||||
return *outMatrix;
|
||||
}
|
||||
|
||||
void BtPlayer::setScale( const Point3F &scale )
|
||||
{
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue