Files
PhysX4.1/physx/samples/samplelargeworld/ChunkLoader.cpp
2025-11-28 23:13:44 +05:30

1237 lines
34 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-2021 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "PxTkRandom.h"
#include "RenderMeshActor.h"
#include "SampleDirManager.h"
#include "ChunkLoader.h"
#include "SampleLargeWorld.h"
#include <algorithm>
#include "MeshBuilder.h"
#include "PsMathUtils.h"
#include "PxTkFile.h"
#include "wavefront.h"
#include "PsString.h"
#include "PxScene.h"
#include "geometry/PxGeometryQuery.h"
#include "extensions/PxCollectionExt.h"
#ifdef _MSC_VER
#pragma warning(disable:4305)
#endif
using namespace PxToolkit;
using namespace physx;
using namespace shdfnd;
#define MATERIAL_ID 0x01
#define CHUNK_DEBRIS_NUM 1
#define ADD_TREES 1
#define ADD_WIND_MILL 1
static const ChunkID FARM_CHUNKID = {{4, 3}};
static const ChunkID CITY_CHUNKID = {{4, 2}};
static const ChunkID FORTRESS_CHUNKID = {{1, 2}};
static const ChunkID FOREST_CHUNKID = {{5, 3}};
static const char* gFarmName = "Farm";
static const char* gCityName = "City";
static const char* gTreeName = "Tree";
static const float gFortrestScale = 0.05f;
static const PxBounds3 gCityBounds( PxVec3(927.65833,17.403145,452.00970), PxVec3(1119.6967,256.83026,576.55121));
static const PxBounds3 gFarmBounds( PxVec3(1001.8151,9.4852743,732.46741), PxVec3(1036.2266,16.729010,787.07898));
static const PxBounds3 gFortressBounds( PxVec3(-584.924011,0.000122,-586.000305), PxVec3(584.924011,1875.750488,585.999939));
static const PxVec3 gCityPos(1024.0000,17.414299,512.00000);
static const PxVec3 gFarmPos(1024.0000, 9.4852762, 768.0f);
static const PxVec3 gFortressPos(256.00000,81.219383,512.000000);
static PX_INLINE ChunkID getChunkIdFromPosition(const PxVec3& position, CoordType terrainDim, PxF32 terrainSize)
{
CoordType xCoor = (CoordType)((position.x + terrainSize*0.5f) / terrainSize);
xCoor = PxClamp((CoordType)xCoor, (CoordType)0, (CoordType)(terrainDim -1));
CoordType yCoor = (CoordType)((position.z + terrainSize*0.5f) / terrainSize);
yCoor = PxClamp((CoordType)yCoor, (CoordType)0, (CoordType)(terrainDim -1));
ChunkID res;
res.coord.x = xCoor;
res.coord.y = yCoor;
return res;
}
static PxI32 getFileSize(const char* name)
{
if(!name)
return 0;
SampleFramework::File* fp = NULL;
PxToolkit::fopen_s(&fp, name, "rb");
if( !fp )
return 0;
fseek(fp, 0, SEEK_END);
PxI32 filesize = (PxI32)ftell(fp);
fclose(fp);
return filesize;
}
void SampleLargeWorld::addWindMills(const PxTriangleMeshGeometry& meshGeom, BasicRandom& random, const PxBounds3& bound, PxCollection& outCollection)
{
PxPhysics& physics = getPhysics();
PxMaterial& material = getDefaultMaterial();
const PxReal millHalfHeight = 20.0f;
const PxReal millRadius =9.0f;
const PxReal fanRadius = 22.0f;
const PxReal fanWidth = 3.0f;
const PxReal fanThin = 0.5f;
const PxReal axisHalfHeight = 5.0f;
const PxReal axisRadius = 1.0f;
const PxVec3 millExtent(
millRadius*0.5f + axisHalfHeight + axisRadius + fanThin,
fanRadius*0.5f + millRadius + millHalfHeight,
millRadius*0.5f + axisHalfHeight + axisRadius + fanThin
) ;
PxBounds3 millBornBound = bound;
millBornBound.fattenFast(-millRadius*2.0f);
//Store mill position to check if new mill is overlaped with old mills
PxVec3 millPts[5];
PxU32 mills = 0, retryNum = 1;
do
{
//Raycast against terrain from random pos to get mill foot point
PxVec3 rayStart(
random.rand( millBornBound.minimum.x, millBornBound.maximum.x),
bound.maximum.y,
random.rand( millBornBound.minimum.z, millBornBound.maximum.z));
PxReal maxDist = 2*(bound.maximum.y - bound.minimum.y);
PxRaycastHit hit;
PxU32 hitsNum = PxGeometryQuery::raycast(
rayStart, PxVec3(0.0f,-1.0f,0.0f),
meshGeom, PxTransform(PxIdentity), maxDist,
PxHitFlag::ePOSITION, 1, &hit);
if( hitsNum )
{
PxVec3 millPt = hit.position;
millPt.y += millHalfHeight - millRadius;
PxVec3 axisPt = millPt;
axisPt.y += millHalfHeight + millRadius;
axisPt.x += millRadius;
PxVec3 fanPt = axisPt;
fanPt.x += axisHalfHeight;
//Roughly check if fans of mill collided with terrain
PxQueryFilterData filter( PxQueryFlag::eANY_HIT);
PxOverlapBuffer buf;
PxBoxGeometry fanBox(fanThin, fanRadius, fanRadius);
if(PxGeometryQuery::overlap(fanBox, PxTransform(fanPt), meshGeom, PxTransform(PxIdentity)))
{
continue;
}
PxBounds3 curBounds = PxBounds3::centerExtents( millPt, millExtent);
bool needRegenerate = false;
for(PxU32 t = 0; t < mills; t++)
{
PxBounds3 otherBounds = PxBounds3::centerExtents( millPts[t], millExtent);
if( otherBounds.intersects(curBounds) )
needRegenerate = true;
}
if(needRegenerate)
{
continue;
}
millPts[mills] = millPt;
millPts[mills].x = millPts[mills].x + millRadius - millExtent.x;
++mills;
PxCapsuleGeometry millGeom(millRadius, millHalfHeight);
PxCapsuleGeometry axisGeom(axisRadius, axisHalfHeight);
PxBoxGeometry fanGeom(fanRadius, fanWidth, fanThin);
PxQuat verticleRote( PxHalfPi, PxVec3(0.0f,0.0f,1.0f));
PxQuat y90Rote( PxHalfPi, PxVec3(0.0f,1.0f,0.0f));
PxRigidStatic* millBody = physics.createRigidStatic(PxTransform(millPt));
PxShape* millShape0 = PxRigidActorExt::createExclusiveShape(*millBody, millGeom, material);
millShape0->setLocalPose(PxTransform(verticleRote));
PxShape* millShape1 = PxRigidActorExt::createExclusiveShape(*millBody, axisGeom, material);
millShape1->setLocalPose(PxTransform(axisPt - millPt));
PxRigidDynamic* fan = physics.createRigidDynamic(PxTransform(fanPt));
PxShape* fanShape0 = PxRigidActorExt::createExclusiveShape(*fan, fanGeom, material);
fanShape0->setLocalPose(PxTransform(PxIdentity));
PxShape* fanShape1 = PxRigidActorExt::createExclusiveShape(*fan, fanGeom, material);
fanShape1->setLocalPose(PxTransform(verticleRote));
//Disable the collision between fans and other objects in scene
setCollisionGroup(fan, FAN_COLLISION_GROUP);
PxRevoluteJoint* joint = PxRevoluteJointCreate(physics,
millBody, PxTransform(fanPt - millPt),
fan, PxTransform(PxVec3(0), y90Rote));
joint->setDriveVelocity(10.0f);
joint->setRevoluteJointFlag(PxRevoluteJointFlag::eDRIVE_ENABLED, true);
outCollection.add(*millBody);
outCollection.add(*fan);
outCollection.add(*joint);
}
}while( mills < retryNum);
}
void SampleLargeWorld::addTrees(const PxTriangleMeshGeometry& meshGeom, const PxBounds3& bound, PxCollection& outCollection)
{
const char* filename = getSampleMediaFilename("tree.obj");
const char* filenameCooked = getSampleOutputFilePath("tree.obj", "");
PxReal maxDist = 2*(bound.maximum.y - bound.minimum.y);
for(PxU32 i = (PxU32)bound.minimum.z; i < (PxU32)bound.maximum.z; i += 80)
{
PxVec3 rayStart(bound.minimum.x, bound.maximum.y, (PxReal)i);
PxRaycastBuffer hit;
PxQueryFilterData filter(PxQueryFlag::eSTATIC);
PxScene& scene = getActiveScene();
bool ret = false;
{
PxSceneReadLock scopedLock(scene);
ret = scene.raycast(rayStart, PxVec3(0.0f,-1.0f,0.0f), maxDist, hit, PxHitFlag::ePOSITION, filter);
}
if( ret )
{
MeshBuilder::addObjMeshToPxCollection(
getPhysics(), getCooking(), getDefaultMaterial(),
filename, filenameCooked,
PxTransform(hit.block.position, PxQuat(-PxHalfPi, PxVec3(1,0,0))), PxVec3(200, 200, 200),
outCollection, gTreeName);
}
}
}
static void flattenChunk(SampleArray<PxVec3>& terrainVertices, const PxBounds3& bound, PxReal dataY)
{
PxBounds3 bound1 = bound;
PxReal deltaX = bound1.maximum.x - bound1.minimum.x;
PxReal deltaZ = bound1.maximum.z- bound1.minimum.z;
PxReal delta = deltaX < deltaZ ? deltaX : deltaZ;
bound1.fattenFast(delta/32);
const PxU32 size = terrainVertices.size();
for(PxU32 i = 0; i < size; ++i)
{
if((terrainVertices[i].x <= bound1.maximum.x && terrainVertices[i].x >= bound1.minimum.x)
&& (terrainVertices[i].z <= bound1.maximum.z && terrainVertices[i].z >= bound1.minimum.z))
{
terrainVertices[i].y = dataY;
}
}
}
void SampleLargeWorld::addCity(SampleArray<PxVec3>& terrainVertices, const PxBounds3& bound, PxCollection& outCollection)
{
const char* filename = getSampleMediaFilename("city.obj");
const char* filenameCooked = getSampleOutputFilePath("city.obj", "");
MeshBuilder::addObjMeshToPxCollection(
getPhysics(), getCooking(), getDefaultMaterial(),
filename, filenameCooked,
PxTransform(gCityPos, PxQuat(-PxHalfPi, PxVec3(1,0,0))), PxVec3(100, 100, 120),
outCollection, gCityName);
}
void SampleLargeWorld::addFarm(SampleArray<PxVec3>& terrainVertices, const PxBounds3& bound, PxCollection& outCollection)
{
const char* filename = getSampleMediaFilename("barn.obj");
const char* filenameCooked = getSampleOutputFilePath("barn.obj", "");
MeshBuilder::addObjMeshToPxCollection(
getPhysics(), getCooking(), getDefaultMaterial(),
filename, filenameCooked,
PxTransform(gFarmPos, PxQuat(PxPi, PxVec3(1,0,0))), PxVec3(100, 100, 100),
outCollection, gFarmName);
}
void SampleLargeWorld::addFortress(SampleArray<PxVec3>& terrainVertices, const PxBounds3& bound, PxCollection& outCollection)
{
const char* pathname = getSampleMediaFilename("WallTower.repx");
MeshBuilder* mb = SAMPLE_NEW(MeshBuilder)(*this, pathname );
const PxQuat q = PxShortestRotation(PxVec3(0.0f, 0.0f, 1.0f), PxVec3(0.0f, 1.0f, 0.0f));
mb->addRepXToPxCollection( PxTransform(gFortressPos, q), PxVec3(0.05f, 0.05f, 0.05f), outCollection);
DELETESINGLE( mb );
}
PxTriangleMesh* SampleLargeWorld::generateTriMesh(const SampleArray<PxVec3>* vertices, const SampleArray<PxU32>* indices)
{
//Cooking mesh
PxTriangleMeshDesc meshDesc;
meshDesc.points.count = vertices->size();
meshDesc.triangles.count = indices->size()/3;
meshDesc.points.stride = sizeof(PxVec3);
meshDesc.triangles.stride = sizeof(PxU32)*3;
meshDesc.points.data = &(*vertices)[0];
meshDesc.triangles.data = &(*indices)[0];
meshDesc.flags = PxMeshFlags(0);
PxDefaultMemoryOutputStream stream;
bool ok = getCooking().cookTriangleMesh(meshDesc, stream);
if( !ok )
return NULL;
PxDefaultMemoryInputData rb(stream.getData(), stream.getSize());
return getPhysics().createTriangleMesh(rb);
}
void SampleLargeWorld::createChunk(CoordType coordX, CoordType coordY, PxCollection& outCollection)
{
CoordType dim = binData.mDim;
SampleArray<SampleArray<PxVec3> >& terrainVertices = binData.mTerrainVertices;
SampleArray<SampleArray<PxU32> >& terrainIndices = binData.mTerrainIndices;
SampleArray<PxVec3> tmpV;
SampleArray<PxU32> tmpI;
SampleArray<PxVec3>* vertices;
SampleArray<PxU32>* indices;
PxVec3 xAxis( (coordX / dim ) * CHUNK_WIDTH * dim - 0.5f * CHUNK_WIDTH, 0.0f, 0.0f );
PxVec3 zAxis( 0.0f, 0.0f, ( coordY / dim ) * CHUNK_WIDTH * dim - 0.5f * CHUNK_WIDTH );
CoordType x = coordX %( dim << 1 ), z = coordY % ( dim <<1 );
PxVec3 xRevert(1.0f), zRevert(1.0f);
bool needRevertMesh = false;
bool useRawDirectly = true;
//The terrain template is 16*16, if chunk is out of the range, use mirror to create new one
if(coordX >= dim || coordY >= dim)
useRawDirectly = false;
if( useRawDirectly )
{
vertices = &terrainVertices[coordY * dim + coordX];
indices = &terrainIndices[coordY * dim + coordX];
}
else //Just use mirror to create new terrain, so terrain can be expand to infinite size
{
if( x >= dim )
{
x = x % dim;
xRevert.x = -1.0f;
needRevertMesh = !needRevertMesh;
x = dim - x - 1;
}
if( z >= dim )
{
z = z % dim;
zRevert.z = -1.0f;
needRevertMesh = !needRevertMesh;
z = dim - z - 1;
}
SampleArray<PxVec3>& templateV = terrainVertices[z*dim + x];
for( PxU32 v = 0; v < templateV.size(); v++)
{
PxVec3 vert = templateV[v];
if(xRevert.x < 0.0f)
{
vert.x = CHUNK_WIDTH * dim - 0.5f * CHUNK_WIDTH - templateV[v].x + xAxis.x;
}
else
{
vert.x = templateV[v].x - -0.5f * CHUNK_WIDTH + xAxis.x;
}
if(zRevert.z < 0.0f)
{
vert.z = CHUNK_WIDTH * dim - 0.5f * CHUNK_WIDTH - templateV[v].z + zAxis.z;
}
else
{
vert.z = templateV[v].z - -0.5f * CHUNK_WIDTH + zAxis.z;
}
tmpV.pushBack(vert);
}
vertices = &tmpV;
if(needRevertMesh)
{
SampleArray<PxU32>& templateI = terrainIndices[z*dim + x];
for(PxU32 d = 0; d < templateI.size(); d += 3)
{
tmpI.pushBack(templateI[d]);
tmpI.pushBack(templateI[d+2]);
tmpI.pushBack(templateI[d+1]);
}
indices = &tmpI;
}
else
indices = &terrainIndices[z*dim + x];
}
//Both pose and scale are identity, so local bounds == global bounds
PxBounds3 bound = PxBounds3::empty();
for( SampleArray<PxVec3>::Iterator iter = vertices->begin();
iter != vertices->end(); iter++ )
{
bound.include( *iter );
}
ChunkID id;
id.coord.x = coordX;
id.coord.y = coordY;
BasicRandom random(id.id);
PxBounds3 objBounds[3];
PxF32 flatHeight[3];
const float flattenScale = 1.1f;
objBounds[0] = gCityBounds;
objBounds[1] = gFarmBounds;
objBounds[1].fattenFast(1.5f);
PxBounds3 fortressScaledBound( gFortressBounds.minimum * gFortrestScale, gFortressBounds.maximum * gFortrestScale );
objBounds[2] = PxBounds3::centerExtents( gFortressPos + fortressScaledBound.getCenter(), fortressScaledBound.getExtents() * flattenScale);
flatHeight[0] = gCityPos.y;
flatHeight[1] = gFarmPos.y;
flatHeight[2] = gFortressPos.y;
for(PxU32 i = 0; i < 3; i++)
{
if(bound.intersects(objBounds[i]))
{
flattenChunk(*vertices, objBounds[i], flatHeight[i]);
}
}
bool isEmpty = false;
if(id.id == FARM_CHUNKID.id)
{
addFarm(*vertices, bound, outCollection);
}
else if(id.id == CITY_CHUNKID.id)
{
addCity(*vertices, bound, outCollection);
}
else if(id.id == FORTRESS_CHUNKID.id)
{
addFortress(*vertices, bound, outCollection);
}
else
{
isEmpty = true;
}
PxTriangleMesh* triangleMesh = generateTriMesh(vertices, indices);
if( !triangleMesh )
return;
PxTriangleMeshGeometry meshGeom(triangleMesh);
PxMaterial& material = getDefaultMaterial();
//Create terrain actor
PxRigidStatic* actor = PxCreateStatic(getPhysics(), PxTransform(PxIdentity), meshGeom, material);
//Enable ccd for terrain
PxShape* meshShape = getShape(*actor);
SampleLargeWorld::setCCDActive(*meshShape);
meshShape->setFlag(PxShapeFlag::eVISUALIZATION, false);
if(id.id == FOREST_CHUNKID.id)
{
addTrees(meshGeom, bound, outCollection);
isEmpty = false;
}
if(isEmpty)
{
#if ADD_WIND_MILL
addWindMills(meshGeom, random, bound, outCollection);
#endif
}
outCollection.add(*actor);
return;
}
BackgroundLoader::BackgroundLoader(SampleLargeWorld& sample, CoordType halfRange, CoordType terrainRange, PxF32 chunkWidth)
: mSampleLargeWorld(&sample)
, mPhysics(sample.getPhysics())
, mScene(sample.getActiveScene())
, mMaterial(sample.getDefaultMaterial())
, mHalfRange(halfRange)
, mTerrainRange(terrainRange)
, mChunkWidth(chunkWidth)
{
mCurChunkId.id = 0;
mDiskIOTime = 0;
mPhyXStreamTime = 0;
mGraphicStreamTime = 0;
mLoaderThread = PX_NEW(Thread)(loaderThread, this, "LoaderThread");
mLoaderThread->start(0);
mSr = PxSerialization::createSerializationRegistry(mPhysics);
}
BackgroundLoader::~BackgroundLoader()
{
mLoaderThread->signalQuit();
mRequestReady.set();
mLoaderThread->waitForQuit();
delete mLoaderThread;
deleteLoadedRenderQueue();
deleteCollections();
mSr->release();
}
void BackgroundLoader::onTick()
{
//Use local queue here to minimize the lock time, prevent blocking loader thread.
std::vector<ChunkCommand> theCmdQueue;
std::vector<DeferredLoadedRenderData> theLoadedRenderDataQueue;
mQueueLock.lock();
theCmdQueue = mChunkCmds;
theLoadedRenderDataQueue = mLoadedRenderDataQueue;
mLoadedRenderDataQueue.clear();
mChunkCmds.clear();
mQueueLock.unlock();
size_t cmdQueueSize = theCmdQueue.size();
if(cmdQueueSize == 0)
return;
//filter the same chunk with different commands
for(PxU32 i = 0; i < cmdQueueSize; ++i)
{
ChunkCommand cmdQueue = theCmdQueue[i];
bool isOutdatedCommand = false;
for(PxU32 j = i + 1; j < cmdQueueSize && !isOutdatedCommand; ++j)
{
isOutdatedCommand = (cmdQueue.id == theCmdQueue[j].id);
}
if(isOutdatedCommand)
continue;
ChunkCommandType::Enum type = cmdQueue.type;
if(type == ChunkCommandType::eAdd)
{
addReadyChunkToScene(cmdQueue.id);
}
else if(type == ChunkCommandType::eRemove)
{
destroyChunk(cmdQueue.id);
}
}
Ps::Time localTimer;
for(PxU32 i = 0; i < theLoadedRenderDataQueue.size(); i++)
{
DeferredLoadedRenderData& cq = theLoadedRenderDataQueue[i];
RenderBaseActor* renderMesh = mSampleLargeWorld->createRenderMeshFromRawMesh( cq);
//Link physic actor to render actor
cq.shape->userData = renderMesh;
{
PxSceneReadLock scopedLock(mSampleLargeWorld->getActiveScene());
renderMesh->setPhysicsShape(cq.shape, cq.shape->getActor());
renderMesh->setEnableCameraCull(true);
PxTriangleMeshGeometry geometry;
cq.shape->getTriangleMeshGeometry(geometry);
renderMesh->setMeshScale(geometry.scale);
}
PxVec3 *verts = const_cast<PxVec3*>(cq.mVerts);
SAMPLE_FREE(verts);
PxU32 *indices = const_cast<PxU32*>(cq.mIndices);
SAMPLE_FREE(indices);
PxReal *normals = (PxReal*)const_cast<PxVec3*>(cq.mVertexNormals);
SAMPLE_FREE(normals);
PxReal* uvs = const_cast<PxReal*>(cq.mUVs);
SAMPLE_FREE(uvs);
}
Ps::Time::Second timeCost = localTimer.getElapsedSeconds();
mLoaderStatusLock.lockWriter();
mGraphicStreamTime += timeCost;
mLoaderStatusLock.unlockWriter();
{
PxSceneWriteLock scopedLock(mSampleLargeWorld->getActiveScene());
for(PxU32 i = 0; i < mRemovingActors.size(); i++)
{
mRemovingActors[i]->release();
}
}
mRemovingActors.clear();
}
void BackgroundLoader::updateChunk(const PxVec3& cameraPos)
{
ChunkID id = getChunkIdFromPosition(cameraPos, mTerrainRange, mChunkWidth);
if( id.id == mCurChunkId.id)
return;
mLocalBounds = PxBounds3::centerExtents( PxVec3(id.coord.x*mChunkWidth, 0.0f, id.coord.y*mChunkWidth), PxVec3(mChunkWidth));
updateDynamicChunkId();
mCurChunkId = id;
CoordType x = PxClamp(id.coord.x, (CoordType)mHalfRange, (CoordType)(mTerrainRange - mHalfRange));
CoordType y = PxClamp(id.coord.y, (CoordType)mHalfRange, (CoordType)(mTerrainRange - mHalfRange));
std::vector<ChunkID> requestQueue;
for( CoordType i = PxMax( (CoordType)0, (CoordType)(x - mHalfRange) ); i <= PxMin( (CoordType)N2, (CoordType)(x + mHalfRange)); i++ )
{
for( CoordType j = PxMax( (CoordType)0, (CoordType)(y - mHalfRange)); j <= PxMin( (CoordType)N2, (CoordType)(y + mHalfRange)); j++ )
{
ChunkID id;
id.coord.x = i;
id.coord.y = j;
requestQueue.push_back(id);
}
}
mQueueLock.lock();
mRequestQueue = requestQueue;
mQueueLock.unlock();
mRequestReady.set();
}
static const char* getPlatformName()
{
#if PX_X86
return "PC32";
#elif PX_X64
return "PC64";
#elif PX_ARM_FAMILY
return "ARM";
#else
return "";
#endif
}
const char* BackgroundLoader::getPathname(ChunkID id)
{
char tmpFilename[256];
sprintf(tmpFilename, "chunk_%d_%d_%s.bin", id.coord.x, id.coord.y, getPlatformName());
return mSampleLargeWorld->getSampleOutputFilePath(tmpFilename,"");
}
void BackgroundLoader::serialize(PxCollection* collection, ChunkID id)
{
const char* theBinPath = getPathname( id );
SampleFramework::File* fp = NULL;
PxToolkit::fopen_s(&fp, theBinPath, "rb");
if( fp )
{
fclose(fp);
return;
}
PxCollection* theExtRef = PxCreateCollection();
theExtRef->add(mMaterial, MATERIAL_ID);
PxDefaultMemoryOutputStream memBuf;
PxSerialization::complete(*collection, *mSr, theExtRef);
bool bRet = PxSerialization::serializeCollectionToBinary(memBuf, *collection, *mSr, theExtRef, true );
PX_ASSERT(bRet);
PX_UNUSED(bRet);
PxDefaultFileOutputStream s( getPathname( id ) );
Ps::Time localTimer;
localTimer.getElapsedSeconds();
s.write( memBuf.getData(), memBuf.getSize());
mDiskIOTimeCounter += localTimer.getElapsedSeconds();
theExtRef->release();
}
//PhysX support buffer inserting actor, so no care about whether physic is simulating here
void BackgroundLoader::addReadyChunkToScene(ChunkID id)
{
PxCollection *theCollection = NULL;
{
shdfnd::Mutex::ScopedLock al( mCollectionLock );
CollectionIdMap::iterator it = mCollectionIdMap.find(id);
if(it == mCollectionIdMap.end() || it->second.addToScene )
{
return;
}
theCollection = it->second.collection;
if( !theCollection )
return;
it->second.addToScene = true;
}
PxU32 count = theCollection->getNbObjects();
for(PxU32 i = 0; i < count; i++)
{
PxBase *object = &theCollection->getObject(i);
PX_ASSERT(object);
PxType theType = object->getConcreteType();
if( theType == PxConcreteType::eRIGID_DYNAMIC )
{
PxRigidActor *actor = static_cast<PxRigidActor*>(object);
mSampleLargeWorld->createRenderObjectsFromActor(actor);
}
else if(theType == PxConcreteType::eRIGID_STATIC)
{
PxRigidStatic* actor = static_cast<PxRigidStatic*>(object);
PxShape* shape = getShape( *actor );
PxTriangleMeshGeometry geometry;
shape->getTriangleMeshGeometry(geometry);
//We create static render mesh in another loop
if(shape->getGeometryType() != PxGeometryType::eTRIANGLEMESH)
{
mSampleLargeWorld->createRenderObjectsFromActor(actor, NULL);
}
}
}
{
PxSceneWriteLock scopedLock(mSampleLargeWorld->getActiveScene());
mScene.addCollection(*theCollection);
}
//Melt the freezon important actors on this chunk
for(unsigned i = 0; i < mDyncActors.size(); ++i)
{
DynamicObjects* obj = mDyncActors[i];
if( obj->id.id == id.id && obj->isImportant )
{
PxSceneWriteLock scopedLock(mSampleLargeWorld->getActiveScene());
obj->actor->setRigidBodyFlag(PxRigidBodyFlag::eKINEMATIC, false);
}
}
}
//just removed the actors from scene, do not release them
void BackgroundLoader::destroyChunk(ChunkID id)
{
PxCollection *theCollection = NULL;
bool inScene = false;
void* memory = NULL;
{
shdfnd::Mutex::ScopedLock al(mCollectionLock);
CollectionIdMap::iterator it = mCollectionIdMap.find(id);
if( it == mCollectionIdMap.end() )
return;
inScene = it->second.addToScene;
theCollection = it->second.collection;
memory = it->second.memory;
mCollectionIdMap.erase(it);
}
if(inScene)
{
//remove objects from scene first
PxU32 count = theCollection->getNbObjects();
{
PxSceneWriteLock scopedLock(mSampleLargeWorld->getActiveScene());
for(PxU32 i = 0; i < count; i++)
{
PxBase *object = &theCollection->getObject(i);
PX_ASSERT(object);
PxType type = object->getConcreteType();
if( type == PxConcreteType::eRIGID_STATIC || type == PxConcreteType::eRIGID_DYNAMIC )
{
mScene.removeActor( *static_cast<PxActor*>(object) );
}
}
}
}
//serialize the objects when they are not used
serialize(theCollection, id);
PxCollectionExt::releaseObjects(*theCollection);
theCollection->release();
if(memory)
{
free(memory);
memory = NULL;
}
//Remove the un-important actors on this chunk, freeze the important actors
std::vector<DynamicObjects*>::iterator it = mDyncActors.begin();
while(it != mDyncActors.end())
{
PxRigidDynamic* actor = (*it)->actor;
PxVec3 actorPos;
{
PxSceneReadLock scopedLock(mSampleLargeWorld->getActiveScene());
actorPos = actor->getGlobalPose().p;
}
ChunkID theId = getChunkIdFromPosition(actorPos, mTerrainRange, mChunkWidth);
if( theId.id == id.id )
{
PxSceneWriteLock scopedLock(mSampleLargeWorld->getActiveScene());
if( !(*it)->isImportant )
{
mSampleLargeWorld->removeActor(actor);
mDyncActors.erase(it);
it = mDyncActors.begin();
continue;
}
else
{
actor->setRigidBodyFlag(PxRigidBodyFlag::eKINEMATIC, true);
}
}
++it;
}
}
static bool isActorNULL(DynamicObjects* o)
{
return o->actor == NULL;
}
void BackgroundLoader::updateDynamicChunkId()
{
for(unsigned i = 0; i < mDyncActors.size(); ++i)
{
DynamicObjects* obj = mDyncActors[i];
PxRigidDynamic* actor = mDyncActors[i] ->actor;
PxVec3 pos;
{
PxSceneReadLock scopedLock(mSampleLargeWorld->getActiveScene());
pos = actor->getGlobalPose().p;
}
//Remove the un-important which go out of current grid.
if(obj->isImportant == false && !mLocalBounds.contains(pos))
{
mRemovingActors.push_back(actor);
obj->actor = NULL;
}
else
{
ChunkID theId = getChunkIdFromPosition(pos, mTerrainRange, mChunkWidth);
obj->id.id = theId.id;
}
}
std::vector<DynamicObjects*>::iterator new_end = std::remove_if( mDyncActors.begin(), mDyncActors.end(), isActorNULL);
mDyncActors.erase( new_end, mDyncActors.end());
}
PxCollection* BackgroundLoader::loadChunk(ChunkID id)
{
{
shdfnd::Mutex::ScopedLock al(mCollectionLock);
if( mCollectionIdMap.find(id) != mCollectionIdMap.end() )
return NULL;
}
const char* theBinPath = getPathname( id );
PxCollection* theCollection = NULL;
void *theMemory = NULL;
Ps::Time localTimer;
SampleFramework::File* fp = NULL;
PxToolkit::fopen_s(&fp, theBinPath, "rb");
//If cannot find/deserilize the chunk file, create one collection
if( !fp )
{
theCollection = PxCreateCollection();
mSampleLargeWorld->createChunk( id.coord.x, id.coord.y, *theCollection );
}
else
{
localTimer.getElapsedSeconds();
PxU32 fileSize = getFileSize(theBinPath);
theMemory = malloc(fileSize + PX_SERIAL_FILE_ALIGN);
void* theMemoryA = (void*)((size_t(theMemory) + PX_SERIAL_FILE_ALIGN)&~(PX_SERIAL_FILE_ALIGN-1));
size_t numRead = fread(theMemoryA, 1, fileSize, fp);
fclose(fp);
mDiskIOTimeCounter += localTimer.getElapsedSeconds();
if(numRead != fileSize)
{
free(theMemory);
theMemory = NULL;
return NULL;
}
PxCollection* theExtRef = PxCreateCollection();
theExtRef->add(mMaterial, MATERIAL_ID);
theCollection = PxSerialization::createCollectionFromBinary(theMemoryA, *mSr, theExtRef);
theExtRef->release();
mPhyXStreamTimeCounter += localTimer.getElapsedSeconds();
}
CollectionMemory temp;
temp.collection = theCollection;
temp.memory = theMemory;
temp.addToScene = false;
{
shdfnd::Mutex::ScopedLock al(mCollectionLock);
mCollectionIdMap[id] = temp;
}
return theCollection;
}
void BackgroundLoader::deleteLoadedRenderQueue()
{
std::vector<DeferredLoadedRenderData> theLoadedRenderDataQueue;
mQueueLock.lock();
theLoadedRenderDataQueue = mLoadedRenderDataQueue;
mQueueLock.unlock();
for(PxU32 i = 0; i < theLoadedRenderDataQueue.size(); i++)
{
DeferredLoadedRenderData& cq = theLoadedRenderDataQueue[i];
PxVec3 *verts = const_cast<PxVec3*>(cq.mVerts);
SAMPLE_FREE(verts);
PxU32 *indices = const_cast<PxU32*>(cq.mIndices);
SAMPLE_FREE(indices);
PxReal *normals = (PxReal*)const_cast<PxVec3*>(cq.mVertexNormals);
SAMPLE_FREE(normals);
PxReal* uvs = const_cast<PxReal*>(cq.mUVs);
SAMPLE_FREE(uvs);
}
}
void BackgroundLoader::deleteCollections()
{
CollectionIdMap::iterator it = mCollectionIdMap.begin();
while(it!= mCollectionIdMap.end())
{
destroyChunk(it->first);
it = mCollectionIdMap.begin();
}
}
BackgroundLoader::DeferredLoadedRenderData* BackgroundLoader::createRawMeshFromObjMesh(const char* name, const PxTransform& pos, DeferredLoadedRenderData& rawMesh)
{
PxU32 materialID = 0;
const char* filename = NULL;
if(Ps::stricmp(name, gCityName) == 0)
{
filename = "city.obj";
materialID = MATERIAL_BUILDING;
}
else if(Ps::stricmp(name, gFarmName) == 0)
{
filename = "barn.obj";
materialID = MATERIAL_FARM;
}
else if(Ps::stricmp(name, gTreeName) == 0)
{
filename = "tree.obj";
materialID = MATERIAL_TREE;
}
if( mSampleLargeWorld->createRAWMeshFromObjMesh( filename, pos, materialID, rawMesh ) != NULL )
return &rawMesh;
else
return NULL;
}
void* BackgroundLoader::loaderThread()
{
std::vector<ChunkID> lastUpdateQueue;
#if ENABLE_PROGRESS_BAR
mLoaderStatusLock.lockWriter();
mPhyXStreamTime = 0.0f;
mGraphicStreamTime = 0.0f;
mLoaderStatusLock.unlockWriter();
#endif
while(mRequestReady.wait())
{
physx::shdfnd::Thread::yield();
mRequestReady.reset();
if (mLoaderThread->quitIsSignalled())
{
mLoaderThread->quit();
break;
}
#if ENABLE_PROGRESS_BAR
mLoaderStatusLock.lockWriter();
mQueryLength = 0;
mQueryProgress = 0.0f;
mLoaderStatusLock.unlockWriter();
#endif
bool bDirty = false;
std::vector<ChunkID> requestedQueue;
std::vector<DeferredLoadedRenderData> loadedRenderDataQueue;
std::vector<ChunkCommand> theChunkCmds;
mQueueLock.lock();
requestedQueue = mRequestQueue;
mQueueLock.unlock();
for(unsigned iLast = 0; iLast < lastUpdateQueue.size(); iLast++)
{
bool bFound = false;
for(unsigned iRequest = 0; iRequest < requestedQueue.size(); iRequest++)
{
bFound = lastUpdateQueue[iLast].id == requestedQueue[iRequest].id;
if(bFound)
break;
}
if(!bFound)
{
bDirty = true;
theChunkCmds.push_back(ChunkCommand(ChunkCommandType::eRemove,lastUpdateQueue[iLast]));
}
}
for(unsigned iRequest = 0; iRequest < requestedQueue.size(); iRequest++)
{
bool bFound = false;
for(unsigned iLast = 0; iLast < lastUpdateQueue.size(); iLast++)
{
bFound = requestedQueue[iRequest].id == lastUpdateQueue[iLast].id;
if(bFound)
break;
}
if(!bFound)
{
bDirty = true;
theChunkCmds.push_back(ChunkCommand(ChunkCommandType::eAdd,requestedQueue[iRequest]));
}
}
if(bDirty)
{
lastUpdateQueue = requestedQueue;
}
else
continue;
Ps::Time time;
Ps::Time::Second normalTime = 0.0f;
mDiskIOTimeCounter = 0.0f;
mPhyXStreamTimeCounter = 0.0f;
mGraphicStreamTimeCounter = 0.0f;
PxU32 loadedQueueSize = 0;
PxU32 cmdQueueSize = static_cast<PxU32>(theChunkCmds.size());
for(unsigned i = 0; i < cmdQueueSize; i++)
{
if(theChunkCmds[i].type == ChunkCommandType::eRemove )
{
continue;
}
++loadedQueueSize;
PxCollection* theCollection = loadChunk(theChunkCmds[i].id);
time.getElapsedSeconds();
if(theCollection != NULL )
{
PxU32 nb = theCollection->getNbObjects();
for(PxU32 o = 0; o < nb; o++)
{
PxBase* s = &theCollection->getObject( o );
const PxType serialType = s->getConcreteType();
if( serialType==PxConcreteType::eRIGID_STATIC )
{
PxRigidStatic* actor = static_cast<PxRigidStatic*>(s);
const char* name = actor->getName();
//If actor has name, it should have only one shape
if(name)
{
PxShape* meshShape = NULL;
PxTriangleMeshGeometry geometry;
PxTransform actorPos;
{
meshShape = getShape(*actor);
meshShape->getTriangleMeshGeometry(geometry);
actorPos = actor->getGlobalPose();
}
DeferredLoadedRenderData rawMesh;
if( createRawMeshFromObjMesh(name, actorPos, rawMesh) != NULL )
{
rawMesh.shape = meshShape;
loadedRenderDataQueue.push_back(rawMesh);
}
}
else
{
const PxU32 numShapes = actor->getNbShapes();
PxShape** shapes = (PxShape**)SAMPLE_ALLOC(sizeof(PxShape*)*numShapes);
actor->getShapes(shapes, numShapes);
for(PxU32 i = 0; i < numShapes; i++)
{
PxShape* shape = shapes[i];
if(shape->getGeometryType() == PxGeometryType::eTRIANGLEMESH)
{
PxTriangleMeshGeometry mesh;
shape->getTriangleMeshGeometry(mesh);
DeferredLoadedRenderData rawMesh;
if( mSampleLargeWorld->createRawMeshFromMeshGeom(mesh, rawMesh) != NULL )
{
rawMesh.mTransform = actor->getGlobalPose();
rawMesh.shape = shape;
loadedRenderDataQueue.push_back(rawMesh);
}
}
}
SAMPLE_FREE(shapes);
}
}
}
physx::shdfnd::Thread::yield();
}
normalTime += time.getElapsedSeconds();
#if ENABLE_PROGRESS_BAR
mLoaderStatusLock.lockWriter();
mQueryLength++;
PX_ASSERT(cmdQueueSize != 0);
mQueryProgress = (PxF32)i/(PxF32)cmdQueueSize;
mLoaderStatusLock.unlockWriter();
#endif
if (mLoaderThread->quitIsSignalled())
{
break;
}
physx::shdfnd::Thread::yield();
}
mGraphicStreamTimeCounter += normalTime;
mQueueLock.lock();
mChunkCmds = theChunkCmds;
mLoadedRenderDataQueue = loadedRenderDataQueue;
mQueueLock.unlock();
if (mLoaderThread->quitIsSignalled())
{
mLoaderThread->quit();
break;
}
#if ENABLE_PROGRESS_BAR
mLoaderStatusLock.lockWriter();
mQueryLength=0;
mQueryProgress = 0.0f;
mLoaderStatusLock.unlockWriter();
#endif
mLoaderStatusLock.lockWriter();
mDiskIOTime = mDiskIOTimeCounter;;
mPhyXStreamTime = mPhyXStreamTimeCounter;
mGraphicStreamTime = mGraphicStreamTimeCounter;
mLoaderStatusLock.unlockWriter();
}
return NULL;
}
void* BackgroundLoader::loaderThread(void* loader)
{
return ((BackgroundLoader*) loader)->loaderThread();
}