1387 lines
44 KiB
C++
1387 lines
44 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-2019 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 "cooking/PxTriangleMeshDesc.h"
|
|
|
|
#include "RTreeCooking.h"
|
|
#include "TriangleMeshBuilder.h"
|
|
#include "EdgeList.h"
|
|
#include "MeshCleaner.h"
|
|
#include "GuConvexEdgeFlags.h"
|
|
#include "GuSerialize.h"
|
|
#include "Cooking.h"
|
|
#include "GuMeshData.h"
|
|
#include "GuTriangle32.h"
|
|
#include "GuRTree.h"
|
|
#include "GuInternal.h"
|
|
#include "GuBV4Build.h"
|
|
#include "GuBV32Build.h"
|
|
#include "PsFoundation.h"
|
|
#include "PsHashMap.h"
|
|
#include "PsSort.h"
|
|
|
|
namespace physx {
|
|
|
|
struct int3
|
|
{
|
|
int x, y, z;
|
|
};
|
|
|
|
struct uint3
|
|
{
|
|
unsigned int x, y, z;
|
|
};
|
|
|
|
PX_ALIGN_PREFIX(16)
|
|
struct uint4
|
|
{
|
|
unsigned int x, y, z, w;
|
|
}
|
|
PX_ALIGN_SUFFIX(16);
|
|
|
|
PX_ALIGN_PREFIX(16)
|
|
struct float4
|
|
{
|
|
float x, y, z, w;
|
|
}
|
|
PX_ALIGN_SUFFIX(16);
|
|
|
|
}
|
|
|
|
#include "GrbTriangleMeshCooking.h"
|
|
|
|
using namespace physx;
|
|
using namespace Gu;
|
|
using namespace Ps;
|
|
|
|
namespace physx {
|
|
|
|
TriangleMeshBuilder::TriangleMeshBuilder(TriangleMeshData& m, const PxCookingParams& params) :
|
|
edgeList (NULL),
|
|
mParams (params),
|
|
mMeshData (m)
|
|
{
|
|
}
|
|
|
|
TriangleMeshBuilder::~TriangleMeshBuilder()
|
|
{
|
|
releaseEdgeList();
|
|
}
|
|
|
|
void TriangleMeshBuilder::remapTopology(const PxU32* order)
|
|
{
|
|
if(!mMeshData.mNbTriangles)
|
|
return;
|
|
|
|
// Remap one array at a time to limit memory usage
|
|
|
|
Gu::TriangleT<PxU32>* newTopo = reinterpret_cast<Gu::TriangleT<PxU32>*>(PX_ALLOC(mMeshData.mNbTriangles * sizeof(Gu::TriangleT<PxU32>), "Gu::TriangleT<PxU32>"));
|
|
for(PxU32 i=0;i<mMeshData.mNbTriangles;i++)
|
|
newTopo[i] = reinterpret_cast<Gu::TriangleT<PxU32>*>(mMeshData.mTriangles)[order[i]];
|
|
PX_FREE_AND_RESET(mMeshData.mTriangles);
|
|
mMeshData.mTriangles = newTopo;
|
|
|
|
if(mMeshData.mMaterialIndices)
|
|
{
|
|
PxMaterialTableIndex* newMat = PX_NEW(PxMaterialTableIndex)[mMeshData.mNbTriangles];
|
|
for(PxU32 i=0;i<mMeshData.mNbTriangles;i++)
|
|
newMat[i] = mMeshData.mMaterialIndices[order[i]];
|
|
PX_DELETE_POD(mMeshData.mMaterialIndices);
|
|
mMeshData.mMaterialIndices = newMat;
|
|
}
|
|
|
|
if(!mParams.suppressTriangleMeshRemapTable || mParams.buildGPUData)
|
|
{
|
|
PxU32* newMap = PX_NEW(PxU32)[mMeshData.mNbTriangles];
|
|
for(PxU32 i=0;i<mMeshData.mNbTriangles;i++)
|
|
newMap[i] = mMeshData.mFaceRemap ? mMeshData.mFaceRemap[order[i]] : order[i];
|
|
PX_DELETE_POD(mMeshData.mFaceRemap);
|
|
mMeshData.mFaceRemap = newMap;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool TriangleMeshBuilder::cleanMesh(bool validate, PxTriangleMeshCookingResult::Enum* condition)
|
|
{
|
|
PX_ASSERT(mMeshData.mFaceRemap == NULL);
|
|
|
|
PxF32 meshWeldTolerance = 0.0f;
|
|
if(mParams.meshPreprocessParams & PxMeshPreprocessingFlag::eWELD_VERTICES)
|
|
{
|
|
if(mParams.meshWeldTolerance == 0.f)
|
|
{
|
|
Ps::getFoundation().error(PxErrorCode::eDEBUG_WARNING, __FILE__, __LINE__, "TriangleMesh: Enable mesh welding with 0 weld tolerance!");
|
|
}
|
|
else
|
|
{
|
|
meshWeldTolerance = mParams.meshWeldTolerance;
|
|
}
|
|
}
|
|
MeshCleaner cleaner(mMeshData.mNbVertices, mMeshData.mVertices, mMeshData.mNbTriangles, reinterpret_cast<const PxU32*>(mMeshData.mTriangles), meshWeldTolerance);
|
|
if(!cleaner.mNbTris)
|
|
return false;
|
|
|
|
if(validate)
|
|
{
|
|
// if we do only validate, we check if cleaning did not remove any verts or triangles.
|
|
// such a mesh can be then directly used for cooking without clean flag
|
|
if((cleaner.mNbVerts != mMeshData.mNbVertices) || (cleaner.mNbTris != mMeshData.mNbTriangles))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// PT: deal with the remap table
|
|
{
|
|
// PT: TODO: optimize this
|
|
if(cleaner.mRemap)
|
|
{
|
|
const PxU32 newNbTris = cleaner.mNbTris;
|
|
|
|
// Remap material array
|
|
if(mMeshData.mMaterialIndices)
|
|
{
|
|
PxMaterialTableIndex* tmp = PX_NEW(PxMaterialTableIndex)[newNbTris];
|
|
for(PxU32 i=0;i<newNbTris;i++)
|
|
tmp[i] = mMeshData.mMaterialIndices[cleaner.mRemap[i]];
|
|
|
|
PX_DELETE_POD(mMeshData.mMaterialIndices);
|
|
mMeshData.mMaterialIndices = tmp;
|
|
}
|
|
|
|
if (!mParams.suppressTriangleMeshRemapTable || mParams.buildGPUData)
|
|
{
|
|
mMeshData.mFaceRemap = PX_NEW(PxU32)[newNbTris];
|
|
PxMemCopy(mMeshData.mFaceRemap, cleaner.mRemap, newNbTris*sizeof(PxU32));
|
|
}
|
|
}
|
|
}
|
|
|
|
// PT: deal with geometry
|
|
{
|
|
if(mMeshData.mNbVertices!=cleaner.mNbVerts)
|
|
{
|
|
PX_FREE_AND_RESET(mMeshData.mVertices);
|
|
mMeshData.allocateVertices(cleaner.mNbVerts);
|
|
}
|
|
PxMemCopy(mMeshData.mVertices, cleaner.mVerts, mMeshData.mNbVertices*sizeof(PxVec3));
|
|
}
|
|
|
|
// PT: deal with topology
|
|
{
|
|
PX_ASSERT(!(mMeshData.mFlags & PxTriangleMeshFlag::e16_BIT_INDICES));
|
|
if(mMeshData.mNbTriangles!=cleaner.mNbTris)
|
|
{
|
|
PX_FREE_AND_RESET(mMeshData.mTriangles);
|
|
mMeshData.allocateTriangles(cleaner.mNbTris, true);
|
|
}
|
|
|
|
const float testLength = 500.0f*500.0f*mParams.scale.length*mParams.scale.length;
|
|
bool bigTriangle = false;
|
|
const PxVec3* v = mMeshData.mVertices;
|
|
for(PxU32 i=0;i<mMeshData.mNbTriangles;i++)
|
|
{
|
|
const PxU32 vref0 = cleaner.mIndices[i*3+0];
|
|
const PxU32 vref1 = cleaner.mIndices[i*3+1];
|
|
const PxU32 vref2 = cleaner.mIndices[i*3+2];
|
|
PX_ASSERT(vref0!=vref1 && vref0!=vref2 && vref1!=vref2);
|
|
|
|
reinterpret_cast<Gu::TriangleT<PxU32>*>(mMeshData.mTriangles)[i].v[0] = vref0;
|
|
reinterpret_cast<Gu::TriangleT<PxU32>*>(mMeshData.mTriangles)[i].v[1] = vref1;
|
|
reinterpret_cast<Gu::TriangleT<PxU32>*>(mMeshData.mTriangles)[i].v[2] = vref2;
|
|
|
|
if( (v[vref0] - v[vref1]).magnitudeSquared() >= testLength
|
|
|| (v[vref1] - v[vref2]).magnitudeSquared() >= testLength
|
|
|| (v[vref2] - v[vref0]).magnitudeSquared() >= testLength
|
|
)
|
|
bigTriangle = true;
|
|
}
|
|
if(bigTriangle)
|
|
{
|
|
if(condition)
|
|
*condition = PxTriangleMeshCookingResult::eLARGE_TRIANGLE;
|
|
Ps::getFoundation().error(PxErrorCode::eDEBUG_WARNING, __FILE__, __LINE__, "TriangleMesh: triangles are too big, reduce their size to increase simulation stability!");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TriangleMeshBuilder::createSharedEdgeData(bool buildAdjacencies, bool buildActiveEdges)
|
|
{
|
|
if(buildAdjacencies) // building edges is required if buildAdjacencies is requested
|
|
buildActiveEdges = true;
|
|
|
|
PX_ASSERT(mMeshData.mExtraTrigData == NULL);
|
|
PX_ASSERT(mMeshData.mAdjacencies == NULL);
|
|
|
|
if(!buildActiveEdges)
|
|
return;
|
|
|
|
const PxU32 nTrigs = mMeshData.mNbTriangles;
|
|
|
|
mMeshData.mExtraTrigData = PX_NEW(PxU8)[nTrigs];
|
|
memset(mMeshData.mExtraTrigData, 0, sizeof(PxU8)*nTrigs);
|
|
|
|
const Gu::TriangleT<PxU32>* trigs = reinterpret_cast<const Gu::TriangleT<PxU32>*>(mMeshData.mTriangles);
|
|
if(0x40000000 <= nTrigs)
|
|
{
|
|
//mesh is too big for this algo, need to be able to express trig indices in 30 bits, and still have an index reserved for "unused":
|
|
Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "TriangleMesh: mesh is too big for this algo!");
|
|
return;
|
|
}
|
|
|
|
createEdgeList();
|
|
if(edgeList)
|
|
{
|
|
PX_ASSERT(edgeList->getNbFaces()==mMeshData.mNbTriangles);
|
|
if(edgeList->getNbFaces()==mMeshData.mNbTriangles)
|
|
{
|
|
for(PxU32 i=0;i<edgeList->getNbFaces();i++)
|
|
{
|
|
const Gu::EdgeTriangleData& ET = edgeList->getEdgeTriangle(i);
|
|
// Replicate flags
|
|
if(Gu::EdgeTriangleAC::HasActiveEdge01(ET)) mMeshData.mExtraTrigData[i] |= Gu::ETD_CONVEX_EDGE_01;
|
|
if(Gu::EdgeTriangleAC::HasActiveEdge12(ET)) mMeshData.mExtraTrigData[i] |= Gu::ETD_CONVEX_EDGE_12;
|
|
if(Gu::EdgeTriangleAC::HasActiveEdge20(ET)) mMeshData.mExtraTrigData[i] |= Gu::ETD_CONVEX_EDGE_20;
|
|
}
|
|
}
|
|
}
|
|
|
|
// fill the adjacencies
|
|
if(buildAdjacencies)
|
|
{
|
|
mMeshData.mAdjacencies = PX_NEW(PxU32)[nTrigs*3];
|
|
memset(mMeshData.mAdjacencies, 0xFFFFffff, sizeof(PxU32)*nTrigs*3);
|
|
|
|
PxU32 NbEdges = edgeList->getNbEdges();
|
|
const Gu::EdgeDescData* ED = edgeList->getEdgeToTriangles();
|
|
const Gu::EdgeData* Edges = edgeList->getEdges();
|
|
const PxU32* FBE = edgeList->getFacesByEdges();
|
|
|
|
while(NbEdges--)
|
|
{
|
|
// Get number of triangles sharing current edge
|
|
PxU32 Count = ED->Count;
|
|
|
|
if(Count > 1)
|
|
{
|
|
PxU32 FaceIndex0 = FBE[ED->Offset+0];
|
|
PxU32 FaceIndex1 = FBE[ED->Offset+1];
|
|
|
|
const Gu::EdgeData& edgeData = *Edges;
|
|
const Gu::TriangleT<PxU32>& T0 = trigs[FaceIndex0];
|
|
const Gu::TriangleT<PxU32>& T1 = trigs[FaceIndex1];
|
|
|
|
PxU32 offset0 = T0.findEdgeCCW(edgeData.Ref0,edgeData.Ref1);
|
|
PxU32 offset1 = T1.findEdgeCCW(edgeData.Ref0,edgeData.Ref1);
|
|
|
|
mMeshData.setTriangleAdjacency(FaceIndex0, FaceIndex1, offset0);
|
|
mMeshData.setTriangleAdjacency(FaceIndex1, FaceIndex0, offset1);
|
|
}
|
|
ED++;
|
|
Edges++;
|
|
}
|
|
}
|
|
|
|
#if PX_DEBUG
|
|
for(PxU32 i=0;i<mMeshData.mNbTriangles;i++)
|
|
{
|
|
const Gu::TriangleT<PxU32>& T = trigs[i];
|
|
PX_UNUSED(T);
|
|
const Gu::EdgeTriangleData& ET = edgeList->getEdgeTriangle(i);
|
|
PX_ASSERT((Gu::EdgeTriangleAC::HasActiveEdge01(ET) && (mMeshData.mExtraTrigData[i] & Gu::ETD_CONVEX_EDGE_01)) || (!Gu::EdgeTriangleAC::HasActiveEdge01(ET) && !(mMeshData.mExtraTrigData[i] & Gu::ETD_CONVEX_EDGE_01)));
|
|
PX_ASSERT((Gu::EdgeTriangleAC::HasActiveEdge12(ET) && (mMeshData.mExtraTrigData[i] & Gu::ETD_CONVEX_EDGE_12)) || (!Gu::EdgeTriangleAC::HasActiveEdge12(ET) && !(mMeshData.mExtraTrigData[i] & Gu::ETD_CONVEX_EDGE_12)));
|
|
PX_ASSERT((Gu::EdgeTriangleAC::HasActiveEdge20(ET) && (mMeshData.mExtraTrigData[i] & Gu::ETD_CONVEX_EDGE_20)) || (!Gu::EdgeTriangleAC::HasActiveEdge20(ET) && !(mMeshData.mExtraTrigData[i] & Gu::ETD_CONVEX_EDGE_20)));
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
namespace GrbTrimeshCookerHelper
|
|
{
|
|
|
|
struct SortedNeighbor
|
|
{
|
|
PxU32 v, a; // vertex and adjacent vertex
|
|
bool boundary;
|
|
|
|
SortedNeighbor(PxU32 v_, PxU32 a_, bool b_): v(v_), a(a_), boundary(b_) {}
|
|
|
|
// sort boundary edges to the front so that they are kept when duplicates are removed
|
|
bool operator<(const SortedNeighbor& b) const
|
|
{
|
|
return (v<b.v || (v == b.v && a<b.a) || (v == b.v && a == b.a && boundary && !b.boundary));
|
|
}
|
|
};
|
|
|
|
struct SharpEdgeRange
|
|
{
|
|
PxU32 start, length;
|
|
SharpEdgeRange(): start(0), length(0) {}
|
|
SharpEdgeRange(PxU32 s, PxU32 l): start(s), length(l) {}
|
|
};
|
|
|
|
class LocalIndexer
|
|
{
|
|
public:
|
|
bool insert(PxU32 meshIndex) // returns true if this is a new index
|
|
{
|
|
bool isNew = mMeshToLocal.insert(meshIndex, mLocalToMesh.size());
|
|
if(isNew)
|
|
mLocalToMesh.pushBack(meshIndex);
|
|
return isNew;
|
|
}
|
|
|
|
PxU32 meshIndex(PxU32 localIndex)
|
|
{
|
|
PX_ASSERT(localIndex<mLocalToMesh.size());
|
|
return mLocalToMesh[localIndex];
|
|
}
|
|
|
|
PxU32 localIndex(PxU32 meshIndex)
|
|
{
|
|
PX_ASSERT(mMeshToLocal.find(meshIndex));
|
|
return mMeshToLocal[meshIndex];
|
|
}
|
|
|
|
bool contains(PxU32 meshIndex)
|
|
{
|
|
return mMeshToLocal.find(meshIndex) != 0;
|
|
}
|
|
|
|
PxU32 size()
|
|
{
|
|
return mLocalToMesh.size();
|
|
}
|
|
|
|
private:
|
|
Ps::Array<PxU32> mLocalToMesh;
|
|
Ps::HashMap<PxU32, PxU32> mMeshToLocal;
|
|
};
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
void findSharpVertices(
|
|
Ps::Array<SortedNeighbor>& pairList,
|
|
Ps::Array<SharpEdgeRange>& edgeRanges,
|
|
/*const Ps::Array<Triangle>& triangles,*/
|
|
const uint3* triIndices,
|
|
const uint4* triAdjacencies,
|
|
PxU32 nbTris,
|
|
PxU32 nbVerts
|
|
)
|
|
{
|
|
// sort the edges which are sharp or boundary
|
|
for(PxU32 i=0;i<nbTris;i++)
|
|
{
|
|
const uint4& triAdj = triAdjacencies[i];
|
|
const uint3& triIdx = triIndices[i];
|
|
|
|
if (!isEdgeNonconvex(triAdj.x))
|
|
{
|
|
pairList.pushBack(SortedNeighbor(triIdx.x, triIdx.y, triAdj.x == BOUNDARY));
|
|
pairList.pushBack(SortedNeighbor(triIdx.y, triIdx.x, triAdj.x == BOUNDARY));
|
|
}
|
|
|
|
if (!isEdgeNonconvex(triAdj.y))
|
|
{
|
|
pairList.pushBack(SortedNeighbor(triIdx.y, triIdx.z, triAdj.y == BOUNDARY));
|
|
pairList.pushBack(SortedNeighbor(triIdx.z, triIdx.y, triAdj.y == BOUNDARY));
|
|
}
|
|
|
|
if (!isEdgeNonconvex(triAdj.z))
|
|
{
|
|
pairList.pushBack(SortedNeighbor(triIdx.z, triIdx.x, triAdj.z == BOUNDARY));
|
|
pairList.pushBack(SortedNeighbor(triIdx.x, triIdx.z, triAdj.z == BOUNDARY));
|
|
}
|
|
}
|
|
|
|
Ps::sort(pairList.begin(), pairList.size());
|
|
|
|
// remove duplicates - note that boundary edges are sorted earlier, so we keep them
|
|
PxU32 unique = 1;
|
|
for(PxU32 i=1;i<pairList.size();i++)
|
|
{
|
|
if(pairList[i].v != pairList[i-1].v || pairList[i].a != pairList[i-1].a)
|
|
pairList[unique++] = pairList[i];
|
|
}
|
|
pairList.resizeUninitialized(unique);
|
|
|
|
// a vertex is marked for sharp vertex processing if it has a boundary edge or at least three convex edges
|
|
edgeRanges.resize(nbVerts);
|
|
for(PxU32 p = 0, u ; p<pairList.size(); p = u)
|
|
{
|
|
bool boundary = false;
|
|
for(u=p+1; u<pairList.size() && pairList[u].v == pairList[p].v; u++)
|
|
boundary |= pairList[u].boundary;
|
|
if(boundary || u-p>=3)
|
|
edgeRanges[pairList[p].v] = SharpEdgeRange(p, u-p);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
PxU32 buildVertexConnectionNew_p1(
|
|
Ps::Array<SortedNeighbor> & pairList,
|
|
Ps::Array<SharpEdgeRange> & edgeRanges,
|
|
LocalIndexer & vertexMap,
|
|
|
|
const uint4 * triIndices,
|
|
const uint4 * triAdjacencies,
|
|
|
|
PxU32 nbTris,
|
|
PxU32 nbVerts
|
|
)
|
|
{
|
|
findSharpVertices(
|
|
pairList,
|
|
edgeRanges,
|
|
triIndices,
|
|
triAdjacencies,
|
|
nbTris,
|
|
nbVerts
|
|
);
|
|
|
|
// add all the original triangles and vertices and record how big the core is
|
|
for(PxU32 i=0; i<nbTris; i++)
|
|
{
|
|
const uint4 & triIdx = triIndices[i];
|
|
vertexMap.insert(triIdx.x);
|
|
vertexMap.insert(triIdx.y);
|
|
vertexMap.insert(triIdx.z);
|
|
}
|
|
PxU32 nbCoreVerts = vertexMap.size();
|
|
|
|
PX_ASSERT(nbCoreVerts == nbVerts);
|
|
|
|
// add adjacent triangles
|
|
for(PxU32 i=0;i<nbTris;i++)
|
|
{
|
|
const uint4 & triAdj = triAdjacencies[i];
|
|
|
|
#define IS_TRI(triAdjIdx) (( (triAdjIdx) != BOUNDARY ) && ( !((triAdjIdx) & NONCONVEX_FLAG) ))
|
|
|
|
if(IS_TRI(triAdj.x))
|
|
{
|
|
const uint4 & triIdx = triIndices[triAdj.x];
|
|
vertexMap.insert(triIdx.x);
|
|
vertexMap.insert(triIdx.y);
|
|
vertexMap.insert(triIdx.z);
|
|
}
|
|
|
|
if(IS_TRI(triAdj.y))
|
|
{
|
|
const uint4 & triIdx = triIndices[triAdj.y];
|
|
vertexMap.insert(triIdx.x);
|
|
vertexMap.insert(triIdx.y);
|
|
vertexMap.insert(triIdx.z);
|
|
}
|
|
|
|
if(IS_TRI(triAdj.z))
|
|
{
|
|
const uint4 & triIdx = triIndices[triAdj.z];
|
|
vertexMap.insert(triIdx.x);
|
|
vertexMap.insert(triIdx.y);
|
|
vertexMap.insert(triIdx.z);
|
|
|
|
}
|
|
|
|
#undef IS_TRI
|
|
}
|
|
|
|
// add the neighbors of the sharp vertices
|
|
PxU32 nbNeighbors = 0;
|
|
for(PxU32 i=0;i<nbCoreVerts;i++)
|
|
{
|
|
PxU32 meshIndex = vertexMap.meshIndex(i);
|
|
const SharpEdgeRange& er = edgeRanges[meshIndex];
|
|
for(PxU32 j = 0;j<er.length;j++)
|
|
{
|
|
PX_ASSERT(pairList[er.start+j].v == meshIndex);
|
|
vertexMap.insert(pairList[er.start + j].a);
|
|
}
|
|
nbNeighbors += er.length;
|
|
}
|
|
|
|
return nbNeighbors;
|
|
}
|
|
|
|
void buildVertexConnectionNew_p2(
|
|
PxU32 * adjVertStart,
|
|
PxU32 * vertValency,
|
|
PxU32 * adjVertices,
|
|
|
|
Ps::Array<SortedNeighbor>& pairList,
|
|
Ps::Array<SharpEdgeRange>& edgeRanges,
|
|
LocalIndexer & vertexMap,
|
|
|
|
const uint4 * /*triIndices*/,
|
|
const uint4 * /*triAdjacencies*/,
|
|
|
|
PxU32 /*nbTris*/,
|
|
PxU32 nbVerts,
|
|
PxU32 /*nbNeighbors*/
|
|
)
|
|
{
|
|
PxU32 n = 0;
|
|
for(PxU32 i=0;i<nbVerts;i++)
|
|
{
|
|
PxU32 meshIdx = vertexMap.meshIndex(i);
|
|
const SharpEdgeRange& er = edgeRanges[vertexMap.meshIndex(i)];
|
|
adjVertStart[meshIdx] = n;
|
|
vertValency[meshIdx] = er.length;
|
|
for(PxU32 j = 0;j<er.length;j++)
|
|
adjVertices[n++] = pairList[er.start+j].a;
|
|
}
|
|
}
|
|
#else
|
|
|
|
|
|
PxU32 buildVertexConnectionNew_p1(
|
|
Ps::Array<SortedNeighbor> & pairList,
|
|
Ps::Array<SharpEdgeRange> & edgeRanges,
|
|
|
|
const uint3* triIndices,
|
|
const uint4 * triAdjacencies,
|
|
|
|
PxU32 nbTris,
|
|
PxU32 nbVerts
|
|
)
|
|
{
|
|
findSharpVertices(
|
|
pairList,
|
|
edgeRanges,
|
|
triIndices,
|
|
triAdjacencies,
|
|
nbTris,
|
|
nbVerts
|
|
);
|
|
|
|
|
|
// add the neighbors of the sharp vertices
|
|
PxU32 nbNeighbors = 0;
|
|
for (PxU32 i = 0; i<nbVerts; i++)
|
|
{
|
|
const SharpEdgeRange& er = edgeRanges[i];
|
|
nbNeighbors += er.length;
|
|
}
|
|
|
|
return nbNeighbors;
|
|
}
|
|
|
|
void buildVertexConnectionNew_p2(
|
|
PxU32 * adjVertStart,
|
|
PxU32 * vertValency,
|
|
PxU32 * adjVertices,
|
|
|
|
Ps::Array<SortedNeighbor>& pairList,
|
|
Ps::Array<SharpEdgeRange>& edgeRanges,
|
|
PxU32 nbVerts
|
|
)
|
|
{
|
|
PxU32 n = 0;
|
|
for (PxU32 i = 0; i<nbVerts; i++)
|
|
{
|
|
const SharpEdgeRange& er = edgeRanges[i];
|
|
adjVertStart[i] = n;
|
|
vertValency[i] = er.length;
|
|
for (PxU32 j = 0; j<er.length; j++)
|
|
adjVertices[n++] = pairList[er.start + j].a;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
} // namespace GrbTrimeshCookerHelper
|
|
|
|
void TriangleMeshBuilder::recordTriangleIndices()
|
|
{
|
|
if (mParams.buildGPUData)
|
|
{
|
|
PX_ASSERT(!(mMeshData.mFlags & PxTriangleMeshFlag::e16_BIT_INDICES));
|
|
PX_ASSERT(mMeshData.mGRB_primIndices);
|
|
|
|
//copy the original traingle indices to originalTrangles32
|
|
PxMemCopy(mMeshData.mGRB_primIndices, mMeshData.mTriangles, sizeof(IndTri32) *mMeshData.mNbTriangles);
|
|
|
|
|
|
if (mMeshData.mFaceRemap)
|
|
{
|
|
//We must have discarded some triangles so let's
|
|
mMeshData.mGRB_faceRemap = PX_NEW(PxU32)[mMeshData.mNbTriangles];
|
|
PxMemCopy(mMeshData.mGRB_faceRemap, mMeshData.mFaceRemap, sizeof(PxU32)*mMeshData.mNbTriangles);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void TriangleMeshBuilder::createGRBData()
|
|
{
|
|
|
|
const PxU32 & numTris = mMeshData.mNbTriangles;
|
|
|
|
PX_ASSERT(!(mMeshData.mFlags & PxTriangleMeshFlag::e16_BIT_INDICES));
|
|
|
|
|
|
// Core: Mesh data
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// (by using adjacency info generated by physx cooker)
|
|
PxVec3 * tempNormalsPerTri_prealloc = reinterpret_cast<PxVec3 *>(PX_ALLOC(numTris * sizeof(PxVec3), PX_DEBUG_EXP("tempNormalsPerTri_prealloc")));
|
|
|
|
mMeshData.mGRB_primAdjacencies = PX_ALLOC(sizeof(uint4)*numTris, PX_DEBUG_EXP("GRB_triAdjacencies"));
|
|
|
|
|
|
buildAdjacencies(
|
|
reinterpret_cast<uint4 *>(mMeshData.mGRB_primAdjacencies),
|
|
tempNormalsPerTri_prealloc,
|
|
mMeshData.mVertices,
|
|
reinterpret_cast<uint3*>(mMeshData.mGRB_primIndices),
|
|
numTris
|
|
);
|
|
|
|
|
|
PX_FREE(tempNormalsPerTri_prealloc);
|
|
|
|
}
|
|
|
|
void TriangleMeshBuilder::createGRBMidPhaseAndData(const PxU32 originalTriangleCount)
|
|
{
|
|
if (mParams.buildGPUData)
|
|
{
|
|
|
|
PX_ASSERT(!(mMeshData.mFlags & PxTriangleMeshFlag::e16_BIT_INDICES));
|
|
|
|
BV32Tree* bv32Tree = PX_NEW(BV32Tree);
|
|
mMeshData.mGRB_BV32Tree = bv32Tree;
|
|
|
|
BV32TriangleMeshBuilder::createMidPhaseStructure(mParams, mMeshData, *bv32Tree);
|
|
|
|
createGRBData();
|
|
|
|
//create a remap table from GPU to CPU remap table
|
|
PxU32* orignalToRemap = PX_NEW(PxU32)[originalTriangleCount];
|
|
|
|
PX_ASSERT(mMeshData.mFaceRemap);
|
|
|
|
|
|
for (PxU32 i = 0; i < mMeshData.mNbTriangles; ++i)
|
|
{
|
|
const PxU32 index = mMeshData.mFaceRemap[i];
|
|
PX_ASSERT(index < originalTriangleCount);
|
|
orignalToRemap[index] = i;
|
|
}
|
|
|
|
|
|
//map CPU remap triangle index to GPU remap triangle index
|
|
for (PxU32 i = 0; i < mMeshData.mNbTriangles; ++i)
|
|
{
|
|
const PxU32 index = mMeshData.mGRB_faceRemap[i];
|
|
mMeshData.mGRB_faceRemap[i] = orignalToRemap[index];
|
|
}
|
|
|
|
#if BV32_VALIDATE
|
|
IndTri32* grbTriIndices = reinterpret_cast<IndTri32*>(mMeshData.mGRB_triIndices);
|
|
IndTri32* cpuTriIndices = reinterpret_cast<IndTri32*>(mMeshData.mTriangles);
|
|
//map CPU remap triangle index to GPU remap triangle index
|
|
for (PxU32 i = 0; i < mMeshData.mNbTriangles; ++i)
|
|
{
|
|
PX_ASSERT(grbTriIndices[i].mRef[0] == cpuTriIndices[mMeshData.mGRB_faceRemap[i]].mRef[0]);
|
|
PX_ASSERT(grbTriIndices[i].mRef[1] == cpuTriIndices[mMeshData.mGRB_faceRemap[i]].mRef[1]);
|
|
PX_ASSERT(grbTriIndices[i].mRef[2] == cpuTriIndices[mMeshData.mGRB_faceRemap[i]].mRef[2]);
|
|
}
|
|
#endif
|
|
|
|
if (orignalToRemap)
|
|
PX_DELETE_POD(orignalToRemap);
|
|
|
|
}
|
|
}
|
|
|
|
void TriangleMeshBuilder::createEdgeList()
|
|
{
|
|
Gu::EDGELISTCREATE create;
|
|
create.NbFaces = mMeshData.mNbTriangles;
|
|
if(mMeshData.has16BitIndices())
|
|
{
|
|
create.DFaces = NULL;
|
|
create.WFaces = reinterpret_cast<PxU16*>(mMeshData.mTriangles);
|
|
}
|
|
else
|
|
{
|
|
create.DFaces = reinterpret_cast<PxU32*>(mMeshData.mTriangles);
|
|
create.WFaces = NULL;
|
|
}
|
|
create.FacesToEdges = true;
|
|
create.EdgesToFaces = true;
|
|
create.Verts = mMeshData.mVertices;
|
|
//create.Epsilon = 0.1f;
|
|
// create.Epsilon = convexEdgeThreshold;
|
|
edgeList = PX_NEW(Gu::EdgeListBuilder);
|
|
if(!edgeList->init(create))
|
|
{
|
|
PX_DELETE(edgeList);
|
|
edgeList = 0;
|
|
}
|
|
}
|
|
|
|
void TriangleMeshBuilder::releaseEdgeList()
|
|
{
|
|
PX_DELETE_AND_RESET(edgeList);
|
|
}
|
|
|
|
//
|
|
// When suppressTriangleMeshRemapTable is true, the face remap table is not created. This saves a significant amount of memory,
|
|
// but the SDK will not be able to provide information about which mesh triangle is hit in collisions, sweeps or raycasts hits.
|
|
//
|
|
// The sequence is as follows:
|
|
|
|
bool TriangleMeshBuilder::loadFromDesc(const PxTriangleMeshDesc& _desc, PxTriangleMeshCookingResult::Enum* condition, bool validateMesh)
|
|
{
|
|
const PxU32 originalTriangleCount = _desc.triangles.count;
|
|
if(!_desc.isValid())
|
|
{
|
|
Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "TriangleMesh::loadFromDesc: desc.isValid() failed!");
|
|
return false;
|
|
}
|
|
|
|
// verify the mesh params
|
|
if(!mParams.midphaseDesc.isValid())
|
|
{
|
|
Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "TriangleMesh::loadFromDesc: mParams.midphaseDesc.isValid() failed!");
|
|
return false;
|
|
}
|
|
|
|
// Create a local copy that we can modify
|
|
PxTriangleMeshDesc desc = _desc;
|
|
|
|
// Save simple params
|
|
{
|
|
// Handle implicit topology
|
|
PxU32* topology = NULL;
|
|
if(!desc.triangles.data)
|
|
{
|
|
// We'll create 32-bit indices
|
|
desc.flags &= ~PxMeshFlag::e16_BIT_INDICES;
|
|
desc.triangles.stride = sizeof(PxU32)*3;
|
|
|
|
{
|
|
// Non-indexed mesh => create implicit topology
|
|
desc.triangles.count = desc.points.count/3;
|
|
// Create default implicit topology
|
|
topology = PX_NEW_TEMP(PxU32)[desc.points.count];
|
|
for(PxU32 i=0;i<desc.points.count;i++)
|
|
topology[i] = i;
|
|
desc.triangles.data = topology;
|
|
}
|
|
}
|
|
// Continue as usual using our new descriptor
|
|
|
|
// Convert and clean the input mesh
|
|
if (!importMesh(desc, mParams, condition, validateMesh))
|
|
return false;
|
|
|
|
// Cleanup if needed
|
|
PX_DELETE_POD(topology);
|
|
}
|
|
|
|
|
|
//copy the original triangle indices to grb triangle indices if buildGRBData is true
|
|
recordTriangleIndices();
|
|
|
|
createMidPhaseStructure();
|
|
|
|
// Compute local bounds
|
|
computeLocalBounds(mMeshData); // AP scaffold: local bounds are already computed in builder.createRTree efficiently with SIMD
|
|
|
|
createSharedEdgeData(mParams.buildTriangleAdjacencies, !(mParams.meshPreprocessParams & PxMeshPreprocessingFlag::eDISABLE_ACTIVE_EDGES_PRECOMPUTE));
|
|
|
|
createGRBMidPhaseAndData(originalTriangleCount);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool TriangleMeshBuilder::save(PxOutputStream& stream, bool platformMismatch, const PxCookingParams& params) const
|
|
{
|
|
// Export header
|
|
if(!writeHeader('M', 'E', 'S', 'H', PX_MESH_VERSION, platformMismatch, stream))
|
|
return false;
|
|
|
|
// Export midphase ID
|
|
writeDword(getMidphaseID(), platformMismatch, stream);
|
|
|
|
// Export serialization flags
|
|
PxU32 serialFlags = 0;
|
|
if(mMeshData.mMaterialIndices) serialFlags |= Gu::IMSF_MATERIALS;
|
|
if(mMeshData.mFaceRemap) serialFlags |= Gu::IMSF_FACE_REMAP;
|
|
if(mMeshData.mAdjacencies) serialFlags |= Gu::IMSF_ADJACENCIES;
|
|
if (params.buildGPUData) serialFlags |= Gu::IMSF_GRB_DATA;
|
|
// Compute serialization flags for indices
|
|
PxU32 maxIndex=0;
|
|
const Gu::TriangleT<PxU32>* tris = reinterpret_cast<const Gu::TriangleT<PxU32>*>(mMeshData.mTriangles);
|
|
for(PxU32 i=0;i<mMeshData.mNbTriangles;i++)
|
|
{
|
|
if(tris[i].v[0]>maxIndex) maxIndex = tris[i].v[0];
|
|
if(tris[i].v[1]>maxIndex) maxIndex = tris[i].v[1];
|
|
if(tris[i].v[2]>maxIndex) maxIndex = tris[i].v[2];
|
|
}
|
|
|
|
bool force32 = (params.meshPreprocessParams & PxMeshPreprocessingFlag::eFORCE_32BIT_INDICES);
|
|
if (maxIndex <= 0xFFFF && !force32)
|
|
serialFlags |= (maxIndex <= 0xFF ? Gu::IMSF_8BIT_INDICES : Gu::IMSF_16BIT_INDICES);
|
|
writeDword(serialFlags, platformMismatch, stream);
|
|
|
|
// Export mesh
|
|
writeDword(mMeshData.mNbVertices, platformMismatch, stream);
|
|
writeDword(mMeshData.mNbTriangles, platformMismatch, stream);
|
|
writeFloatBuffer(&mMeshData.mVertices->x, mMeshData.mNbVertices*3, platformMismatch, stream);
|
|
if(serialFlags & Gu::IMSF_8BIT_INDICES)
|
|
{
|
|
const PxU32* indices = tris->v;
|
|
for(PxU32 i=0;i<mMeshData.mNbTriangles*3;i++)
|
|
{
|
|
PxI8 data = PxI8(indices[i]);
|
|
stream.write(&data, sizeof(PxU8));
|
|
}
|
|
}
|
|
else if(serialFlags & Gu::IMSF_16BIT_INDICES)
|
|
{
|
|
const PxU32* indices = tris->v;
|
|
for(PxU32 i=0;i<mMeshData.mNbTriangles*3;i++)
|
|
writeWord(Ps::to16(indices[i]), platformMismatch, stream);
|
|
}
|
|
else
|
|
writeIntBuffer(tris->v, mMeshData.mNbTriangles*3, platformMismatch, stream);
|
|
|
|
if(mMeshData.mMaterialIndices)
|
|
writeWordBuffer(mMeshData.mMaterialIndices, mMeshData.mNbTriangles, platformMismatch, stream);
|
|
|
|
if(mMeshData.mFaceRemap)
|
|
{
|
|
PxU32 maxId = computeMaxIndex(mMeshData.mFaceRemap, mMeshData.mNbTriangles);
|
|
writeDword(maxId, platformMismatch, stream);
|
|
storeIndices(maxId, mMeshData.mNbTriangles, mMeshData.mFaceRemap, stream, platformMismatch);
|
|
// writeIntBuffer(mMeshData.mFaceRemap, mMeshData.mNbTriangles, platformMismatch, stream);
|
|
}
|
|
|
|
if(mMeshData.mAdjacencies)
|
|
writeIntBuffer(mMeshData.mAdjacencies, mMeshData.mNbTriangles*3, platformMismatch, stream);
|
|
|
|
// Export midphase structure
|
|
saveMidPhaseStructure(stream, platformMismatch);
|
|
|
|
// Export local bounds
|
|
writeFloat(mMeshData.mGeomEpsilon, platformMismatch, stream);
|
|
|
|
writeFloat(mMeshData.mAABB.minimum.x, platformMismatch, stream);
|
|
writeFloat(mMeshData.mAABB.minimum.y, platformMismatch, stream);
|
|
writeFloat(mMeshData.mAABB.minimum.z, platformMismatch, stream);
|
|
writeFloat(mMeshData.mAABB.maximum.x, platformMismatch, stream);
|
|
writeFloat(mMeshData.mAABB.maximum.y, platformMismatch, stream);
|
|
writeFloat(mMeshData.mAABB.maximum.z, platformMismatch, stream);
|
|
|
|
if(mMeshData.mExtraTrigData)
|
|
{
|
|
writeDword(mMeshData.mNbTriangles, platformMismatch, stream);
|
|
// No need to convert those bytes
|
|
stream.write(mMeshData.mExtraTrigData, mMeshData.mNbTriangles*sizeof(PxU8));
|
|
}
|
|
else
|
|
writeDword(0, platformMismatch, stream);
|
|
|
|
// GRB write -----------------------------------------------------------------
|
|
if (params.buildGPUData)
|
|
{
|
|
const PxU32* indices = reinterpret_cast<PxU32*>(mMeshData.mGRB_primIndices);
|
|
if (serialFlags & Gu::IMSF_8BIT_INDICES)
|
|
{
|
|
for (PxU32 i = 0; i<mMeshData.mNbTriangles * 3; i++)
|
|
{
|
|
PxI8 data = PxI8(indices[i]);
|
|
stream.write(&data, sizeof(PxU8));
|
|
}
|
|
}
|
|
else if (serialFlags & Gu::IMSF_16BIT_INDICES)
|
|
{
|
|
for (PxU32 i = 0; i<mMeshData.mNbTriangles * 3; i++)
|
|
writeWord(Ps::to16(indices[i]), platformMismatch, stream);
|
|
}
|
|
else
|
|
writeIntBuffer(indices, mMeshData.mNbTriangles * 3, platformMismatch, stream);
|
|
|
|
|
|
//writeIntBuffer(reinterpret_cast<PxU32*>(mMeshData.mGRB_triIndices), , mMeshData.mNbTriangles*3, platformMismatch, stream);
|
|
|
|
//writeIntBuffer(reinterpret_cast<PxU32 *>(mMeshData.mGRB_triIndices), mMeshData.mNbTriangles*4, platformMismatch, stream);
|
|
|
|
writeIntBuffer(reinterpret_cast<PxU32 *>(mMeshData.mGRB_primAdjacencies), mMeshData.mNbTriangles*4, platformMismatch, stream);
|
|
writeIntBuffer(mMeshData.mGRB_faceRemap, mMeshData.mNbTriangles, platformMismatch, stream);
|
|
|
|
//Export GPU midphase structure
|
|
BV32Tree* bv32Tree = reinterpret_cast<BV32Tree*>(mMeshData.mGRB_BV32Tree);
|
|
BV32TriangleMeshBuilder::saveMidPhaseStructure(bv32Tree, stream, platformMismatch);
|
|
}
|
|
|
|
// End of GRB write ----------------------------------------------------------
|
|
|
|
return true;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#if PX_VC
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4996) // permitting use of gatherStrided until we have a replacement.
|
|
#endif
|
|
|
|
bool TriangleMeshBuilder::importMesh(const PxTriangleMeshDesc& desc,const PxCookingParams& params,PxTriangleMeshCookingResult::Enum* condition, bool validate)
|
|
{
|
|
//convert and clean the input mesh
|
|
//this is where the mesh data gets copied from user mem to our mem
|
|
|
|
PxVec3* verts = mMeshData.allocateVertices(desc.points.count);
|
|
Gu::TriangleT<PxU32>* tris = reinterpret_cast<Gu::TriangleT<PxU32>*>(mMeshData.allocateTriangles(desc.triangles.count, true, PxU32(params.buildGPUData)));
|
|
|
|
//copy, and compact to get rid of strides:
|
|
Cooking::gatherStrided(desc.points.data, verts, mMeshData.mNbVertices, sizeof(PxVec3), desc.points.stride);
|
|
|
|
#if PX_CHECKED
|
|
// PT: check all input vertices are valid
|
|
for(PxU32 i=0;i<desc.points.count;i++)
|
|
{
|
|
const PxVec3& p = verts[i];
|
|
if(!PxIsFinite(p.x) || !PxIsFinite(p.y) || !PxIsFinite(p.z))
|
|
{
|
|
Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, "input mesh contains corrupted vertex data");
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//for trigs index stride conversion and eventual reordering is also needed, I don't think flexicopy can do that for us.
|
|
|
|
Gu::TriangleT<PxU32>* dest = tris;
|
|
const Gu::TriangleT<PxU32>* pastLastDest = tris + mMeshData.mNbTriangles;
|
|
const PxU8* source = reinterpret_cast<const PxU8*>(desc.triangles.data);
|
|
|
|
//4 combos of 16 vs 32 and flip vs no flip
|
|
PxU32 c = (desc.flags & PxMeshFlag::eFLIPNORMALS)?PxU32(1):0;
|
|
if (desc.flags & PxMeshFlag::e16_BIT_INDICES)
|
|
{
|
|
//index stride conversion is also needed, I don't think flexicopy can do that for us.
|
|
while (dest < pastLastDest)
|
|
{
|
|
const PxU16 * trig16 = reinterpret_cast<const PxU16*>(source);
|
|
dest->v[0] = trig16[0];
|
|
dest->v[1] = trig16[1+c];
|
|
dest->v[2] = trig16[2-c];
|
|
dest ++;
|
|
source += desc.triangles.stride;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (dest < pastLastDest)
|
|
{
|
|
const PxU32 * trig32 = reinterpret_cast<const PxU32*>(source);
|
|
dest->v[0] = trig32[0];
|
|
dest->v[1] = trig32[1+c];
|
|
dest->v[2] = trig32[2-c];
|
|
dest ++;
|
|
source += desc.triangles.stride;
|
|
}
|
|
}
|
|
|
|
//copy the material index list if any:
|
|
if(desc.materialIndices.data)
|
|
{
|
|
PxMaterialTableIndex* materials = mMeshData.allocateMaterials();
|
|
Cooking::gatherStrided(desc.materialIndices.data, materials, mMeshData.mNbTriangles, sizeof(PxMaterialTableIndex), desc.materialIndices.stride);
|
|
|
|
// Check material indices
|
|
for(PxU32 i=0;i<mMeshData.mNbTriangles;i++) PX_ASSERT(materials[i]!=0xffff);
|
|
}
|
|
|
|
// Clean the mesh using ICE's MeshBuilder
|
|
// This fixes the bug in ConvexTest06 where the inertia tensor computation fails for a mesh => it works with a clean mesh
|
|
|
|
if (!(params.meshPreprocessParams & PxMeshPreprocessingFlag::eDISABLE_CLEAN_MESH) || validate)
|
|
{
|
|
if(!cleanMesh(validate, condition))
|
|
{
|
|
if(!validate)
|
|
Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, "cleaning the mesh failed");
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we need to fill the remap table if no cleaning was done
|
|
if(params.suppressTriangleMeshRemapTable == false)
|
|
{
|
|
PX_ASSERT(mMeshData.mFaceRemap == NULL);
|
|
mMeshData.mFaceRemap = PX_NEW(PxU32)[mMeshData.mNbTriangles];
|
|
for (PxU32 i = 0; i < mMeshData.mNbTriangles; i++)
|
|
mMeshData.mFaceRemap[i] = i;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#if PX_VC
|
|
#pragma warning(pop)
|
|
#endif
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void TriangleMeshBuilder::checkMeshIndicesSize()
|
|
{
|
|
Gu::TriangleMeshData& m = mMeshData;
|
|
|
|
// check if we can change indices from 32bits to 16bits
|
|
if(m.mNbVertices <= 0xffff && !m.has16BitIndices())
|
|
{
|
|
const PxU32 numTriangles = m.mNbTriangles;
|
|
PxU32* PX_RESTRICT indices32 = reinterpret_cast<PxU32*> (m.mTriangles);
|
|
PxU32* PX_RESTRICT grbIndices32 = reinterpret_cast<PxU32*>(m.mGRB_primIndices);
|
|
|
|
m.mTriangles = 0; // force a realloc
|
|
m.allocateTriangles(numTriangles, false, grbIndices32 != NULL ? 1u : 0u);
|
|
PX_ASSERT(m.has16BitIndices()); // realloc'ing without the force32bit flag changed it.
|
|
|
|
PxU16* PX_RESTRICT indices16 = reinterpret_cast<PxU16*> (m.mTriangles);
|
|
for (PxU32 i = 0; i < numTriangles * 3; i++)
|
|
indices16[i] = Ps::to16(indices32[i]);
|
|
|
|
PX_FREE(indices32);
|
|
|
|
if (grbIndices32)
|
|
{
|
|
PxU16* PX_RESTRICT grbIndices16 = reinterpret_cast<PxU16*> (m.mGRB_primIndices);
|
|
for (PxU32 i = 0; i < numTriangles * 3; i++)
|
|
grbIndices16[i] = Ps::to16(grbIndices32[i]);
|
|
}
|
|
|
|
PX_FREE(grbIndices32);
|
|
|
|
onMeshIndexFormatChange();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
BV4TriangleMeshBuilder::BV4TriangleMeshBuilder(const PxCookingParams& params) : TriangleMeshBuilder(mData, params)
|
|
{
|
|
}
|
|
|
|
BV4TriangleMeshBuilder::~BV4TriangleMeshBuilder()
|
|
{
|
|
}
|
|
|
|
void BV4TriangleMeshBuilder::onMeshIndexFormatChange()
|
|
{
|
|
IndTri32* triangles32 = NULL;
|
|
IndTri16* triangles16 = NULL;
|
|
if(mMeshData.mFlags & PxTriangleMeshFlag::e16_BIT_INDICES)
|
|
triangles16 = reinterpret_cast<IndTri16*>(mMeshData.mTriangles);
|
|
else
|
|
triangles32 = reinterpret_cast<IndTri32*>(mMeshData.mTriangles);
|
|
|
|
mData.mMeshInterface.setPointers(triangles32, triangles16, mMeshData.mVertices);
|
|
}
|
|
|
|
void BV4TriangleMeshBuilder::createMidPhaseStructure()
|
|
{
|
|
const float gBoxEpsilon = 2e-4f;
|
|
// const float gBoxEpsilon = 0.1f;
|
|
mData.mMeshInterface.initRemap();
|
|
mData.mMeshInterface.setNbVertices(mMeshData.mNbVertices);
|
|
mData.mMeshInterface.setNbTriangles(mMeshData.mNbTriangles);
|
|
|
|
IndTri32* triangles32 = NULL;
|
|
IndTri16* triangles16 = NULL;
|
|
if (mMeshData.mFlags & PxTriangleMeshFlag::e16_BIT_INDICES)
|
|
{
|
|
triangles16 = reinterpret_cast<IndTri16*>(mMeshData.mTriangles);
|
|
}
|
|
else
|
|
{
|
|
triangles32 = reinterpret_cast<IndTri32*>(mMeshData.mTriangles);
|
|
}
|
|
|
|
mData.mMeshInterface.setPointers(triangles32, triangles16, mMeshData.mVertices);
|
|
|
|
const PxU32 nbTrisPerLeaf = (mParams.midphaseDesc.getType() == PxMeshMidPhase::eBVH34) ? mParams.midphaseDesc.mBVH34Desc.numPrimsPerLeaf : 4;
|
|
|
|
if(!BuildBV4Ex(mData.mBV4Tree, mData.mMeshInterface, gBoxEpsilon, nbTrisPerLeaf))
|
|
{
|
|
Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, "BV4 tree failed to build.");
|
|
return;
|
|
}
|
|
|
|
// remapTopology(mData.mMeshInterface);
|
|
|
|
const PxU32* order = mData.mMeshInterface.getRemap();
|
|
if(mMeshData.mMaterialIndices)
|
|
{
|
|
PxMaterialTableIndex* newMat = PX_NEW(PxMaterialTableIndex)[mMeshData.mNbTriangles];
|
|
for(PxU32 i=0;i<mMeshData.mNbTriangles;i++)
|
|
newMat[i] = mMeshData.mMaterialIndices[order[i]];
|
|
PX_DELETE_POD(mMeshData.mMaterialIndices);
|
|
mMeshData.mMaterialIndices = newMat;
|
|
}
|
|
|
|
if (!mParams.suppressTriangleMeshRemapTable || mParams.buildGPUData)
|
|
{
|
|
PxU32* newMap = PX_NEW(PxU32)[mMeshData.mNbTriangles];
|
|
for(PxU32 i=0;i<mMeshData.mNbTriangles;i++)
|
|
newMap[i] = mMeshData.mFaceRemap ? mMeshData.mFaceRemap[order[i]] : order[i];
|
|
PX_DELETE_POD(mMeshData.mFaceRemap);
|
|
mMeshData.mFaceRemap = newMap;
|
|
}
|
|
mData.mMeshInterface.releaseRemap();
|
|
}
|
|
|
|
void BV4TriangleMeshBuilder::saveMidPhaseStructure(PxOutputStream& stream, bool mismatch) const
|
|
{
|
|
// PT: in version 1 we defined "mismatch" as:
|
|
// const bool mismatch = (littleEndian() == 1);
|
|
// i.e. the data was *always* saved to file in big-endian format no matter what.
|
|
// In version>1 we now do the same as for other structures in the SDK: the data is
|
|
// exported either as little or big-endian depending on the passed parameter.
|
|
const PxU32 bv4StructureVersion = 3;
|
|
|
|
writeChunk('B', 'V', '4', ' ', stream);
|
|
writeDword(bv4StructureVersion, mismatch, stream);
|
|
|
|
writeFloat(mData.mBV4Tree.mLocalBounds.mCenter.x, mismatch, stream);
|
|
writeFloat(mData.mBV4Tree.mLocalBounds.mCenter.y, mismatch, stream);
|
|
writeFloat(mData.mBV4Tree.mLocalBounds.mCenter.z, mismatch, stream);
|
|
writeFloat(mData.mBV4Tree.mLocalBounds.mExtentsMagnitude, mismatch, stream);
|
|
|
|
writeDword(mData.mBV4Tree.mInitData, mismatch, stream);
|
|
|
|
writeFloat(mData.mBV4Tree.mCenterOrMinCoeff.x, mismatch, stream);
|
|
writeFloat(mData.mBV4Tree.mCenterOrMinCoeff.y, mismatch, stream);
|
|
writeFloat(mData.mBV4Tree.mCenterOrMinCoeff.z, mismatch, stream);
|
|
writeFloat(mData.mBV4Tree.mExtentsOrMaxCoeff.x, mismatch, stream);
|
|
writeFloat(mData.mBV4Tree.mExtentsOrMaxCoeff.y, mismatch, stream);
|
|
writeFloat(mData.mBV4Tree.mExtentsOrMaxCoeff.z, mismatch, stream);
|
|
|
|
// PT: version 3
|
|
writeDword(PxU32(mData.mBV4Tree.mQuantized), mismatch, stream);
|
|
|
|
writeDword(mData.mBV4Tree.mNbNodes, mismatch, stream);
|
|
|
|
#ifdef GU_BV4_USE_SLABS
|
|
// PT: we use BVDataPacked to get the size computation right, but we're dealing with BVDataSwizzled here!
|
|
#ifdef GU_BV4_COMPILE_NON_QUANTIZED_TREE
|
|
const PxU32 NodeSize = mData.mBV4Tree.mQuantized ? sizeof(BVDataPackedQ) : sizeof(BVDataPackedNQ);
|
|
#else
|
|
const PxU32 NodeSize = sizeof(BVDataPackedQ);
|
|
#endif
|
|
stream.write(mData.mBV4Tree.mNodes, NodeSize*mData.mBV4Tree.mNbNodes);
|
|
PX_ASSERT(!mismatch);
|
|
#else
|
|
#error Not implemented
|
|
#endif
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void BV32TriangleMeshBuilder::createMidPhaseStructure(const PxCookingParams& params, Gu::TriangleMeshData& meshData, Gu::BV32Tree& bv32Tree)
|
|
{
|
|
const float gBoxEpsilon = 2e-4f;
|
|
|
|
Gu::SourceMesh meshInterface;
|
|
// const float gBoxEpsilon = 0.1f;
|
|
meshInterface.initRemap();
|
|
meshInterface.setNbVertices(meshData.mNbVertices);
|
|
meshInterface.setNbTriangles(meshData.mNbTriangles);
|
|
|
|
PX_ASSERT(!(meshData.mFlags & PxTriangleMeshFlag::e16_BIT_INDICES));
|
|
|
|
IndTri32* triangles32 = reinterpret_cast<IndTri32*>(meshData.mGRB_primIndices);
|
|
|
|
meshInterface.setPointers(triangles32, NULL, meshData.mVertices);
|
|
|
|
PxU32 nbTrisPerLeaf = 32;
|
|
|
|
if (!BuildBV32Ex(bv32Tree, meshInterface, gBoxEpsilon, nbTrisPerLeaf))
|
|
{
|
|
Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, "BV32 tree failed to build.");
|
|
return;
|
|
}
|
|
|
|
const PxU32* order = meshInterface.getRemap();
|
|
|
|
if (!params.suppressTriangleMeshRemapTable || params.buildGPUData)
|
|
{
|
|
PxU32* newMap = PX_NEW(PxU32)[meshData.mNbTriangles];
|
|
for (PxU32 i = 0; i<meshData.mNbTriangles; i++)
|
|
newMap[i] = meshData.mGRB_faceRemap ? meshData.mGRB_faceRemap[order[i]] : order[i];
|
|
PX_DELETE_POD(meshData.mGRB_faceRemap);
|
|
meshData.mGRB_faceRemap = newMap;
|
|
}
|
|
|
|
meshInterface.releaseRemap();
|
|
|
|
}
|
|
|
|
void BV32TriangleMeshBuilder::saveMidPhaseStructure(Gu::BV32Tree* bv32Tree, PxOutputStream& stream, bool mismatch)
|
|
{
|
|
// PT: in version 1 we defined "mismatch" as:
|
|
// const bool mismatch = (littleEndian() == 1);
|
|
// i.e. the data was *always* saved to file in big-endian format no matter what.
|
|
// In version>1 we now do the same as for other structures in the SDK: the data is
|
|
// exported either as little or big-endian depending on the passed parameter.
|
|
const PxU32 bv32StructureVersion = 2;
|
|
|
|
writeChunk('B', 'V', '3', '2', stream);
|
|
writeDword(bv32StructureVersion, mismatch, stream);
|
|
|
|
writeFloat(bv32Tree->mLocalBounds.mCenter.x, mismatch, stream);
|
|
writeFloat(bv32Tree->mLocalBounds.mCenter.y, mismatch, stream);
|
|
writeFloat(bv32Tree->mLocalBounds.mCenter.z, mismatch, stream);
|
|
writeFloat(bv32Tree->mLocalBounds.mExtentsMagnitude, mismatch, stream);
|
|
|
|
writeDword(bv32Tree->mInitData, mismatch, stream);
|
|
|
|
writeDword(bv32Tree->mNbPackedNodes, mismatch, stream);
|
|
|
|
PX_ASSERT(bv32Tree->mNbPackedNodes > 0);
|
|
for (PxU32 i = 0; i < bv32Tree->mNbPackedNodes; ++i)
|
|
{
|
|
BV32DataPacked& node = bv32Tree->mPackedNodes[i];
|
|
|
|
const PxU32 nbElements = node.mNbNodes * 4;
|
|
writeDword(node.mNbNodes, mismatch, stream);
|
|
WriteDwordBuffer(node.mData, node.mNbNodes, mismatch, stream);
|
|
writeFloatBuffer(&node.mCenter[0].x, nbElements, mismatch, stream);
|
|
writeFloatBuffer(&node.mExtents[0].x, nbElements, mismatch, stream);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
RTreeTriangleMeshBuilder::RTreeTriangleMeshBuilder(const PxCookingParams& params) : TriangleMeshBuilder(mData, params)
|
|
{
|
|
}
|
|
|
|
RTreeTriangleMeshBuilder::~RTreeTriangleMeshBuilder()
|
|
{
|
|
}
|
|
|
|
struct RTreeCookerRemap : RTreeCooker::RemapCallback
|
|
{
|
|
PxU32 mNbTris;
|
|
RTreeCookerRemap(PxU32 numTris) : mNbTris(numTris)
|
|
{
|
|
}
|
|
|
|
virtual void remap(PxU32* val, PxU32 start, PxU32 leafCount)
|
|
{
|
|
PX_ASSERT(leafCount > 0);
|
|
PX_ASSERT(leafCount <= 16); // sanity check
|
|
PX_ASSERT(start < mNbTris);
|
|
PX_ASSERT(start+leafCount <= mNbTris);
|
|
PX_ASSERT(val);
|
|
LeafTriangles lt;
|
|
// here we remap from ordered leaf index in the rtree to index in post-remap in triangles
|
|
// this post-remap will happen later
|
|
lt.SetData(leafCount, start);
|
|
*val = lt.Data;
|
|
}
|
|
};
|
|
|
|
void RTreeTriangleMeshBuilder::createMidPhaseStructure()
|
|
{
|
|
const PxReal meshSizePerformanceTradeOff = mParams.midphaseDesc.mBVH33Desc.meshSizePerformanceTradeOff;
|
|
const PxMeshCookingHint::Enum meshCookingHint = mParams.midphaseDesc.mBVH33Desc.meshCookingHint;
|
|
|
|
Array<PxU32> resultPermute;
|
|
RTreeCookerRemap rc(mMeshData.mNbTriangles);
|
|
RTreeCooker::buildFromTriangles(
|
|
mData.mRTree,
|
|
mMeshData.mVertices, mMeshData.mNbVertices,
|
|
(mMeshData.mFlags & PxTriangleMeshFlag::e16_BIT_INDICES) ? reinterpret_cast<PxU16*>(mMeshData.mTriangles) : NULL,
|
|
!(mMeshData.mFlags & PxTriangleMeshFlag::e16_BIT_INDICES) ? reinterpret_cast<PxU32*>(mMeshData.mTriangles) : NULL,
|
|
mMeshData.mNbTriangles, resultPermute, &rc, meshSizePerformanceTradeOff, meshCookingHint);
|
|
|
|
PX_ASSERT(resultPermute.size() == mMeshData.mNbTriangles);
|
|
|
|
remapTopology(resultPermute.begin());
|
|
}
|
|
|
|
void RTreeTriangleMeshBuilder::saveMidPhaseStructure(PxOutputStream& stream, bool mismatch) const
|
|
{
|
|
// PT: in version 1 we defined "mismatch" as:
|
|
// const bool mismatch = (littleEndian() == 1);
|
|
// i.e. the data was *always* saved to file in big-endian format no matter what.
|
|
// In version>1 we now do the same as for other structures in the SDK: the data is
|
|
// exported either as little or big-endian depending on the passed parameter.
|
|
const PxU32 rtreeStructureVersion = 2;
|
|
|
|
// save the RTree root structure followed immediately by RTreePage pages to an output stream
|
|
writeChunk('R', 'T', 'R', 'E', stream);
|
|
|
|
writeDword(rtreeStructureVersion, mismatch, stream);
|
|
const RTree& d = mData.mRTree;
|
|
writeFloatBuffer(&d.mBoundsMin.x, 4, mismatch, stream);
|
|
writeFloatBuffer(&d.mBoundsMax.x, 4, mismatch, stream);
|
|
writeFloatBuffer(&d.mInvDiagonal.x, 4, mismatch, stream);
|
|
writeFloatBuffer(&d.mDiagonalScaler.x, 4, mismatch, stream);
|
|
writeDword(d.mPageSize, mismatch, stream);
|
|
writeDword(d.mNumRootPages, mismatch, stream);
|
|
writeDword(d.mNumLevels, mismatch, stream);
|
|
writeDword(d.mTotalNodes, mismatch, stream);
|
|
writeDword(d.mTotalPages, mismatch, stream);
|
|
PxU32 unused = 0; writeDword(unused, mismatch, stream); // backwards compatibility
|
|
for (PxU32 j = 0; j < d.mTotalPages; j++)
|
|
{
|
|
writeFloatBuffer(d.mPages[j].minx, RTREE_N, mismatch, stream);
|
|
writeFloatBuffer(d.mPages[j].miny, RTREE_N, mismatch, stream);
|
|
writeFloatBuffer(d.mPages[j].minz, RTREE_N, mismatch, stream);
|
|
writeFloatBuffer(d.mPages[j].maxx, RTREE_N, mismatch, stream);
|
|
writeFloatBuffer(d.mPages[j].maxy, RTREE_N, mismatch, stream);
|
|
writeFloatBuffer(d.mPages[j].maxz, RTREE_N, mismatch, stream);
|
|
WriteDwordBuffer(d.mPages[j].ptrs, RTREE_N, mismatch, stream);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}
|