2555 lines
92 KiB
C++
2555 lines
92 KiB
C++
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions
|
|
// are met:
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
// * Neither the name of NVIDIA CORPORATION nor the names of its
|
|
// contributors may be used to endorse or promote products derived
|
|
// from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Copyright (c) 2008-2019 NVIDIA Corporation. All rights reserved.
|
|
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
|
|
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
|
|
|
|
#include "common/PxProfileZone.h"
|
|
#include "geometry/PxMeshQuery.h"
|
|
#include "PxRigidDynamic.h"
|
|
|
|
#include "CctCharacterController.h"
|
|
#include "CctCharacterControllerManager.h"
|
|
#include "CctSweptBox.h"
|
|
#include "CctSweptCapsule.h"
|
|
#include "CctObstacleContext.h"
|
|
#include "CmRenderOutput.h"
|
|
#include "GuIntersectionBoxBox.h"
|
|
#include "GuDistanceSegmentBox.h"
|
|
#include "PsMathUtils.h"
|
|
#include "PsFPU.h"
|
|
|
|
// PT: TODO: remove those includes.... shouldn't be allowed from here
|
|
#include "characterkinematic/PxControllerObstacles.h" // (*)
|
|
#include "characterkinematic/PxControllerManager.h" // (*)
|
|
#include "characterkinematic/PxControllerBehavior.h" // (*)
|
|
#include "CctInternalStructs.h" // (*)
|
|
|
|
//#define DEBUG_MTD
|
|
#ifdef DEBUG_MTD
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
#define MAX_ITER 10
|
|
|
|
using namespace physx;
|
|
using namespace Cct;
|
|
using namespace Gu;
|
|
using namespace Cm;
|
|
|
|
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);
|
|
if(capsuleGeom.halfHeight==0.0f)
|
|
capsuleGeom.halfHeight = FLT_EPSILON;
|
|
}
|
|
|
|
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*>(reserveContainerMemory(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*>(reserveContainerMemory(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
|
|
RenderOutput out(*mRenderBuffer);
|
|
|
|
if(mRenderFlags & PxControllerDebugRenderFlag::eTEMPORAL_BV)
|
|
{
|
|
out << gTBVDebugColor;
|
|
out << DebugBox(getBounds3(worldTemporalBox));
|
|
}
|
|
|
|
if(mRenderFlags & PxControllerDebugRenderFlag::eCACHED_BV)
|
|
{
|
|
if(newCachedBox)
|
|
out << PxU32(PxDebugColor::eARGB_RED);
|
|
else
|
|
out << PxU32(PxDebugColor::eARGB_GREEN);
|
|
out << 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, PxU64 contextID)
|
|
{
|
|
// 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;
|
|
|
|
PX_PROFILE_ZONE("CharacterController.doSweepTest", contextID);
|
|
PX_UNUSED(contextID);
|
|
|
|
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,
|
|
PxU64 contextID)
|
|
{
|
|
PX_PROFILE_ZONE("CharacterController.moveCharacter", contextID);
|
|
PX_UNUSED(contextID);
|
|
|
|
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, contextID))
|
|
{
|
|
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, contextID))
|
|
{
|
|
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, contextID);
|
|
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, contextID))
|
|
{
|
|
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, contextID))
|
|
{
|
|
// 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 "characterkinematic/PxControllerBehavior.h"
|
|
#include "PxActor.h"
|
|
#include "PxScene.h"
|
|
|
|
#include "CctInternalStructs.h"
|
|
#include "CctBoxController.h"
|
|
#include "CctCapsuleController.h"
|
|
#include "CctCharacterControllerManager.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 = PxHitFlags(0);
|
|
|
|
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, PxHitFlags(0), 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
|
|
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());
|
|
|
|
{
|
|
PX_PROFILE_ZONE("CharacterController.filterCandidateControllers", getContextId());
|
|
|
|
// 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)*/)
|
|
{
|
|
RenderOutput out(*renderBuffer);
|
|
out << gCCTBoxDebugColor;
|
|
|
|
out << PxTransform(toVec3(obb.center), obb.rot);
|
|
|
|
out << 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))
|
|
{
|
|
RenderOutput out(*renderBuffer);
|
|
out << gObstacleDebugColor;
|
|
|
|
out << PxTransform(toVec3(userBoxObstacle.mPos), userBoxObstacle.mRot);
|
|
|
|
out << 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))
|
|
{
|
|
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, getContextId());
|
|
|
|
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, getContextId());
|
|
|
|
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_PROFILE_ZONE("CharacterController.move", getContextId());
|
|
|
|
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_PROFILE_ZONE("CharacterController.move", getContextId());
|
|
|
|
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);
|
|
}
|
|
|