// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of NVIDIA CORPORATION nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY // OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Copyright (c) 2008-2019 NVIDIA Corporation. All rights reserved. // Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. // Copyright (c) 2001-2004 NovodeX AG. All rights reserved. #include "CctCharacterControllerManager.h" #include "CctBoxController.h" #include "CctCapsuleController.h" #include "CctObstacleContext.h" #include "GuDistanceSegmentSegment.h" #include "GuDistanceSegmentBox.h" #include "PsUtilities.h" #include "PsMathUtils.h" #include "PxRigidDynamic.h" #include "PxScene.h" #include "PxPhysics.h" #include "PsFoundation.h" #include "CmRadixSortBuffered.h" using namespace physx; using namespace Cct; static const PxF32 gMaxOverlapRecover = 4.0f; // PT: TODO: expose this /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// CharacterControllerManager::CharacterControllerManager(PxScene& scene, bool lockingEnabled) : mScene (scene), mRenderBuffer (NULL), mDebugRenderingFlags (0), mMaxEdgeLength (1.0f), mTessellation (false), mOverlapRecovery (true), mPreciseSweeps (true), mPreventVerticalSlidingAgainstCeiling (false), mLockingEnabled (lockingEnabled) { // PT: register ourself as a deletion listener, to be called by the SDK whenever an object is deleted PxPhysics& physics = scene.getPhysics(); physics.registerDeletionListener(*this, PxDeletionEventFlag::eUSER_RELEASE); } CharacterControllerManager::~CharacterControllerManager() { PX_DELETE_AND_RESET(mRenderBuffer); } void CharacterControllerManager::release() { // PT: TODO: use non virtual calls & move to dtor while(getNbControllers()!= 0) releaseController(*getController(0)); while(getNbObstacleContexts()!= 0) mObstacleContexts[0]->release(); PxPhysics& physics = mScene.getPhysics(); physics.unregisterDeletionListener(*this); delete this; Ps::Foundation::decRefCount(); } PxScene& CharacterControllerManager::getScene() const { return mScene; } PxRenderBuffer& CharacterControllerManager::getRenderBuffer() { if(!mRenderBuffer) mRenderBuffer = PX_NEW(Cm::RenderBuffer); return *mRenderBuffer; } void CharacterControllerManager::setDebugRenderingFlags(PxControllerDebugRenderFlags flags) { mDebugRenderingFlags = flags; if(!flags) { PX_DELETE_AND_RESET(mRenderBuffer); } } PxU32 CharacterControllerManager::getNbControllers() const { return mControllers.size(); } Controller** CharacterControllerManager::getControllers() { return mControllers.begin(); } PxController* CharacterControllerManager::getController(PxU32 index) { if(index>=mControllers.size()) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "PxControllerManager::getController(): out-of-range index"); return NULL; } PX_ASSERT(mControllers[index]); return mControllers[index]->getPxController(); } PxController* CharacterControllerManager::createController(const PxControllerDesc& desc) { if(!desc.isValid()) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "PxControllerManager::createController(): desc.isValid() fails."); return NULL; } Controller* newController = NULL; PxController* N = NULL; if(desc.getType()==PxControllerShapeType::eBOX) { BoxController* boxController = PX_NEW(BoxController)(desc, mScene.getPhysics(), &mScene); newController = boxController; N = boxController; } else if(desc.getType()==PxControllerShapeType::eCAPSULE) { CapsuleController* capsuleController = PX_NEW(CapsuleController)(desc, mScene.getPhysics(), &mScene); newController = capsuleController; N = capsuleController; } else PX_ALWAYS_ASSERT_MESSAGE( "INTERNAL ERROR - invalid CCT type, should have been caught by isValid()."); if(newController) { mControllers.pushBack(newController); newController->setCctManager(this); PxShape* shape = NULL; PxU32 nb = N->getActor()->getShapes(&shape, 1); PX_ASSERT(nb==1); PX_UNUSED(nb); mCCTShapes.insert(shape); } return N; } void CharacterControllerManager::releaseController(PxController& controller) { for(PxU32 i = 0; igetPxController() == &controller) { mControllers.replaceWithLast(i); break; } } PxShape* shape = NULL; PxU32 nb = controller.getActor()->getShapes(&shape, 1); PX_ASSERT(nb==1); PX_UNUSED(nb); mCCTShapes.erase(shape); if(controller.getType() == PxControllerShapeType::eCAPSULE) { CapsuleController* cc = static_cast(&controller); PX_DELETE(cc); } else if(controller.getType() == PxControllerShapeType::eBOX) { BoxController* bc = static_cast(&controller); PX_DELETE(bc); } else PX_ASSERT(0); } void CharacterControllerManager::purgeControllers() { while(mControllers.size()) releaseController(*mControllers[0]->getPxController()); } void CharacterControllerManager::onRelease(const PxBase* observed, void* , PxDeletionEventFlag::Enum deletionEvent) { PX_ASSERT(deletionEvent == PxDeletionEventFlag::eUSER_RELEASE); // the only type we registered for PX_UNUSED(deletionEvent); if(!(observed->getConcreteType()==PxConcreteType:: eRIGID_DYNAMIC || observed->getConcreteType()==PxConcreteType:: eRIGID_STATIC || observed->getConcreteType()==PxConcreteType::eSHAPE)) return; // check if object was registered if(mLockingEnabled) mWriteLock.lock(); const ObservedRefCountMap::Entry* releaseEntry = mObservedRefCountMap.find(observed); if(mLockingEnabled) mWriteLock.unlock(); if(releaseEntry) { for(PxU32 i = 0; i < mControllers.size(); i++) { Controller* controller = mControllers[i]; if(mLockingEnabled) controller->mWriteLock.lock(); controller->onRelease(*observed); if(mLockingEnabled) controller->mWriteLock.unlock(); } } } void CharacterControllerManager::registerObservedObject(const PxBase* obj) { if(mLockingEnabled) mWriteLock.lock(); mObservedRefCountMap[obj].refCount++; if(mLockingEnabled) mWriteLock.unlock(); } void CharacterControllerManager::unregisterObservedObject(const PxBase* obj) { if(mLockingEnabled) mWriteLock.lock(); ObservedRefCounter& refCounter = mObservedRefCountMap[obj]; PX_ASSERT(refCounter.refCount); refCounter.refCount--; if(!refCounter.refCount) mObservedRefCountMap.erase(obj); if(mLockingEnabled) mWriteLock.unlock(); } PxU32 CharacterControllerManager::getNbObstacleContexts() const { return mObstacleContexts.size(); } PxObstacleContext* CharacterControllerManager::getObstacleContext(PxU32 index) { if(index>=mObstacleContexts.size()) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "PxControllerManager::getObstacleContext(): out-of-range index"); return NULL; } PX_ASSERT(mObstacleContexts[index]); return mObstacleContexts[index]; } PxObstacleContext* CharacterControllerManager::createObstacleContext() { ObstacleContext* oc = PX_NEW(ObstacleContext)(*this); mObstacleContexts.pushBack(oc); return oc; } void CharacterControllerManager::releaseObstacleContext(ObstacleContext& oc) { PX_ASSERT(mObstacleContexts.find(&oc) != mObstacleContexts.end()); mObstacleContexts.findAndReplaceWithLast(&oc); PX_DELETE(&oc); } void CharacterControllerManager::onObstacleRemoved(ObstacleHandle index) const { for(PxU32 i = 0; imCctModule.onObstacleRemoved(index); } } void CharacterControllerManager::onObstacleUpdated(ObstacleHandle index, const PxObstacleContext* context) const { for(PxU32 i = 0; imCctModule.onObstacleUpdated(index,context, toVec3(mControllers[i]->mPosition), -mControllers[i]->mUserParams.mUpDirection, mControllers[i]->getHalfHeightInternal()); } } void CharacterControllerManager::onObstacleAdded(ObstacleHandle index, const PxObstacleContext* context) const { for(PxU32 i = 0; imCctModule.onObstacleAdded(index,context, toVec3(mControllers[i]->mPosition), -mControllers[i]->mUserParams.mUpDirection, mControllers[i]->getHalfHeightInternal()); } } // PT: TODO: move to array class? template void resetOrClear(T& a) { const PxU32 c = a.capacity(); if(!c) return; const PxU32 s = a.size(); if(s>c/2) a.clear(); else a.reset(); } void CharacterControllerManager::resetObstaclesBuffers() { resetOrClear(mBoxUserData); resetOrClear(mBoxes); resetOrClear(mCapsuleUserData); resetOrClear(mCapsules); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CharacterControllerManager::setTessellation(bool flag, float maxEdgeLength) { mTessellation = flag; mMaxEdgeLength = maxEdgeLength; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CharacterControllerManager::setOverlapRecoveryModule(bool flag) { mOverlapRecovery = flag; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CharacterControllerManager::setPreciseSweeps(bool flag) { mPreciseSweeps = flag; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CharacterControllerManager::setPreventVerticalSlidingAgainstCeiling(bool flag) { mPreventVerticalSlidingAgainstCeiling = flag; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CharacterControllerManager::shiftOrigin(const PxVec3& shift) { for(PxU32 i=0; i < mControllers.size(); i++) { mControllers[i]->onOriginShift(shift); } for(PxU32 i=0; i < mObstacleContexts.size(); i++) { mObstacleContexts[i]->onOriginShift(shift); } if(mRenderBuffer) mRenderBuffer->shift(-shift); // assumption is that these are just used for temporary stuff PX_ASSERT(!mBoxes.size()); PX_ASSERT(!mCapsules.size()); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static bool computeMTD(PxVec3& mtd, PxF32& depth, const PxVec3& e0, const PxVec3& c0, const PxMat33& r0, const PxVec3& e1, const PxVec3& c1, const PxMat33& r1) { // Translation, in parent frame const PxVec3 v = c1 - c0; // Translation, in A's frame const PxVec3 T(v.dot(r0[0]), v.dot(r0[1]), v.dot(r0[2])); // B's basis with respect to A's local frame PxReal R[3][3]; PxReal FR[3][3]; PxReal ra, rb, t, d; PxReal overlap[15]; // Calculate rotation matrix for(PxU32 i=0;i<3;i++) { for(PxU32 k=0;k<3;k++) { R[i][k] = r0[i].dot(r1[k]); FR[i][k] = 1e-6f + PxAbs(R[i][k]); // Precompute fabs matrix } } // A's basis vectors for(PxU32 i=0;i<3;i++) { ra = e0[i]; rb = e1[0]*FR[i][0] + e1[1]*FR[i][1] + e1[2]*FR[i][2]; t = PxAbs(T[i]); d = ra + rb - t; if(d<0.0f) return false; overlap[i] = d; } // B's basis vectors for(PxU32 k=0;k<3;k++) { ra = e0[0]*FR[0][k] + e0[1]*FR[1][k] + e0[2]*FR[2][k]; rb = e1[k]; t = PxAbs(T[0]*R[0][k] + T[1]*R[1][k] + T[2]*R[2][k]); d = ra + rb - t; if(d<0.0f) return false; overlap[k+3] = d; } // PT: edge-edge tests are skipped, by design PxU32 minIndex=0; PxReal minD = overlap[0]; for(PxU32 i=1;i<6;i++) { if(overlap[i]mType>entity1->mType) Ps::swap(entity0, entity1); if(entity0->mType==PxControllerShapeType::eCAPSULE && entity1->mType==PxControllerShapeType::eCAPSULE) { CapsuleController* cc0 = static_cast(entity0); CapsuleController* cc1 = static_cast(entity1); PxExtendedCapsule capsule0; cc0->getCapsule(capsule0); PxExtendedCapsule capsule1; cc1->getCapsule(capsule1); const PxF32 r = capsule0.radius + capsule1.radius; const PxVec3 p00 = toVec3(capsule0.p0); const PxVec3 p01 = toVec3(capsule0.p1); const PxVec3 p10 = toVec3(capsule1.p0); const PxVec3 p11 = toVec3(capsule1.p1); PxF32 s,t; const PxF32 d = sqrtf(Gu::distanceSegmentSegmentSquared(p00, p01 - p00, p10, p11 - p10, &s, &t)); if(dmCctModule.mUserParams.mUpDirection; dir = fixDir(center0 - center1, up); overlap = r - d; } } else if(entity0->mType==PxControllerShapeType::eBOX && entity1->mType==PxControllerShapeType::eCAPSULE) { BoxController* cc0 = static_cast(entity0); CapsuleController* cc1 = static_cast(entity1); PxExtendedBox obb; cc0->getOBB(obb); PxExtendedCapsule capsule; cc1->getCapsule(capsule); const PxVec3 p0 = toVec3(capsule.p0); const PxVec3 p1 = toVec3(capsule.p1); PxF32 t; PxVec3 p; const PxMat33 M(obb.rot); const PxVec3 boxCenter = toVec3(obb.center); const PxF32 d = sqrtf(Gu::distanceSegmentBoxSquared(p0, p1, boxCenter, obb.extents, M, &t, &p)); if(dmCctModule.mUserParams.mUpDirection; dir = fixDir(center0 - center1, up); overlap = capsule.radius - d; } } else { PX_ASSERT(entity0->mType==PxControllerShapeType::eBOX); PX_ASSERT(entity1->mType==PxControllerShapeType::eBOX); BoxController* cc0 = static_cast(entity0); BoxController* cc1 = static_cast(entity1); PxExtendedBox obb0; cc0->getOBB(obb0); PxExtendedBox obb1; cc1->getOBB(obb1); PxVec3 mtd; PxF32 depth; if(computeMTD( mtd, depth, obb0.extents, toVec3(obb0.center), PxMat33(obb0.rot), obb1.extents, toVec3(obb1.center), PxMat33(obb1.rot))) { const PxVec3 center0 = toVec3(obb0.center); const PxVec3 center1 = toVec3(obb1.center); const PxVec3 witness = center0 - center1; if(mtd.dot(witness)<0.0f) dir = -mtd; else dir = mtd; const PxVec3 up = entity0->mCctModule.mUserParams.mUpDirection; dir = fixDir(dir, up); overlap = depth; } } if(overlap!=0.0f) { // We want to limit this to some reasonable amount, to avoid obvious "popping". const PxF32 maxOverlap = gMaxOverlapRecover * elapsedTime; if(overlap>maxOverlap) overlap=maxOverlap; const PxVec3 sep = dir * overlap * 0.5f; entity0->mOverlapRecover += sep; entity1->mOverlapRecover -= sep; } } // PT: TODO: this is the very old version, revisit with newer one static void completeBoxPruning(const PxBounds3* bounds, PxU32 nb, Ps::Array& pairs) { if(!nb) return; pairs.clear(); float* PosList = reinterpret_cast(PX_ALLOC_TEMP(sizeof(float)*nb, "completeBoxPruning")); for(PxU32 i=0;i(PX_ALLOC_TEMP(sizeof(PxBounds3)*nbControllers, "CharacterControllerManager::computeInteractions")); // PT: TODO: get rid of alloc PxBounds3* runningBoxes = boxes; while(nbControllers--) { Controller* current = *controllers++; PxExtendedBounds3 extBox; current->getWorldBox(extBox); *runningBoxes++ = PxBounds3(toVec3(extBox.minimum), toVec3(extBox.maximum)); // ### LOSS OF ACCURACY } // const PxU32 nbEntities = PxU32(runningBoxes - boxes); Ps::Array pairs; // PT: TODO: get rid of alloc completeBoxPruning(boxes, nbEntities, pairs); PxU32 nbPairs = pairs.size()>>1; const PxU32* indices = pairs.begin(); while(nbPairs--) { const PxU32 index0 = *indices++; const PxU32 index1 = *indices++; Controller* ctrl0 = mControllers[index0]; Controller* ctrl1 = mControllers[index1]; bool keep=true; if(cctFilterCb) keep = cctFilterCb->filter(*ctrl0->getPxController(), *ctrl1->getPxController()); if(keep) InteractionCharacterCharacter(ctrl0, ctrl1, elapsedTime); } PX_FREE(boxes); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Public factory methods PX_C_EXPORT PxControllerManager* PX_CALL_CONV PxCreateControllerManager(PxScene& scene, bool lockingEnabled) { Ps::Foundation::incRefCount(); return PX_NEW(CharacterControllerManager)(scene, lockingEnabled); }