aboutsummaryrefslogtreecommitdiff
path: root/PhysX_3.4/Source/PhysXCharacterKinematic/src/CctCharacterController.cpp
diff options
context:
space:
mode:
authorgit perforce import user <a@b>2016-10-25 12:29:14 -0600
committerSheikh Dawood Abdul Ajees <Sheikh Dawood Abdul Ajees>2016-10-25 18:56:37 -0500
commit3dfe2108cfab31ba3ee5527e217d0d8e99a51162 (patch)
treefa6485c169e50d7415a651bf838f5bcd0fd3bfbd /PhysX_3.4/Source/PhysXCharacterKinematic/src/CctCharacterController.cpp
downloadphysx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.tar.xz
physx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.zip
Initial commit:
PhysX 3.4.0 Update @ 21294896 APEX 1.4.0 Update @ 21275617 [CL 21300167]
Diffstat (limited to 'PhysX_3.4/Source/PhysXCharacterKinematic/src/CctCharacterController.cpp')
-rw-r--r--PhysX_3.4/Source/PhysXCharacterKinematic/src/CctCharacterController.cpp2536
1 files changed, 2536 insertions, 0 deletions
diff --git a/PhysX_3.4/Source/PhysXCharacterKinematic/src/CctCharacterController.cpp b/PhysX_3.4/Source/PhysXCharacterKinematic/src/CctCharacterController.cpp
new file mode 100644
index 00000000..00a49316
--- /dev/null
+++ b/PhysX_3.4/Source/PhysXCharacterKinematic/src/CctCharacterController.cpp
@@ -0,0 +1,2536 @@
+// This code contains NVIDIA Confidential Information and is disclosed to you
+// under a form of NVIDIA software license agreement provided separately to you.
+//
+// Notice
+// NVIDIA Corporation and its licensors retain all intellectual property and
+// proprietary rights in and to this software and related documentation and
+// any modifications thereto. Any use, reproduction, disclosure, or
+// distribution of this software and related documentation without an express
+// license agreement from NVIDIA Corporation is strictly prohibited.
+//
+// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES
+// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO
+// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT,
+// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE.
+//
+// Information and code furnished is believed to be accurate and reliable.
+// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such
+// information or for any infringement of patents or other rights of third parties that may
+// result from its use. No license is granted by implication or otherwise under any patent
+// or patent rights of NVIDIA Corporation. Details are subject to change without notice.
+// This code supersedes and replaces all information previously supplied.
+// NVIDIA Corporation products are not authorized for use as critical
+// components in life support devices or systems without express written approval of
+// NVIDIA Corporation.
+//
+// Copyright (c) 2008-2016 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 "CctCharacterController.h"
+#include "CctCharacterControllerManager.h"
+#include "CctSweptBox.h"
+#include "CctSweptCapsule.h"
+#include "CctObstacleContext.h"
+#include "PxRigidDynamic.h"
+#include "CmRenderOutput.h"
+#include "PsMathUtils.h"
+#include "GuIntersectionBoxBox.h"
+#include "GuDistanceSegmentBox.h"
+#include "PxMeshQuery.h"
+#include "PsFPU.h"
+
+// PT: TODO: remove those includes.... shouldn't be allowed from here
+#include "PxControllerObstacles.h" // (*)
+#include "CctInternalStructs.h" // (*)
+#include "PxControllerManager.h" // (*)
+#include "PxControllerBehavior.h" // (*)
+
+//#define DEBUG_MTD
+#ifdef DEBUG_MTD
+ #include <stdio.h>
+#endif
+
+#define MAX_ITER 10
+
+using namespace physx;
+using namespace Cct;
+using namespace Gu;
+
+static const PxU32 gObstacleDebugColor = PxU32(PxDebugColor::eARGB_CYAN);
+//static const PxU32 gCCTBoxDebugColor = PxU32(PxDebugColor::eARGB_YELLOW);
+static const PxU32 gTBVDebugColor = PxU32(PxDebugColor::eARGB_MAGENTA);
+static const bool gUsePartialUpdates = true;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static PX_FORCE_INLINE PxHitFlags getSweepHitFlags(const CCTParams& params)
+{
+ PxHitFlags sweepHitFlags = PxHitFlag::eDEFAULT/*|PxHitFlag::eMESH_BOTH_SIDES*/;
+// sweepHitFlags |= PxHitFlag::eASSUME_NO_INITIAL_OVERLAP;
+ if(params.mPreciseSweeps)
+ sweepHitFlags |= PxHitFlag::ePRECISE_SWEEP;
+ return sweepHitFlags;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool shouldApplyRecoveryModule(const PxRigidActor& rigidActor)
+{
+ // PT: we must let the dynamic objects go through the CCT for proper 2-way interactions.
+ // But we should still apply the recovery module for kinematics.
+
+ const PxType type = rigidActor.getConcreteType();
+ if(type==PxConcreteType::eRIGID_STATIC)
+ return true;
+
+ if(type!=PxConcreteType::eRIGID_DYNAMIC)
+ return false;
+
+ return static_cast<const PxRigidBody&>(rigidActor).getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static const bool gUseLocalSpace = true;
+static PxVec3 worldToLocal(const PxObstacle& obstacle, const PxExtendedVec3& worldPos)
+{
+ const PxTransform tr(toVec3(obstacle.mPos), obstacle.mRot);
+ return tr.transformInv(toVec3(worldPos));
+}
+
+static PxVec3 localToWorld(const PxObstacle& obstacle, const PxVec3& localPos)
+{
+ const PxTransform tr(toVec3(obstacle.mPos), obstacle.mRot);
+ return tr.transform(localPos);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef PX_BIG_WORLDS
+ typedef PxExtendedBounds3 PxCCTBounds3;
+ typedef PxExtendedVec3 PxCCTVec3;
+#else
+ typedef PxBounds3 PxCCTBounds3;
+ typedef PxVec3 PxCCTVec3;
+#endif
+
+static PX_INLINE void scale(PxCCTBounds3& b, const PxVec3& scale)
+{
+ PxCCTVec3 center; getCenter(b, center);
+ PxVec3 extents; getExtents(b, extents);
+ extents.x *= scale.x;
+ extents.y *= scale.y;
+ extents.z *= scale.z;
+ setCenterExtents(b, center, extents);
+}
+
+static PX_INLINE void computeReflexionVector(PxVec3& reflected, const PxVec3& incomingDir, const PxVec3& outwardNormal)
+{
+ reflected = incomingDir - outwardNormal * 2.0f * (incomingDir.dot(outwardNormal));
+}
+
+static PX_INLINE void collisionResponse(PxExtendedVec3& targetPosition, const PxExtendedVec3& currentPosition, const PxVec3& currentDir, const PxVec3& hitNormal, PxF32 bump, PxF32 friction, bool normalize=false)
+{
+ // Compute reflect direction
+ PxVec3 reflectDir;
+ computeReflexionVector(reflectDir, currentDir, hitNormal);
+ reflectDir.normalize();
+
+ // Decompose it
+ PxVec3 normalCompo, tangentCompo;
+ Ps::decomposeVector(normalCompo, tangentCompo, reflectDir, hitNormal);
+
+ // Compute new destination position
+ const PxF32 amplitude = (targetPosition - currentPosition).magnitude();
+
+ targetPosition = currentPosition;
+ if(bump!=0.0f)
+ {
+ if(normalize)
+ normalCompo.normalize();
+ targetPosition += normalCompo*bump*amplitude;
+ }
+ if(friction!=0.0f)
+ {
+ if(normalize)
+ tangentCompo.normalize();
+ targetPosition += tangentCompo*friction*amplitude;
+ }
+}
+
+static PX_INLINE void relocateBox(PxBoxGeometry& boxGeom, PxTransform& pose, const PxExtendedVec3& center, const PxVec3& extents, const PxExtendedVec3& origin, const PxQuat& quatFromUp)
+{
+ boxGeom.halfExtents = extents;
+
+ pose.p.x = float(center.x - origin.x);
+ pose.p.y = float(center.y - origin.y);
+ pose.p.z = float(center.z - origin.z);
+
+ pose.q = quatFromUp;
+}
+
+static PX_INLINE void relocateBox(PxBoxGeometry& boxGeom, PxTransform& pose, const TouchedUserBox& userBox)
+{
+ relocateBox(boxGeom, pose, userBox.mBox.center, userBox.mBox.extents, userBox.mOffset, userBox.mBox.rot);
+}
+
+static PX_INLINE void relocateBox(PxBoxGeometry& boxGeom, PxTransform& pose, const TouchedBox& box)
+{
+ boxGeom.halfExtents = box.mExtents;
+
+ pose.p = box.mCenter;
+ pose.q = box.mRot;
+}
+
+static PX_INLINE void relocateCapsule(
+ PxCapsuleGeometry& capsuleGeom, PxTransform& pose, const SweptCapsule* sc,
+ const PxQuat& quatFromUp,
+ const PxExtendedVec3& center, const PxExtendedVec3& origin)
+{
+ capsuleGeom.radius = sc->mRadius;
+ capsuleGeom.halfHeight = 0.5f * sc->mHeight;
+
+ pose.p.x = float(center.x - origin.x);
+ pose.p.y = float(center.y - origin.y);
+ pose.p.z = float(center.z - origin.z);
+
+ pose.q = quatFromUp;
+}
+
+static PX_INLINE void relocateCapsule(PxCapsuleGeometry& capsuleGeom, PxTransform& pose, const PxVec3& p0, const PxVec3& p1, PxReal radius)
+{
+ capsuleGeom.radius = radius;
+ pose = PxTransformFromSegment(p0, p1, &capsuleGeom.halfHeight);
+}
+
+static PX_INLINE void relocateCapsule(PxCapsuleGeometry& capsuleGeom, PxTransform& pose, const TouchedUserCapsule& userCapsule)
+{
+ PxVec3 p0, p1;
+ p0.x = float(userCapsule.mCapsule.p0.x - userCapsule.mOffset.x);
+ p0.y = float(userCapsule.mCapsule.p0.y - userCapsule.mOffset.y);
+ p0.z = float(userCapsule.mCapsule.p0.z - userCapsule.mOffset.z);
+ p1.x = float(userCapsule.mCapsule.p1.x - userCapsule.mOffset.x);
+ p1.y = float(userCapsule.mCapsule.p1.y - userCapsule.mOffset.y);
+ p1.z = float(userCapsule.mCapsule.p1.z - userCapsule.mOffset.z);
+
+ relocateCapsule(capsuleGeom, pose, p0, p1, userCapsule.mCapsule.radius);
+}
+
+static bool SweepBoxUserBox(const SweepTest* test, const SweptVolume* volume, const TouchedGeom* geom, const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eBOX);
+ PX_ASSERT(geom->mType==TouchedGeomType::eUSER_BOX);
+ const SweptBox* SB = static_cast<const SweptBox*>(volume);
+ const TouchedUserBox* TC = static_cast<const TouchedUserBox*>(geom);
+
+ PxBoxGeometry boxGeom0;
+ PxTransform boxPose0;
+ // To precompute
+ relocateBox(boxGeom0, boxPose0, center, SB->mExtents, TC->mOffset, test->mUserParams.mQuatFromUp);
+
+ PxBoxGeometry boxGeom1;
+ PxTransform boxPose1;
+ relocateBox(boxGeom1, boxPose1, *TC);
+
+ PxSweepHit sweepHit;
+ if(!PxGeometryQuery::sweep(dir, impact.mDistance, boxGeom0, boxPose0, boxGeom1, boxPose1, sweepHit, getSweepHitFlags(test->mUserParams)))
+ return false;
+
+ if(sweepHit.distance >= impact.mDistance)
+ return false;
+
+ impact.mWorldNormal = sweepHit.normal;
+ impact.mDistance = sweepHit.distance;
+ impact.mInternalIndex = PX_INVALID_U32;
+ impact.mTriangleIndex = PX_INVALID_U32;
+ impact.setWorldPos(sweepHit.position, TC->mOffset);
+ return true;
+}
+
+static bool SweepBoxUserCapsule(const SweepTest* test, const SweptVolume* volume, const TouchedGeom* geom, const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eBOX);
+ PX_ASSERT(geom->mType==TouchedGeomType::eUSER_CAPSULE);
+ const SweptBox* SB = static_cast<const SweptBox*>(volume);
+ const TouchedUserCapsule* TC = static_cast<const TouchedUserCapsule*>(geom);
+
+ PxBoxGeometry boxGeom;
+ PxTransform boxPose;
+ // To precompute
+ relocateBox(boxGeom, boxPose, center, SB->mExtents, TC->mOffset, test->mUserParams.mQuatFromUp);
+
+ PxCapsuleGeometry capsuleGeom;
+ PxTransform capsulePose;
+ relocateCapsule(capsuleGeom, capsulePose, *TC);
+
+ PxSweepHit sweepHit;
+ if(!PxGeometryQuery::sweep(dir, impact.mDistance, boxGeom, boxPose, capsuleGeom, capsulePose, sweepHit, getSweepHitFlags(test->mUserParams)))
+ return false;
+
+ if(sweepHit.distance >= impact.mDistance)
+ return false;
+
+ impact.mDistance = sweepHit.distance;
+ impact.mWorldNormal = sweepHit.normal;
+ impact.mInternalIndex = PX_INVALID_U32;
+ impact.mTriangleIndex = PX_INVALID_U32;
+ //TO CHECK: Investigate whether any significant performance improvement can be achieved through
+ // making the impact point computation optional in the sweep calls and compute it later
+ /*{
+ // ### check this
+ float t;
+ PxVec3 p;
+ float d = gUtilLib->PxSegmentOBBSqrDist(Capsule, Box0.center, Box0.extents, Box0.rot, &t, &p);
+ Box0.rot.multiply(p,p);
+ impact.mWorldPos.x = p.x + Box0.center.x + TC->mOffset.x;
+ impact.mWorldPos.y = p.y + Box0.center.y + TC->mOffset.y;
+ impact.mWorldPos.z = p.z + Box0.center.z + TC->mOffset.z;
+ }*/
+ {
+ impact.setWorldPos(sweepHit.position, TC->mOffset);
+ }
+ return true;
+}
+
+static bool sweepVolumeVsMesh( const SweepTest* sweepTest, const TouchedMesh* touchedMesh, SweptContact& impact,
+ const PxVec3& unitDir, const PxGeometry& geom, const PxTransform& pose,
+ PxU32 nbTris, const PxTriangle* triangles,
+ PxU32 cachedIndex)
+{
+ PxSweepHit sweepHit;
+ if(PxMeshQuery::sweep(unitDir, impact.mDistance, geom, pose, nbTris, triangles, sweepHit, getSweepHitFlags(sweepTest->mUserParams), &cachedIndex))
+ {
+ if(sweepHit.distance >= impact.mDistance)
+ return false;
+
+ impact.mDistance = sweepHit.distance;
+ impact.mWorldNormal = sweepHit.normal;
+ impact.setWorldPos(sweepHit.position, touchedMesh->mOffset);
+
+ // Returned index is only between 0 and nbTris, i.e. it indexes the array of cached triangles, not the original mesh.
+ PX_ASSERT(sweepHit.faceIndex < nbTris);
+ sweepTest->mCachedTriIndex[sweepTest->mCachedTriIndexIndex] = sweepHit.faceIndex;
+
+ // The CCT loop will use the index from the start of the cache...
+ impact.mInternalIndex = sweepHit.faceIndex + touchedMesh->mIndexWorldTriangles;
+ const PxU32* triangleIndices = &sweepTest->mTriangleIndices[touchedMesh->mIndexWorldTriangles];
+ impact.mTriangleIndex = triangleIndices[sweepHit.faceIndex];
+ return true;
+ }
+ return false;
+}
+
+static bool SweepBoxMesh(const SweepTest* sweep_test, const SweptVolume* volume, const TouchedGeom* geom, const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eBOX);
+ PX_ASSERT(geom->mType==TouchedGeomType::eMESH);
+ const SweptBox* SB = static_cast<const SweptBox*>(volume);
+ const TouchedMesh* TM = static_cast<const TouchedMesh*>(geom);
+
+ PxU32 nbTris = TM->mNbTris;
+ if(!nbTris)
+ return false;
+
+ // Fetch triangle data for current mesh (the stream may contain triangles from multiple meshes)
+ const PxTriangle* T = &sweep_test->mWorldTriangles.getTriangle(TM->mIndexWorldTriangles);
+
+ // PT: this only really works when the CCT collides with a single mesh, but that's the most common case. When it doesn't, there's just no speedup but it still works.
+ PxU32 CachedIndex = sweep_test->mCachedTriIndex[sweep_test->mCachedTriIndexIndex];
+ if(CachedIndex>=nbTris)
+ CachedIndex=0;
+
+ PxBoxGeometry boxGeom;
+ boxGeom.halfExtents = SB->mExtents;
+ PxTransform boxPose(PxVec3(float(center.x - TM->mOffset.x), float(center.y - TM->mOffset.y), float(center.z - TM->mOffset.z)), sweep_test->mUserParams.mQuatFromUp); // Precompute
+ return sweepVolumeVsMesh(sweep_test, TM, impact, dir, boxGeom, boxPose, nbTris, T, CachedIndex);
+}
+
+static bool SweepCapsuleMesh(
+ const SweepTest* sweep_test, const SweptVolume* volume, const TouchedGeom* geom,
+ const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eCAPSULE);
+ PX_ASSERT(geom->mType==TouchedGeomType::eMESH);
+ const SweptCapsule* SC = static_cast<const SweptCapsule*>(volume);
+ const TouchedMesh* TM = static_cast<const TouchedMesh*>(geom);
+
+ PxU32 nbTris = TM->mNbTris;
+ if(!nbTris)
+ return false;
+
+ // Fetch triangle data for current mesh (the stream may contain triangles from multiple meshes)
+ const PxTriangle* T = &sweep_test->mWorldTriangles.getTriangle(TM->mIndexWorldTriangles);
+
+ // PT: this only really works when the CCT collides with a single mesh, but that's the most common case.
+ // When it doesn't, there's just no speedup but it still works.
+ PxU32 CachedIndex = sweep_test->mCachedTriIndex[sweep_test->mCachedTriIndexIndex];
+ if(CachedIndex>=nbTris)
+ CachedIndex=0;
+
+ PxCapsuleGeometry capsuleGeom;
+ PxTransform capsulePose;
+ relocateCapsule(capsuleGeom, capsulePose, SC, sweep_test->mUserParams.mQuatFromUp, center, TM->mOffset);
+
+ return sweepVolumeVsMesh(sweep_test, TM, impact, dir, capsuleGeom, capsulePose, nbTris, T, CachedIndex);
+}
+
+static bool SweepBoxBox(const SweepTest* test, const SweptVolume* volume, const TouchedGeom* geom, const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eBOX);
+ PX_ASSERT(geom->mType==TouchedGeomType::eBOX);
+ const SweptBox* SB = static_cast<const SweptBox*>(volume);
+ const TouchedBox* TB = static_cast<const TouchedBox*>(geom);
+
+ PxBoxGeometry boxGeom0;
+ PxTransform boxPose0;
+ // To precompute
+ relocateBox(boxGeom0, boxPose0, center, SB->mExtents, TB->mOffset, test->mUserParams.mQuatFromUp);
+
+ PxBoxGeometry boxGeom1;
+ PxTransform boxPose1;
+ relocateBox(boxGeom1, boxPose1, *TB);
+
+ PxSweepHit sweepHit;
+ if(!PxGeometryQuery::sweep(dir, impact.mDistance, boxGeom0, boxPose0, boxGeom1, boxPose1, sweepHit, getSweepHitFlags(test->mUserParams)))
+ return false;
+
+ if(sweepHit.distance >= impact.mDistance)
+ return false;
+
+ impact.mWorldNormal = sweepHit.normal;
+ impact.mDistance = sweepHit.distance;
+ impact.mInternalIndex = PX_INVALID_U32;
+ impact.mTriangleIndex = PX_INVALID_U32;
+ impact.setWorldPos(sweepHit.position, TB->mOffset);
+ return true;
+}
+
+static bool SweepBoxSphere(const SweepTest* test, const SweptVolume* volume, const TouchedGeom* geom, const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eBOX);
+ PX_ASSERT(geom->mType==TouchedGeomType::eSPHERE);
+ const SweptBox* SB = static_cast<const SweptBox*>(volume);
+ const TouchedSphere* TS = static_cast<const TouchedSphere*>(geom);
+
+ PxBoxGeometry boxGeom;
+ PxTransform boxPose;
+ // To precompute
+ relocateBox(boxGeom, boxPose, center, SB->mExtents, TS->mOffset, test->mUserParams.mQuatFromUp);
+
+ PxSphereGeometry sphereGeom;
+ sphereGeom.radius = TS->mRadius;
+ PxTransform spherePose;
+ spherePose.p = TS->mCenter;
+ spherePose.q = PxQuat(PxIdentity);
+
+ PxSweepHit sweepHit;
+ if(!PxGeometryQuery::sweep(dir, impact.mDistance, boxGeom, boxPose, sphereGeom, spherePose, sweepHit, getSweepHitFlags(test->mUserParams)))
+ return false;
+
+ impact.mDistance = sweepHit.distance;
+ impact.mWorldNormal = sweepHit.normal;
+ impact.mInternalIndex = PX_INVALID_U32;
+ impact.mTriangleIndex = PX_INVALID_U32;
+ //TO CHECK: Investigate whether any significant performance improvement can be achieved through
+ // making the impact point computation optional in the sweep calls and compute it later
+ /*
+ {
+ // The sweep test doesn't compute the impact point automatically, so we have to do it here.
+ PxVec3 NewSphereCenter = TS->mSphere.center - d * dir;
+ PxVec3 Closest;
+ gUtilLib->PxPointOBBSqrDist(NewSphereCenter, Box0.center, Box0.extents, Box0.rot, &Closest);
+ // Compute point on the box, after sweep
+ Box0.rot.multiply(Closest, Closest);
+ impact.mWorldPos.x = TS->mOffset.x + Closest.x + Box0.center.x + d * dir.x;
+ impact.mWorldPos.y = TS->mOffset.y + Closest.y + Box0.center.y + d * dir.y;
+ impact.mWorldPos.z = TS->mOffset.z + Closest.z + Box0.center.z + d * dir.z;
+
+ impact.mWorldNormal = -impact.mWorldNormal;
+ }*/
+ {
+ impact.setWorldPos(sweepHit.position, TS->mOffset);
+ }
+ return true;
+}
+
+static bool SweepBoxCapsule(const SweepTest* test, const SweptVolume* volume, const TouchedGeom* geom, const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eBOX);
+ PX_ASSERT(geom->mType==TouchedGeomType::eCAPSULE);
+ const SweptBox* SB = static_cast<const SweptBox*>(volume);
+ const TouchedCapsule* TC = static_cast<const TouchedCapsule*>(geom);
+
+ PxBoxGeometry boxGeom;
+ PxTransform boxPose;
+ // To precompute
+ relocateBox(boxGeom, boxPose, center, SB->mExtents, TC->mOffset, test->mUserParams.mQuatFromUp);
+
+ PxCapsuleGeometry capsuleGeom;
+ PxTransform capsulePose;
+ relocateCapsule(capsuleGeom, capsulePose, TC->mP0, TC->mP1, TC->mRadius);
+
+ PxSweepHit sweepHit;
+ if(!PxGeometryQuery::sweep(dir, impact.mDistance, boxGeom, boxPose, capsuleGeom, capsulePose, sweepHit, getSweepHitFlags(test->mUserParams)))
+ return false;
+
+ if(sweepHit.distance >= impact.mDistance)
+ return false;
+
+ impact.mDistance = sweepHit.distance;
+ impact.mWorldNormal = sweepHit.normal;
+ impact.mInternalIndex = PX_INVALID_U32;
+ impact.mTriangleIndex = PX_INVALID_U32;
+ //TO CHECK: Investigate whether any significant performance improvement can be achieved through
+ // making the impact point computation optional in the sweep calls and compute it later
+ /*{
+ float t;
+ PxVec3 p;
+ float d = gUtilLib->PxSegmentOBBSqrDist(TC->mCapsule, Box0.center, Box0.extents, Box0.rot, &t, &p);
+ Box0.rot.multiply(p,p);
+ impact.mWorldPos.x = p.x + Box0.center.x + TC->mOffset.x;
+ impact.mWorldPos.y = p.y + Box0.center.y + TC->mOffset.y;
+ impact.mWorldPos.z = p.z + Box0.center.z + TC->mOffset.z;
+ }*/
+ {
+ impact.setWorldPos(sweepHit.position, TC->mOffset);
+ }
+ return true;
+}
+
+static bool SweepCapsuleBox(const SweepTest* test, const SweptVolume* volume, const TouchedGeom* geom, const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eCAPSULE);
+ PX_ASSERT(geom->mType==TouchedGeomType::eBOX);
+ const SweptCapsule* SC = static_cast<const SweptCapsule*>(volume);
+ const TouchedBox* TB = static_cast<const TouchedBox*>(geom);
+
+ PxCapsuleGeometry capsuleGeom;
+ PxTransform capsulePose;
+ relocateCapsule(capsuleGeom, capsulePose, SC, test->mUserParams.mQuatFromUp, center, TB->mOffset);
+
+ PxBoxGeometry boxGeom;
+ PxTransform boxPose;
+ // To precompute
+ relocateBox(boxGeom, boxPose, *TB);
+
+ // The box and capsule coordinates are relative to the center of the cached bounding box
+ PxSweepHit sweepHit;
+ if(!PxGeometryQuery::sweep(dir, impact.mDistance, capsuleGeom, capsulePose, boxGeom, boxPose, sweepHit, getSweepHitFlags(test->mUserParams)))
+ return false;
+
+ if(sweepHit.distance >= impact.mDistance)
+ return false;
+
+ impact.mDistance = sweepHit.distance;
+ impact.mWorldNormal = sweepHit.normal;
+ impact.mInternalIndex = PX_INVALID_U32;
+ impact.mTriangleIndex = PX_INVALID_U32;
+
+ //TO CHECK: Investigate whether any significant performance improvement can be achieved through
+ // making the impact point computation optional in the sweep calls and compute it later
+ /*{
+ float t;
+ PxVec3 p;
+ float d = gUtilLib->PxSegmentOBBSqrDist(Capsule, TB->mBox.center, TB->mBox.extents, TB->mBox.rot, &t, &p);
+ TB->mBox.rot.multiply(p,p);
+ p += TB->mBox.center;
+ impact.mWorldPos.x = p.x + TB->mOffset.x;
+ impact.mWorldPos.y = p.y + TB->mOffset.y;
+ impact.mWorldPos.z = p.z + TB->mOffset.z;
+ }*/
+ {
+ impact.setWorldPos(sweepHit.position, TB->mOffset);
+ }
+ return true;
+}
+
+static bool SweepCapsuleSphere(const SweepTest* test, const SweptVolume* volume, const TouchedGeom* geom, const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eCAPSULE);
+ PX_ASSERT(geom->mType==TouchedGeomType::eSPHERE);
+ const SweptCapsule* SC = static_cast<const SweptCapsule*>(volume);
+ const TouchedSphere* TS = static_cast<const TouchedSphere*>(geom);
+
+ PxCapsuleGeometry capsuleGeom;
+ PxTransform capsulePose;
+ relocateCapsule(capsuleGeom, capsulePose, SC, test->mUserParams.mQuatFromUp, center, TS->mOffset);
+
+ PxSphereGeometry sphereGeom;
+ sphereGeom.radius = TS->mRadius;
+ PxTransform spherePose;
+ spherePose.p = TS->mCenter;
+ spherePose.q = PxQuat(PxIdentity);
+
+ PxSweepHit sweepHit;
+ if(!PxGeometryQuery::sweep(dir, impact.mDistance, capsuleGeom, capsulePose, sphereGeom, spherePose, sweepHit, getSweepHitFlags(test->mUserParams)))
+ return false;
+
+ if(sweepHit.distance >= impact.mDistance)
+ return false;
+
+ impact.mDistance = sweepHit.distance;
+ impact.mWorldNormal = sweepHit.normal;
+ impact.mInternalIndex = PX_INVALID_U32;
+ impact.mTriangleIndex = PX_INVALID_U32;
+ impact.setWorldPos(sweepHit.position, TS->mOffset);
+ return true;
+}
+
+static bool SweepCapsuleCapsule(const SweepTest* test, const SweptVolume* volume, const TouchedGeom* geom, const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eCAPSULE);
+ PX_ASSERT(geom->mType==TouchedGeomType::eCAPSULE);
+ const SweptCapsule* SC = static_cast<const SweptCapsule*>(volume);
+ const TouchedCapsule* TC = static_cast<const TouchedCapsule*>(geom);
+
+ PxCapsuleGeometry capsuleGeom0;
+ PxTransform capsulePose0;
+ relocateCapsule(capsuleGeom0, capsulePose0, SC, test->mUserParams.mQuatFromUp, center, TC->mOffset);
+
+ PxCapsuleGeometry capsuleGeom1;
+ PxTransform capsulePose1;
+ relocateCapsule(capsuleGeom1, capsulePose1, TC->mP0, TC->mP1, TC->mRadius);
+
+ PxSweepHit sweepHit;
+ if(!PxGeometryQuery::sweep(dir, impact.mDistance, capsuleGeom0, capsulePose0, capsuleGeom1, capsulePose1, sweepHit, getSweepHitFlags(test->mUserParams)))
+ return false;
+
+ if(sweepHit.distance >= impact.mDistance)
+ return false;
+
+ impact.mDistance = sweepHit.distance;
+ impact.mWorldNormal = sweepHit.normal;
+ impact.mInternalIndex = PX_INVALID_U32;
+ impact.mTriangleIndex = PX_INVALID_U32;
+ impact.setWorldPos(sweepHit.position, TC->mOffset);
+ return true;
+}
+
+static bool SweepCapsuleUserCapsule(const SweepTest* test, const SweptVolume* volume, const TouchedGeom* geom, const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eCAPSULE);
+ PX_ASSERT(geom->mType==TouchedGeomType::eUSER_CAPSULE);
+ const SweptCapsule* SC = static_cast<const SweptCapsule*>(volume);
+ const TouchedUserCapsule* TC = static_cast<const TouchedUserCapsule*>(geom);
+
+ PxCapsuleGeometry capsuleGeom0;
+ PxTransform capsulePose0;
+ relocateCapsule(capsuleGeom0, capsulePose0, SC, test->mUserParams.mQuatFromUp, center, TC->mOffset);
+
+ PxCapsuleGeometry capsuleGeom1;
+ PxTransform capsulePose1;
+ relocateCapsule(capsuleGeom1, capsulePose1, *TC);
+
+ PxSweepHit sweepHit;
+ if(!PxGeometryQuery::sweep(dir, impact.mDistance, capsuleGeom0, capsulePose0, capsuleGeom1, capsulePose1, sweepHit, getSweepHitFlags(test->mUserParams)))
+ return false;
+
+ if(sweepHit.distance >= impact.mDistance)
+ return false;
+
+ impact.mDistance = sweepHit.distance;
+ impact.mWorldNormal = sweepHit.normal;
+ impact.mInternalIndex = PX_INVALID_U32;
+ impact.mTriangleIndex = PX_INVALID_U32;
+ impact.setWorldPos(sweepHit.position, TC->mOffset);
+ return true;
+}
+
+static bool SweepCapsuleUserBox(const SweepTest* test, const SweptVolume* volume, const TouchedGeom* geom, const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact)
+{
+ PX_ASSERT(volume->getType()==SweptVolumeType::eCAPSULE);
+ PX_ASSERT(geom->mType==TouchedGeomType::eUSER_BOX);
+ const SweptCapsule* SC = static_cast<const SweptCapsule*>(volume);
+ const TouchedUserBox* TB = static_cast<const TouchedUserBox*>(geom);
+
+ PxCapsuleGeometry capsuleGeom;
+ PxTransform capsulePose;
+ relocateCapsule(capsuleGeom, capsulePose, SC, test->mUserParams.mQuatFromUp, center, TB->mOffset);
+
+ PxBoxGeometry boxGeom;
+ PxTransform boxPose;
+ relocateBox(boxGeom, boxPose, *TB);
+
+ PxSweepHit sweepHit;
+ if(!PxGeometryQuery::sweep(dir, impact.mDistance, capsuleGeom, capsulePose, boxGeom, boxPose, sweepHit, getSweepHitFlags(test->mUserParams)))
+ return false;
+
+ if(sweepHit.distance >= impact.mDistance)
+ return false;
+
+ impact.mDistance = sweepHit.distance;
+ impact.mWorldNormal = sweepHit.normal;
+ impact.mInternalIndex = PX_INVALID_U32;
+ impact.mTriangleIndex = PX_INVALID_U32;
+
+ //TO CHECK: Investigate whether any significant performance improvement can be achieved through
+ // making the impact point computation optional in the sweep calls and compute it later
+ /*{
+ // ### check this
+ float t;
+ PxVec3 p;
+ float d = gUtilLib->PxSegmentOBBSqrDist(Capsule, Box.center, Box.extents, Box.rot, &t, &p);
+ p += Box.center;
+ impact.mWorldPos.x = p.x + TB->mOffset.x;
+ impact.mWorldPos.y = p.y + TB->mOffset.y;
+ impact.mWorldPos.z = p.z + TB->mOffset.z;
+ }*/
+ {
+ impact.setWorldPos(sweepHit.position, TB->mOffset);
+ }
+ return true;
+}
+
+typedef bool (*SweepFunc) (const SweepTest*, const SweptVolume*, const TouchedGeom*, const PxExtendedVec3&, const PxVec3&, SweptContact&);
+
+static SweepFunc gSweepMap[SweptVolumeType::eLAST][TouchedGeomType::eLAST] = {
+ // Box funcs
+ {
+ SweepBoxUserBox,
+ SweepBoxUserCapsule,
+ SweepBoxMesh,
+ SweepBoxBox,
+ SweepBoxSphere,
+ SweepBoxCapsule
+ },
+
+ // Capsule funcs
+ {
+ SweepCapsuleUserBox,
+ SweepCapsuleUserCapsule,
+ SweepCapsuleMesh,
+ SweepCapsuleBox,
+ SweepCapsuleSphere,
+ SweepCapsuleCapsule
+ }
+};
+
+PX_COMPILE_TIME_ASSERT(sizeof(gSweepMap)==SweptVolumeType::eLAST*TouchedGeomType::eLAST*sizeof(SweepFunc));
+
+static const PxU32 GeomSizes[] =
+{
+ sizeof(TouchedUserBox),
+ sizeof(TouchedUserCapsule),
+ sizeof(TouchedMesh),
+ sizeof(TouchedBox),
+ sizeof(TouchedSphere),
+ sizeof(TouchedCapsule),
+};
+
+static const TouchedGeom* CollideGeoms(
+ const SweepTest* sweep_test, const SweptVolume& volume, const IntArray& geom_stream,
+ const PxExtendedVec3& center, const PxVec3& dir, SweptContact& impact, bool discardInitialOverlap)
+{
+ impact.mInternalIndex = PX_INVALID_U32;
+ impact.mTriangleIndex = PX_INVALID_U32;
+ impact.mGeom = NULL;
+
+ const PxU32* Data = geom_stream.begin();
+ const PxU32* Last = geom_stream.end();
+ while(Data!=Last)
+ {
+ const TouchedGeom* CurrentGeom = reinterpret_cast<const TouchedGeom*>(Data);
+
+ SweepFunc ST = gSweepMap[volume.getType()][CurrentGeom->mType];
+ if(ST)
+ {
+ SweptContact C;
+ C.mDistance = impact.mDistance; // Initialize with current best distance
+ C.mInternalIndex = PX_INVALID_U32;
+ C.mTriangleIndex = PX_INVALID_U32;
+ if((ST)(sweep_test, &volume, CurrentGeom, center, dir, C))
+ {
+ if(C.mDistance==0.0f)
+ {
+ if(!discardInitialOverlap)
+ {
+ if(CurrentGeom->mType==TouchedGeomType::eUSER_BOX || CurrentGeom->mType==TouchedGeomType::eUSER_CAPSULE)
+ {
+ }
+ else
+ {
+ const PxRigidActor* touchedActor = CurrentGeom->mActor;
+ PX_ASSERT(touchedActor);
+
+ if(shouldApplyRecoveryModule(*touchedActor))
+ {
+ impact = C;
+ impact.mGeom = const_cast<TouchedGeom*>(CurrentGeom);
+ return CurrentGeom;
+ }
+ }
+ }
+ }
+/* else
+ if(discardInitialOverlap && C.mDistance==0.0f)
+ {
+ // PT: we previously used eINITIAL_OVERLAP without eINITIAL_OVERLAP_KEEP, i.e. initially overlapping shapes got ignored.
+ // So we replicate this behavior here.
+ }*/
+ else if(C.mDistance<impact.mDistance)
+ {
+ impact = C;
+ impact.mGeom = const_cast<TouchedGeom*>(CurrentGeom);
+ if(C.mDistance <= 0.0f) // there is no point testing for closer hits
+ return CurrentGeom; // since we are touching a shape already
+ }
+ }
+ }
+
+ const PxU8* ptr = reinterpret_cast<const PxU8*>(Data);
+ ptr += GeomSizes[CurrentGeom->mType];
+ Data = reinterpret_cast<const PxU32*>(ptr);
+ }
+ return impact.mGeom;
+}
+
+static PxVec3 computeMTD(const SweepTest* sweep_test, const SweptVolume& volume, const IntArray& geom_stream, const PxExtendedVec3& center, float contactOffset)
+{
+ PxVec3 p = toVec3(center);
+
+// contactOffset += 0.01f;
+
+ const PxU32 maxIter = 4;
+ PxU32 nbIter = 0;
+ bool isValid = true;
+ while(isValid && nbIter<maxIter)
+ {
+ const PxU32* Data = geom_stream.begin();
+ const PxU32* Last = geom_stream.end();
+ while(Data!=Last)
+ {
+ const TouchedGeom* CurrentGeom = reinterpret_cast<const TouchedGeom*>(Data);
+
+ if(CurrentGeom->mType==TouchedGeomType::eUSER_BOX || CurrentGeom->mType==TouchedGeomType::eUSER_CAPSULE)
+ {
+ }
+ else
+ {
+ const PxRigidActor* touchedActor = CurrentGeom->mActor;
+ PX_ASSERT(touchedActor);
+
+ if(shouldApplyRecoveryModule(*touchedActor))
+ {
+ const PxShape* touchedShape = reinterpret_cast<const PxShape*>(CurrentGeom->mTGUserData);
+ PX_ASSERT(touchedShape);
+
+ const PxGeometryHolder gh = touchedShape->getGeometry();
+ const PxTransform globalPose = getShapeGlobalPose(*touchedShape, *touchedActor);
+
+ PxVec3 mtd;
+ PxF32 depth;
+
+ const PxTransform volumePose(p, sweep_test->mUserParams.mQuatFromUp);
+ if(volume.getType()==SweptVolumeType::eCAPSULE)
+ {
+ const SweptCapsule& sc = static_cast<const SweptCapsule&>(volume);
+ const PxCapsuleGeometry capsuleGeom(sc.mRadius+contactOffset, sc.mHeight*0.5f);
+ isValid = PxGeometryQuery::computePenetration(mtd, depth, capsuleGeom, volumePose, gh.any(), globalPose);
+ }
+ else
+ {
+ PX_ASSERT(volume.getType()==SweptVolumeType::eBOX);
+ const SweptBox& sb = static_cast<const SweptBox&>(volume);
+ const PxBoxGeometry boxGeom(sb.mExtents+PxVec3(contactOffset));
+ isValid = PxGeometryQuery::computePenetration(mtd, depth, boxGeom, volumePose, gh.any(), globalPose);
+ }
+
+ if(isValid)
+ {
+ nbIter++;
+ PX_ASSERT(depth>=0.0f);
+ PX_ASSERT(mtd.isFinite());
+ PX_ASSERT(PxIsFinite(depth));
+#ifdef DEBUG_MTD
+ PX_ASSERT(depth<=1.0f);
+ if(depth>1.0f || !mtd.isFinite() || !PxIsFinite(depth))
+ {
+ int stop=1;
+ (void)stop;
+ }
+ printf("Depth: %f\n", depth);
+ printf("mtd: %f %f %f\n", mtd.x, mtd.y, mtd.z);
+#endif
+ p += mtd * depth;
+ }
+ }
+ }
+
+ const PxU8* ptr = reinterpret_cast<const PxU8*>(Data);
+ ptr += GeomSizes[CurrentGeom->mType];
+ Data = reinterpret_cast<const PxU32*>(ptr);
+ }
+ }
+ return p;
+}
+
+
+
+static bool ParseGeomStream(const void* object, const IntArray& geom_stream)
+{
+ const PxU32* Data = geom_stream.begin();
+ const PxU32* Last = geom_stream.end();
+ while(Data!=Last)
+ {
+ const TouchedGeom* CurrentGeom = reinterpret_cast<const TouchedGeom*>(Data);
+ if(CurrentGeom->mTGUserData==object)
+ return true;
+
+ const PxU8* ptr = reinterpret_cast<const PxU8*>(Data);
+ ptr += GeomSizes[CurrentGeom->mType];
+ Data = reinterpret_cast<const PxU32*>(ptr);
+ }
+ return false;
+}
+
+CCTParams::CCTParams() :
+ mNonWalkableMode (PxControllerNonWalkableMode::ePREVENT_CLIMBING),
+ mQuatFromUp (PxQuat(PxIdentity)),
+ mUpDirection (PxVec3(0.0f)),
+ mSlopeLimit (0.0f),
+ mContactOffset (0.0f),
+ mStepOffset (0.0f),
+ mInvisibleWallHeight (0.0f),
+ mMaxJumpHeight (0.0f),
+ mMaxEdgeLength2 (0.0f),
+ mTessellation (false),
+ mHandleSlope (false),
+ mOverlapRecovery (false),
+ mPreciseSweeps (true),
+ mPreventVerticalSlidingAgainstCeiling (false)
+{
+}
+
+SweepTest::SweepTest(bool registerDeletionListener) :
+ mRenderBuffer (NULL),
+ mRenderFlags (0),
+ mTriangleIndices (PX_DEBUG_EXP("sweepTestTriangleIndices")),
+ mGeomStream (PX_DEBUG_EXP("sweepTestStream")),
+ mTouchedShape (registerDeletionListener),
+ mTouchedActor (registerDeletionListener),
+ mSQTimeStamp (0xffffffff),
+ mNbFullUpdates (0),
+ mNbPartialUpdates (0),
+ mNbTessellation (0),
+ mNbIterations (0),
+ mFlags (0),
+ mRegisterDeletionListener(registerDeletionListener),
+ mCctManager (NULL)
+{
+ mCacheBounds.setEmpty();
+ mCachedTriIndexIndex = 0;
+ mCachedTriIndex[0] = mCachedTriIndex[1] = mCachedTriIndex[2] = 0;
+ mNbCachedStatic = 0;
+ mNbCachedT = 0;
+
+ mTouchedObstacleHandle = INVALID_OBSTACLE_HANDLE;
+ mTouchedPos = PxVec3(0);
+ mTouchedPosShape_Local = PxVec3(0);
+ mTouchedPosShape_World = PxVec3(0);
+ mTouchedPosObstacle_Local = PxVec3(0);
+ mTouchedPosObstacle_World = PxVec3(0);
+
+// mVolumeGrowth = 1.2f; // Must be >1.0f and not too big
+ mVolumeGrowth = 1.5f; // Must be >1.0f and not too big
+// mVolumeGrowth = 2.0f; // Must be >1.0f and not too big
+
+ mContactNormalDownPass = PxVec3(0.0f);
+ mContactNormalSidePass = PxVec3(0.0f);
+ mTouchedTriMin = 0.0f;
+ mTouchedTriMax = 0.0f;
+}
+
+
+SweepTest::~SweepTest()
+{
+ // set the TouchedObject to NULL so we unregister the actor/shape
+ mTouchedShape = NULL;
+ mTouchedActor = NULL;
+}
+
+void SweepTest::voidTestCache()
+{
+ mTouchedShape = NULL;
+ mTouchedActor = NULL;
+ mCacheBounds.setEmpty();
+ mTouchedObstacleHandle = INVALID_OBSTACLE_HANDLE;
+}
+
+void SweepTest::onRelease(const PxBase& observed)
+{
+ if (mTouchedActor == &observed)
+ {
+ mTouchedShape = NULL;
+ mTouchedActor = NULL;
+ return;
+ }
+
+ if(ParseGeomStream(&observed, mGeomStream))
+ mCacheBounds.setEmpty();
+
+ if (mTouchedShape == &observed)
+ mTouchedShape = NULL;
+}
+
+void SweepTest::updateCachedShapesRegistration(PxU32 startIndex, bool unregister)
+{
+ if(!mRegisterDeletionListener)
+ return;
+
+ if(!mGeomStream.size() || startIndex == mGeomStream.size())
+ return;
+
+ PX_ASSERT(startIndex <= mGeomStream.size());
+
+ const PxU32* data = &mGeomStream[startIndex];
+ const PxU32* last = mGeomStream.end();
+ while (data != last)
+ {
+ const TouchedGeom* CurrentGeom = reinterpret_cast<const TouchedGeom*>(data);
+ if (CurrentGeom->mActor)
+ {
+ if(unregister)
+ mCctManager->unregisterObservedObject(reinterpret_cast<const PxBase*>(CurrentGeom->mTGUserData));
+ else
+ mCctManager->registerObservedObject(reinterpret_cast<const PxBase*>(CurrentGeom->mTGUserData));
+ }
+ else
+ {
+ // we can early exit, the rest of the data are user obstacles
+ return;
+ }
+
+ const PxU8* ptr = reinterpret_cast<const PxU8*>(data);
+ ptr += GeomSizes[CurrentGeom->mType];
+ data = reinterpret_cast<const PxU32*>(ptr);
+ }
+}
+
+void SweepTest::onObstacleAdded(ObstacleHandle index, const PxObstacleContext* context, const PxVec3& origin, const PxVec3& unitDir, const PxReal distance )
+{
+ if(mTouchedObstacleHandle != INVALID_OBSTACLE_HANDLE)
+ {
+ // check if new obstacle is closer
+ const ObstacleContext* obstContext = static_cast<const ObstacleContext*> (context);
+ PxRaycastHit obstacleHit;
+ const PxObstacle* obst = obstContext->raycastSingle(obstacleHit,index,origin,unitDir,distance);
+
+ if(obst && (obstacleHit.position.dot(unitDir))<(mTouchedPosObstacle_World.dot(unitDir)))
+ {
+ PX_ASSERT(obstacleHit.distance<=distance);
+ mTouchedObstacleHandle = index;
+ if(!gUseLocalSpace)
+ {
+ mTouchedPos = toVec3(obst->mPos);
+ }
+ else
+ {
+ mTouchedPosObstacle_World = obstacleHit.position;
+ mTouchedPosObstacle_Local = worldToLocal(*obst, PxExtendedVec3(PxExtended(obstacleHit.position.x),PxExtended(obstacleHit.position.y),PxExtended(obstacleHit.position.z)));
+ }
+ }
+ }
+}
+
+void SweepTest::onObstacleRemoved(ObstacleHandle index)
+{
+ if(index == mTouchedObstacleHandle)
+ {
+ mTouchedObstacleHandle = INVALID_OBSTACLE_HANDLE;
+ }
+}
+
+void SweepTest::onObstacleUpdated(ObstacleHandle index, const PxObstacleContext* context, const PxVec3& origin, const PxVec3& unitDir, const PxReal distance)
+{
+ if(index == mTouchedObstacleHandle)
+ {
+ // check if updated obstacle is still closest
+ const ObstacleContext* obstContext = static_cast<const ObstacleContext*> (context);
+ PxRaycastHit obstacleHit;
+ ObstacleHandle closestHandle = INVALID_OBSTACLE_HANDLE;
+ const PxObstacle* obst = obstContext->raycastSingle(obstacleHit,origin,unitDir,distance,closestHandle);
+
+ if(mTouchedObstacleHandle == closestHandle)
+ return;
+
+ if(obst)
+ {
+ PX_ASSERT(obstacleHit.distance<=distance);
+ mTouchedObstacleHandle = closestHandle;
+ if(!gUseLocalSpace)
+ {
+ mTouchedPos = toVec3(obst->mPos);
+ }
+ else
+ {
+ mTouchedPosObstacle_World = obstacleHit.position;
+ mTouchedPosObstacle_Local = worldToLocal(*obst, PxExtendedVec3(PxExtended(obstacleHit.position.x),PxExtended(obstacleHit.position.y),PxExtended(obstacleHit.position.z)));
+ }
+ }
+ }
+}
+
+void SweepTest::onOriginShift(const PxVec3& shift)
+{
+ mCacheBounds.minimum -= shift;
+ mCacheBounds.maximum -= shift;
+
+ if(mTouchedShape)
+ {
+ const PxRigidActor* rigidActor = mTouchedActor.get();
+ if(rigidActor->getConcreteType() != PxConcreteType::eRIGID_STATIC)
+ {
+ mTouchedPosShape_World -= shift;
+ }
+ }
+ else if (mTouchedObstacleHandle != INVALID_OBSTACLE_HANDLE)
+ {
+ if(!gUseLocalSpace)
+ {
+ mTouchedPos -= shift;
+ }
+ else
+ {
+ mTouchedPosObstacle_World -= shift;
+ }
+ }
+
+ // adjust cache
+ PxU32* data = mGeomStream.begin();
+ PxU32* last = mGeomStream.end();
+ while(data != last)
+ {
+ TouchedGeom* currentGeom = reinterpret_cast<TouchedGeom*>(data);
+
+ currentGeom->mOffset -= shift;
+
+ PxU8* ptr = reinterpret_cast<PxU8*>(data);
+ ptr += GeomSizes[currentGeom->mType];
+ data = reinterpret_cast<PxU32*>(ptr);
+ }
+}
+
+static PxBounds3 getBounds3(const PxExtendedBounds3& extended)
+{
+ return PxBounds3(toVec3(extended.minimum), toVec3(extended.maximum)); // LOSS OF ACCURACY
+}
+
+// PT: finds both touched CCTs and touched user-defined obstacles
+void SweepTest::findTouchedObstacles(const UserObstacles& userObstacles, const PxExtendedBounds3& worldBox)
+{
+ PxExtendedVec3 Origin; // Will be TouchedGeom::mOffset
+ getCenter(worldBox, Origin);
+
+ {
+ const PxU32 nbBoxes = userObstacles.mNbBoxes;
+ const PxExtendedBox* boxes = userObstacles.mBoxes;
+ const void** boxUserData = userObstacles.mBoxUserData;
+
+ const PxBounds3 singlePrecisionWorldBox = getBounds3(worldBox);
+
+ // Find touched boxes, i.e. other box controllers
+ for(PxU32 i=0;i<nbBoxes;i++)
+ {
+ const Gu::Box obb(
+ toVec3(boxes[i].center), // LOSS OF ACCURACY
+ boxes[i].extents,
+ PxMat33(boxes[i].rot)); // #### PT: TODO: useless conversion here
+
+ if(!Gu::intersectOBBAABB(obb, singlePrecisionWorldBox))
+ continue;
+
+ TouchedUserBox* UserBox = reinterpret_cast<TouchedUserBox*>(reserve(mGeomStream, sizeof(TouchedUserBox)/sizeof(PxU32)));
+ UserBox->mType = TouchedGeomType::eUSER_BOX;
+ UserBox->mTGUserData = boxUserData[i];
+ UserBox->mActor = NULL;
+ UserBox->mOffset = Origin;
+ UserBox->mBox = boxes[i];
+ }
+ }
+
+ {
+ // Find touched capsules, i.e. other capsule controllers
+ const PxU32 nbCapsules = userObstacles.mNbCapsules;
+ const PxExtendedCapsule* capsules = userObstacles.mCapsules;
+ const void** capsuleUserData = userObstacles.mCapsuleUserData;
+
+ PxExtendedVec3 Center;
+ PxVec3 Extents;
+ getCenter(worldBox, Center);
+ getExtents(worldBox, Extents);
+
+ for(PxU32 i=0;i<nbCapsules;i++)
+ {
+ // PT: do a quick AABB check first, to avoid calling the SDK too much
+ const PxF32 r = capsules[i].radius;
+ const PxExtended capMinx = PxMin(capsules[i].p0.x, capsules[i].p1.x);
+ const PxExtended capMaxx = PxMax(capsules[i].p0.x, capsules[i].p1.x);
+ if((capMinx - PxExtended(r) > worldBox.maximum.x) || (worldBox.minimum.x > capMaxx + PxExtended(r))) continue;
+
+ const PxExtended capMiny = PxMin(capsules[i].p0.y, capsules[i].p1.y);
+ const PxExtended capMaxy = PxMax(capsules[i].p0.y, capsules[i].p1.y);
+ if((capMiny - PxExtended(r) > worldBox.maximum.y) || (worldBox.minimum.y > capMaxy + PxExtended(r))) continue;
+
+ const PxExtended capMinz = PxMin(capsules[i].p0.z, capsules[i].p1.z);
+ const PxExtended capMaxz = PxMax(capsules[i].p0.z, capsules[i].p1.z);
+ if((capMinz - PxExtended(r) > worldBox.maximum.z) || (worldBox.minimum.z > capMaxz + PxExtended(r))) continue;
+
+ // PT: more accurate capsule-box test. Not strictly necessary but worth doing if available
+ const PxReal d2 = Gu::distanceSegmentBoxSquared(toVec3(capsules[i].p0), toVec3(capsules[i].p1), toVec3(Center), Extents, PxMat33(PxIdentity));
+ if(d2>r*r)
+ continue;
+
+ TouchedUserCapsule* UserCapsule = reinterpret_cast<TouchedUserCapsule*>(reserve(mGeomStream, sizeof(TouchedUserCapsule)/sizeof(PxU32)));
+ UserCapsule->mType = TouchedGeomType::eUSER_CAPSULE;
+ UserCapsule->mTGUserData = capsuleUserData[i];
+ UserCapsule->mActor = NULL;
+ UserCapsule->mOffset = Origin;
+ UserCapsule->mCapsule = capsules[i];
+ }
+ }
+}
+
+void SweepTest::updateTouchedGeoms( const InternalCBData_FindTouchedGeom* userData, const UserObstacles& userObstacles,
+ const PxExtendedBounds3& worldTemporalBox, const PxControllerFilters& filters, const PxVec3& sideVector)
+{
+ /*
+ - if this is the first iteration (new frame) we have to redo the dynamic objects & the CCTs. The static objects can
+ be cached.
+ - if this is not, we can cache everything
+ */
+
+ // PT: using "worldTemporalBox" instead of "mCacheBounds" seems to produce TTP 6207
+//#define DYNAMIC_BOX worldTemporalBox
+#define DYNAMIC_BOX mCacheBounds
+
+ bool newCachedBox = false;
+
+ CCTFilter filter;
+ filter.mFilterData = filters.mFilterData;
+ filter.mFilterCallback = filters.mFilterCallback;
+ filter.mPreFilter = filters.mFilterFlags & PxQueryFlag::ePREFILTER;
+ filter.mPostFilter = filters.mFilterFlags & PxQueryFlag::ePOSTFILTER;
+
+ // PT: detect changes to the static pruning structure
+ bool sceneHasChanged = false;
+ {
+ const PxU32 currentTimestamp = getSceneTimestamp(userData);
+ if(currentTimestamp!=mSQTimeStamp)
+ {
+ mSQTimeStamp = currentTimestamp;
+ sceneHasChanged = true;
+ }
+ }
+
+ // If the input box is inside the cached box, nothing to do
+ if(gUsePartialUpdates && !sceneHasChanged && worldTemporalBox.isInside(mCacheBounds))
+ {
+ //printf("CACHEIN%d\n", mFirstUpdate);
+ if(mFlags & STF_FIRST_UPDATE)
+ {
+ mFlags &= ~STF_FIRST_UPDATE;
+
+ // Only redo the dynamic
+ updateCachedShapesRegistration(mNbCachedStatic, true);
+ mGeomStream.forceSize_Unsafe(mNbCachedStatic);
+ mWorldTriangles.forceSize_Unsafe(mNbCachedT);
+ mTriangleIndices.forceSize_Unsafe(mNbCachedT);
+
+ filter.mStaticShapes = false;
+ if(filters.mFilterFlags & PxQueryFlag::eDYNAMIC)
+ filter.mDynamicShapes = true;
+ findTouchedGeometry(userData, DYNAMIC_BOX, mWorldTriangles, mTriangleIndices, mGeomStream, filter, mUserParams, mNbTessellation);
+ updateCachedShapesRegistration(mNbCachedStatic, false);
+
+ findTouchedObstacles(userObstacles, DYNAMIC_BOX);
+
+ mNbPartialUpdates++;
+ }
+ }
+ else
+ {
+ //printf("CACHEOUTNS=%d\n", mNbCachedStatic);
+ newCachedBox = true;
+
+ // Cache BV used for the query
+ mCacheBounds = worldTemporalBox;
+
+ // Grow the volume a bit. The temporal box here doesn't take sliding & collision response into account.
+ // In bad cases it is possible to eventually touch a portion of space not covered by this volume. Just
+ // in case, we grow the initial volume slightly. Then, additional tests are performed within the loop
+ // to make sure the TBV is always correct. There's a tradeoff between the original (artificial) growth
+ // of the volume, and the number of TBV recomputations performed at runtime...
+ scale(mCacheBounds, PxVec3(mVolumeGrowth));
+// scale(mCacheBounds, PxVec3(mVolumeGrowth, 1.0f, mVolumeGrowth));
+
+ if(1 && !sideVector.isZero())
+ {
+ const PxVec3 sn = sideVector.getNormalized();
+ float dp0 = PxAbs((worldTemporalBox.maximum - worldTemporalBox.minimum).dot(sn));
+ float dp1 = PxAbs((mCacheBounds.maximum - mCacheBounds.minimum).dot(sn));
+ dp1 -= dp0;
+ dp1 *= 0.5f * 0.9f;
+ const PxVec3 offset = sn * dp1;
+// printf("%f %f %f\n", offset.x, offset.y, offset.z);
+ mCacheBounds.minimum += offset;
+ mCacheBounds.maximum += offset;
+ add(mCacheBounds, worldTemporalBox);
+ PX_ASSERT(worldTemporalBox.isInside(mCacheBounds));
+ }
+
+ updateCachedShapesRegistration(0, true);
+
+ // Gather triangles touched by this box. This covers multiple meshes.
+ mWorldTriangles.clear();
+ mTriangleIndices.clear();
+ mGeomStream.clear();
+// mWorldTriangles.reset();
+// mTriangleIndices.reset();
+// mGeomStream.reset();
+
+ mCachedTriIndexIndex = 0;
+ mCachedTriIndex[0] = mCachedTriIndex[1] = mCachedTriIndex[2] = 0;
+
+ mNbFullUpdates++;
+
+ if(filters.mFilterFlags & PxQueryFlag::eSTATIC)
+ filter.mStaticShapes = true;
+ filter.mDynamicShapes = false;
+ findTouchedGeometry(userData, mCacheBounds, mWorldTriangles, mTriangleIndices, mGeomStream, filter, mUserParams, mNbTessellation);
+
+ mNbCachedStatic = mGeomStream.size();
+ mNbCachedT = mWorldTriangles.size();
+ PX_ASSERT(mTriangleIndices.size()==mNbCachedT);
+
+ filter.mStaticShapes = false;
+ if(filters.mFilterFlags & PxQueryFlag::eDYNAMIC)
+ filter.mDynamicShapes = true;
+ findTouchedGeometry(userData, DYNAMIC_BOX, mWorldTriangles, mTriangleIndices, mGeomStream, filter, mUserParams, mNbTessellation);
+ // We can't early exit when no tris are touched since we also have to handle the boxes
+ updateCachedShapesRegistration(0, false);
+
+ findTouchedObstacles(userObstacles, DYNAMIC_BOX);
+
+ mFlags &= ~STF_FIRST_UPDATE;
+ //printf("CACHEOUTNSDONE=%d\n", mNbCachedStatic);
+ }
+
+ if(mRenderBuffer)
+ {
+ // PT: worldTemporalBox = temporal BV for this frame
+ Cm::RenderOutput out(*mRenderBuffer);
+
+ if(mRenderFlags & PxControllerDebugRenderFlag::eTEMPORAL_BV)
+ {
+ out << gTBVDebugColor;
+ out << Cm::DebugBox(getBounds3(worldTemporalBox));
+ }
+
+ if(mRenderFlags & PxControllerDebugRenderFlag::eCACHED_BV)
+ {
+ if(newCachedBox)
+ out << PxU32(PxDebugColor::eARGB_RED);
+ else
+ out << PxU32(PxDebugColor::eARGB_GREEN);
+ out << Cm::DebugBox(getBounds3(mCacheBounds));
+ }
+ }
+}
+
+// This is the generic sweep test for all swept volumes, but not character-controller specific
+bool SweepTest::doSweepTest(const InternalCBData_FindTouchedGeom* userData,
+ InternalCBData_OnHit* userHitData,
+ const UserObstacles& userObstacles,
+ SweptVolume& swept_volume,
+ const PxVec3& direction, const PxVec3& sideVector, PxU32 max_iter, PxU32* nb_collisions,
+ float min_dist, const PxControllerFilters& filters, SweepPass sweepPass,
+ const PxRigidActor*& touchedActorOut, const PxShape*& touchedShapeOut)
+{
+ // Early exit when motion is zero. Since the motion is decomposed into several vectors
+ // and this function is called for each of them, it actually happens quite often.
+ if(direction.isZero())
+ return false;
+
+ bool hasMoved = false;
+ mFlags &= ~(STF_VALIDATE_TRIANGLE_DOWN|STF_TOUCH_OTHER_CCT|STF_TOUCH_OBSTACLE);
+ touchedShapeOut = NULL;
+ touchedActorOut = NULL;
+ mTouchedObstacleHandle = INVALID_OBSTACLE_HANDLE;
+
+ PxExtendedVec3 currentPosition = swept_volume.mCenter;
+ PxExtendedVec3 targetOrientation = swept_volume.mCenter;
+ targetOrientation += direction;
+
+ PxU32 NbCollisions = 0;
+ while(max_iter--)
+ {
+ mNbIterations++;
+ // Compute current direction
+ PxVec3 currentDirection = targetOrientation - currentPosition;
+
+ // Make sure the new TBV is still valid
+ {
+ // Compute temporal bounding box. We could use a capsule or an OBB instead:
+ // - the volume would be smaller
+ // - but the query would be slower
+ // Overall it's unclear whether it's worth it or not.
+ // TODO: optimize this part ?
+ PxExtendedBounds3 temporalBox;
+ swept_volume.computeTemporalBox(*this, temporalBox, currentPosition, currentDirection);
+
+ // Gather touched geoms
+ updateTouchedGeoms(userData, userObstacles, temporalBox, filters, sideVector);
+ }
+
+ const float Length = currentDirection.magnitude();
+ if(Length<=min_dist) //Use <= to handle the case where min_dist is zero.
+ break;
+
+ currentDirection /= Length;
+
+ // From Quake2: "if velocity is against the original velocity, stop dead to avoid tiny occilations in sloping corners"
+ if((currentDirection.dot(direction)) <= 0.0f)
+ break;
+
+ // From this point, we're going to update the position at least once
+ hasMoved = true;
+
+ // Find closest collision
+ SweptContact C;
+ C.mDistance = Length + mUserParams.mContactOffset;
+
+ if(!CollideGeoms(this, swept_volume, mGeomStream, currentPosition, currentDirection, C, !mUserParams.mOverlapRecovery))
+ {
+ // no collision found => move to desired position
+ currentPosition = targetOrientation;
+ break;
+ }
+
+ PX_ASSERT(C.mGeom); // If we reach this point, we must have touched a geom
+
+ if(mUserParams.mOverlapRecovery && C.mDistance==0.0f)
+ {
+/* SweptContact C;
+ C.mDistance = 10.0f;
+ CollideGeoms(this, swept_volume, mGeomStream, currentPosition, -currentDirection, C, true);
+ currentPosition -= currentDirection*C.mDistance;
+
+ C.mDistance = 10.0f;
+ CollideGeoms(this, swept_volume, mGeomStream, currentPosition, currentDirection, C, true);
+ const float DynSkin = mUserParams.mContactOffset;
+ if(C.mDistance>DynSkin)
+ currentPosition += currentDirection*(C.mDistance-DynSkin);*/
+
+ const PxVec3 mtd = computeMTD(this, swept_volume, mGeomStream, currentPosition, mUserParams.mContactOffset);
+
+ NbCollisions++;
+
+ if(nb_collisions)
+ *nb_collisions = NbCollisions;
+
+#ifdef DEBUG_MTD
+ printf("MTD FIXUP: %f %f %f\n", mtd.x - swept_volume.mCenter.x, mtd.y - swept_volume.mCenter.y, mtd.z - swept_volume.mCenter.z);
+#endif
+ swept_volume.mCenter.x = PxExtended(mtd.x);
+ swept_volume.mCenter.y = PxExtended(mtd.y);
+ swept_volume.mCenter.z = PxExtended(mtd.z);
+ return hasMoved;
+// currentPosition.x = mtd.x;
+// currentPosition.y = mtd.y;
+// currentPosition.z = mtd.z;
+// continue;
+ }
+
+ bool preventVerticalMotion = false;
+ bool stopSliding = true;
+ if(C.mGeom->mType==TouchedGeomType::eUSER_BOX || C.mGeom->mType==TouchedGeomType::eUSER_CAPSULE)
+ {
+ if(sweepPass!=SWEEP_PASS_SENSOR)
+ {
+ // We touched a user object, typically another CCT, but can also be a user-defined obstacle
+
+ // PT: TODO: technically lines marked with (*) shouldn't be here... revisit later
+
+ const PxObstacle* touchedObstacle = NULL; // (*)
+ ObstacleHandle touchedObstacleHandle = INVALID_OBSTACLE_HANDLE;
+ // if(mValidateCallback)
+ {
+ PxInternalCBData_OnHit* internalData = static_cast<PxInternalCBData_OnHit*>(userHitData); // (*)
+ internalData->touchedObstacle = NULL; // (*)
+ internalData->touchedObstacleHandle = INVALID_OBSTACLE_HANDLE;
+ const PxU32 behaviorFlags = userHitCallback(userHitData, C, currentDirection, Length);
+ stopSliding = (behaviorFlags & PxControllerBehaviorFlag::eCCT_SLIDE)==0; // (*)
+ touchedObstacle = internalData->touchedObstacle; // (*)
+ touchedObstacleHandle = internalData->touchedObstacleHandle;
+ }
+ // printf("INTERNAL: %d\n", int(touchedObstacle));
+
+ if(sweepPass==SWEEP_PASS_DOWN)
+ {
+ // (*)
+ if(touchedObstacle)
+ {
+ mFlags |= STF_TOUCH_OBSTACLE;
+
+ mTouchedObstacleHandle = touchedObstacleHandle;
+ if(!gUseLocalSpace)
+ {
+ mTouchedPos = toVec3(touchedObstacle->mPos);
+ }
+ else
+ {
+ mTouchedPosObstacle_World = toVec3(C.mWorldPos);
+ mTouchedPosObstacle_Local = worldToLocal(*touchedObstacle, C.mWorldPos);
+ }
+ }
+ else
+ {
+ mFlags |= STF_TOUCH_OTHER_CCT;
+ }
+ }
+ }
+ }
+ else
+ {
+ const PxShape* touchedShape = reinterpret_cast<const PxShape*>(C.mGeom->mTGUserData);
+ PX_ASSERT(touchedShape);
+ const PxRigidActor* touchedActor = C.mGeom->mActor;
+ PX_ASSERT(touchedActor);
+
+ // We touched a normal object
+ if(sweepPass==SWEEP_PASS_DOWN)
+ {
+ mFlags &= ~(STF_TOUCH_OTHER_CCT|STF_TOUCH_OBSTACLE);
+
+#ifdef USE_CONTACT_NORMAL_FOR_SLOPE_TEST
+ mFlags |= STF_VALIDATE_TRIANGLE_DOWN;
+ mContactNormalDownPass = C.mWorldNormal;
+#else
+
+ // Work out if the shape is attached to a static or dynamic actor.
+ // The slope limit is currently only considered when walking on static actors.
+ // It is ignored for shapes attached attached to dynamics and kinematics.
+ // TODO: 1. should we treat stationary kinematics the same as statics.
+ // 2. should we treat all kinematics the same as statics.
+ // 3. should we treat no kinematics the same as statics.
+ if((touchedActor->getConcreteType() == PxConcreteType::eRIGID_STATIC) && (C.mInternalIndex!=PX_INVALID_U32))
+ {
+ mFlags |= STF_VALIDATE_TRIANGLE_DOWN;
+ const PxTriangle& touchedTri = mWorldTriangles.getTriangle(C.mInternalIndex);
+ const PxVec3& upDirection = mUserParams.mUpDirection;
+ const float dp0 = touchedTri.verts[0].dot(upDirection);
+ const float dp1 = touchedTri.verts[1].dot(upDirection);
+ const float dp2 = touchedTri.verts[2].dot(upDirection);
+ float dpmin = dp0;
+ dpmin = physx::intrinsics::selectMin(dpmin, dp1);
+ dpmin = physx::intrinsics::selectMin(dpmin, dp2);
+ float dpmax = dp0;
+ dpmax = physx::intrinsics::selectMax(dpmax, dp1);
+ dpmax = physx::intrinsics::selectMax(dpmax, dp2);
+
+ PxExtendedVec3 cacheCenter;
+ getCenter(mCacheBounds, cacheCenter);
+ const float offset = upDirection.dot(toVec3(cacheCenter));
+ mTouchedTriMin = dpmin + offset;
+ mTouchedTriMax = dpmax + offset;
+
+ touchedTri.normal(mContactNormalDownPass);
+ }
+#endif
+ // Update touched shape in down pass
+ touchedShapeOut = const_cast<PxShape*>(touchedShape);
+ touchedActorOut = touchedActor;
+// mTouchedPos = getShapeGlobalPose(*touchedShape).p;
+ const PxTransform shapeTransform = getShapeGlobalPose(*touchedShape, *touchedActor);
+ const PxVec3 worldPos = toVec3(C.mWorldPos);
+ mTouchedPosShape_World = worldPos;
+ mTouchedPosShape_Local = shapeTransform.transformInv(worldPos);
+ }
+ else if(sweepPass==SWEEP_PASS_SIDE || sweepPass==SWEEP_PASS_SENSOR)
+ {
+ if((touchedActor->getConcreteType() == PxConcreteType::eRIGID_STATIC) && (C.mInternalIndex!=PX_INVALID_U32))
+ {
+ mFlags |= STF_VALIDATE_TRIANGLE_SIDE;
+ const PxTriangle& touchedTri = mWorldTriangles.getTriangle(C.mInternalIndex);
+ touchedTri.normal(mContactNormalSidePass);
+// printf("%f | %f | %f\n", mContactNormalSidePass.x, mContactNormalSidePass.y, mContactNormalSidePass.z);
+ if(mUserParams.mPreventVerticalSlidingAgainstCeiling && mContactNormalSidePass.dot(mUserParams.mUpDirection)<0.0f)
+ preventVerticalMotion = true;
+ }
+ }
+
+ if(sweepPass!=SWEEP_PASS_SENSOR)
+// if(mValidateCallback)
+ {
+ const PxU32 behaviorFlags = shapeHitCallback(userHitData, C, currentDirection, Length);
+ stopSliding = (behaviorFlags & PxControllerBehaviorFlag::eCCT_SLIDE)==0; // (*)
+ }
+ }
+
+ if(sweepPass==SWEEP_PASS_DOWN && !stopSliding)
+ {
+ // Trying to solve the following problem:
+ // - by default, the CCT "friction" is infinite, i.e. a CCT will not slide on a slope (this is by design)
+ // - this produces bad results when a capsule CCT stands on top of another capsule CCT, without sliding. Visually it looks
+ // like the character is standing on the other character's head, it looks bad. So, here, we would like to let the CCT
+ // slide away, i.e. we don't want friction.
+ // So here we simply increase the number of iterations (== let the CCT slide) when the first down collision is with another CCT.
+ if(!NbCollisions)
+ max_iter += 9;
+// max_iter += 1;
+ }
+
+ NbCollisions++;
+// mContactPointHeight = (float)C.mWorldPos[mUserParams.mUpDirection]; // UBI
+ mContactPointHeight = toVec3(C.mWorldPos).dot(mUserParams.mUpDirection); // UBI
+
+ const float DynSkin = mUserParams.mContactOffset;
+
+ if(C.mDistance>DynSkin/*+0.01f*/)
+ currentPosition += currentDirection*(C.mDistance-DynSkin);
+// DE6513
+/* else if(sweepPass==SWEEP_PASS_SIDE)
+ {
+ // Might be better to do a proper sweep pass here, in the opposite direction
+ currentPosition += currentDirection*(C.mDistance-DynSkin);
+ }*/
+//~DE6513
+
+ PxVec3 WorldNormal = C.mWorldNormal;
+ if(preventVerticalMotion || ((mFlags & STF_WALK_EXPERIMENT) && (mUserParams.mNonWalkableMode!=PxControllerNonWalkableMode::ePREVENT_CLIMBING_AND_FORCE_SLIDING)))
+ {
+ // Make sure the auto-step doesn't bypass this !
+ // PT: cancel out normal compo
+// WorldNormal[mUserParams.mUpDirection]=0.0f;
+// WorldNormal.normalize();
+ PxVec3 normalCompo, tangentCompo;
+ Ps::decomposeVector(normalCompo, tangentCompo, WorldNormal, mUserParams.mUpDirection);
+ WorldNormal = tangentCompo;
+ WorldNormal.normalize();
+ }
+
+ const float Bump = 0.0f; // ### doesn't work when !=0 because of Quake2 hack!
+ const float Friction = 1.0f;
+ collisionResponse(targetOrientation, currentPosition, currentDirection, WorldNormal, Bump, Friction, (mFlags & STF_NORMALIZE_RESPONSE)!=0);
+ }
+
+ if(nb_collisions)
+ *nb_collisions = NbCollisions;
+
+ // Final box position that should be reflected in the graphics engine
+ swept_volume.mCenter = currentPosition;
+
+ // If we didn't move, don't update the box position at all (keeping possible lazy-evaluated structures valid)
+ return hasMoved;
+}
+
+// ### have a return code to tell if we really moved or not
+
+// Using swept code & direct position update (no physics engine)
+// This function is the generic character controller logic, valid for all swept volumes
+PxControllerCollisionFlags SweepTest::moveCharacter(
+ const InternalCBData_FindTouchedGeom* userData,
+ InternalCBData_OnHit* userHitData,
+ SweptVolume& volume,
+ const PxVec3& direction,
+ const UserObstacles& userObstacles,
+ float min_dist,
+ const PxControllerFilters& filters,
+ bool constrainedClimbingMode,
+ bool standingOnMoving,
+ const PxRigidActor*& touchedActor,
+ const PxShape*& touchedShape
+ )
+{
+ bool standingOnMovingUp = standingOnMoving;
+
+ mFlags &= ~STF_HIT_NON_WALKABLE;
+ PxControllerCollisionFlags CollisionFlags = PxControllerCollisionFlags(0);
+ const PxU32 maxIter = MAX_ITER; // 1 for "collide and stop"
+ const PxU32 maxIterSides = maxIter;
+ const PxU32 maxIterDown = ((mFlags & STF_WALK_EXPERIMENT) && mUserParams.mNonWalkableMode==PxControllerNonWalkableMode::ePREVENT_CLIMBING_AND_FORCE_SLIDING) ? maxIter : 1;
+// const PxU32 maxIterDown = 1;
+
+ // ### this causes the artificial gap on top of chars
+ float stepOffset = mUserParams.mStepOffset; // Default step offset can be cancelled in some cases.
+
+ // Save initial height
+ const PxVec3& upDirection = mUserParams.mUpDirection;
+ const PxExtended originalHeight = volume.mCenter.dot(upDirection);
+ const PxExtended originalBottomPoint = originalHeight - PxExtended(volume.mHalfHeight); // UBI
+
+ // TEST! Disable auto-step when flying. Not sure this is really useful.
+// if(direction[upDirection]>0.0f)
+ const float dir_dot_up = direction.dot(upDirection);
+//printf("%f\n", dir_dot_up);
+ if(dir_dot_up>0.0f)
+ {
+ mFlags |= STF_IS_MOVING_UP;
+
+ // PT: this makes it fail on a platform moving up when jumping
+ // However if we don't do that a jump when moving up a slope doesn't work anymore!
+ // Not doing this also creates jittering when a capsule CCT jumps against another capsule CCT
+ if(!standingOnMovingUp) // PT: if we're standing on something moving up it's safer to do the up motion anyway, even though this won't work well before we add the flag in TA13542
+ {
+// static int count=0; printf("Cancelling step offset... %d\n", count++);
+ stepOffset = 0.0f;
+ }
+ }
+ else
+ {
+ mFlags &= ~STF_IS_MOVING_UP;
+ }
+
+ // Decompose motion into 3 independent motions: up, side, down
+ // - if the motion is purely down (gravity only), the up part is needed to fight accuracy issues. For example if the
+ // character is already touching the geometry a bit, the down sweep test might have troubles. If we first move it above
+ // the geometry, the problems disappear.
+ // - if the motion is lateral (character moving forward under normal gravity) the decomposition provides the autostep feature
+ // - if the motion is purely up, the down part can be skipped
+
+ PxVec3 UpVector(0.0f, 0.0f, 0.0f);
+ PxVec3 DownVector(0.0f, 0.0f, 0.0f);
+
+ PxVec3 normal_compo, tangent_compo;
+ Ps::decomposeVector(normal_compo, tangent_compo, direction, upDirection);
+
+// if(direction[upDirection]<0.0f)
+ if(dir_dot_up<=0.0f)
+// DownVector[upDirection] = direction[upDirection];
+ DownVector = normal_compo;
+ else
+// UpVector[upDirection] = direction[upDirection];
+ UpVector = normal_compo;
+
+// PxVec3 SideVector = direction;
+// SideVector[upDirection] = 0.0f;
+ PxVec3 SideVector = tangent_compo;
+
+ // If the side motion is zero, i.e. if the character is not really moving, disable auto-step.
+ // This is important to prevent the CCT from automatically climbing on small objects that move
+ // against it. We should climb over those only if there's a valid side motion from the player.
+ const bool sideVectorIsZero = !standingOnMovingUp && Ps::isAlmostZero(SideVector); // We can't use PxVec3::isZero() safely with arbitrary up vectors
+ // #### however if we do this the up pass is disabled, with bad consequences when the CCT is on a dynamic object!!
+ // ### this line makes it possible to push other CCTs by jumping on them
+// const bool sideVectorIsZero = false;
+// printf("sideVectorIsZero: %d\n", sideVectorIsZero);
+
+// if(!SideVector.isZero())
+ if(!sideVectorIsZero)
+// UpVector[upDirection] += stepOffset;
+ UpVector += upDirection*stepOffset;
+// printf("stepOffset: %f\n", stepOffset);
+
+ // ==========[ Initial volume query ]===========================
+ // PT: the main difference between this initial query and subsequent ones is that we use the
+ // full direction vector here, not the components along each axis. So there is a good chance
+ // that this initial query will contain all the motion we need, and thus subsequent queries
+ // will be skipped.
+ {
+ PxExtendedBounds3 temporalBox;
+ volume.computeTemporalBox(*this, temporalBox, volume.mCenter, direction);
+
+ // Gather touched geoms
+ updateTouchedGeoms(userData, userObstacles, temporalBox, filters, SideVector);
+ }
+
+ // ==========[ UP PASS ]===========================
+
+ mCachedTriIndexIndex = 0;
+ const bool performUpPass = true;
+ PxU32 NbCollisions=0;
+
+ PxU32 maxIterUp;
+ if(mUserParams.mPreventVerticalSlidingAgainstCeiling)
+ maxIterUp = 1;
+ else
+ maxIterUp = Ps::isAlmostZero(SideVector) ? maxIter : 1;
+
+ if(performUpPass)
+ {
+// printf("%f | %f | %f\n", UpVector.x, UpVector.y, UpVector.z);
+
+ // Prevent user callback for up motion. This up displacement is artificial, and only needed for auto-stepping.
+ // If we call the user for this, we might eventually apply upward forces to objects resting on top of us, even
+ // if we visually don't move. This produces weird-looking motions.
+// mValidateCallback = false;
+ // PT: actually I think the previous comment is wrong. It's not only needed for auto-stepping: when the character
+ // jumps there's a legit up motion and the lack of callback in that case could need some object can't be pushed
+ // by the character's 'head' (for example). So I now think it's better to use the callback all the time, and
+ // let users figure out what to do using the available state (like "isMovingUp", etc).
+// mValidateCallback = true;
+
+ // In the walk-experiment we explicitly want to ban any up motions, to avoid characters climbing slopes they shouldn't climb.
+ // So let's bypass the whole up pass.
+ if(!(mFlags & STF_WALK_EXPERIMENT))
+ {
+ // ### maxIter here seems to "solve" the V bug
+ if(doSweepTest(userData, userHitData, userObstacles, volume, UpVector, SideVector, maxIterUp, &NbCollisions, min_dist, filters, SWEEP_PASS_UP, touchedActor, touchedShape))
+ {
+ if(NbCollisions)
+ {
+ CollisionFlags |= PxControllerCollisionFlag::eCOLLISION_UP;
+
+ // Clamp step offset to make sure we don't undo more than what we did
+ float Delta = float(volume.mCenter.dot(upDirection) - originalHeight);
+
+ if(Delta<stepOffset)
+ {
+ stepOffset=Delta;
+ }
+ }
+ }
+ }
+ }
+
+ // ==========[ SIDE PASS ]===========================
+
+ mCachedTriIndexIndex = 1;
+// mValidateCallback = true;
+ const bool PerformSidePass = true;
+
+ mFlags &= ~STF_VALIDATE_TRIANGLE_SIDE;
+ if(PerformSidePass)
+ {
+ NbCollisions=0;
+ //printf("BS:%.2f %.2f %.2f NS=%d\n", volume.mCenter.x, volume.mCenter.y, volume.mCenter.z, mNbCachedStatic);
+ if(doSweepTest(userData, userHitData, userObstacles, volume, SideVector, SideVector, maxIterSides, &NbCollisions, min_dist, filters, SWEEP_PASS_SIDE, touchedActor, touchedShape))
+ {
+ if(NbCollisions)
+ CollisionFlags |= PxControllerCollisionFlag::eCOLLISION_SIDES;
+ }
+ //printf("AS:%.2f %.2f %.2f NS=%d\n", volume.mCenter.x, volume.mCenter.y, volume.mCenter.z, mNbCachedStatic);
+
+ if(1 && constrainedClimbingMode && volume.getType()==SweptVolumeType::eCAPSULE && !(mFlags & STF_VALIDATE_TRIANGLE_SIDE))
+ {
+ const float capsuleRadius = static_cast<const SweptCapsule&>(volume).mRadius;
+
+ const float sideM = SideVector.magnitude();
+ if(sideM<capsuleRadius)
+ {
+ const PxVec3 sensor = SideVector.getNormalized() * capsuleRadius;
+
+ mFlags &= ~STF_VALIDATE_TRIANGLE_SIDE;
+ NbCollisions=0;
+ //printf("BS:%.2f %.2f %.2f NS=%d\n", volume.mCenter.x, volume.mCenter.y, volume.mCenter.z, mNbCachedStatic);
+ const PxExtendedVec3 saved = volume.mCenter;
+ doSweepTest(userData, userHitData, userObstacles, volume, sensor, SideVector, 1, &NbCollisions, min_dist, filters, SWEEP_PASS_SENSOR, touchedActor, touchedShape);
+ volume.mCenter = saved;
+ }
+ }
+ }
+
+ // ==========[ DOWN PASS ]===========================
+
+ mCachedTriIndexIndex = 2;
+ const bool PerformDownPass = true;
+
+ if(PerformDownPass)
+ {
+ NbCollisions=0;
+
+// if(!SideVector.isZero()) // We disabled that before so we don't have to undo it in that case
+ if(!sideVectorIsZero) // We disabled that before so we don't have to undo it in that case
+// DownVector[upDirection] -= stepOffset; // Undo our artificial up motion
+ DownVector -= upDirection*stepOffset; // Undo our artificial up motion
+
+ mFlags &= ~STF_VALIDATE_TRIANGLE_DOWN;
+ touchedShape = NULL;
+ touchedActor = NULL;
+ mTouchedObstacleHandle = INVALID_OBSTACLE_HANDLE;
+
+ // min_dist actually makes a big difference :(
+ // AAARRRGGH: if we get culled because of min_dist here, mValidateTriangle never becomes valid!
+ if(doSweepTest(userData, userHitData, userObstacles, volume, DownVector, SideVector, maxIterDown, &NbCollisions, min_dist, filters, SWEEP_PASS_DOWN, touchedActor, touchedShape))
+ {
+ if(NbCollisions)
+ {
+ if(dir_dot_up<=0.0f) // PT: fix attempt
+ CollisionFlags |= PxControllerCollisionFlag::eCOLLISION_DOWN;
+
+ if(mUserParams.mHandleSlope && !(mFlags & (STF_TOUCH_OTHER_CCT|STF_TOUCH_OBSTACLE))) // PT: I think the following fix shouldn't be performed when mHandleSlope is false.
+ {
+ // PT: the following code is responsible for a weird capsule behaviour,
+ // when colliding against a highly tesselated terrain:
+ // - with a large direction vector, the capsule gets stuck against some part of the terrain
+ // - with a slower direction vector (but in the same direction!) the capsule manages to move
+ // I will keep that code nonetheless, since it seems to be useful for them.
+//printf("%d\n", mFlags & STF_VALIDATE_TRIANGLE_SIDE);
+ // constrainedClimbingMode
+ if((mFlags & STF_VALIDATE_TRIANGLE_SIDE) && testSlope(mContactNormalSidePass, upDirection, mUserParams.mSlopeLimit))
+ {
+//printf("%d\n", mFlags & STF_VALIDATE_TRIANGLE_SIDE);
+ if(constrainedClimbingMode && PxExtended(mContactPointHeight) > originalBottomPoint + PxExtended(stepOffset))
+ {
+ mFlags |= STF_HIT_NON_WALKABLE;
+ if(!(mFlags & STF_WALK_EXPERIMENT))
+ return CollisionFlags;
+ // printf("Contrained\n");
+ }
+ }
+ //~constrainedClimbingMode
+ }
+ }
+ }
+ //printf("AD:%.2f %.2f %.2f NS=%d\n", volume.mCenter.x, volume.mCenter.y, volume.mCenter.z, mNbCachedStatic);
+// printf("%d\n", mTouchOtherCCT);
+
+ // TEST: do another down pass if we're on a non-walkable poly
+ // ### kind of works but still not perfect
+ // ### could it be because we zero the Y impulse later?
+ // ### also check clamped response vectors
+// if(mUserParams.mHandleSlope && mValidateTriangle && direction[upDirection]<0.0f)
+// if(mUserParams.mHandleSlope && !mTouchOtherCCT && !mTouchObstacle && mValidateTriangle && dir_dot_up<0.0f)
+ if(mUserParams.mHandleSlope && !(mFlags & (STF_TOUCH_OTHER_CCT|STF_TOUCH_OBSTACLE)) && (mFlags & STF_VALIDATE_TRIANGLE_DOWN) && dir_dot_up<=0.0f)
+ {
+ PxVec3 Normal;
+ #ifdef USE_CONTACT_NORMAL_FOR_SLOPE_TEST
+ Normal = mContactNormalDownPass;
+ #else
+ //mTouchedTriangle.normal(Normal);
+ Normal = mContactNormalDownPass;
+ #endif
+
+ const float touchedTriHeight = float(PxExtended(mTouchedTriMax) - originalBottomPoint);
+
+/* if(touchedTriHeight>mUserParams.mStepOffset)
+ {
+ if(constrainedClimbingMode && mContactPointHeight > originalBottomPoint + stepOffset)
+ {
+ mFlags |= STF_HIT_NON_WALKABLE;
+ if(!(mFlags & STF_WALK_EXPERIMENT))
+ return CollisionFlags;
+ }
+ }*/
+
+ if(touchedTriHeight>mUserParams.mStepOffset && testSlope(Normal, upDirection, mUserParams.mSlopeLimit))
+ {
+ mFlags |= STF_HIT_NON_WALKABLE;
+ // Early exit if we're going to run this again anyway...
+ if(!(mFlags & STF_WALK_EXPERIMENT))
+ return CollisionFlags;
+ /* CatchScene()->GetRenderer()->AddLine(mTouchedTriangle.mVerts[0], mTouched.mVerts[1], ARGB_YELLOW);
+ CatchScene()->GetRenderer()->AddLine(mTouchedTriangle.mVerts[0], mTouched.mVerts[2], ARGB_YELLOW);
+ CatchScene()->GetRenderer()->AddLine(mTouchedTriangle.mVerts[1], mTouched.mVerts[2], ARGB_YELLOW);
+ */
+
+ // ==========[ WALK EXPERIMENT ]===========================
+
+ mFlags |= STF_NORMALIZE_RESPONSE;
+
+ const PxExtended tmp = volume.mCenter.dot(upDirection);
+ float Delta = tmp > originalHeight ? float(tmp - originalHeight) : 0.0f;
+ Delta += fabsf(direction.dot(upDirection));
+ float Recover = Delta;
+
+ NbCollisions=0;
+ const float MD = Recover < min_dist ? Recover/float(maxIter) : min_dist;
+
+ PxVec3 RecoverPoint(0,0,0);
+ RecoverPoint = -upDirection*Recover;
+
+ // PT: we pass "SWEEP_PASS_UP" for compatibility with previous code, but it's technically wrong (this is a 'down' pass)
+ if(doSweepTest(userData, userHitData, userObstacles, volume, RecoverPoint, SideVector, maxIter, &NbCollisions, MD, filters, SWEEP_PASS_UP, touchedActor, touchedShape))
+ {
+ // if(NbCollisions) CollisionFlags |= COLLISION_Y_DOWN;
+ // PT: why did we do this ? Removed for now. It creates a bug (non registered event) when we land on a steep poly.
+ // However this might have been needed when we were sliding on those polygons, and we didn't want the land anim to
+ // start while we were sliding.
+ // if(NbCollisions) CollisionFlags &= ~PxControllerCollisionFlag::eCOLLISION_DOWN;
+ }
+ mFlags &= ~STF_NORMALIZE_RESPONSE;
+ }
+ }
+ }
+
+ return CollisionFlags;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// This is an interface between NX users and the internal character controller module.
+
+#include "CctInternalStructs.h"
+#include "CctBoxController.h"
+#include "CctCapsuleController.h"
+#include "CctCharacterControllerManager.h"
+#include "PxActor.h"
+#include "PxScene.h"
+#include "PxControllerBehavior.h"
+#include "CctObstacleContext.h"
+
+ // PT: we use a local class instead of making "Controller" a PxQueryFilterCallback, since it would waste more memory.
+ // Ideally we'd have a C-style callback and a user-data pointer, instead of being forced to create a class.
+ class ControllerFilter : public PxQueryFilterCallback
+ {
+ public:
+ PxQueryHitType::Enum preFilter(const PxFilterData& filterData, const PxShape* shape, const PxRigidActor* actor, PxHitFlags& queryFlags)
+ {
+ // PT: ignore triggers
+ if(shape->getFlags() & physx::PxShapeFlag::eTRIGGER_SHAPE)
+ return PxQueryHitType::eNONE;
+
+ // PT: we want to discard our own internal shapes only
+ if(mShapeHashSet->contains(const_cast<PxShape*>(shape)))
+ return PxQueryHitType::eNONE;
+
+ // PT: otherwise we revert to the user-callback, if it exists, and if users enabled that call
+ if(mUserFilterCallback && (mUserFilterFlags | PxQueryFlag::ePREFILTER))
+ return mUserFilterCallback->preFilter(filterData, shape, actor, queryFlags);
+
+ return PxQueryHitType::eBLOCK;
+ }
+
+ PxQueryHitType::Enum postFilter(const PxFilterData& filterData, const PxQueryHit& hit)
+ {
+ // PT: we may get called if users have asked for such a callback
+ if(mUserFilterCallback && (mUserFilterFlags | PxQueryFlag::ePOSTFILTER))
+ return mUserFilterCallback->postFilter(filterData, hit);
+
+ PX_ASSERT(0); // PT: otherwise we shouldn't have been called
+ return PxQueryHitType::eNONE;
+ }
+
+ Ps::HashSet<PxShape*>* mShapeHashSet;
+ PxQueryFilterCallback* mUserFilterCallback;
+ PxQueryFlags mUserFilterFlags;
+ };
+
+
+bool Controller::filterTouchedShape(const PxControllerFilters& filters)
+{
+ PxQueryFlags filterFlags = PxQueryFlag::eDYNAMIC|PxQueryFlag::ePREFILTER;
+ PxQueryFilterData filterData(filters.mFilterData ? *filters.mFilterData : PxFilterData(), filterFlags);
+ PxHitFlags hitFlags = PxHitFlag::eDISTANCE;
+
+ if(filters.mFilterCallback && (filters.mFilterFlags | PxQueryFlag::ePREFILTER))
+ {
+ PxQueryHitType::Enum retVal = filters.mFilterCallback->preFilter(filterData.data, mCctModule.mTouchedShape.get(), mCctModule.mTouchedActor.get(), hitFlags);
+ if(retVal != PxQueryHitType::eNONE)
+ return true;
+ else
+ return false;
+ }
+
+ return true;
+}
+
+void Controller::findTouchedObject(const PxControllerFilters& filters, const PxObstacleContext* obstacleContext, const PxVec3& upDirection)
+{
+ PX_ASSERT(!mCctModule.mTouchedShape && (mCctModule.mTouchedObstacleHandle == INVALID_OBSTACLE_HANDLE));
+
+ // PT: the CCT works perfectly on statics without this extra mechanism, so we only raycasts against dynamics.
+ // The pre-filter callback is used to filter out our own proxy actor shapes. We need to make sure our own filter
+ // doesn't disturb the user-provided filter(s).
+
+ // PT: for starter, if user doesn't want to collide against dynamics, we can skip the whole thing
+ if(filters.mFilterFlags & PxQueryFlag::eDYNAMIC)
+ {
+ ControllerFilter preFilter;
+ preFilter.mShapeHashSet = &mManager->mCCTShapes;
+ preFilter.mUserFilterCallback = filters.mFilterCallback;
+ preFilter.mUserFilterFlags = filters.mFilterFlags;
+
+ // PT: for our own purpose we just want dynamics & pre-filter
+ PxQueryFlags filterFlags = PxQueryFlag::eDYNAMIC|PxQueryFlag::ePREFILTER;
+ // PT: but we may need the post-filter callback as well if users want it
+ if(filters.mFilterFlags & PxQueryFlag::ePOSTFILTER)
+ filterFlags |= PxQueryFlag::ePOSTFILTER;
+
+ PxQueryFilterData filterData(filters.mFilterData ? *filters.mFilterData : PxFilterData(), filterFlags);
+
+ const PxF32 probeLength = getHalfHeightInternal(); // Distance to feet
+ const PxF32 extra = 0.0f;//probeLength * 0.1f;
+
+ const PxVec3 rayOrigin = toVec3(mPosition);
+
+ PxRaycastBuffer hit;
+ hit.block.distance = FLT_MAX;
+ if(mScene->raycast(rayOrigin, -upDirection, probeLength+extra, hit, PxHitFlag::eDISTANCE, filterData, &preFilter))
+ {
+ // copy touching hit to blocking so that the rest of the code works with .block
+ hit.block = hit.getAnyHit(0);
+ PX_ASSERT(hit.block.shape);
+ PX_ASSERT(hit.block.actor);
+ PX_ASSERT(hit.block.distance<=probeLength+extra);
+ mCctModule.mTouchedShape = hit.block.shape;
+ mCctModule.mTouchedActor = hit.block.actor;
+// mCctModule.mTouchedPos = getShapeGlobalPose(*hit.shape).p - upDirection*(probeLength-hit.distance);
+ // PT: we only care about the up delta here
+ const PxTransform shapeTransform = getShapeGlobalPose(*hit.block.shape, *hit.block.actor);
+ mCctModule.mTouchedPosShape_World = PxVec3(0) - upDirection*(probeLength-hit.block.distance);
+ mCctModule.mTouchedPosShape_Local = shapeTransform.transformInv(PxVec3(0));
+
+ mPreviousSceneTimestamp = mScene->getTimestamp()-1; // PT: just make sure cached timestamp is different
+ }
+
+ if(obstacleContext)
+ {
+ const ObstacleContext* obstacles = static_cast<const ObstacleContext*>(obstacleContext);
+ PxRaycastHit obstacleHit;
+ ObstacleHandle obstacleHandle;
+ const PxObstacle* touchedObstacle = obstacles->raycastSingle(obstacleHit, rayOrigin, -upDirection, probeLength+extra, obstacleHandle);
+// printf("Touched raycast obstacle: %d\n", int(touchedObstacle));
+ if(touchedObstacle && obstacleHit.distance<hit.block.distance)
+ {
+ PX_ASSERT(obstacleHit.distance<=probeLength+extra);
+ mCctModule.mTouchedObstacleHandle = obstacleHandle;
+ if(!gUseLocalSpace)
+ {
+ mCctModule.mTouchedPos = toVec3(touchedObstacle->mPos) - upDirection*(probeLength-obstacleHit.distance);
+ }
+ else
+ {
+ // PT: we only care about the up delta here
+ mCctModule.mTouchedPosObstacle_World = PxVec3(0) - upDirection*(probeLength-obstacleHit.distance);
+ mCctModule.mTouchedPosObstacle_Local = worldToLocal(*touchedObstacle, PxExtendedVec3(0,0,0));
+ }
+ }
+ }
+ }
+}
+
+bool Controller::rideOnTouchedObject(SweptVolume& volume, const PxVec3& upDirection, PxVec3& disp, const PxObstacleContext* obstacleContext)
+{
+ PX_ASSERT(mCctModule.mTouchedShape || (mCctModule.mTouchedObstacleHandle != INVALID_OBSTACLE_HANDLE));
+
+ bool standingOnMoving = false;
+
+ bool canDoUpdate = true; // Always true on obstacles
+ PxU32 behaviorFlags = 0; // Default on shapes
+ PxVec3 delta(0);
+ float timeCoeff = 1.0f;
+
+ if(mCctModule.mTouchedShape)
+ {
+ // PT: riding on a shape
+
+ // PT: it is important to skip this stuff for static meshes,
+ // otherwise accuracy issues create bugs like TA14007.
+ const PxRigidActor& rigidActor = *mCctModule.mTouchedActor.get();
+ if(rigidActor.getConcreteType()!=PxConcreteType::eRIGID_STATIC)
+ {
+ // PT: we only do the update when the timestamp has changed, otherwise "delta" will be zero
+ // even if the underlying shape is moving.
+ const PxU32 timestamp = mScene->getTimestamp();
+// printf("TimeStamp: %d\n", timestamp);
+ canDoUpdate = timestamp!=mPreviousSceneTimestamp;
+ if(canDoUpdate)
+ {
+ mPreviousSceneTimestamp = timestamp;
+
+ timeCoeff = computeTimeCoeff();
+
+ if(mBehaviorCallback)
+ behaviorFlags = mBehaviorCallback->getBehaviorFlags(*mCctModule.mTouchedShape.get(), *mCctModule.mTouchedActor.get());
+
+// delta = getShapeGlobalPose(*mCctModule.mTouchedShape).p - mCctModule.mTouchedPos;
+ const PxTransform shapeTransform = getShapeGlobalPose(*mCctModule.mTouchedShape.get(), rigidActor);
+ const PxVec3 posPreviousFrame = mCctModule.mTouchedPosShape_World;
+ const PxVec3 posCurrentFrame = shapeTransform.transform(mCctModule.mTouchedPosShape_Local);
+ delta = posCurrentFrame - posPreviousFrame;
+ }
+ }
+ }
+ else
+ {
+ // PT: riding on an obstacle
+ behaviorFlags = PxControllerBehaviorFlag::eCCT_CAN_RIDE_ON_OBJECT; // Default on obstacles
+
+ timeCoeff = computeTimeCoeff();
+
+ const PxObstacle* touchedObstacle = obstacleContext->getObstacleByHandle(mCctModule.mTouchedObstacleHandle);
+ PX_ASSERT(touchedObstacle);
+
+ if(mBehaviorCallback)
+ behaviorFlags = mBehaviorCallback->getBehaviorFlags(*touchedObstacle);
+
+ if(!gUseLocalSpace)
+ {
+ delta = toVec3(touchedObstacle->mPos) - mCctModule.mTouchedPos;
+ }
+ else
+ {
+ PxVec3 posPreviousFrame = mCctModule.mTouchedPosObstacle_World;
+ PxVec3 posCurrentFrame = localToWorld(*touchedObstacle, mCctModule.mTouchedPosObstacle_Local);
+ delta = posCurrentFrame - posPreviousFrame;
+ }
+ }
+
+ if(canDoUpdate && !(behaviorFlags & PxControllerBehaviorFlag::eCCT_USER_DEFINED_RIDE))
+ {
+ // PT: amazingly enough even isAlmostZero doesn't solve this one.
+ // Moving on a static mesh sometimes produces delta bigger than 1e-6f!
+ // This may also explain the drift on some rotating platforms. It looks
+ // like this delta computation is not very accurate.
+// standingOnMoving = !delta.isZero();
+ standingOnMoving = !Ps::isAlmostZero(delta);
+ mCachedStandingOnMoving = standingOnMoving;
+//printf("%f %f %f\n", delta.x, delta.y, delta.z);
+ if(standingOnMoving)
+ {
+ const float dir_dot_up = delta.dot(upDirection);
+ const bool deltaMovingUp = dir_dot_up>0.0f;
+
+ PxVec3 deltaUpDisp, deltaSideDisp;
+ Ps::decomposeVector(deltaUpDisp, deltaSideDisp, delta, upDirection);
+
+ if(deltaMovingUp)
+ {
+ volume.mCenter.x += PxExtended(deltaUpDisp.x);
+ volume.mCenter.y += PxExtended(deltaUpDisp.y);
+ volume.mCenter.z += PxExtended(deltaUpDisp.z);
+ }
+ else
+ {
+ disp += deltaUpDisp;
+ }
+
+ if(behaviorFlags & PxControllerBehaviorFlag::eCCT_CAN_RIDE_ON_OBJECT)
+ disp += deltaSideDisp;
+ }
+// printf("delta in: %f %f %f (%f)\n", delta.x, delta.y, delta.z, 1.0f/timeCoeff);
+ mDeltaXP = delta * timeCoeff;
+ }
+ else
+ {
+ standingOnMoving = mCachedStandingOnMoving;
+ }
+// mDelta = delta;
+
+ return standingOnMoving;
+}
+
+PxControllerCollisionFlags Controller::move(SweptVolume& volume, const PxVec3& originalDisp, PxF32 minDist, PxF32 elapsedTime, const PxControllerFilters& filters, const PxObstacleContext* obstacleContext, bool constrainedClimbingMode)
+{
+ const bool lockWrite = mManager->mLockingEnabled;
+ if(lockWrite)
+ mWriteLock.lock();
+
+ mGlobalTime += PxF64(elapsedTime);
+
+ // Init CCT with per-controller settings
+ Cm::RenderBuffer* renderBuffer = mManager->mRenderBuffer;
+ const PxU32 debugRenderFlags = mManager->mDebugRenderingFlags;
+ mCctModule.mRenderBuffer = renderBuffer;
+ mCctModule.mRenderFlags = debugRenderFlags;
+ mCctModule.mUserParams = mUserParams;
+ mCctModule.mFlags |= STF_FIRST_UPDATE;
+ mCctModule.mUserParams.mMaxEdgeLength2 = mManager->mMaxEdgeLength * mManager->mMaxEdgeLength;
+ mCctModule.mUserParams.mTessellation = mManager->mTessellation;
+ mCctModule.mUserParams.mOverlapRecovery = mManager->mOverlapRecovery;
+ mCctModule.mUserParams.mPreciseSweeps = mManager->mPreciseSweeps;
+ mCctModule.mUserParams.mPreventVerticalSlidingAgainstCeiling = mManager->mPreventVerticalSlidingAgainstCeiling;
+ mCctModule.resetStats();
+
+ const PxVec3& upDirection = mUserParams.mUpDirection;
+
+ ///////////
+
+ PxVec3 disp = originalDisp + mOverlapRecover;
+ mOverlapRecover = PxVec3(0.0f);
+
+ bool standingOnMoving = false; // PT: whether the CCT is currently standing on a moving object
+ //printf("Touched shape: %d\n", int(mCctModule.mTouchedShape));
+//standingOnMoving=true;
+// printf("Touched obstacle: %d\n", int(mCctModule.mTouchedObstacle));
+
+ if(mCctModule.mTouchedActor && mCctModule.mTouchedShape)
+ {
+ PxU32 nbShapes = mCctModule.mTouchedActor->getNbShapes();
+ bool found = false;
+ for(PxU32 i=0;i<nbShapes;i++)
+ {
+ PxShape* shape = NULL;
+ mCctModule.mTouchedActor->getShapes(&shape, 1, i);
+ if(mCctModule.mTouchedShape==shape)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if(!found)
+ {
+ mCctModule.mTouchedActor = NULL;
+ mCctModule.mTouchedShape = NULL;
+ }
+ else
+ {
+ // check if we are still in the same scene
+ if(mCctModule.mTouchedActor->getScene() != mScene)
+ {
+ mCctModule.mTouchedShape = NULL;
+ mCctModule.mTouchedActor = NULL;
+ }
+ else
+ {
+ // check if the shape still does have the sq flag
+ if(!(mCctModule.mTouchedShape->getFlags() & PxShapeFlag::eSCENE_QUERY_SHAPE))
+ {
+ mCctModule.mTouchedShape = NULL;
+ mCctModule.mTouchedActor = NULL;
+ }
+ else
+ {
+ // invoke the CCT filtering for the shape
+ if(!filterTouchedShape(filters))
+ {
+ mCctModule.mTouchedShape = NULL;
+ mCctModule.mTouchedActor = NULL;
+ }
+ }
+ }
+ }
+ }
+
+ if(!mCctModule.mTouchedShape && (mCctModule.mTouchedObstacleHandle == INVALID_OBSTACLE_HANDLE))
+ findTouchedObject(filters, obstacleContext, upDirection);
+
+ if(mCctModule.mTouchedShape || (mCctModule.mTouchedObstacleHandle != INVALID_OBSTACLE_HANDLE))
+ {
+ standingOnMoving = rideOnTouchedObject(volume, upDirection, disp, obstacleContext);
+ }
+ else
+ {
+ mCachedStandingOnMoving = false;
+ mDeltaXP = PxVec3(0.0f);
+ }
+// printf("standingOnMoving: %d\n", standingOnMoving);
+
+ ///////////
+ Ps::Array<const void*>& boxUserData = mManager->mBoxUserData;
+ Ps::Array<PxExtendedBox>& boxes = mManager->mBoxes;
+ Ps::Array<const void*>& capsuleUserData = mManager->mCapsuleUserData;
+ Ps::Array<PxExtendedCapsule>& capsules = mManager->mCapsules;
+ PX_ASSERT(!boxUserData.size());
+ PX_ASSERT(!boxes.size());
+ PX_ASSERT(!capsuleUserData.size());
+ PX_ASSERT(!capsules.size());
+
+ {
+ // Experiment - to do better
+ const PxU32 nbControllers = mManager->getNbControllers();
+ Controller** controllers = mManager->getControllers();
+
+ for(PxU32 i=0;i<nbControllers;i++)
+ {
+ Controller* currentController = controllers[i];
+ if(currentController==this)
+ continue;
+
+ bool keepController = true;
+ if(filters.mCCTFilterCallback)
+ keepController = filters.mCCTFilterCallback->filter(*getPxController(), *currentController->getPxController());
+
+ if(keepController)
+ {
+ if(currentController->mType==PxControllerShapeType::eBOX)
+ {
+ // PT: TODO: optimize this
+ BoxController* BC = static_cast<BoxController*>(currentController);
+ PxExtendedBox obb;
+ BC->getOBB(obb);
+
+ boxes.pushBack(obb);
+
+#ifdef REMOVED
+ if(renderBuffer /*&& (debugRenderFlags & PxControllerDebugRenderFlag::eOBSTACLES)*/)
+ {
+ Cm::RenderOutput out(*renderBuffer);
+ out << gCCTBoxDebugColor;
+
+ out << PxTransform(toVec3(obb.center), obb.rot);
+
+ out << Cm::DebugBox(obb.extents, true);
+ }
+#endif
+ const size_t code = encodeUserObject(i, USER_OBJECT_CCT);
+ boxUserData.pushBack(reinterpret_cast<const void*>(code));
+ }
+ else if(currentController->mType==PxControllerShapeType::eCAPSULE)
+ {
+ CapsuleController* CC = static_cast<CapsuleController*>(currentController);
+
+ // PT: TODO: optimize this
+ PxExtendedCapsule worldCapule;
+ CC->getCapsule(worldCapule);
+ capsules.pushBack(worldCapule);
+
+ const size_t code = encodeUserObject(i, USER_OBJECT_CCT);
+ capsuleUserData.pushBack(reinterpret_cast<const void*>(code));
+ }
+ else PX_ASSERT(0);
+ }
+ }
+ }
+
+ const ObstacleContext* obstacles = NULL;
+ if(obstacleContext)
+ {
+ obstacles = static_cast<const ObstacleContext*>(obstacleContext);
+
+ // PT: TODO: optimize this
+ const PxU32 nbExtraBoxes = obstacles->mBoxObstacles.size();
+ for(PxU32 i=0;i<nbExtraBoxes;i++)
+ {
+ const PxBoxObstacle& userBoxObstacle = obstacles->mBoxObstacles[i].mData;
+
+ PxExtendedBox extraBox;
+ extraBox.center = userBoxObstacle.mPos;
+ extraBox.extents = userBoxObstacle.mHalfExtents;
+ extraBox.rot = userBoxObstacle.mRot;
+ boxes.pushBack(extraBox);
+
+ const size_t code = encodeUserObject(i, USER_OBJECT_BOX_OBSTACLE);
+ boxUserData.pushBack(reinterpret_cast<const void*>(code));
+
+ if(renderBuffer && (debugRenderFlags & PxControllerDebugRenderFlag::eOBSTACLES))
+ {
+ Cm::RenderOutput out(*renderBuffer);
+ out << gObstacleDebugColor;
+
+ out << PxTransform(toVec3(userBoxObstacle.mPos), userBoxObstacle.mRot);
+
+ out << Cm::DebugBox(userBoxObstacle.mHalfExtents, true);
+ }
+ }
+
+ const PxU32 nbExtraCapsules = obstacles->mCapsuleObstacles.size();
+ for(PxU32 i=0;i<nbExtraCapsules;i++)
+ {
+ const PxCapsuleObstacle& userCapsuleObstacle = obstacles->mCapsuleObstacles[i].mData;
+
+ PxExtendedCapsule extraCapsule;
+ const PxVec3 capsuleAxis = userCapsuleObstacle.mRot.getBasisVector0() * userCapsuleObstacle.mHalfHeight;
+ extraCapsule.p0 = PxExtendedVec3( userCapsuleObstacle.mPos.x - PxExtended(capsuleAxis.x),
+ userCapsuleObstacle.mPos.y - PxExtended(capsuleAxis.y),
+ userCapsuleObstacle.mPos.z - PxExtended(capsuleAxis.z));
+ extraCapsule.p1 = PxExtendedVec3( userCapsuleObstacle.mPos.x + PxExtended(capsuleAxis.x),
+ userCapsuleObstacle.mPos.y + PxExtended(capsuleAxis.y),
+ userCapsuleObstacle.mPos.z + PxExtended(capsuleAxis.z));
+
+ extraCapsule.radius = userCapsuleObstacle.mRadius;
+ capsules.pushBack(extraCapsule);
+ const size_t code = encodeUserObject(i, USER_OBJECT_CAPSULE_OBSTACLE);
+ capsuleUserData.pushBack(reinterpret_cast<const void*>(code));
+
+ if(renderBuffer && (debugRenderFlags & PxControllerDebugRenderFlag::eOBSTACLES))
+ {
+ Cm::RenderOutput out(*renderBuffer);
+ out << gObstacleDebugColor;
+ out.outputCapsule(userCapsuleObstacle.mRadius, userCapsuleObstacle.mHalfHeight, PxTransform(toVec3(userCapsuleObstacle.mPos), userCapsuleObstacle.mRot));
+ }
+ }
+ }
+
+
+ UserObstacles userObstacles;
+
+ const PxU32 nbBoxes = boxes.size();
+ userObstacles.mNbBoxes = nbBoxes;
+ userObstacles.mBoxes = nbBoxes ? boxes.begin() : NULL;
+ userObstacles.mBoxUserData = nbBoxes ? boxUserData.begin() : NULL;
+
+ const PxU32 nbCapsules = capsules.size();
+ userObstacles.mNbCapsules = nbCapsules;
+ userObstacles.mCapsules = nbCapsules ? capsules.begin() : NULL;
+ userObstacles.mCapsuleUserData = nbCapsules ? capsuleUserData.begin() : NULL;
+
+ PxInternalCBData_OnHit userHitData;
+ userHitData.controller = this;
+ userHitData.obstacles = obstacles;
+
+ ///////////
+
+ PxControllerCollisionFlags collisionFlags = PxControllerCollisionFlags(0);
+
+ PxInternalCBData_FindTouchedGeom findGeomData;
+ findGeomData.scene = mScene;
+ findGeomData.renderBuffer = renderBuffer;
+ findGeomData.cctShapeHashSet = &mManager->mCCTShapes;
+
+ mCctModule.mFlags &= ~STF_WALK_EXPERIMENT;
+
+ // store new touched actor/shape. Then set new actor/shape to avoid register/unregister for same objects
+ const PxRigidActor* touchedActor = NULL;
+ const PxShape* touchedShape = NULL;
+ PxExtendedVec3 Backup = volume.mCenter;
+ collisionFlags = mCctModule.moveCharacter(&findGeomData, &userHitData, volume, disp, userObstacles, minDist, filters, constrainedClimbingMode, standingOnMoving, touchedActor, touchedShape);
+
+ if(mCctModule.mFlags & STF_HIT_NON_WALKABLE)
+ {
+ // A bit slow, but everything else I tried was less convincing...
+ mCctModule.mFlags |= STF_WALK_EXPERIMENT;
+ volume.mCenter = Backup;
+
+ PxVec3 xpDisp;
+ if(mUserParams.mNonWalkableMode==PxControllerNonWalkableMode::ePREVENT_CLIMBING_AND_FORCE_SLIDING)
+ {
+ PxVec3 tangent_compo;
+ Ps::decomposeVector(xpDisp, tangent_compo, disp, upDirection);
+ }
+ else xpDisp = disp;
+
+ collisionFlags = mCctModule.moveCharacter(&findGeomData, &userHitData, volume, xpDisp, userObstacles, minDist, filters, constrainedClimbingMode, standingOnMoving, touchedActor, touchedShape);
+
+ mCctModule.mFlags &= ~STF_WALK_EXPERIMENT;
+ }
+ mCctModule.mTouchedActor = touchedActor;
+ mCctModule.mTouchedShape = touchedShape;
+
+ mCollisionFlags = collisionFlags;
+
+ // Copy results back
+ mPosition = volume.mCenter;
+
+ // Update kinematic actor
+ if(mKineActor)
+ {
+ const PxVec3 delta = Backup - volume.mCenter;
+ const PxF32 deltaM2 = delta.magnitudeSquared();
+ if(deltaM2!=0.0f)
+ {
+ PxTransform targetPose = mKineActor->getGlobalPose();
+ targetPose.p = toVec3(mPosition);
+ targetPose.q = mUserParams.mQuatFromUp;
+ mKineActor->setKinematicTarget(targetPose);
+ }
+ }
+
+ mManager->resetObstaclesBuffers();
+
+ if (lockWrite)
+ mWriteLock.unlock();
+
+ return collisionFlags;
+}
+
+
+PxControllerCollisionFlags BoxController::move(const PxVec3& disp, PxF32 minDist, PxF32 elapsedTime, const PxControllerFilters& filters, const PxObstacleContext* obstacles)
+{
+ PX_SIMD_GUARD;
+
+ // Create internal swept box
+ SweptBox sweptBox;
+ sweptBox.mCenter = mPosition;
+ sweptBox.mExtents = PxVec3(mHalfHeight, mHalfSideExtent, mHalfForwardExtent);
+ sweptBox.mHalfHeight = mHalfHeight; // UBI
+ return Controller::move(sweptBox, disp, minDist, elapsedTime, filters, obstacles, false);
+}
+
+PxControllerCollisionFlags CapsuleController::move(const PxVec3& disp, PxF32 minDist, PxF32 elapsedTime, const PxControllerFilters& filters, const PxObstacleContext* obstacles)
+{
+ PX_SIMD_GUARD;
+
+ // Create internal swept capsule
+ SweptCapsule sweptCapsule;
+ sweptCapsule.mCenter = mPosition;
+ sweptCapsule.mRadius = mRadius;
+ sweptCapsule.mHeight = mHeight;
+ sweptCapsule.mHalfHeight = mHeight*0.5f + mRadius; // UBI
+ return Controller::move(sweptCapsule, disp, minDist, elapsedTime, filters, obstacles, mClimbingMode==PxCapsuleClimbingMode::eCONSTRAINED);
+}
+