// // 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/PxGeometryQuery.h" #include "NpRigidDynamic.h" #include "NpQueryShared.h" #include "SqPruner.h" #include "GuIntersectionRayBox.h" #include "GuBounds.h" #include "GuIntersectionRay.h" // Synchronous scene queries using namespace physx; using namespace Sq; using namespace Gu; #if PX_SUPPORT_PVD #include "NpPvdSceneQueryCollector.h" #endif namespace local { // helper class to encapsulate Scb::Actor and Shape together with PxActorShape struct ActorShape : PxActorShape { const Scb::Shape* scbShape; const Scb::Actor* scbActor; ActorShape() : PxActorShape() {} ActorShape(PxRigidActor* eaActor, PxShape* eaShape, Scb::Shape* sShape, Scb::Actor* sActor) : PxActorShape(eaActor, eaShape) { scbShape = sShape; scbActor = sActor; } }; // fill the helper actor shape static PX_FORCE_INLINE void populate(const PrunerPayload& payload, ActorShape& as) { Scb::Shape* localShape = reinterpret_cast(payload.data[0]); Scb::Actor* localActor = reinterpret_cast(payload.data[1]); as.scbShape = localShape; as.scbActor = localActor; as.actor = static_cast(static_cast(localActor->getActorCore()).getPxActor()); as.shape = localShape->getScShape().getPxShape(); } } /////////////////////////////////////////////////////////////////////////////// bool NpSceneQueries::raycast( const PxVec3& origin, const PxVec3& unitDir, const PxReal distance, PxHitCallback& hits, PxHitFlags hitFlags, const PxQueryFilterData& filterData, PxQueryFilterCallback* filterCall, const PxQueryCache* cache) const { PX_PROFILE_ZONE("SceneQuery.raycast", getContextId()); NP_READ_CHECK(this); PX_SIMD_GUARD; MultiQueryInput input(origin, unitDir, distance); return multiQuery(input, hits, hitFlags, cache, filterData, filterCall, NULL); } ////////////////////////////////////////////////////////////////////////// bool NpSceneQueries::overlap( const PxGeometry& geometry, const PxTransform& pose, PxOverlapCallback& hits, const PxQueryFilterData& filterData, PxQueryFilterCallback* filterCall) const { PX_PROFILE_ZONE("SceneQuery.overlap", getContextId()); NP_READ_CHECK(this); PX_SIMD_GUARD; MultiQueryInput input(&geometry, &pose); // we are not supporting cache for overlaps for some reason return multiQuery(input, hits, PxHitFlags(), NULL, filterData, filterCall, NULL); } /////////////////////////////////////////////////////////////////////////////// bool NpSceneQueries::sweep( const PxGeometry& geometry, const PxTransform& pose, const PxVec3& unitDir, const PxReal distance, PxHitCallback& hits, PxHitFlags hitFlags, const PxQueryFilterData& filterData, PxQueryFilterCallback* filterCall, const PxQueryCache* cache, const PxReal inflation) const { PX_PROFILE_ZONE("SceneQuery.sweep", getContextId()); NP_READ_CHECK(this); PX_SIMD_GUARD; #if PX_CHECKED if(!PxGeometryQuery::isValid(geometry)) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "Provided geometry is not valid"); return false; } #endif // PX_CHECKED if((hitFlags & PxHitFlag::ePRECISE_SWEEP) && (hitFlags & PxHitFlag::eMTD)) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, " Precise sweep doesn't support MTD. Perform MTD with default sweep"); hitFlags &= ~PxHitFlag::ePRECISE_SWEEP; } if((hitFlags & PxHitFlag::eASSUME_NO_INITIAL_OVERLAP) && (hitFlags & PxHitFlag::eMTD)) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, " eMTD cannot be used in conjunction with eASSUME_NO_INITIAL_OVERLAP. eASSUME_NO_INITIAL_OVERLAP will be ignored"); hitFlags &= ~PxHitFlag::eASSUME_NO_INITIAL_OVERLAP; } PxReal realInflation = inflation; if((hitFlags & PxHitFlag::ePRECISE_SWEEP)&& inflation > 0.f) { realInflation = 0.f; Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, " Precise sweep doesn't support inflation, inflation will be overwritten to be zero"); } MultiQueryInput input(&geometry, &pose, unitDir, distance, realInflation); return multiQuery(input, hits, hitFlags, cache, filterData, filterCall, NULL); } /////////////////////////////////////////////////////////////////////////////// //======================================================================================================================== static PX_FORCE_INLINE bool applyAllPreFiltersSQ( const local::ActorShape* as, PxQueryHitType::Enum& hitType, const PxQueryFlags& inFilterFlags, const PxQueryFilterData& filterData, PxQueryFilterCallback* filterCall, BatchQueryFilterData* bfd, PxHitFlags& queryFlags/*, PxU32 maxNbTouches*/) { // AP: the !bfd clause is here because there's no other way to pass data to BQ pre/post filter shaders // For normal query the data can be passed with inherited callback instance // So if for BQ SPU filter shader the user tries to pass data via FD, the equation will always cut it out // AP scaffold TODO: once SPU is officially phased out we can remove the !bfd clause, fix broken UTs (that are wrong) // and also remove support for filter shaders if(!bfd && !applyFilterEquation(*as->scbShape, filterData.data)) return false; if((inFilterFlags & PxQueryFlag::ePREFILTER) && (filterCall || bfd)) { PxHitFlags outQueryFlags = queryFlags; if(filterCall) hitType = filterCall->preFilter(filterData.data, as->shape, as->actor, outQueryFlags); else if(bfd->preFilterShader) hitType = bfd->preFilterShader( filterData.data, as->scbShape->getScShape().getQueryFilterData(), bfd->filterShaderData, bfd->filterShaderDataSize, outQueryFlags); // AP: at this point the callback might return eTOUCH but the touch buffer can be empty, the hit will be discarded //PX_CHECK_MSG(hitType == PxQueryHitType::eTOUCH ? maxNbTouches > 0 : true, // "SceneQuery: preFilter returned eTOUCH but empty touch buffer was provided, hit discarded."); queryFlags = (queryFlags & ~PxHitFlag::eMODIFIABLE_FLAGS) | (outQueryFlags & PxHitFlag::eMODIFIABLE_FLAGS); } // test passed, continue to return as; return true; } //======================================================================================================================== // performs a single geometry query for any HitType (PxSweepHit, PxOverlapHit, PxRaycastHit) template struct GeomQueryAny { static PX_FORCE_INLINE PxU32 geomHit( const NpSceneQueries& sceneQueries, const MultiQueryInput& input, const ShapeData& sd, const PxGeometry& sceneGeom, const PxTransform& pose, PxHitFlags hitFlags, PxU32 maxHits, HitType* hits, const PxReal shrunkMaxDistance, PxBounds3* precomputedBounds) { const PxGeometry& geom0 = *input.geometry; const PxTransform& pose0 = *input.pose; const PxGeometry& geom1 = sceneGeom; const PxTransform& pose1 = pose; // Handle raycasts if(HitTypeSupport::IsRaycast) { // the test for mesh AABB is archived in //sw/physx/dev/apokrovsky/graveyard/sqMeshAABBTest.cpp // TODO: investigate performance impact (see US12801) PX_CHECK_AND_RETURN_VAL(input.getDir().isFinite(), "PxScene::raycast(): rayDir is not valid.", 0); PX_CHECK_AND_RETURN_VAL(input.getOrigin().isFinite(), "PxScene::raycast(): rayOrigin is not valid.", 0); PX_CHECK_AND_RETURN_VAL(pose1.isValid(), "PxScene::raycast(): pose is not valid.", 0); PX_CHECK_AND_RETURN_VAL(shrunkMaxDistance >= 0.0f, "PxScene::raycast(): maxDist is negative.", 0); PX_CHECK_AND_RETURN_VAL(PxIsFinite(shrunkMaxDistance), "PxScene::raycast(): maxDist is not valid.", 0); PX_CHECK_AND_RETURN_VAL(PxAbs(input.getDir().magnitudeSquared()-1)<1e-4f, "PxScene::raycast(): ray direction must be unit vector.", 0); // PT: TODO: investigate perf difference const RaycastFunc func = sceneQueries.mCachedRaycastFuncs[geom1.getType()]; return func(geom1, pose1, input.getOrigin(), input.getDir(), shrunkMaxDistance, hitFlags, maxHits, reinterpret_cast(hits)); } // Handle sweeps else if(HitTypeSupport::IsSweep) { PX_ASSERT(precomputedBounds != NULL); // b0 = query shape bounds // b1 = scene shape bounds // AP: Here we clip the sweep to bounds with sum of extents. This is needed for GJK stability. // because sweep is equivalent to a raycast vs a scene shape with inflated bounds. // This also may (or may not) provide an optimization for meshes because top level of rtree has multiple boxes // and there is no bounds test for the whole mesh elsewhere PxBounds3 b0 = *precomputedBounds, b1; // compute the scene geometry bounds // PT: TODO: avoid recomputing the bounds here Gu::computeBounds(b1, sceneGeom, pose, 0.0f, NULL, 1.0f); const PxVec3 combExt = (b0.getExtents() + b1.getExtents())*1.01f; PxF32 tnear, tfar; if(!intersectRayAABB2(-combExt, combExt, b0.getCenter() - b1.getCenter(), input.getDir(), shrunkMaxDistance, tnear, tfar)) // returns (tneartfar) // this second test is needed because shrunkMaxDistance can be 0 for 0 length sweep return 0; PX_ASSERT(input.getDir().isNormalized()); // tfar is now the t where the ray exits the AABB. input.getDir() is normalized const PxVec3& unitDir = input.getDir(); PxSweepHit& sweepHit = reinterpret_cast(hits[0]); // if we don't start inside the AABB box, offset the start pos, because of precision issues with large maxDist const bool offsetPos = (tnear > GU_RAY_SURFACE_OFFSET); const PxReal offset = offsetPos ? (tnear - GU_RAY_SURFACE_OFFSET) : 0.0f; const PxVec3 offsetVec(offsetPos ? (unitDir*offset) : PxVec3(0.0f)); // we move the geometry we sweep against, so that we avoid the Gu::Capsule/Box recomputation const PxTransform pose1Offset(pose1.p - offsetVec, pose1.q); const PxReal distance = PxMin(tfar, shrunkMaxDistance) - offset; const PxReal inflation = input.inflation; PX_CHECK_AND_RETURN_VAL(pose0.isValid(), "PxScene::sweep(): pose0 is not valid.", 0); PX_CHECK_AND_RETURN_VAL(pose1Offset.isValid(), "PxScene::sweep(): pose1 is not valid.", 0); PX_CHECK_AND_RETURN_VAL(unitDir.isFinite(), "PxScene::sweep(): unitDir is not valid.", 0); PX_CHECK_AND_RETURN_VAL(PxIsFinite(distance), "PxScene::sweep(): distance is not valid.", 0); PX_CHECK_AND_RETURN_VAL((distance >= 0.0f && !(hitFlags & PxHitFlag::eASSUME_NO_INITIAL_OVERLAP)) || distance > 0.0f, "PxScene::sweep(): sweep distance must be >=0 or >0 with eASSUME_NO_INITIAL_OVERLAP.", 0); PxU32 retVal = 0; const GeomSweepFuncs& sf = sceneQueries.mCachedSweepFuncs; switch(geom0.getType()) { case PxGeometryType::eSPHERE: { const PxSphereGeometry& sphereGeom = static_cast(geom0); // PT: TODO: technically this capsule with 0.0 half-height is invalid ("isValid" returns false) const PxCapsuleGeometry capsuleGeom(sphereGeom.radius, 0.0f); const Capsule worldCapsule(pose0.p, pose0.p, sphereGeom.radius); // AP: precompute? const bool precise = hitFlags & PxHitFlag::ePRECISE_SWEEP; const SweepCapsuleFunc func = precise ? sf.preciseCapsuleMap[geom1.getType()] : sf.capsuleMap[geom1.getType()]; retVal = PxU32(func(geom1, pose1Offset, capsuleGeom, pose0, worldCapsule, unitDir, distance, sweepHit, hitFlags, inflation)); } break; case PxGeometryType::eCAPSULE: { const bool precise = hitFlags & PxHitFlag::ePRECISE_SWEEP; const SweepCapsuleFunc func = precise ? sf.preciseCapsuleMap[geom1.getType()] : sf.capsuleMap[geom1.getType()]; retVal = PxU32(func(geom1, pose1Offset, static_cast(geom0), pose0, sd.getGuCapsule(), unitDir, distance, sweepHit, hitFlags, inflation)); } break; case PxGeometryType::eBOX: { const bool precise = hitFlags & PxHitFlag::ePRECISE_SWEEP; const SweepBoxFunc func = precise ? sf.preciseBoxMap[geom1.getType()] : sf.boxMap[geom1.getType()]; retVal = PxU32(func(geom1, pose1Offset, static_cast(geom0), pose0, sd.getGuBox(), unitDir, distance, sweepHit, hitFlags, inflation)); } break; case PxGeometryType::eCONVEXMESH: { const PxConvexMeshGeometry& convexGeom = static_cast(geom0); const SweepConvexFunc func = sf.convexMap[geom1.getType()]; retVal = PxU32(func(geom1, pose1Offset, convexGeom, pose0, unitDir, distance, sweepHit, hitFlags, inflation)); } break; case PxGeometryType::ePLANE: case PxGeometryType::eTRIANGLEMESH: case PxGeometryType::eHEIGHTFIELD: case PxGeometryType::eGEOMETRY_COUNT: case PxGeometryType::eINVALID: physx::shdfnd::getFoundation().error(physx::PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "PxScene::sweep(): first geometry object parameter must be sphere, capsule, box or convex geometry."); break; } if (retVal) { // we need to offset the distance back sweepHit.distance += offset; // we need to offset the hit position back as we moved the geometry we sweep against sweepHit.position += offsetVec; } return retVal; } // Handle overlaps else if(HitTypeSupport::IsOverlap) { const GeomOverlapTable* overlapFuncs = sceneQueries.mCachedOverlapFuncs; return PxU32(Gu::overlap(geom0, pose0, geom1, pose1, overlapFuncs)); } else { PX_ALWAYS_ASSERT_MESSAGE("Unexpected template expansion in GeomQueryAny::geomHit"); return 0; } } }; // struct to access protected data members in the public PxHitCallback API template struct MultiQueryCallback : public PrunerCallback { const NpSceneQueries& mScene; const MultiQueryInput& mInput; PxHitCallback& mHitCall; const PxHitFlags mHitFlags; const PxQueryFilterData& mFilterData; PxQueryFilterCallback* mFilterCall; PxReal mShrunkDistance; BatchQueryFilterData* mBfd; // only not NULL for batch queries const PxHitFlags mMeshAnyHitFlags; bool mReportTouchesAgain; bool mFarBlockFound; // this is to prevent repeated searches for far block bool mNoBlock; const bool mAnyHit; bool mIsCached; // is this call coming as a callback from the pruner or a single item cached callback? // The reason we need these bounds is because we need to know combined(inflated shape) bounds to clip the sweep path // to be tolerable by GJK precision issues. This test is done for (queryShape vs touchedShapes) // So it makes sense to cache the bounds for sweep query shape, otherwise we'd have to recompute them every time // Currently only used for sweeps. PxBounds3 mQueryShapeBounds; bool mQueryShapeBoundsValid; const ShapeData* mShapeData; MultiQueryCallback( const NpSceneQueries& scene, const MultiQueryInput& input, bool anyHit, PxHitCallback& hitCall, PxHitFlags hitFlags, const PxQueryFilterData& filterData, PxQueryFilterCallback* filterCall, PxReal shrunkDistance, BatchQueryFilterData* aBfd) : mScene (scene), mInput (input), mHitCall (hitCall), mHitFlags (hitFlags), mFilterData (filterData), mFilterCall (filterCall), mShrunkDistance (shrunkDistance), mBfd (aBfd), mMeshAnyHitFlags ((hitFlags.isSet(PxHitFlag::eMESH_ANY) || anyHit) ? PxHitFlag::eMESH_ANY : PxHitFlag::Enum(0)), mReportTouchesAgain (true), mFarBlockFound (filterData.flags & PxQueryFlag::eNO_BLOCK), mNoBlock (filterData.flags & PxQueryFlag::eNO_BLOCK), mAnyHit (anyHit), mIsCached (false), mQueryShapeBoundsValid (false), mShapeData (NULL) { } virtual PxAgain invoke(PxReal& aDist, const PrunerPayload& aPayload) { const PxU32 tempCount = 1; HitType tempBuf[tempCount]; // PT: TODO: do we need actorShape.actor/actorShape.shape immediately? local::ActorShape actorShape; local::populate(aPayload, actorShape); const PxQueryFlags filterFlags = mFilterData.flags; // for no filter callback, default to eTOUCH for MULTIPLE, eBLOCK otherwise // also always treat as eBLOCK if currently tested shape is cached // Using eRESERVED flag as a special condition to default to eTOUCH hits while only looking for a single blocking hit // from a nested query (see other comments containing #LABEL1) PxQueryHitType::Enum shapeHitType = ((mHitCall.maxNbTouches || (mFilterData.flags & PxQueryFlag::eRESERVED)) && !mIsCached) ? PxQueryHitType::eTOUCH : PxQueryHitType::eBLOCK; // apply pre-filter PxHitFlags filteredHitFlags = mHitFlags; if(!mIsCached) // don't run filters on single item cache if(!applyAllPreFiltersSQ(&actorShape, shapeHitType/*in&out*/, filterFlags, mFilterData, mFilterCall, mBfd, filteredHitFlags/*, mHitCall.maxNbTouches*/)) return true; // skip this shape from reporting if prefilter said to do so if(shapeHitType == PxQueryHitType::eNONE) return true; PX_ASSERT(actorShape.actor && actorShape.shape); const Scb::Shape* shape = actorShape.scbShape; const Scb::Actor* actor = actorShape.scbActor; // compute the global pose for the cached shape and actor PX_ALIGN(16, PxTransform) globalPose; NpActor::getGlobalPose(globalPose, *shape, *actor); const PxGeometry& shapeGeom = shape->getGeometry(); // Here we decide whether to use the user provided buffer in place or a local stack buffer // see if we have more room left in the callback results buffer than in the parent stack buffer // if so get subHits in-place in the hit buffer instead of the parent stack buffer // nbTouches is the number of accumulated touch hits so far // maxNbTouches is the size of the user buffer PxU32 maxSubHits1 = mHitCall.maxNbTouches - mHitCall.nbTouches; // how much room is left in the user buffer HitType* subHits1 = mHitCall.touches + mHitCall.nbTouches; // pointer to the first free hit in the user buffer if(mHitCall.nbTouches >= mHitCall.maxNbTouches) // if there's no room left in the user buffer, use a stack buffer { // tried using 64 here - causes check stack code to get generated on xbox, perhaps because of guard page // need this buffer in case the input buffer is full but we still want to correctly merge results from later hits maxSubHits1 = tempCount; subHits1 = reinterpret_cast(tempBuf); } // limit number of hits to 1 for meshes if eMESH_MULTIPLE wasn't specified. this tells geomQuery to only look for a closest hit if(shapeGeom.getType() == PxGeometryType::eTRIANGLEMESH && !(filteredHitFlags & PxHitFlag::eMESH_MULTIPLE)) maxSubHits1 = 1; // required to only receive 1 hit to pass UTs // call the geometry specific intersection template PxU32 nbSubHits = GeomQueryAny::geomHit( mScene, mInput, *mShapeData, shapeGeom, globalPose, filteredHitFlags | mMeshAnyHitFlags, maxSubHits1, subHits1, mShrunkDistance, mQueryShapeBoundsValid ? &mQueryShapeBounds : NULL); // ------------------------- iterate over geometry subhits ----------------------------------- for (PxU32 iSubHit = 0; iSubHit < nbSubHits; iSubHit++) { HitType& hit = subHits1[iSubHit]; hit.actor = actorShape.actor; hit.shape = actorShape.shape; // some additional processing only for sweep hits with initial overlap if(HitTypeSupport::IsSweep && HITDIST(hit) == 0.0f && !(filteredHitFlags & PxHitFlag::eMTD)) // PT: necessary as some leaf routines are called with reversed params, thus writing +unitDir there. // AP: apparently still necessary to also do in Gu because Gu can be used standalone (without SQ) reinterpret_cast(hit).normal = -mInput.getDir(); // start out with hitType for this cached shape set to a pre-filtered hit type PxQueryHitType::Enum hitType = shapeHitType; // run the post-filter if specified in filterFlags and filterCall is non-NULL if(!mIsCached && (mFilterCall || mBfd) && (filterFlags & PxQueryFlag::ePOSTFILTER)) { if(mFilterCall) hitType = mFilterCall->postFilter(mFilterData.data, hit); else if(mBfd->postFilterShader) hitType = mBfd->postFilterShader( mFilterData.data, actorShape.scbShape->getScShape().getQueryFilterData(), mBfd->filterShaderData, mBfd->filterShaderDataSize, hit); } // early out on any hit if eANY_HIT was specified, regardless of hit type if(mAnyHit && hitType != PxQueryHitType::eNONE) { // block or touch qualifies for qType=ANY type hit => return it as blocking according to spec. Ignore eNONE. mHitCall.block = hit; mHitCall.hasBlock = true; return false; // found a hit for ANY qType, can early exit now } if(mNoBlock) hitType = PxQueryHitType::eTOUCH; PX_WARN_ONCE_IF(HitTypeSupport::IsOverlap && hitType == PxQueryHitType::eBLOCK, "eBLOCK returned from user filter for overlap() query. This may cause undesired behavior. " "Consider using PxQueryFlag::eNO_BLOCK for overlap queries."); if(hitType == PxQueryHitType::eTOUCH) { // -------------------------- handle eTOUCH hits --------------------------------- // for qType=multiple, store the hit. For other qTypes ignore it. // <= is important for initially overlapping sweeps #if PX_CHECKED if(mHitCall.maxNbTouches == 0 && !mBfd && !mFilterData.flags.isSet(PxQueryFlag::eRESERVED)) // issue a warning if eTOUCH was returned by the prefilter, we have 0 touch buffer and not a batch query // not doing for BQ because the touches buffer can be overflown and thats ok by spec // eRESERVED to avoid a warning from nested callback (closest blocking hit recursive search) Ps::getFoundation().error(PxErrorCode::eINVALID_OPERATION, __FILE__, __LINE__, "User filter returned PxQueryHitType::eTOUCH but the touches buffer was empty. Hit was discarded."); #endif if(mHitCall.maxNbTouches && mReportTouchesAgain && HITDIST(hit) <= mShrunkDistance) { // Buffer full: need to find the closest blocking hit, clip touch hits and flush the buffer if(mHitCall.nbTouches == mHitCall.maxNbTouches) { // issue a second nested query just looking for the closest blocking hit // could do better perf-wise by saving traversal state (start looking for blocking from this point) // but this is not a perf critical case because users can provide a bigger buffer // that covers non-degenerate cases // far block search doesn't apply to overlaps because overlaps don't work with blocking hits if(HitTypeSupport::IsOverlap == 0) { // AP: the use of eRESERVED is a bit tricky, see other comments containing #LABEL1 PxQueryFilterData fd1 = mFilterData; fd1.flags |= PxQueryFlag::eRESERVED; PxHitBuffer buf1; // create a temp callback buffer for a single blocking hit if(!mFarBlockFound && mHitCall.maxNbTouches > 0 && mScene.NpSceneQueries::multiQuery( mInput, buf1, mHitFlags, NULL, fd1, mFilterCall, mBfd)) { mHitCall.block = buf1.block; mHitCall.hasBlock = true; mHitCall.nbTouches = clipHitsToNewMaxDist(mHitCall.touches, mHitCall.nbTouches, HITDIST(buf1.block)); mShrunkDistance = HITDIST(buf1.block); aDist = mShrunkDistance; } mFarBlockFound = true; } if(mHitCall.nbTouches == mHitCall.maxNbTouches) { mReportTouchesAgain = mHitCall.processTouches(mHitCall.touches, mHitCall.nbTouches); if(!mReportTouchesAgain) return false; // optimization - buffer is full else mHitCall.nbTouches = 0; // reset nbTouches so we can continue accumulating again } } //if(hitCall.nbTouches < hitCall.maxNbTouches) // can be true if maxNbTouches is 0 mHitCall.touches[mHitCall.nbTouches++] = hit; } // if(hitCall.maxNbTouches && reportTouchesAgain && HITDIST(hit) <= shrunkDistance) } // if(hitType == PxQueryHitType::eTOUCH) else if(hitType == PxQueryHitType::eBLOCK) { // -------------------------- handle eBLOCK hits ---------------------------------- // only eBLOCK qualifies as a closest hit candidate => compare against best distance and store // <= is needed for eTOUCH hits to be recorded correctly vs same eBLOCK distance for overlaps if(HITDIST(hit) <= mShrunkDistance) { if(HitTypeSupport::IsOverlap == 0) { mShrunkDistance = HITDIST(hit); aDist = mShrunkDistance; } mHitCall.block = hit; mHitCall.hasBlock = true; } } // if(hitType == eBLOCK) else { PX_ASSERT(hitType == PxQueryHitType::eNONE); } } // for iSubHit return true; } private: MultiQueryCallback& operator=(const MultiQueryCallback&); }; //======================================================================================================================== #if PX_SUPPORT_PVD template struct CapturePvdOnReturn : public PxHitCallback { // copy the arguments of multiQuery into a struct, this is strictly for PVD recording const NpSceneQueries* mSQ; const MultiQueryInput& mInput; PxHitFlags mHitFlags; // PT: TODO: this is not used! const PxQueryCache* mCache; // PT: TODO: this is not used! const PxQueryFilterData& mFilterData; PxQueryFilterCallback* mFilterCall; // PT: TODO: this is not used! BatchQueryFilterData* mBFD; // PT: TODO: check if this is sometimes not NULL Ps::Array mAllHits; PxHitCallback& mParentCallback; CapturePvdOnReturn( const NpSceneQueries* sq, const MultiQueryInput& input, PxHitFlags hitFlags, const PxQueryCache* cache, const PxQueryFilterData& filterData, PxQueryFilterCallback* filterCall, BatchQueryFilterData* bfd, PxHitCallback& parentCallback) : PxHitCallback (parentCallback.touches, parentCallback.maxNbTouches), mSQ (sq), mInput (input), mHitFlags (hitFlags), mCache (cache), mFilterData (filterData), mFilterCall (filterCall), mBFD (bfd), mParentCallback (parentCallback) {} virtual PxAgain processTouches(const HitType* hits, PxU32 nbHits) { const PxAgain again = mParentCallback.processTouches(hits, nbHits); for(PxU32 i=0; igetScene().getScenePvdClient(); if(!(pvdClient.checkPvdDebugFlag() && (pvdClient.getScenePvdFlagsFast() & PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES))) return; physx::Vd::PvdSceneQueryCollector& collector = mBFD ? mSQ->getBatchedSqCollector() : mSQ->getSingleSqCollector(); if(mParentCallback.nbTouches) { for(PxU32 i = 0; i < mParentCallback.nbTouches; i++) mAllHits.pushBack(mParentCallback.touches[i]); } if(mParentCallback.hasBlock) mAllHits.pushBack(mParentCallback.block); // PT: TODO: why do we need reinterpret_casts below? if(HitTypeSupport::IsRaycast) collector.raycast (mInput.getOrigin(), mInput.getDir(), mInput.maxDistance, reinterpret_cast(mAllHits.begin()), mAllHits.size(), mFilterData, this->maxNbTouches!=0); else if(HitTypeSupport::IsOverlap) collector.overlapMultiple (*mInput.geometry, *mInput.pose, reinterpret_cast(mAllHits.begin()), mAllHits.size(), mFilterData); else if(HitTypeSupport::IsSweep) collector.sweep (*mInput.geometry, *mInput.pose, mInput.getDir(), mInput.maxDistance, reinterpret_cast(mAllHits.begin()), mAllHits.size(), mFilterData, this->maxNbTouches!=0); } private: CapturePvdOnReturn& operator=(const CapturePvdOnReturn&); }; #endif // PX_SUPPORT_PVD //======================================================================================================================== template struct IssueCallbacksOnReturn { PxHitCallback& hits; PxAgain again; // query was stopped by previous processTouches. This means that nbTouches is still non-zero // but we don't need to issue processTouches again PX_FORCE_INLINE IssueCallbacksOnReturn(PxHitCallback& aHits) : hits(aHits) { again = true; } ~IssueCallbacksOnReturn() { if(again) // only issue processTouches if query wasn't stopped // this is because nbTouches doesn't get reset to 0 in this case (according to spec) // and the touches in touches array were already processed by the callback { if(hits.hasBlock && hits.nbTouches) hits.nbTouches = clipHitsToNewMaxDist(hits.touches, hits.nbTouches, HITDIST(hits.block)); if(hits.nbTouches) { bool again_ = hits.processTouches(hits.touches, hits.nbTouches); if(again_) hits.nbTouches = 0; } } hits.finalizeQuery(); } private: IssueCallbacksOnReturn& operator=(const IssueCallbacksOnReturn&); }; #undef HITDIST //======================================================================================================================== template bool NpSceneQueries::multiQuery( const MultiQueryInput& input, PxHitCallback& hits, PxHitFlags hitFlags, const PxQueryCache* cache, const PxQueryFilterData& filterData, PxQueryFilterCallback* filterCall, BatchQueryFilterData* bfd) const { const bool anyHit = (filterData.flags & PxQueryFlag::eANY_HIT) == PxQueryFlag::eANY_HIT; if(HitTypeSupport::IsRaycast == 0) { PX_CHECK_AND_RETURN_VAL(input.pose != NULL, "NpSceneQueries::overlap/sweep pose is NULL.", 0); PX_CHECK_AND_RETURN_VAL(input.pose->isValid(), "NpSceneQueries::overlap/sweep pose is not valid.", 0); } else { PX_CHECK_AND_RETURN_VAL(input.getOrigin().isFinite(), "NpSceneQueries::raycast pose is not valid.", 0); } if(HitTypeSupport::IsOverlap == 0) { PX_CHECK_AND_RETURN_VAL(input.getDir().isFinite(), "NpSceneQueries multiQuery input check: unitDir is not valid.", 0); PX_CHECK_AND_RETURN_VAL(input.getDir().isNormalized(), "NpSceneQueries multiQuery input check: direction must be normalized", 0); } if(HitTypeSupport::IsRaycast) { PX_CHECK_AND_RETURN_VAL(input.maxDistance > 0.0f, "NpSceneQueries::multiQuery input check: distance cannot be negative or zero", 0); } if(HitTypeSupport::IsOverlap && !anyHit) { PX_CHECK_AND_RETURN_VAL(hits.maxNbTouches > 0, "PxScene::overlap() and PxBatchQuery::overlap() calls without eANY_HIT flag require a touch hit buffer for return results.", 0); } if(HitTypeSupport::IsSweep) { PX_CHECK_AND_RETURN_VAL(input.maxDistance >= 0.0f, "NpSceneQueries multiQuery input check: distance cannot be negative", 0); PX_CHECK_AND_RETURN_VAL(input.maxDistance != 0.0f || !(hitFlags & PxHitFlag::eASSUME_NO_INITIAL_OVERLAP), "NpSceneQueries multiQuery input check: zero-length sweep only valid without the PxHitFlag::eASSUME_NO_INITIAL_OVERLAP flag", 0); } PX_CHECK_MSG(!cache || (cache && cache->shape && cache->actor), "Raycast cache specified but shape or actor pointer is NULL!"); PxU32 cachedCompoundId = INVALID_PRUNERHANDLE; const PrunerData cacheData = cache ? NpActor::getShapeManager(*cache->actor)->findSceneQueryData(*static_cast(cache->shape), cachedCompoundId) : SQ_INVALID_PRUNER_DATA; // this function is logically const for the SDK user, as flushUpdates() will not have an API-visible effect on this object // internally however, flushUpdates() changes the states of the Pruners in mSQManager // because here is the only place we need this, const_cast instead of making SQM mutable const_cast(this)->mSQManager.flushUpdates(); #if PX_SUPPORT_PVD CapturePvdOnReturn pvdCapture(this, input, hitFlags, cache, filterData, filterCall, bfd, hits); #endif IssueCallbacksOnReturn cbr(hits); // destructor will execute callbacks on return from this function hits.hasBlock = false; hits.nbTouches = 0; PxReal shrunkDistance = HitTypeSupport::IsOverlap ? PX_MAX_REAL : input.maxDistance; // can be progressively shrunk as we go over the list of shapes if(HitTypeSupport::IsSweep) shrunkDistance = PxMin(shrunkDistance, PX_MAX_SWEEP_DISTANCE); MultiQueryCallback pcb(*this, input, anyHit, hits, hitFlags, filterData, filterCall, shrunkDistance, bfd); if(cacheData!=SQ_INVALID_PRUNER_DATA && hits.maxNbTouches == 0) // don't use cache for queries that can return touch hits { // this block is only executed for single shape cache const PrunerPayload& cachedPayload = mSQManager.getPayload(cachedCompoundId, cacheData); pcb.mIsCached = true; PxReal dummyDist; PxAgain againAfterCache; if(HitTypeSupport::IsSweep) { // AP: for sweeps we cache the bounds because we need to know them for the test to clip the sweep to bounds // otherwise GJK becomes unstable. The bounds can be used multiple times so this is an optimization. const ShapeData sd(*input.geometry, *input.pose, input.inflation); pcb.mQueryShapeBounds = sd.getPrunerInflatedWorldAABB(); pcb.mQueryShapeBoundsValid = true; pcb.mShapeData = &sd; againAfterCache = pcb.invoke(dummyDist, cachedPayload); pcb.mShapeData = NULL; } else againAfterCache = pcb.invoke(dummyDist, cachedPayload); pcb.mIsCached = false; if(!againAfterCache) // if PxAgain result for cached shape was false (abort query), return here return hits.hasAnyHits(); } const Pruner* staticPruner = mSQManager.get(PruningIndex::eSTATIC).pruner(); const Pruner* dynamicPruner = mSQManager.get(PruningIndex::eDYNAMIC).pruner(); const CompoundPruner* compoundPruner = mSQManager.getCompoundPruner().pruner(); const PxU32 doStatics = filterData.flags & PxQueryFlag::eSTATIC; const PxU32 doDynamics = filterData.flags & PxQueryFlag::eDYNAMIC; if(HitTypeSupport::IsRaycast) { bool again = doStatics ? staticPruner->raycast(input.getOrigin(), input.getDir(), pcb.mShrunkDistance, pcb) : true; if(!again) return hits.hasAnyHits(); if(doDynamics) again = dynamicPruner->raycast(input.getOrigin(), input.getDir(), pcb.mShrunkDistance, pcb); if(again) again = compoundPruner->raycast(input.getOrigin(), input.getDir(), pcb.mShrunkDistance, pcb, filterData.flags); cbr.again = again; // update the status to avoid duplicate processTouches() return hits.hasAnyHits(); } else if(HitTypeSupport::IsOverlap) { PX_ASSERT(input.geometry); const ShapeData sd(*input.geometry, *input.pose, input.inflation); pcb.mShapeData = &sd; PxAgain again = doStatics ? staticPruner->overlap(sd, pcb) : true; if(!again) // && (filterData.flags & PxQueryFlag::eANY_HIT)) return hits.hasAnyHits(); if(doDynamics) again = dynamicPruner->overlap(sd, pcb); if(again) again = compoundPruner->overlap(sd, pcb, filterData.flags); cbr.again = again; // update the status to avoid duplicate processTouches() return hits.hasAnyHits(); } else { PX_ASSERT(HitTypeSupport::IsSweep); PX_ASSERT(input.geometry); const ShapeData sd(*input.geometry, *input.pose, input.inflation); pcb.mQueryShapeBounds = sd.getPrunerInflatedWorldAABB(); pcb.mQueryShapeBoundsValid = true; pcb.mShapeData = &sd; PxAgain again = doStatics ? staticPruner->sweep(sd, input.getDir(), pcb.mShrunkDistance, pcb) : true; if(!again) return hits.hasAnyHits(); if(doDynamics) again = dynamicPruner->sweep(sd, input.getDir(), pcb.mShrunkDistance, pcb); if(again) again = compoundPruner->sweep(sd, input.getDir(), pcb.mShrunkDistance, pcb, filterData.flags); cbr.again = again; // update the status to avoid duplicate processTouches() return hits.hasAnyHits(); } } void NpSceneQueries::sceneQueriesStaticPrunerUpdate(PxBaseTask* ) { PX_PROFILE_ZONE("SceneQuery.sceneQueriesStaticPrunerUpdate", getContextId()); // run pruner build only, this will build the new tree only, no commit happens mSQManager.sceneQueryBuildStep(PruningIndex::eSTATIC); } void NpSceneQueries::sceneQueriesDynamicPrunerUpdate(PxBaseTask*) { PX_PROFILE_ZONE("SceneQuery.sceneQueriesDynamicPrunerUpdate", getContextId()); // run pruner build only, this will build the new tree only, no commit happens mSQManager.sceneQueryBuildStep(PruningIndex::eDYNAMIC); } //explicit template instantiation template bool NpSceneQueries::multiQuery(const MultiQueryInput&, PxHitCallback&, PxHitFlags, const PxQueryCache*, const PxQueryFilterData&, PxQueryFilterCallback*, BatchQueryFilterData*) const; template bool NpSceneQueries::multiQuery(const MultiQueryInput&, PxHitCallback&, PxHitFlags, const PxQueryCache*, const PxQueryFilterData&, PxQueryFilterCallback*, BatchQueryFilterData*) const; template bool NpSceneQueries::multiQuery(const MultiQueryInput&, PxHitCallback&, PxHitFlags, const PxQueryCache*, const PxQueryFilterData&, PxQueryFilterCallback*, BatchQueryFilterData*) const;