Files
XCEngine/engine/third_party/physx/snippets/snippetmultipruners/SnippetMultiPruners.cpp

884 lines
29 KiB
C++

// 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 <ctype.h>
#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<PxRigidStatic*> 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;i<gNbPruners;i++)
mPrunerData[i].mPrunerIndex = customSQ->addPruner(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<gNbPruners;i++)
{
if(mPrunerData[i].mNbObjects==0)
return &mPrunerData[i];
}
PX_ASSERT(0);
return NULL;
}
// This is called by the streaming code to release a pruner when a region is deleted
void releasePruner(PxU32 index)
{
PX_ASSERT(mPrunerData[index].mNbObjects==0);
mPrunerData[index].mNbObjects=0;
}
// This is called by PxCustomSceneQuerySystem to assign a pruner index to a new actor/shape
virtual PxU32 getPrunerIndex(const PxRigidActor& actor, const PxShape& /*shape*/) const
{
const PrunerData* prunerData = reinterpret_cast<const PrunerData*>(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<PxU64, StreamRegion> 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;j<nbExtraObjects;j++)
{
PxVec3 c = center;
c.x += gRandom.randomFloat() * extents.x * (2.0f - objectScale);
c.y += fabsf(gRandom.randomFloat()) * coeffY;
c.z += gRandom.randomFloat() * extents.z * (2.0f - objectScale);
PxRigidStatic* actor = PxCreateStatic(*gPhysics, PxTransform(c), *shape);
actors[j] = actor;
regionData->mObjects.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<PxActor**>(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;i<nbActors;i++)
{
const PxVec3 c = regionData->mObjects[i]->getGlobalPose().p;
PxShape* shape;
regionData->mObjects[i]->getShapes(&shape, 1);
const PxBoxGeometry& boxGeom = static_cast<const PxBoxGeometry&>(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;i<nbObjects;i++)
{
const PxTransform pose = regionData->mObjects[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;i<nbObjects;i++)
regionData->mObjects[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<z1;j++)
{
for(PxI32 i=x0;i<x1;i++)
{
const PxU64 Key = (PxU64(i)<<32)|PxU64(PxU32(j));
StreamRegion& region = mStreamingCache[Key];
if(region.mTimestamp!=INVALID_ID)
{
// This region was already active => 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<PxU64> 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;i<nbToGo;i++)
{
bool b = mStreamingCache.erase(toRemove[i]);
PX_ASSERT(b);
PX_UNUSED(b);
}
}
// Finally we do our initial loop again looking for new regions (mTimestamp==INVALID_ID) and actually add them.
for(PxI32 j=z0;j<z1;j++)
{
for(PxI32 i=x0;i<x1;i++)
{
const PxU64 Key = (PxU64(i)<<32)|PxU64(PxU32(j));
StreamRegion& region = mStreamingCache[Key];
if(region.mTimestamp==INVALID_ID)
{
// New entry
region.mKey = Key;
region.mTimestamp = mTimestamp;
region.mCellBounds.minimum = PxVec3(float(i)*cellSizeX, 0.0f, float(j)*cellSizeZ);
region.mCellBounds.maximum = PxVec3(float(i+1)*cellSizeX, 0.0f, float(j+1)*cellSizeZ);
addRegion(region);
}
}
}
mTimestamp++;
}
void Streamer::renderDebug()
{
#ifdef RENDER_SNIPPET
Snippets::DrawBounds(mStreamingBounds, gGreen);
for(StreamingCache::Iterator iter = mStreamingCache.getIterator(); !iter.done(); ++iter)
Snippets::DrawBounds(iter->second.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<PxRigidActor*> actors(nbActors);
scene->getActors(PxActorTypeFlag::eRIGID_DYNAMIC | PxActorTypeFlag::eRIGID_STATIC, reinterpret_cast<PxActor**>(&actors[0]), nbActors);
Snippets::renderActors(&actors[0], static_cast<PxU32>(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; i<gNbPruners; i++)
taskBuildStep[i].mIndex = i;
PxTaskManager* tm = gScene->getTaskManager();
tm->resetDependencies();
tm->startSimulation();
taskWait.setContinuation(*tm, NULL);
for(PxU32 i=0; i<gNbPruners; i++)
taskBuildStep[i].setContinuation(&taskWait);
taskWait.removeReference();
for(PxU32 i=0; i<gNbPruners; i++)
taskBuildStep[i].removeReference();
SnippetUtils::syncWait(buildStepsComplete);
SnippetUtils::syncRelease(buildStepsComplete);
}
gCustomSQ->finishCustomBuildstep();
}
#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<frameCount; i++)
stepPhysics(false);
cleanupPhysics(false);
}
int snippetMain(int, const char*const*)
{
printf("Multi Pruners snippet.\n");
#ifdef RENDER_SNIPPET
if(gEnableRendering)
{
extern void renderLoop();
renderLoop();
}
else
runWithoutRendering();
#else
runWithoutRendering();
#endif
return 0;
}