884 lines
29 KiB
C++
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;
|
|
}
|
|
|