// 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. // **************************************************************************** // This snippet illustrates how to use a custom query system and the low-level // cooking functions. // // This is similar to SnippetStandaloneBVH, but this time using a more // advanced query system instead of a single PxBVH. // // This snippet illustrates a basic setup and a single type of query // (raycast closest hit). For more queries see SnippetQuerySystemAllQueries. // // **************************************************************************** #include #include "PxPhysicsAPI.h" #include "GuQuerySystem.h" #include "GuFactory.h" #include "GuCooking.h" #include "foundation/PxArray.h" #include "../snippetcommon/SnippetPrint.h" #include "../snippetcommon/SnippetPVD.h" #include "../snippetutils/SnippetUtils.h" #ifdef RENDER_SNIPPET #include "../snippetrender/SnippetCamera.h" #include "../snippetrender/SnippetRender.h" #endif using namespace physx; using namespace Gu; // The query system can compute the bounds for you, or you can do it manually. Manual bounds // computation can be useful for specific effects (like using temporal bounds) or if there is // an external system that already computed the bounds and recomputing them would be a waste. // Automatic bounds computation is easier to use and less error-prone. static const bool gManualBoundsComputation = false; // The query system can delay internal transform/bounds updates or use the data immediately. // Delaying updates can be faster due to batching, but it uses more memory to store the data until // the actual update happens. Delaying the update can also serve as a double-buffering mechanism, // allowing one to query the old state of the system until a later user-controlled point in time. static const bool gUseDelayedUpdates = true; // Bounds in the query system can be inflated a bit to fight numerical inaccuracy errors that can happen // when a ray or a query-volume just touches the bounds. Users can manually inflate bounds or let the // system do it. Because the system can compute the bounds automatically, it is necessary to let it know // about the inflation value. static const float gBoundsInflation = 0.001f; #define MAX_NB_OBJECTS 32 namespace { class CustomScene : public Adapter { public: CustomScene(); ~CustomScene() {} // Adapter virtual const PxGeometry& getGeometry(const PrunerPayload& payload) const; //~Adapter void release(); void addGeom(const PxGeometry& geom, const PxTransform& pose); void render(); bool raycast(const PxVec3& origin, const PxVec3& unitDir, float maxDist, PxGeomRaycastHit& hit) const; void updateObjects(); struct Object { PxGeometryHolder mGeom; ActorShapeData mData; }; PxU32 mNbObjects; Object mObjects[MAX_NB_OBJECTS]; QuerySystem* mQuerySystem; PxU32 mPrunerIndex; }; static const PxGeometry& getGeometryFromPayload(const PrunerPayload& payload) { const CustomScene* cs = reinterpret_cast(payload.data[1]); return cs->mObjects[PxU32(payload.data[0])].mGeom.any(); } const PxGeometry& CustomScene::getGeometry(const PrunerPayload& payload) const { // This function is called by the system to compute bounds. It will never be // called if 'gManualBoundsComputation' is true. PX_ASSERT(!gManualBoundsComputation); return getGeometryFromPayload(payload); } void CustomScene::release() { PX_DELETE(mQuerySystem); PX_DELETE_THIS; } CustomScene::CustomScene() : mNbObjects(0) { // The contextID is a parameter sent to the profiler to identify the owner of a profile event. // In PhysX this is usually the PxScene pointer, and it is used by PVD to group together all profile events of a given scene. // We do not have a PxScene object here so we can put an arbitrary value there. The value will only be used by // PVD to display profiling results. const PxU64 contextID = PxU64(this); // First we create a query system and give it an adapter. The adapter is used to retrieve the geometry // of objects in the system. This is needed when the system automatically computes bounds for users. // The geometry is not stored directly in the system to avoid duplication, and make it easy to reuse // the system with PxShape-based objects. In this snippet the system will call the // 'CustomScene::getGeometry' function above to fetch an object's geometry. mQuerySystem = PX_NEW(QuerySystem)(contextID, gBoundsInflation, *this); // PhysX uses a hardcoded number of pruners (one for static objects, one for dynamic objects, // and an optional one for compound). The query system here is more flexible and supports an // arbitrary number of pruners, which have to be created by users and added to the system // explicitly. In this snippet we just use a single pruner of a chosen type: Pruner* pruner = createAABBPruner(contextID, true, COMPANION_PRUNER_INCREMENTAL, BVH_SPLATTER_POINTS, 4); // Then we add it to the query system, which takes ownership of the object (it will delete // the pruner when the query system is released). Each pruner is given an index by the // system, used in 'addPrunerShape' to identify which pruner each object is added to. mPrunerIndex = mQuerySystem->addPruner(pruner, 0); } void CustomScene::addGeom(const PxGeometry& geom, const PxTransform& pose) { PX_ASSERT(mQuerySystem); // The query system operates on anonymous 'payloads', which are basically glorified user-data. // We put here what we need to implement 'CustomScene::getGeometry'. PrunerPayload payload; payload.data[0] = mNbObjects; // This will be the index of our new object, see below. payload.data[1] = size_t(this); // We store the geometry first, because in automatic mode the 'CustomScene::getGeometry' function // will be called by 'addPrunerShape' below, so we need the geometry to be properly setup first. Object& obj = mObjects[mNbObjects]; obj.mGeom.storeAny(geom); // The query system manages a built-in timestamp for static objects, which is used by external // sub-systems like character controllers to invalidate their caches. This is not needed in // this snippet so any value works here. const bool isDynamic = true; // In automatic mode the system will compute the bounds for us. // In manual mode we compute the bounds first and pass them to the system. // // Note that contrary to bounds, the transforms are duplicated and stored within the query // system. In this snippet we take advantage of this by not storing the poses anywhere // else - we will retrieve them from the query system when we need them. In a more complex // example this could create a duplication of the transforms, but this 'double-buffering' is // actually done on purpose to make sure the query system can run in parallel to the app's // code when/if it modifies the objects' poses. The poses are double-buffered but the geometries // are not, because poses of dynamic objects change each frame while geometries do not. if(gManualBoundsComputation) { PxBounds3 bounds; PxGeometryQuery::computeGeomBounds(bounds, geom, pose, 0.0f, 1.0f + gBoundsInflation); obj.mData = mQuerySystem->addPrunerShape(payload, mPrunerIndex, isDynamic, pose, &bounds); } else { obj.mData = mQuerySystem->addPrunerShape(payload, mPrunerIndex, isDynamic, pose, NULL); } mNbObjects++; } void CustomScene::updateObjects() { if(!mQuerySystem) return; static float time = 0.0f; time += 0.01f; const PxU32 nbObjects = mNbObjects; for(PxU32 i=0;iupdatePrunerShape(obj.mData, !gUseDelayedUpdates, pose, &bounds); } else { // Note: in this codepath the system will compute the bounds automatically: // - if 'immediately' is true, the system will call back 'CustomScene::getGeometry' // during the 'updatePrunerShape' call. // - otherwise it will call 'CustomScene::getGeometry' later during the // 'mQuerySystem->update' call (below). mQuerySystem->updatePrunerShape(obj.mData, !gUseDelayedUpdates, pose, NULL); } } // This is the per-pruner update (b) we mentioned just above. It commits the // updates we just made and reflects them into the internal data-structures. // // Note that this function must also be called after adding & removing objects. // // Finally, this function also manages the incremental rebuild of internal structures // if the 'buildStep' parameter is true. This is a convenience function that does // everything needed in a single call. If all the updates to the system happen in // a single place, like in this snippet, then this is everything you need. mQuerySystem->update(true, true); } namespace { struct CustomPrunerFilterCallback : public PrunerFilterCallback { virtual const PxGeometry* validatePayload(const PrunerPayload& payload, PxHitFlags& /*hitFlags*/) { return &getGeometryFromPayload(payload); } }; } static CustomPrunerFilterCallback gFilterCallback; static CachedFuncs gCachedFuncs; bool CustomScene::raycast(const PxVec3& origin, const PxVec3& unitDir, float maxDist, PxGeomRaycastHit& hit) const { if(!mQuerySystem) return false; // In this snippet our update loop is simple: // // - update all objects ('mQuerySystem->updatePrunerShape') // - call 'mQuerySystem->update' // - then perform raycasts // // Because we call 'mQuerySystem->update' just before performing the raycasts, // we guarantee that the internal data-structures are up-to-date and we can // immediately call 'mQuerySystem->raycast'. // // However things can become more complicated: // - sometimes 'mQuerySystem->updatePrunerShape' is immediately followed by a // raycast (ex: spawning an object, using a raycast to locate it on the map, repeat) // - sometimes raycasts happen from multiple threads in no clear order // // In these cases the following call to 'commitUpdates' is needed to commit the // minimal amount of updates to the system, so that correct raycast results are // guaranteed. In particular this call omits the 'build step' performed in the // main 'mQuerySystem->update' function (users should have one build step per // frame). This function is also thread-safe, i.e. you can call commitUpdates // and raycasts from multiple threads (contrary to 'mQuerySystem->update'). // // Note that in PxScene::raycast() this call is always executed. But this custom // query system lets users control when & where it happens. The system becomes // more flexible, but puts more burden on users. if(0) mQuerySystem->commitUpdates(); // After that the raycast code itself is rather simple. DefaultPrunerRaycastClosestCallback CB(gFilterCallback, gCachedFuncs.mCachedRaycastFuncs, origin, unitDir, maxDist, PxHitFlag::eDEFAULT); mQuerySystem->raycast(origin, unitDir, maxDist, CB, NULL); if(CB.mFoundHit) hit = CB.mClosestHit; return CB.mFoundHit; } void CustomScene::render() { updateObjects(); #ifdef RENDER_SNIPPET const PxVec3 color(1.0f, 0.5f, 0.25f); const PxU32 nbObjects = mNbObjects; for(PxU32 i=0;igetPayloadData(obj.mData, &ppd); Snippets::DrawBounds(*ppd.mBounds); Snippets::renderGeoms(1, &obj.mGeom, ppd.mTransform, false, color); } //mQuerySystem->visualize(true, true, PxRenderOutput) const PxU32 screenWidth = Snippets::getScreenWidth(); const PxU32 screenHeight = Snippets::getScreenHeight(); Snippets::Camera* sCamera = Snippets::getCamera(); const PxVec3 camPos = sCamera->getEye(); const PxVec3 camDir = sCamera->getDir(); #if PX_DEBUG const PxU32 RAYTRACING_RENDER_WIDTH = 64; const PxU32 RAYTRACING_RENDER_HEIGHT = 64; #else const PxU32 RAYTRACING_RENDER_WIDTH = 256; const PxU32 RAYTRACING_RENDER_HEIGHT = 256; #endif const PxU32 textureWidth = RAYTRACING_RENDER_WIDTH; const PxU32 textureHeight = RAYTRACING_RENDER_HEIGHT; GLubyte* pixels = new GLubyte[textureWidth*textureHeight*4]; const float fScreenWidth = float(screenWidth)/float(RAYTRACING_RENDER_WIDTH); const float fScreenHeight = float(screenHeight)/float(RAYTRACING_RENDER_HEIGHT); { // Contrary to PxBVH, the Gu-level query system does not contain any built-in "SIMD guard". // It is up to users to make sure the SIMD control word is properly setup before calling // these low-level functions. See also OPTIM_SKIP_INTERNAL_SIMD_GUARD in SnippetPathTracing. PX_SIMD_GUARD GLubyte* buffer = pixels; for(PxU32 j=0;jaddGeom(PxBoxGeometry(PxVec3(1.0f, 2.0f, 0.5f)), PxTransform(PxVec3(0.0f, 0.0f, 0.0f))); gScene->addGeom(PxSphereGeometry(1.5f), PxTransform(PxVec3(4.0f, 0.0f, 0.0f))); gScene->addGeom(PxCapsuleGeometry(1.0f, 1.0f), PxTransform(PxVec3(-4.0f, 0.0f, 0.0f))); gScene->addGeom(PxConvexMeshGeometry(gConvexMesh), PxTransform(PxVec3(0.0f, 0.0f, 4.0f))); gScene->addGeom(PxTriangleMeshGeometry(gTriangleMesh), PxTransform(PxVec3(0.0f, 0.0f, -4.0f))); } } void renderScene() { if(gScene) gScene->render(); } void stepPhysics(bool /*interactive*/) { } void cleanupPhysics(bool /*interactive*/) { PX_RELEASE(gScene); PX_RELEASE(gTriangleMesh); PX_RELEASE(gConvexMesh); PX_RELEASE(gFoundation); printf("SnippetStandaloneQuerySystem done.\n"); } void keyPress(unsigned char /*key*/, const PxTransform& /*camera*/) { } int snippetMain(int, const char*const*) { printf("Standalone Query System snippet.\n"); #ifdef RENDER_SNIPPET extern void renderLoop(); renderLoop(); #else static const PxU32 frameCount = 100; initPhysics(false); for(PxU32 i=0; i