// 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 shows how to use a custom scene query system with N pruners // instead of the traditional two in PxScene. This is mainly useful for large // world with a lot of actors and a lot of updates. The snippet creates a // "worst case scenario" involving hundreds of thousands of actors, which are // all artificially updated each frame to put a lot of pressure on the query // structures. This is not entirely realistic but it makes the gains from the // custom query system more obvious. There is a virtual "player" moving around // in that world and regions are added and removed at runtime according to the // player's position. Each region contains thousands of actors, which stresses // the tree building code, the tree refit code, the build step code, and many // parts of the SQ update pipeline. Pruners can be updated in parallel, which // is more useful with N pruners than it was with the two PxScene build-in pruners. // // Rendering is disabled by default since it can be quite slow for so many // actors. // // Note that the cost of actual scene queries (raycasts, etc) might go up when // using multiple pruners. However the cost of updating the SQ structures can be // much higher than the cost of the scene queries themselves, so this can be a // good trade-off. // **************************************************************************** #include #include "PxPhysicsAPI.h" #include "foundation/PxArray.h" #include "foundation/PxTime.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; // Define this to use a custom pruner. Undefine to use the default PxScene code. #define USE_CUSTOM_PRUNER // This number of threads is used both for the default PhysX CPU dispatcher, and // for the custom concurrent build steps when a custom pruner is used. It does not // have much of an impact when USE_CUSTOM_PRUNER is undefined. #define NUM_WORKER_THREADS 8 //#define NUM_WORKER_THREADS 4 //#define NUM_WORKER_THREADS 2 //#define NUM_WORKER_THREADS 1 #ifdef RENDER_SNIPPET // Enable or disable rendering. Disabled by default, since the default scene uses 648081 actors. // There is a custom piece of code that renders the scene as a single mesh though, so it should // still be usable on fast PCs when enabled. static const bool gEnableRendering = false; #endif // Number of frames to simulate, only used when gEnableRendering == false static const PxU32 gNbFramesToSimulate = 100; // The PhysX tree rebuild rate hint. It is usually a *bad idea* to decrease it to 10 (the default // value is 100), but people do this, and it puts more stress on the build code, which fits the // worst case scenario we're looking for in this snippet. But do note that in most cases it should // really be left to "100", or actually increased in large scenes. static PxU32 gDynamicTreeRebuildRateHint = 10; // How many objects are added in each region. This has a direct impact on performance in all parts // of the system. //static const PxU32 gNbObjectsPerRegion = 400; //static const PxU32 gNbObjectsPerRegion = 800; //static const PxU32 gNbObjectsPerRegion = 2000; //static const PxU32 gNbObjectsPerRegion = 4000; static const PxU32 gNbObjectsPerRegion = 8000; static const float gGlobalScale = 1.0f; // Size of added objects. static const float gObjectScale = 0.01f * gGlobalScale; // This controls whether objects are artificially updated each frame. This resets the objects' positions // to what they already are, no nothing is moving but internally it forces all trees to be refit & rebuilt // constantly. This has a big impact on performance. // // Using "false" here means that: // - if USE_CUSTOM_PRUNER is NOT defined, the new objects are added to a unique tree in PxScene, which triggers // a rebuild. The refit operation is not necessary and skipped. // - if USE_CUSTOM_PRUNER is defined, the new objects are added to a per-region pruner in each region. There is // no rebuild necessary, and no refit either. // // Using "true" here means that all involved trees are constantly refit & rebuilt over a number of frames. static const bool gUpdateObjectsInRegion = true; // Range of player's motion static const float gRange = 10.0f * gGlobalScale; #ifdef RENDER_SNIPPET // Size of player static const float gPlayerSize = 0.1f * gGlobalScale; #endif // Speed of player. If you increase it too much the player might leave a region before its tree gets rebuilt, // which means some parts of the update pipeline are never executed. static const float gPlayerSpeed = 0.1f; //static const float gPlayerSpeed = 0.01f; // Size of active area. The world is effectively infinite but only this active area is considered by the // streaming code. The active area is a square whose edge size is gActiveAreaSize*2. static const float gActiveAreaSize = 5.0f * gGlobalScale; // Number of cells per side == number of regions per side. static const PxU32 gNbCellsPerSide = 8; #ifdef USE_CUSTOM_PRUNER // Number of pruners in the system static const PxU32 gNbPruners = (gNbCellsPerSide+1)*(gNbCellsPerSide+1); // Use true to update all pruners in parallel, false to update them sequentially static const bool gUseConcurrentBuildSteps = true; // Use tree of pruners or not. This is mainly useful if you have a large number of pruners in // the system. There is a small cost associated with maintaining that extra tree but since the // number of pruners should still be vastly smaller than the total number of objects, this is // usually quite cheap. You can profile the ratcast cost by modifying the code at the end of // this snippet and see how using a tree of pruners improves performance. static const bool gUseTreeOfPruners = false; #endif static float gGlobalTime = 0.0f; static SnippetUtils::BasicRandom gRandom(42); static const PxVec3 gYellow(1.0f, 1.0f, 0.0f); static const PxVec3 gRed(1.0f, 0.0f, 0.0f); static const PxVec3 gGreen(0.0f, 1.0f, 0.0f); static PxVec3 computePlayerPos(float globalTime) { const float Amplitude = gRange; const float t = globalTime * gPlayerSpeed; const float x = sinf(t*2.17f) * sinf(t) * Amplitude; const float z = sinf(t*0.77f) * cosf(t) * Amplitude; return PxVec3(x, 0.0f, z); } static PxDefaultAllocator gAllocator; static PxDefaultErrorCallback gErrorCallback; static PxFoundation* gFoundation = NULL; static PxPhysics* gPhysics = NULL; static PxDefaultCpuDispatcher* gDispatcher = NULL; static PxScene* gScene = NULL; static PxMaterial* gMaterial = NULL; static PxPvd* gPvd = NULL; #define INVALID_ID 0xffffffff #ifdef RENDER_SNIPPET static PxU8 gBoxIndices[] = { 0,2,1, 0,3,2, 1,6,5, 1,2,6, 5,7,4, 5,6,7, 4,3,0, 4,7,3, 3,6,2, 3,7,6, 5,0,1, 5,4,0 }; #endif namespace { #ifdef USE_CUSTOM_PRUNER struct PrunerData { PrunerData() : mPrunerIndex(INVALID_ID), mNbObjects(0) {} PxU32 mPrunerIndex; PxU32 mNbObjects; }; #endif struct RegionData { PxArray mObjects; #ifdef USE_CUSTOM_PRUNER PrunerData* mPrunerData; #endif #ifdef RENDER_SNIPPET PxU32 mNbVerts, mNbTris; PxVec3* mVerts; PxU32* mIndices; #endif }; #ifdef USE_CUSTOM_PRUNER // This adapter class will be used by the PxCustomSceneQuerySystem to map each new actor/shape to a user-defined pruner class SnippetCustomSceneQuerySystemAdapter : public PxCustomSceneQuerySystemAdapter { public: PrunerData mPrunerData[gNbPruners]; SnippetCustomSceneQuerySystemAdapter() { } void createPruners(PxCustomSceneQuerySystem* customSQ) { // We create a pool of pruners, large enough to provide one pruner per region. This is an arbitrary choice, we could also // map multiple regions to the same pruner (as long as these regions are close to each other it's just fine). if(customSQ) { for(PxU32 i=0;iaddPruner(PxPruningStructureType::eDYNAMIC_AABB_TREE, PxDynamicTreeSecondaryPruner::eINCREMENTAL); //mPrunerData[i].mPrunerIndex = customSQ->addPruner(PxPruningStructureType::eDYNAMIC_AABB_TREE, PxDynamicTreeSecondaryPruner::eBVH); } } // This is called by the streaming code to assign a pruner to a region PrunerData* findFreePruner() { for(PxU32 i=0;i(actor.userData); return prunerData->mPrunerIndex; } // This is called by PxCustomSceneQuerySystem to validate a pruner for scene queries virtual bool processPruner(PxU32 /*prunerIndex*/, const PxQueryThreadContext* /*context*/, const PxQueryFilterData& /*filterData*/, PxQueryFilterCallback* /*filterCall*/) const { // We could filter out empty pruners here if we have some, but for now we don't bother return true; } }; #endif struct StreamRegion { PX_FORCE_INLINE StreamRegion() : mKey(0), mTimestamp(INVALID_ID), mRegionData(NULL) {} PxU64 mKey; PxU32 mTimestamp; PxBounds3 mCellBounds; RegionData* mRegionData; }; typedef PxHashMap StreamingCache; class Streamer { PX_NOCOPY(Streamer) public: Streamer(float activeAreaSize, PxU32 nbCellsPerSide); ~Streamer(); void update(const PxVec3& playerPos); void renderDebug(); void render(); PxBounds3 mStreamingBounds; StreamingCache mStreamingCache; PxU32 mTimestamp; const float mActiveAreaSize; const PxU32 mNbCellsPerSide; void addRegion(StreamRegion& region); void updateRegion(StreamRegion& region); void removeRegion(StreamRegion& region); }; } #ifdef USE_CUSTOM_PRUNER static SnippetCustomSceneQuerySystemAdapter gAdapter; #endif Streamer::Streamer(float activeAreaSize, PxU32 nbCellsPerSide) : mTimestamp(0), mActiveAreaSize(activeAreaSize), mNbCellsPerSide(nbCellsPerSide) { mStreamingBounds.setEmpty(); } Streamer::~Streamer() { } void Streamer::addRegion(StreamRegion& region) { PX_ASSERT(region.mRegionData==NULL); // We disable the simulation flag to measure the cost of SQ structures exclusively const PxShapeFlags shapeFlags = PxShapeFlag::eVISUALIZATION | PxShapeFlag::eSCENE_QUERY_SHAPE; PxVec3 extents = region.mCellBounds.getExtents(); extents.x -= 0.01f * gGlobalScale; extents.z -= 0.01f * gGlobalScale; extents.y = 0.1f * gGlobalScale; PxVec3 center = region.mCellBounds.getCenter(); center.y = -0.1f * gGlobalScale; // In each region we create one "ground" shape and a number of extra smaller objects on it. We use // static objects to make sure the timings aren't polluted by dynamics-related costs. Dynamic actors // can also move to neighboring regions, which in theory means they should be transferred to another // pruner to avoid unpleasant visual artefacts when removing an entire region. This is beyond the // scope of this snippet so, static actors it is. // // The number of static actors in the world is usually much larger than the number of dynamic actors. // Dynamic actors move constantly, so they usually always trigger a refit & rebuild of the corresponding // trees. It is therefore a good idea to separate static and dynamic actors, to avoid the cost of // refit & rebuild for the static parts. In the context of a streaming world it is sometimes good enough // to put static actors in separate pruners (like we do here) but still stuff all dynamic actors in a // unique separate pruner (i.e. the same for the whole world). It avoids the aforementioned issues with // dynamic actors moving to other regions, and if the total number of dynamic actors remains small a // single structure for dynamic actors is often enough. PxShape* groundShape = gPhysics->createShape(PxBoxGeometry(extents), *gMaterial, false, shapeFlags); PxRigidStatic* ground = PxCreateStatic(*gPhysics, PxTransform(center), *groundShape); RegionData* regionData = new RegionData; regionData->mObjects.pushBack(ground); region.mRegionData = regionData; #ifdef USE_CUSTOM_PRUNER regionData->mPrunerData = gAdapter.findFreePruner(); ground->userData = regionData->mPrunerData; regionData->mPrunerData->mNbObjects++; #endif gScene->addActor(*ground); if(gNbObjectsPerRegion) { const PxU32 nbExtraObjects = gNbObjectsPerRegion; const float objectScale = gObjectScale; const float coeffY = objectScale * 20.0f; center.y = objectScale; const PxBoxGeometry boxGeom(objectScale, objectScale, objectScale); PxShape* shape = gPhysics->createShape(boxGeom, *gMaterial, false, shapeFlags); PxRigidActor* actors[nbExtraObjects]; for(PxU32 j=0;jmObjects.pushBack(actor); #ifdef USE_CUSTOM_PRUNER actor->userData = regionData->mPrunerData; regionData->mPrunerData->mNbObjects++; #endif } /* if(0) { PxPruningStructure* ps = physics.createPruningStructure(actors, nbDynamicObjects); scene.addActors(*ps); } else*/ { gScene->addActors(reinterpret_cast(actors), nbExtraObjects); } } #ifdef RENDER_SNIPPET // Precompute single render mesh for this region (rendering them as individual actors is too // slow). This is also only possible because we used static actors. { const PxU32 nbActors = regionData->mObjects.size(); const PxU32 nbVerts = nbActors*8; const PxU32 nbTris = nbActors*12; PxVec3* pts = new PxVec3[nbVerts]; PxVec3* dstPts = pts; PxU32* indices = new PxU32[nbTris*3]; PxU32* dstIndices = indices; PxU32 baseIndex = 0; for(PxU32 i=0;imObjects[i]->getGlobalPose().p; PxShape* shape; regionData->mObjects[i]->getShapes(&shape, 1); const PxBoxGeometry& boxGeom = static_cast(shape->getGeometry()); const PxVec3 minimum = c - boxGeom.halfExtents; const PxVec3 maximum = c + boxGeom.halfExtents; dstPts[0] = PxVec3(minimum.x, minimum.y, minimum.z); dstPts[1] = PxVec3(maximum.x, minimum.y, minimum.z); dstPts[2] = PxVec3(maximum.x, maximum.y, minimum.z); dstPts[3] = PxVec3(minimum.x, maximum.y, minimum.z); dstPts[4] = PxVec3(minimum.x, minimum.y, maximum.z); dstPts[5] = PxVec3(maximum.x, minimum.y, maximum.z); dstPts[6] = PxVec3(maximum.x, maximum.y, maximum.z); dstPts[7] = PxVec3(minimum.x, maximum.y, maximum.z); dstPts += 8; for(PxU32 j=0;j<12*3;j++) dstIndices[j] = gBoxIndices[j] + baseIndex; dstIndices += 12*3; baseIndex += 8; } regionData->mVerts = pts; regionData->mIndices = indices; regionData->mNbVerts = nbVerts; regionData->mNbTris = nbTris; } #endif } void Streamer::updateRegion(StreamRegion& region) { RegionData* regionData = region.mRegionData; if(gUpdateObjectsInRegion) { // Artificial update to trigger the tree refit & rebuild code const PxU32 nbObjects = regionData->mObjects.size(); for(PxU32 i=0;imObjects[i]->getGlobalPose(); regionData->mObjects[i]->setGlobalPose(pose); } } } void Streamer::removeRegion(StreamRegion& region) { PX_ASSERT(region.mRegionData); RegionData* regionData = region.mRegionData; #ifdef RENDER_SNIPPET delete [] regionData->mIndices; delete [] regionData->mVerts; #endif // Because we used static actors only we can just release all actors, they didn't move to other regions const PxU32 nbObjects = regionData->mObjects.size(); for(PxU32 i=0;imObjects[i]->release(); #ifdef USE_CUSTOM_PRUNER PX_ASSERT(regionData->mPrunerData); regionData->mPrunerData->mNbObjects-=nbObjects; PX_ASSERT(regionData->mPrunerData->mNbObjects==0); gAdapter.releasePruner(regionData->mPrunerData->mPrunerIndex); #endif delete region.mRegionData; region.mRegionData = NULL; } void Streamer::update(const PxVec3& playerPos) { const float activeAreaSize = mActiveAreaSize; mStreamingBounds = PxBounds3::centerExtents(playerPos, PxVec3(activeAreaSize)); const float worldSize = activeAreaSize*2.0f; const PxU32 nbCellsPerSide = mNbCellsPerSide; const float cellSize = worldSize/float(nbCellsPerSide); const float cellSizeX = cellSize; const float cellSizeZ = cellSize; const PxI32 x0 = PxI32(floorf(mStreamingBounds.minimum.x/cellSizeX)); const PxI32 z0 = PxI32(floorf(mStreamingBounds.minimum.z/cellSizeZ)); const PxI32 x1 = PxI32(ceilf(mStreamingBounds.maximum.x/cellSizeX)); const PxI32 z1 = PxI32(ceilf(mStreamingBounds.maximum.z/cellSizeZ)); // Generally speaking when streaming objects in and out of the game world, we want to first remove // old objects then add new objects (in this order) to give the system a chance to recycle removed // entries and use less resources overall. That's why we split the loop to add objects in two parts. // The first part below finds currently touched regions and updates their timestamp. for(PxI32 j=z0;j update its timestamp. PX_ASSERT(region.mKey==Key); region.mTimestamp = mTimestamp; updateRegion(region); } } } // This loop checks all regions in the system and removes the ones that are neither new // (mTimestamp==INVALID_ID) nor persistent (mTimestamp==current timestamp). { PxArray toRemove; // Delayed removal to avoid touching the hashmap while we're iterating it for(StreamingCache::Iterator iter = mStreamingCache.getIterator(); !iter.done(); ++iter) { if(iter->second.mTimestamp!=mTimestamp && iter->second.mTimestamp!=INVALID_ID) { removeRegion(iter->second); toRemove.pushBack(iter->second.mKey); } } const PxU32 nbToGo = toRemove.size(); for(PxU32 i=0;isecond.mCellBounds, gRed); #endif } void Streamer::render() { #ifdef RENDER_SNIPPET for(StreamingCache::Iterator iter = mStreamingCache.getIterator(); !iter.done(); ++iter) { const RegionData* data = iter->second.mRegionData; Snippets::renderMesh(data->mNbVerts, data->mVerts, data->mNbTris, data->mIndices, PxVec3(0.1f, 0.2f, 0.3f)); } #endif } static Streamer* gStreamer = NULL; #ifdef USE_CUSTOM_PRUNER static PxCustomSceneQuerySystem* gCustomSQ = NULL; #endif static bool gHasRaycastHit; static PxRaycastHit gRaycastHit; static PxVec3 gOrigin; void renderScene() { #ifdef RENDER_SNIPPET if(0) // Disabled, this is too slow { PxScene* scene; PxGetPhysics().getScenes(&scene,1); PxU32 nbActors = scene->getNbActors(PxActorTypeFlag::eRIGID_DYNAMIC | PxActorTypeFlag::eRIGID_STATIC); if(nbActors) { //printf("Rendering %d actors\n", nbActors); PxArray actors(nbActors); scene->getActors(PxActorTypeFlag::eRIGID_DYNAMIC | PxActorTypeFlag::eRIGID_STATIC, reinterpret_cast(&actors[0]), nbActors); Snippets::renderActors(&actors[0], static_cast(actors.size()), false, PxVec3(0.1f, 0.2f, 0.3f), NULL, true, false); } } else { if(gStreamer) gStreamer->render(); } if(gHasRaycastHit) { Snippets::DrawLine(gOrigin, gRaycastHit.position, gYellow); Snippets::DrawFrame(gRaycastHit.position, 1.0f); } else Snippets::DrawLine(gOrigin, gOrigin - PxVec3(0.0f, 100.0f, 0.0f), gYellow); const PxVec3 playerPos = computePlayerPos(gGlobalTime); const PxBounds3 playerBounds = PxBounds3::centerExtents(playerPos, PxVec3(gPlayerSize)); Snippets::DrawBounds(playerBounds, gYellow); // const PxBounds3 activeAreaBounds = PxBounds3::centerExtents(playerPos, PxVec3(gActiveAreaSize)); // Snippets::DrawBounds(activeAreaBounds, gGreen); if(gStreamer) gStreamer->renderDebug(); #endif } void initPhysics(bool /*interactive*/) { gFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, gAllocator, gErrorCallback); if(1) { gPvd = PxCreatePvd(*gFoundation); PxPvdTransport* transport = PxDefaultPvdSocketTransportCreate(PVD_HOST, 5425, 10); //gPvd->connect(*transport,PxPvdInstrumentationFlag::eALL); gPvd->connect(*transport,PxPvdInstrumentationFlag::ePROFILE); } gPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *gFoundation, PxTolerancesScale(), false, gPvd); gDispatcher = PxDefaultCpuDispatcherCreate(NUM_WORKER_THREADS); PxSceneDesc sceneDesc(gPhysics->getTolerancesScale()); sceneDesc.gravity = PxVec3(0.0f, -9.81f, 0.0f); sceneDesc.cpuDispatcher = gDispatcher; sceneDesc.filterShader = PxDefaultSimulationFilterShader; sceneDesc.staticStructure = PxPruningStructureType::eDYNAMIC_AABB_TREE; sceneDesc.dynamicStructure = PxPruningStructureType::eDYNAMIC_AABB_TREE; sceneDesc.dynamicTreeRebuildRateHint = gDynamicTreeRebuildRateHint; #ifdef USE_CUSTOM_PRUNER // For concurrent build steps we're going to use the new custom API in PxCustomSceneQuerySystem so we tell the system // to disable the built-in build-step & commit functions (which are otherwise executed in fetchResults). sceneDesc.sceneQueryUpdateMode = gUseConcurrentBuildSteps ? PxSceneQueryUpdateMode::eBUILD_DISABLED_COMMIT_DISABLED : PxSceneQueryUpdateMode::eBUILD_ENABLED_COMMIT_ENABLED; // Create our custom scene query system and tell PxSceneDesc about it PxCustomSceneQuerySystem* customSQ = PxCreateCustomSceneQuerySystem(sceneDesc.sceneQueryUpdateMode, 0x0102030405060708, gAdapter, gUseTreeOfPruners); if(customSQ) { gAdapter.createPruners(customSQ); sceneDesc.sceneQuerySystem = customSQ; gCustomSQ = customSQ; } #endif gScene = gPhysics->createScene(sceneDesc); PxPvdSceneClient* pvdClient = gScene->getScenePvdClient(); if(pvdClient) { pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONSTRAINTS, true); pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONTACTS, true); pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES, true); } gMaterial = gPhysics->createMaterial(0.5f, 0.5f, 0.6f); gStreamer = new Streamer(gActiveAreaSize, gNbCellsPerSide); } #ifdef USE_CUSTOM_PRUNER class TaskBuildStep : public PxLightCpuTask { public: TaskBuildStep() : PxLightCpuTask(), mIndex(INVALID_ID) {} virtual void run() { PX_SIMD_GUARD gCustomSQ->customBuildstep(mIndex); } virtual const char* getName() const { return "TaskBuildStep"; } PxU32 mIndex; }; class TaskWait: public PxLightCpuTask { public: TaskWait(SnippetUtils::Sync* syncHandle) : PxLightCpuTask(), mSyncHandle(syncHandle) {} virtual void run() {} PX_INLINE void release() { PxLightCpuTask::release(); SnippetUtils::syncSet(mSyncHandle); } virtual const char* getName() const { return "TaskWait"; } private: SnippetUtils::Sync* mSyncHandle; }; static void concurrentBuildSteps() { const PxU32 nbPruners = gCustomSQ->startCustomBuildstep(); PX_UNUSED(nbPruners); { PX_ASSERT(nbPruners==gNbPruners); SnippetUtils::Sync* buildStepsComplete = SnippetUtils::syncCreate(); SnippetUtils::syncReset(buildStepsComplete); TaskWait taskWait(buildStepsComplete); TaskBuildStep taskBuildStep[gNbPruners]; for(PxU32 i=0; igetTaskManager(); tm->resetDependencies(); tm->startSimulation(); taskWait.setContinuation(*tm, NULL); for(PxU32 i=0; ifinishCustomBuildstep(); } #endif static PxTime gTime; void stepPhysics(bool /*interactive*/) { if(gStreamer) { const PxVec3 playerPos = computePlayerPos(gGlobalTime); gStreamer->update(playerPos); gOrigin = playerPos+PxVec3(0.0f, 10.0f, 0.0f); } const PxU32 nbActors = gScene->getNbActors(PxActorTypeFlag::eRIGID_DYNAMIC | PxActorTypeFlag::eRIGID_STATIC); const float dt = 1.0f/60.0f; gTime.getElapsedSeconds(); { gScene->simulate(dt); gScene->fetchResults(true); #ifdef USE_CUSTOM_PRUNER if(gUseConcurrentBuildSteps) concurrentBuildSteps(); #endif } PxTime::Second time = gTime.getElapsedSeconds()*1000.0; // Ignore first frames to skip the cost of creating all the initial regions static PxU32 nbIgnored = 16; static PxU32 nbCalls = 0; static PxF64 totalTime = 0; static PxF64 peakTime = 0; if(nbIgnored) nbIgnored--; else { nbCalls++; totalTime+=time; if(time>peakTime) peakTime = time; if(1) printf("%d: time: %f ms | avg: %f ms | peak: %f ms | %d actors\n", nbCalls, time, totalTime/PxU64(nbCalls), peakTime, nbActors); } gTime.getElapsedSeconds(); { PxRaycastBuffer buf; gScene->raycast(gOrigin, PxVec3(0.0f, -1.0f, 0.0f), 100.0f, buf); gHasRaycastHit = buf.hasBlock; if(buf.hasBlock) gRaycastHit = buf.block; } time = gTime.getElapsedSeconds()*1000.0; if(0) printf("raycast time: %f us\n", time*1000.0); gGlobalTime += dt; } void cleanupPhysics(bool /*interactive*/) { delete gStreamer; PX_RELEASE(gMaterial); PX_RELEASE(gScene); PX_RELEASE(gDispatcher); PX_RELEASE(gPhysics); if(gPvd) { PxPvdTransport* transport = gPvd->getTransport(); PX_RELEASE(gPvd); PX_RELEASE(transport); } PX_RELEASE(gFoundation); printf("SnippetMultiPruners done.\n"); } void keyPress(unsigned char /*key*/, const PxTransform& /*camera*/) { } static void runWithoutRendering() { static const PxU32 frameCount = gNbFramesToSimulate; initPhysics(false); for(PxU32 i=0; i