//----------------------------------------------------------------------------- // 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/physX/pxPlayer.h" #include "T3D/physics/physicsPlugin.h" #include "T3D/physics/physX/pxWorld.h" #include "T3D/physics/physX/pxCasts.h" #include "collision/collision.h" //#include "gfx/gfxDrawUtil.h" //#include "sim/netConnection.h" PxPlayer::PxPlayer() : PhysicsPlayer(), mController( NULL ), mWorld( NULL ), mObject( NULL ), mSkinWidth( 0.1f ), mOriginOffset( 0.0f ) { PHYSICSMGR->getPhysicsResetSignal().notify( this, &PxPlayer::_onPhysicsReset ); } PxPlayer::~PxPlayer() { _releaseController(); PHYSICSMGR->getPhysicsResetSignal().remove( this, &PxPlayer::_onPhysicsReset ); } void PxPlayer::_releaseController() { if ( mController ) { mController->getActor()->userData = NULL; mWorld->getStaticChangedSignal().remove( this, &PxPlayer::_onStaticChanged ); mWorld->releaseController( *mController ); mController = NULL; } } void PxPlayer::init( const char *type, const Point3F &size, F32 runSurfaceCos, F32 stepHeight, SceneObject *obj, PhysicsWorld *world ) { AssertFatal( obj, "PxPlayer::init - Got a null scene object!" ); AssertFatal( world, "PxPlayer::init - Got a null world!" ); AssertFatal( dynamic_cast( world ), "PxPlayer::init - The world is the wrong type!" ); // Cleanup any previous controller. _releaseController(); mObject = obj; mWorld = (PxWorld*)world; mOriginOffset = size.z * 0.5f; //if ( dStricmp( type, "Capsule" ) == 0 ) { NxCapsuleControllerDesc desc; desc.skinWidth = 0.05f; // Expose? desc.radius = getMax( size.x, size.y ) * 0.5f; desc.radius -= desc.skinWidth; desc.height = size.z - ( desc.radius * 2.0f ); desc.height -= desc.skinWidth * 2.0f; desc.climbingMode = CLIMB_CONSTRAINED; desc.position.set( 0, 0, 0 ); desc.upDirection = NX_Z; desc.callback = this; // TODO: Fix this as well! desc.slopeLimit = runSurfaceCos; desc.stepOffset = stepHeight; mController = mWorld->createController( desc ); } //else { //mColShape = new btBoxShape( btVector3( 0.5f, 0.5f, 1.0f ) ); //mOriginOffset = 1.0f; } mWorld->getStaticChangedSignal().notify( this, &PxPlayer::_onStaticChanged ); // Put the kinematic actor on group 29. NxActor *kineActor = mController->getActor(); kineActor->setGroup( 29 ); NxShape *const *shapes = kineActor->getShapes(); for ( U32 i=0; i < kineActor->getNbShapes(); i++ ) shapes[i]->setGroup( 29 ); mUserData.setObject( obj ); kineActor->userData = &mUserData; } void PxPlayer::_onStaticChanged() { mController->reportSceneChanged(); } void PxPlayer::_onPhysicsReset( PhysicsResetEvent reset ) { // The PhysX controller will crash out if it doesn't clear its // list of static elements when they are deleted. By calling this // on physics events we clear the cache and we don't get crashes. // // This all depends on not doing moves and sweeps when the // simulation is paused... we need to stop operating. if ( mController ) mController->reportSceneChanged(); } Point3F PxPlayer::move( const VectorF &disp, CollisionList &outCol ) { AssertFatal( mController, "PxPlayer::move - The controller is null!" ); // Return the last position if the simulation is stopped. // // See PxPlayer::_onPhysicsReset if ( !mWorld->isEnabled() ) { Point3F newPos = pxCast( mController->getDebugPosition() ); newPos.z -= mOriginOffset; //outCol->point = newPos; //outCol->normal.set( 0, 0, 1 ); return newPos; } mWorld->releaseWriteLock(); mCollisionList = &outCol; // PhysX 2.8.4 checks up an up displacement and if found will assume // the player is flying and remove the step offset. If we have a small // z displacement here, zero it out. NxVec3 dispNx( disp.x, disp.y, disp.z ); if (mIsZero(disp.z)) dispNx.z = 0.0f; NxU32 activeGroups = 0xFFFFFFFF; activeGroups &= ~( 1<<31 ); // Skip activeGroup for triggers ( 31 ) activeGroups &= ~( 1<<30 ); // Skip activeGroup for debris / non interactive dynamics ( 30 ) NxU32 collisionFlags = NXCC_COLLISION_SIDES | NXCC_COLLISION_DOWN | NXCC_COLLISION_UP; mController->move( dispNx, activeGroups, 0.0001f, collisionFlags ); Point3F newPos = pxCast( mController->getDebugPosition() ); newPos.z -= mOriginOffset; mCollisionList = NULL; return newPos; } NxControllerAction PxPlayer::onShapeHit( const NxControllerShapeHit& hit ) { if (!mCollisionList || mCollisionList->getCount() >= CollisionList::MaxCollisions) return NX_ACTION_NONE; NxActor *actor = &hit.shape->getActor(); PhysicsUserData *userData = PhysicsUserData::cast( actor->userData ); if ( actor->readActorFlag( NX_AF_DISABLE_RESPONSE ) ) return NX_ACTION_NONE; // Fill out the Collision // structure for use later. Collision &col = mCollisionList->increment(); dMemset( &col, 0, sizeof( col ) ); col.normal = pxCast( hit.worldNormal ); col.point.set( hit.worldPos.x, hit.worldPos.y, hit.worldPos.z ); col.distance = hit.length; if ( userData ) col.object = userData->getObject(); // 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 (mIsZero(hit.dir.z)) { 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(); } } else { // PhysX doesn't perform callbacks in its upwards collision check so if // this isn't a sideways collision then it must be a downwards one. 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); } /* if ( userData && userData->mCanPush && actor->isDynamic() && !actor->readBodyFlag( NX_BF_KINEMATIC ) && !mDummyMove ) { NxActor *ctrlActor = mController->getActor(); // So the object is neither // a static or a kinematic, // meaning we need to figure out // if we have enough force to push it. // Get the hit object's force // and scale it by the amount // that it's acceleration is going // against our acceleration. const Point3F &hitObjLinVel = pxCast( actor->getLinearVelocity() ); F32 hitObjMass = actor->getMass(); VectorF hitObjDeltaVel = hitObjLinVel * TickSec; VectorF hitObjAccel = hitObjDeltaVel / TickSec; VectorF controllerLinVel = pxCast( controllerActor->getLinearVelocity() ); VectorF controllerDeltaVel = controllerLinVel * TickSec; VectorF controllerAccel = controllerDeltaVel / TickSec; Point3F hitObjForce = (hitObjMass * hitObjAccel); Point3F playerForce = (controllerActor->getMass() * controllerAccel); VectorF normalizedObjVel( hitObjLinVel ); normalizedObjVel.normalizeSafe(); VectorF normalizedPlayerVel( pxCast( controllerActor->getLinearVelocity() ) ); normalizedPlayerVel.normalizeSafe(); F32 forceDot = mDot( normalizedObjVel, normalizedPlayerVel ); hitObjForce *= forceDot; playerForce = playerForce - hitObjForce; if ( playerForce.x > 0.0f || playerForce.y > 0.0f || playerForce.z > 0.0f ) actor->addForceAtPos( NxVec3( playerForce.x, playerForce.y, playerForce.z ), actor->getCMassGlobalPosition() ); //Con::printf( "onShapeHit: %f %f %f", playerForce.x, playerForce.y, playerForce.z ); } */ return NX_ACTION_PUSH; } NxControllerAction PxPlayer::onControllerHit( const NxControllersHit& hit ) { if (!mCollisionList || mCollisionList->getCount() >= CollisionList::MaxCollisions) return NX_ACTION_NONE; NxActor *actor = hit.other->getActor(); PhysicsUserData *userData = PhysicsUserData::cast( actor->userData ); if ( actor->readActorFlag( NX_AF_DISABLE_RESPONSE ) ) return NX_ACTION_NONE; // For controller-to-controller hit we don't have an actual hit point, so all // we can do is set the hit object. Collision &col = mCollisionList->increment(); dMemset( &col, 0, sizeof( col ) ); if ( userData ) col.object = userData->getObject(); return NX_ACTION_NONE; } void PxPlayer::findContact( SceneObject **contactObject, VectorF *contactNormal, Vector *outOverlapObjects ) const { AssertFatal( mController, "PxPlayer::findContact - The controller is null!" ); // See PxPlayer::_onPhysicsReset if ( !mWorld->isEnabled() ) return; // Calculate the sweep motion... F32 halfCapSize = mOriginOffset; F32 halfSmallCapSize = halfCapSize * 0.8f; F32 diff = halfCapSize - halfSmallCapSize; const F32 mSkinWidth = 0.1f; F32 offsetDist = diff + mSkinWidth + 0.01f; NxVec3 motion(0,0,-offsetDist); /* // Construct the capsule... F32 radius = mCapsuleController->getRadius(); F32 halfHeight = mCapsuleController->getHeight() * 0.5f; NxCapsule capsule; capsule.p0 = capsule.p1 = pxCast( mCapsuleController->getDebugPosition() ); capsule.p0.z -= halfHeight; capsule.p1.z += halfHeight; capsule.radius = radius; */ NxSweepQueryHit sweepHit; NxU32 hitCount = mController->getActor()->linearSweep( motion, NX_SF_STATICS | NX_SF_DYNAMICS, NULL, 1, &sweepHit, NULL ); if ( hitCount > 0 ) { PhysicsUserData *data = PhysicsUserData::cast( sweepHit.hitShape->getActor().userData ); if ( data ) { *contactObject = data->getObject(); *contactNormal = pxCast( sweepHit.normal ); } } // Check for overlapped objects ( triggers ) if ( !outOverlapObjects ) return; NxCapsuleShape *shape = reinterpret_cast( mController->getActor()->getShapes()[0] ); NxCapsule worldCapsule; shape->getWorldCapsule( worldCapsule ); // Test only against activeGroup with triggers ( 31 ). NxU32 activeGroups = 1 << 31; NxShape *shapes[10]; hitCount = mWorld->getScene()->overlapCapsuleShapes( worldCapsule, NX_ALL_SHAPES, 10, shapes, NULL, activeGroups ); for ( S32 i = 0; i < hitCount; i++ ) { PhysicsUserData *data = PhysicsUserData::cast( shapes[i]->getActor().userData ); if ( data ) outOverlapObjects->push_back( data->getObject() ); } } void PxPlayer::enableCollision() { AssertFatal( mController, "PxPlayer::enableCollision - The controller is null!" ); mWorld->releaseWriteLock(); mController->setCollision( true ); } void PxPlayer::disableCollision() { AssertFatal( mController, "PxPlayer::disableCollision - The controller is null!" ); mWorld->releaseWriteLock(); mController->setCollision( false ); } PhysicsWorld* PxPlayer::getWorld() { return mWorld; } void PxPlayer::setTransform( const MatrixF &transform ) { AssertFatal( mController, "PxPlayer::setTransform - The controller is null!" ); mWorld->releaseWriteLock(); Point3F newPos = transform.getPosition(); newPos.z += mOriginOffset; const Point3F &curPos = pxCast(mController->getDebugPosition()); if ( !(newPos - curPos ).isZero() ) mController->setPosition( pxCast(newPos) ); } MatrixF& PxPlayer::getTransform( MatrixF *outMatrix ) { AssertFatal( mController, "PxPlayer::getTransform - The controller is null!" ); Point3F newPos = pxCast( mController->getDebugPosition() ); newPos.z -= mOriginOffset; outMatrix->setPosition( newPos ); return *outMatrix; } void PxPlayer::setScale( const Point3F &scale ) { } Box3F PxPlayer::getWorldBounds() { Con::warnf( "PxPlayer::getWorldBounds - not implemented" ); return Box3F::Invalid; }