// 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-2025 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 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 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 mPrunerExt; PxArray mDirtyPruners; PxArray 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(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(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(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(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 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 DefaultPrunerSphereSweepCallback; typedef DefaultPrunerSweepCallbackT DefaultPrunerBoxSweepCallback; typedef DefaultPrunerSweepCallbackT DefaultPrunerCapsuleSweepCallback; typedef DefaultPrunerSweepCallbackT DefaultPrunerConvexSweepCallback; } } #endif