Revision control

+ // 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-2026 NVIDIA Corporation. All rights reserved.
+ // Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
+ // Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
+
+ #ifndef GU_QUERY_SYSTEM_H
+ #define GU_QUERY_SYSTEM_H
+
+ #include "foundation/PxUserAllocated.h"
+ #include "foundation/PxBitMap.h"
+ #include "foundation/PxArray.h"
+ #include "foundation/PxMutex.h"
+ #include "foundation/PxBounds3.h"
+ #include "GuPruner.h"
+ #include "GuActorShapeMap.h"
+
+ namespace physx
+ {
+ class PxGeometry;
+
+ namespace Gu
+ {
+ class BVH;
+
+ class Adapter
+ {
+ public:
+ Adapter() {}
+ virtual ~Adapter() {}
+
+ virtual const PxGeometry& getGeometry(const PrunerPayload& payload) const = 0;
+ };
+
+ class PrunerFilter
+ {
+ public:
+ PrunerFilter() {}
+ virtual ~PrunerFilter() {}
+
+ virtual bool processPruner(PxU32 prunerIndex/*, const PxQueryThreadContext* context*/) const = 0;
+ };
+
+ typedef PxU32 PrunerInfo;
+ PX_FORCE_INLINE PrunerInfo createPrunerInfo(PxU32 prunerIndex, bool isDynamic) { return (prunerIndex << 1) | PxU32(isDynamic); }
+ PX_FORCE_INLINE PxU32 getPrunerIndex(PrunerInfo info) { return PxU32(info)>>1; }
+ PX_FORCE_INLINE PxU32 getDynamic(PrunerInfo info) { return PxU32(info) & 1; }
+
+ PX_FORCE_INLINE ActorShapeData createActorShapeData(PrunerInfo info, PrunerHandle h) { return (ActorShapeData(h) << 32) | ActorShapeData(info); }
+ PX_FORCE_INLINE PrunerInfo getPrunerInfo(ActorShapeData data) { return PrunerInfo(data); }
+ PX_FORCE_INLINE PrunerHandle getPrunerHandle(ActorShapeData data) { return PrunerHandle(data >> 32); }
+
+ #define INVALID_ACTOR_SHAPE_DATA PxU64(-1)
+
+ class QuerySystem : public PxUserAllocated
+ {
+ public: // PT: TODO: public only to implement checkPrunerIndex easily, revisit this
+ struct PrunerExt : public PxUserAllocated
+ {
+ PrunerExt(Pruner* pruner, PxU32 preallocated);
+ ~PrunerExt();
+
+ void flushMemory();
+
+ void addToDirtyList(PrunerHandle handle, PxU32 dynamic, const PxTransform& transform, const PxBounds3* userBounds=NULL);
+ void removeFromDirtyList(PrunerHandle handle);
+ bool processDirtyList(const Adapter& adapter, float inflation);
+
+ Pruner* mPruner;
+ PxBitMap mDirtyMap;
+ PxArray<PrunerHandle> mDirtyList;
+ PxU32 mNbStatic; // nb static objects in pruner
+ PxU32 mNbDynamic; // nb dynamic objects in pruner
+ bool mDirtyStatic; // true if dirty list contains a static
+
+ struct Data
+ {
+ PxTransform mPose;
+ PxBounds3 mBounds;
+ };
+ PxArray<Data> mDirtyData;
+
+ PX_NOCOPY(PrunerExt)
+ };
+
+ public:
+ PX_PHYSX_COMMON_API QuerySystem(PxU64 contextID, float inflation, const Adapter& adapter, bool usesTreeOfPruners=false);
+ PX_PHYSX_COMMON_API ~QuerySystem();
+
+ PX_FORCE_INLINE PxU64 getContextId() const { return mContextID; }
+ PX_FORCE_INLINE const Adapter& getAdapter() const { return mAdapter; }
+ PX_FORCE_INLINE PxU32 getStaticTimestamp() const { return mStaticTimestamp; }
+
+ PX_PHYSX_COMMON_API PxU32 addPruner(Pruner* pruner, PxU32 preallocated);
+ PX_PHYSX_COMMON_API void removePruner(PxU32 prunerIndex);
+ PX_FORCE_INLINE PxU32 getNbPruners() const { return mPrunerExt.size(); }
+ PX_FORCE_INLINE const Pruner* getPruner(PxU32 index) const { return mPrunerExt[index]->mPruner; }
+ PX_FORCE_INLINE Pruner* getPruner(PxU32 index) { return mPrunerExt[index]->mPruner; }
+
+ PX_PHYSX_COMMON_API ActorShapeData addPrunerShape(const PrunerPayload& payload, PxU32 prunerIndex, bool dynamic, const PxTransform& transform, const PxBounds3* userBounds=NULL);
+ PX_PHYSX_COMMON_API void removePrunerShape(ActorShapeData data, PrunerPayloadRemovalCallback* removalCallback);
+ PX_PHYSX_COMMON_API void updatePrunerShape(ActorShapeData data, bool immediately, const PxTransform& transform, const PxBounds3* userBounds=NULL);
+
+ PX_PHYSX_COMMON_API const PrunerPayload& getPayloadData(ActorShapeData data, PrunerPayloadData* ppd=NULL) const;
+
+ PX_PHYSX_COMMON_API void commitUpdates();
+ PX_PHYSX_COMMON_API void update(bool buildStep, bool commit);
+ PX_PHYSX_COMMON_API void sync(PxU32 prunerIndex, const PrunerHandle* handles, const PxU32* boundsIndices, const PxBounds3* bounds, const PxTransform32* transforms, PxU32 count);
+
+ PX_PHYSX_COMMON_API void flushMemory();
+
+ PX_PHYSX_COMMON_API void raycast(const PxVec3& origin, const PxVec3& unitDir, float& inOutDistance, PrunerRaycastCallback& cb, const PrunerFilter* prunerFilter) const;
+ PX_PHYSX_COMMON_API void overlap(const ShapeData& queryVolume, PrunerOverlapCallback& cb, const PrunerFilter* prunerFilter) const;
+ PX_PHYSX_COMMON_API void sweep(const ShapeData& queryVolume, const PxVec3& unitDir, float& inOutDistance, PrunerRaycastCallback& cb, const PrunerFilter* prunerFilter) const;
+
+ PxU32 startCustomBuildstep();
+ void customBuildstep(PxU32 index);
+ void finishCustomBuildstep();
+
+ void createTreeOfPruners();
+ private:
+ const Adapter& mAdapter;
+ PxArray<PrunerExt*> mPrunerExt;
+ PxArray<PxU32> mDirtyPruners;
+ PxArray<PxU32> mFreePruners;
+
+ Gu::BVH* mTreeOfPruners;
+
+ const PxU64 mContextID;
+ PxU32 mStaticTimestamp;
+ const float mInflation; // SQ_PRUNER_EPSILON
+
+ PxMutex mSQLock; // to make sure only one query updates the dirty pruner structure if multiple queries run in parallel
+
+ volatile bool mPrunerNeedsUpdating;
+ volatile bool mTimestampNeedsUpdating;
+ const bool mUsesTreeOfPruners;
+
+ void processDirtyLists();
+ PX_FORCE_INLINE void invalidateStaticTimestamp() { mStaticTimestamp++; }
+
+ PX_NOCOPY(QuerySystem)
+ };
+ }
+ }
+
+
+ #include "geometry/PxGeometryHit.h"
+ #include "geometry/PxSphereGeometry.h"
+ #include "geometry/PxCapsuleGeometry.h"
+ #include "geometry/PxBoxGeometry.h"
+ #include "geometry/PxConvexMeshGeometry.h"
+ #include "GuCachedFuncs.h"
+ #include "GuCapsule.h"
+ #include "GuBounds.h"
+
+ #if PX_VC
+ #pragma warning(disable: 4355 ) // "this" used in base member initializer list
+ #endif
+
+ namespace physx
+ {
+ namespace Gu
+ {
+ // PT: TODO: use templates instead of v-calls?
+
+ // PT: we decouple the filter callback from the rest, so that the same filter callback can easily be reused for all pruner queries.
+ // This combines the pre-filter callback and fetching the payload's geometry in a single call. Return null to ignore that object.
+ struct PrunerFilterCallback
+ {
+ virtual ~PrunerFilterCallback() {}
+
+ // Query's hit flags can be tweaked per object. (Note that 'hitFlags' is unused for overlaps though)
+ virtual const PxGeometry* validatePayload(const PrunerPayload& payload, PxHitFlags& hitFlags) = 0;
+ };
+
+ struct DefaultPrunerRaycastCallback : public PrunerRaycastCallback, public PxRaycastThreadContext
+ {
+ PxRaycastThreadContext* mContext;
+ PrunerFilterCallback& mFilterCB;
+ const GeomRaycastTable& mCachedRaycastFuncs;
+ const PxVec3& mOrigin;
+ const PxVec3& mDir;
+ PxGeomRaycastHit* mLocalHits;
+ const PxU32 mMaxLocalHits;
+ const PxHitFlags mHitFlags;
+ PxGeomRaycastHit mClosestHit;
+ PrunerPayload mClosestPayload;
+ bool mFoundHit;
+ const bool mAnyHit;
+
+ DefaultPrunerRaycastCallback(PrunerFilterCallback& filterCB, const GeomRaycastTable& funcs, const PxVec3& origin, const PxVec3& dir, float distance, PxU32 maxLocalHits, PxGeomRaycastHit* localHits, PxHitFlags hitFlags, bool anyHit, PxRaycastThreadContext* context=NULL) :
+ mContext (context ? context : this),
+ mFilterCB (filterCB),
+ mCachedRaycastFuncs (funcs),
+ mOrigin (origin),
+ mDir (dir),
+ mLocalHits (localHits),
+ mMaxLocalHits (maxLocalHits),
+ mHitFlags (hitFlags),
+ mFoundHit (false),
+ mAnyHit (anyHit)
+ {
+ mClosestHit.distance = distance;
+ }
+
+ virtual bool reportHits(const PrunerPayload& /*payload*/, PxU32 /*nbHits*/, PxGeomRaycastHit* /*hits*/)
+ {
+ return true;
+ }
+
+ virtual bool invoke(PxReal& aDist, PxU32 primIndex, const PrunerPayload* payloads, const PxTransform* transforms) PX_OVERRIDE PX_FINAL
+ {
+ PX_ASSERT(payloads && transforms);
+
+ const PrunerPayload& payload = payloads[primIndex];
+
+ PxHitFlags filteredHitFlags = mHitFlags;
+ const PxGeometry* shapeGeom = mFilterCB.validatePayload(payload, filteredHitFlags);
+ if(!shapeGeom)
+ return true;
+
+ const RaycastFunc func = mCachedRaycastFuncs[shapeGeom->getType()];
+ const PxU32 nbHits = func(*shapeGeom, transforms[primIndex], mOrigin, mDir, aDist, filteredHitFlags, mMaxLocalHits, mLocalHits, sizeof(PxGeomRaycastHit), mContext);
+ if(!nbHits || !reportHits(payload, nbHits, mLocalHits))
+ return true;
+
+ const PxGeomRaycastHit& localHit = mLocalHits[0];
+ if(localHit.distance < mClosestHit.distance)
+ {
+ mFoundHit = true;
+ if(mAnyHit)
+ return false;
+
+ aDist = localHit.distance;
+ mClosestHit = localHit;
+ mClosestPayload = payload;
+ }
+ return true;
+ }
+
+ PX_NOCOPY(DefaultPrunerRaycastCallback)
+ };
+
+ struct DefaultPrunerRaycastAnyCallback : public DefaultPrunerRaycastCallback
+ {
+ PxGeomRaycastHit mLocalHit;
+
+ DefaultPrunerRaycastAnyCallback(PrunerFilterCallback& filterCB, const GeomRaycastTable& funcs, const PxVec3& origin, const PxVec3& dir, float distance) :
+ DefaultPrunerRaycastCallback (filterCB, funcs, origin, dir, distance, 1, &mLocalHit, PxHitFlag::eANY_HIT, true) {}
+ };
+
+ struct DefaultPrunerRaycastClosestCallback : public DefaultPrunerRaycastCallback
+ {
+ PxGeomRaycastHit mLocalHit;
+
+ DefaultPrunerRaycastClosestCallback(PrunerFilterCallback& filterCB, const GeomRaycastTable& funcs, const PxVec3& origin, const PxVec3& dir, float distance, PxHitFlags hitFlags) :
+ DefaultPrunerRaycastCallback (filterCB, funcs, origin, dir, distance, 1, &mLocalHit, hitFlags, false) {}
+ };
+
+ struct DefaultPrunerOverlapCallback : public PrunerOverlapCallback, public PxOverlapThreadContext
+ {
+ PxOverlapThreadContext* mContext;
+ PrunerFilterCallback& mFilterCB;
+ const GeomOverlapTable* mCachedFuncs;
+ const PxGeometry& mGeometry;
+ const PxTransform& mPose;
+ PxHitFlags mUnused;
+
+ DefaultPrunerOverlapCallback(PrunerFilterCallback& filterCB, const GeomOverlapTable* funcs, const PxGeometry& geometry, const PxTransform& pose, PxOverlapThreadContext* context=NULL) :
+ mContext (context ? context : this),
+ mFilterCB (filterCB),
+ mCachedFuncs (funcs),
+ mGeometry (geometry),
+ mPose (pose)
+ {
+ }
+
+ virtual bool reportHit(const PrunerPayload& /*payload*/)
+ {
+ return true;
+ }
+
+ virtual bool invoke(PxU32 primIndex, const PrunerPayload* payloads, const PxTransform* transforms) PX_OVERRIDE PX_FINAL
+ {
+ PX_ASSERT(payloads && transforms);
+
+ const PrunerPayload& payload = payloads[primIndex];
+
+ const PxGeometry* shapeGeom = mFilterCB.validatePayload(payload, mUnused);
+ if(!shapeGeom || !Gu::overlap(mGeometry, mPose, *shapeGeom, transforms[primIndex], mCachedFuncs, mContext))
+ return true;
+
+ return reportHit(payload);
+ }
+
+ PX_NOCOPY(DefaultPrunerOverlapCallback)
+ };
+
+ struct BoxShapeCast
+ {
+ static PX_FORCE_INLINE PxU32 sweep( const GeomSweepFuncs& sf, const PxGeometry& geom, const PxTransform& pose,
+ const PxGeometry& queryGeom, const PxTransform& queryPose, const ShapeData& queryVolume,
+ const PxVec3& unitDir, PxReal distance, PxGeomSweepHit& sweepHit, PxHitFlags hitFlags, PxReal inflation, PxSweepThreadContext* context)
+ {
+ PX_ASSERT(queryGeom.getType()==PxGeometryType::eBOX);
+ const bool precise = hitFlags & PxHitFlag::ePRECISE_SWEEP;
+ const SweepBoxFunc func = precise ? sf.preciseBoxMap[geom.getType()] : sf.boxMap[geom.getType()];
+ return PxU32(func(geom, pose, static_cast<const PxBoxGeometry&>(queryGeom), queryPose, queryVolume.getGuBox(), unitDir, distance, sweepHit, hitFlags, inflation, context));
+ }
+ };
+
+ struct SphereShapeCast
+ {
+ static PX_FORCE_INLINE PxU32 sweep( const GeomSweepFuncs& sf, const PxGeometry& geom, const PxTransform& pose,
+ const PxGeometry& queryGeom, const PxTransform& queryPose, const ShapeData& /*queryVolume*/,
+ const PxVec3& unitDir, PxReal distance, PxGeomSweepHit& sweepHit, PxHitFlags hitFlags, PxReal inflation, PxSweepThreadContext* context)
+ {
+ PX_ASSERT(queryGeom.getType()==PxGeometryType::eSPHERE);
+ // PT: we don't use sd.getGuSphere() here because PhysX doesn't expose a set of 'SweepSphereFunc' functions,
+ // we have to go through a capsule (which is then seen as a sphere internally when the half-length is zero).
+ const PxSphereGeometry& sphereGeom = static_cast<const PxSphereGeometry&>(queryGeom);
+ const PxCapsuleGeometry capsuleGeom(sphereGeom.radius, 0.0f);
+ const Capsule worldCapsule(queryPose.p, queryPose.p, sphereGeom.radius); // AP: precompute?
+ const bool precise = hitFlags & PxHitFlag::ePRECISE_SWEEP;
+ const SweepCapsuleFunc func = precise ? sf.preciseCapsuleMap[geom.getType()] : sf.capsuleMap[geom.getType()];
+ return PxU32(func(geom, pose, capsuleGeom, queryPose, worldCapsule, unitDir, distance, sweepHit, hitFlags, inflation, context));
+ }
+ };
+
+ struct CapsuleShapeCast
+ {
+ static PX_FORCE_INLINE PxU32 sweep( const GeomSweepFuncs& sf, const PxGeometry& geom, const PxTransform& pose,
+ const PxGeometry& queryGeom, const PxTransform& queryPose, const ShapeData& queryVolume,
+ const PxVec3& unitDir, PxReal distance, PxGeomSweepHit& sweepHit, PxHitFlags hitFlags, PxReal inflation, PxSweepThreadContext* context)
+ {
+ PX_ASSERT(queryGeom.getType()==PxGeometryType::eCAPSULE);
+ const bool precise = hitFlags & PxHitFlag::ePRECISE_SWEEP;
+ const SweepCapsuleFunc func = precise ? sf.preciseCapsuleMap[geom.getType()] : sf.capsuleMap[geom.getType()];
+ return PxU32(func(geom, pose, static_cast<const PxCapsuleGeometry&>(queryGeom), queryPose, queryVolume.getGuCapsule(), unitDir, distance, sweepHit, hitFlags, inflation, context));
+ }
+ };
+
+ struct ConvexShapeCast
+ {
+ static PX_FORCE_INLINE PxU32 sweep( const GeomSweepFuncs& sf, const PxGeometry& geom, const PxTransform& pose,
+ const PxGeometry& queryGeom, const PxTransform& queryPose, const ShapeData& /*queryVolume*/,
+ const PxVec3& unitDir, PxReal distance, PxGeomSweepHit& sweepHit, PxHitFlags hitFlags, PxReal inflation, PxSweepThreadContext* context)
+ {
+ PX_ASSERT(queryGeom.getType()==PxGeometryType::eCONVEXMESH);
+ const SweepConvexFunc func = sf.convexMap[geom.getType()];
+ return PxU32(func(geom, pose, static_cast<const PxConvexMeshGeometry&>(queryGeom), queryPose, unitDir, distance, sweepHit, hitFlags, inflation, context));
+ }
+ };
+
+ struct DefaultPrunerSweepCallback : public PrunerRaycastCallback, public PxSweepThreadContext
+ {
+ virtual bool reportHit(const PrunerPayload& /*payload*/, PxGeomSweepHit& /*hit*/)
+ {
+ return true;
+ }
+ };
+
+ template<class ShapeCast>
+ struct DefaultPrunerSweepCallbackT : public DefaultPrunerSweepCallback
+ {
+ PxSweepThreadContext* mContext;
+ PrunerFilterCallback& mFilterCB;
+ const GeomSweepFuncs& mCachedFuncs;
+ const PxGeometry& mGeometry;
+ const PxTransform& mPose;
+ const ShapeData& mQueryVolume;
+ const PxVec3& mDir;
+ PxGeomSweepHit mLocalHit;
+ const PxHitFlags mHitFlags;
+ PxGeomSweepHit mClosestHit;
+ PrunerPayload mClosestPayload;
+ bool mFoundHit;
+ const bool mAnyHit;
+
+ DefaultPrunerSweepCallbackT(PrunerFilterCallback& filterCB, const GeomSweepFuncs& funcs,
+ const PxGeometry& geometry, const PxTransform& pose, const ShapeData& queryVolume,
+ const PxVec3& dir, float distance, PxHitFlags hitFlags, bool anyHit, PxSweepThreadContext* context=NULL) :
+ mContext (context ? context : this),
+ mFilterCB (filterCB),
+ mCachedFuncs (funcs),
+ mGeometry (geometry),
+ mPose (pose),
+ mQueryVolume (queryVolume),
+ mDir (dir),
+ mHitFlags (hitFlags),
+ mFoundHit (false),
+ mAnyHit (anyHit)
+ {
+ mClosestHit.distance = distance;
+ }
+
+ virtual bool invoke(PxReal& aDist, PxU32 primIndex, const PrunerPayload* payloads, const PxTransform* transforms) PX_OVERRIDE PX_FINAL
+ {
+ PX_ASSERT(payloads && transforms);
+
+ const PrunerPayload& payload = payloads[primIndex];
+
+ PxHitFlags filteredHitFlags = mHitFlags;
+ const PxGeometry* shapeGeom = mFilterCB.validatePayload(payload, filteredHitFlags);
+ if(!shapeGeom)
+ return true;
+
+ // PT: ### TODO: missing bit from PhysX version here
+
+ const float inflation = 0.0f; // ####
+ const PxU32 retVal = ShapeCast::sweep(mCachedFuncs, *shapeGeom, transforms[primIndex], mGeometry, mPose, mQueryVolume, mDir, aDist, mLocalHit, filteredHitFlags, inflation, mContext);
+
+ if(!retVal || !reportHit(payload, mLocalHit))
+ return true;
+
+ if(mLocalHit.distance < mClosestHit.distance)
+ {
+ mFoundHit = true;
+ if(mAnyHit)
+ return false;
+
+ aDist = mLocalHit.distance;
+ mClosestHit = mLocalHit;
+ mClosestPayload = payload;
+ }
+ return true;
+ }
+
+ PX_NOCOPY(DefaultPrunerSweepCallbackT)
+ };
+
+ typedef DefaultPrunerSweepCallbackT<SphereShapeCast> DefaultPrunerSphereSweepCallback;
+ typedef DefaultPrunerSweepCallbackT<BoxShapeCast> DefaultPrunerBoxSweepCallback;
+ typedef DefaultPrunerSweepCallbackT<CapsuleShapeCast> DefaultPrunerCapsuleSweepCallback;
+ typedef DefaultPrunerSweepCallbackT<ConvexShapeCast> DefaultPrunerConvexSweepCallback;
+
+ }
+ }
+
+ #endif