// 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. #include "GuConvexMesh.h" #include "foundation/PxMathUtils.h" #include "foundation/PxAlloca.h" #include "GuCooking.h" #include "GuBigConvexData2.h" #include "GuBounds.h" #include "GuCookingVolumeIntegration.h" #include "GuCookingConvexMeshBuilder.h" #include "GuCookingBigConvexDataBuilder.h" #include "CmUtils.h" #include "foundation/PxVecMath.h" #include "GuCookingSDF.h" using namespace physx; using namespace Gu; using namespace aos; /////////////////////////////////////////////////////////////////////////////// PX_IMPLEMENT_OUTPUT_ERROR /////////////////////////////////////////////////////////////////////////////// ConvexMeshBuilder::ConvexMeshBuilder(const bool buildGRBData) : hullBuilder(&mHullData, buildGRBData), mSdfData(NULL), mBigConvexData(NULL), mMass(0.0f), mInertia(PxIdentity) { } ConvexMeshBuilder::~ConvexMeshBuilder() { PX_DELETE(mSdfData); PX_DELETE(mBigConvexData); } // load the mesh data from given polygons bool ConvexMeshBuilder::build(const PxConvexMeshDesc& desc, PxU32 gaussMapVertexLimit, bool validateOnly, ConvexHullLib* hullLib) { if(!desc.isValid()) return outputError(__LINE__, "Gu::ConvexMesh::loadFromDesc: desc.isValid() failed!"); if(!loadConvexHull(desc, hullLib)) return false; // Compute local bounds (*after* hull has been created) PxBounds3 minMaxBounds; computeBoundsAroundVertices(minMaxBounds, mHullData.mNbHullVertices, hullBuilder.mHullDataHullVertices); mHullData.mAABB = CenterExtents(minMaxBounds); if(mHullData.mNbHullVertices > gaussMapVertexLimit) { if(!computeGaussMaps()) { return false; } } if(validateOnly) return true; // TEST_INTERNAL_OBJECTS computeInternalObjects(); //~TEST_INTERNAL_OBJECTS if (desc.sdfDesc) { computeSDF(desc); } return true; } PX_COMPILE_TIME_ASSERT(sizeof(PxMaterialTableIndex)==sizeof(PxU16)); bool ConvexMeshBuilder::save(PxOutputStream& stream, bool platformMismatch) const { // Export header if(!writeHeader('C', 'V', 'X', 'M', PX_CONVEX_VERSION, platformMismatch, stream)) return false; // Export serialization flags PxU32 serialFlags = 0; writeDword(serialFlags, platformMismatch, stream); if(!hullBuilder.save(stream, platformMismatch)) return false; // Export local bounds // writeFloat(geomEpsilon, platformMismatch, stream); writeFloat(0.0f, platformMismatch, stream); writeFloat(mHullData.mAABB.getMin(0), platformMismatch, stream); writeFloat(mHullData.mAABB.getMin(1), platformMismatch, stream); writeFloat(mHullData.mAABB.getMin(2), platformMismatch, stream); writeFloat(mHullData.mAABB.getMax(0), platformMismatch, stream); writeFloat(mHullData.mAABB.getMax(1), platformMismatch, stream); writeFloat(mHullData.mAABB.getMax(2), platformMismatch, stream); // Export mass info writeFloat(mMass, platformMismatch, stream); writeFloatBuffer(reinterpret_cast(&mInertia), 9, platformMismatch, stream); writeFloatBuffer(&mHullData.mCenterOfMass.x, 3, platformMismatch, stream); // Export gaussmaps if(mBigConvexData) { writeFloat(1.0f, platformMismatch, stream); //gauss map flag true BigConvexDataBuilder SVMB(&mHullData, mBigConvexData, hullBuilder.mHullDataHullVertices); SVMB.save(stream, platformMismatch); } else writeFloat(-1.0f, platformMismatch, stream); //gauss map flag false if (mSdfData) { writeFloat(1.0f, platformMismatch, stream); //sdf flag true // Export sdf values writeFloat(mSdfData->mMeshLower.x, platformMismatch, stream); writeFloat(mSdfData->mMeshLower.y, platformMismatch, stream); writeFloat(mSdfData->mMeshLower.z, platformMismatch, stream); writeFloat(mSdfData->mSpacing, platformMismatch, stream); writeDword(mSdfData->mDims.x, platformMismatch, stream); writeDword(mSdfData->mDims.y, platformMismatch, stream); writeDword(mSdfData->mDims.z, platformMismatch, stream); writeDword(mSdfData->mNumSdfs, platformMismatch, stream); writeDword(mSdfData->mNumSubgridSdfs, platformMismatch, stream); writeDword(mSdfData->mNumStartSlots, platformMismatch, stream); writeDword(mSdfData->mSubgridSize, platformMismatch, stream); writeDword(mSdfData->mSdfSubgrids3DTexBlockDim.x, platformMismatch, stream); writeDword(mSdfData->mSdfSubgrids3DTexBlockDim.y, platformMismatch, stream); writeDword(mSdfData->mSdfSubgrids3DTexBlockDim.z, platformMismatch, stream); writeFloat(mSdfData->mSubgridsMinSdfValue, platformMismatch, stream); writeFloat(mSdfData->mSubgridsMaxSdfValue, platformMismatch, stream); writeDword(mSdfData->mBytesPerSparsePixel, platformMismatch, stream); writeFloatBuffer(mSdfData->mSdf, mSdfData->mNumSdfs, platformMismatch, stream); writeByteBuffer(mSdfData->mSubgridSdf, mSdfData->mNumSubgridSdfs, stream); writeIntBuffer(mSdfData->mSubgridStartSlots, mSdfData->mNumStartSlots, platformMismatch, stream); } else writeFloat(-1.0f, platformMismatch, stream); //sdf flag false // TEST_INTERNAL_OBJECTS writeFloat(mHullData.mInternal.mInternalRadius, platformMismatch, stream); writeFloat(mHullData.mInternal.mInternalExtents.x, platformMismatch, stream); writeFloat(mHullData.mInternal.mInternalExtents.y, platformMismatch, stream); writeFloat(mHullData.mInternal.mInternalExtents.z, platformMismatch, stream); //~TEST_INTERNAL_OBJECTS return true; } ////////////////////////////////////////////////////////////////////////// // instead of saving the data into stream, we copy the mesh data into internal Gu::ConvexMesh. bool ConvexMeshBuilder::copy(Gu::ConvexHullInitData& hullData) { // hull builder data copy PxU32 nb = 0; hullBuilder.copy(hullData.mHullData, nb); hullData.mNb = nb; hullData.mInertia = mInertia; hullData.mMass = mMass; // mass props hullData.mHullData.mAABB = mHullData.mAABB; hullData.mHullData.mCenterOfMass = mHullData.mCenterOfMass; // big convex data if(mBigConvexData) { hullData.mHullData.mBigConvexRawData = &mBigConvexData->mData; hullData.mBigConvexData = mBigConvexData; mBigConvexData = NULL; } else { hullData.mHullData.mBigConvexRawData = NULL; hullData.mBigConvexData = NULL; } if (mSdfData) { hullData.mHullData.mSdfData = mSdfData; hullData.mSdfData = mSdfData; mSdfData = NULL; } else { hullData.mHullData.mSdfData = NULL; hullData.mSdfData = NULL; } // internal data hullData.mHullData.mInternal.mInternalExtents = mHullData.mInternal.mInternalExtents; hullData.mHullData.mInternal.mInternalRadius = mHullData.mInternal.mInternalRadius; return true; } // compute mass and inertia of the convex mesh void ConvexMeshBuilder::computeMassInfo(bool lowerPrecision) { if(mMass <= 0.0f) //not yet computed. { PxIntegrals integrals; PxConvexMeshDesc meshDesc; meshDesc.points.count = mHullData.mNbHullVertices; meshDesc.points.data = hullBuilder.mHullDataHullVertices; meshDesc.points.stride = sizeof(PxVec3); meshDesc.polygons.data = hullBuilder.mHullDataPolygons; meshDesc.polygons.stride = sizeof(Gu::HullPolygonData); meshDesc.polygons.count = hullBuilder.mHull->mNbPolygons; meshDesc.indices.data = hullBuilder.mHullDataVertexData8; // using the centroid of the convex for the volume integration solved accuracy issues in cases where the inertia tensor // ended up close to not being positive definite and after a few further transforms the diagonalized inertia tensor ended // up with negative values. PxVec3 mean(0.0f); for(PxU32 i=0; i < mHullData.mNbHullVertices; i++) mean += hullBuilder.mHullDataHullVertices[i]; mean *= (1.0f / mHullData.mNbHullVertices); if(computeVolumeIntegralsEberly(meshDesc, 1.0f, integrals, mean, lowerPrecision)) { integrals.getOriginInertia(mInertia); mHullData.mCenterOfMass = integrals.COM; //note: the mass will be negative for an inside-out mesh! if(mInertia.column0.isFinite() && mInertia.column1.isFinite() && mInertia.column2.isFinite() && mHullData.mCenterOfMass.isFinite() && PxIsFinite(PxReal(integrals.mass))) { if (integrals.mass < 0) { outputError(__LINE__, "Gu::ConvexMesh: Mesh has a negative volume! Is it open or do (some) faces have reversed winding? (Taking absolute value.)"); integrals.mass = -integrals.mass; mInertia = -mInertia; } mMass = PxReal(integrals.mass); //set mass to valid value. return; } } outputError(__LINE__, "Gu::ConvexMesh: Error computing mesh mass properties!\n"); } } #if PX_VC #pragma warning(push) #pragma warning(disable:4996) // permitting use of gatherStrided until we have a replacement. #endif bool ConvexMeshBuilder::loadConvexHull(const PxConvexMeshDesc& desc, ConvexHullLib* hullLib) { // gather points PxVec3* geometry = reinterpret_cast(PxAlloca(sizeof(PxVec3)*desc.points.count)); immediateCooking::gatherStrided(desc.points.data, geometry, desc.points.count, sizeof(PxVec3), desc.points.stride); PxU32* topology = NULL; // gather indices // store the indices into topology if we have the polygon data if(desc.indices.data) { topology = reinterpret_cast(PxAlloca(sizeof(PxU32)*desc.indices.count)); if (desc.flags & PxConvexFlag::e16_BIT_INDICES) { // conversion; 16 bit index -> 32 bit index & stride PxU32* dest = topology; const PxU32* pastLastDest = topology + desc.indices.count; const PxU8* source = reinterpret_cast(desc.indices.data); while (dest < pastLastDest) { const PxU16 * trig16 = reinterpret_cast(source); *dest++ = *trig16; source += desc.indices.stride; } } else { immediateCooking::gatherStrided(desc.indices.data, topology, desc.indices.count, sizeof(PxU32), desc.indices.stride); } } // gather polygons PxHullPolygon* hullPolygons = NULL; if(desc.polygons.data) { hullPolygons = reinterpret_cast(PxAlloca(sizeof(PxHullPolygon)*desc.polygons.count)); immediateCooking::gatherStrided(desc.polygons.data,hullPolygons,desc.polygons.count,sizeof(PxHullPolygon),desc.polygons.stride); // if user polygons, make sure the largest one is the first one if (!hullLib) { PxU32 largestPolygon = 0; for (PxU32 i = 1; i < desc.polygons.count; i++) { if(hullPolygons[i].mNbVerts > hullPolygons[largestPolygon].mNbVerts) largestPolygon = i; } if(largestPolygon != 0) { PxHullPolygon movedPolygon = hullPolygons[0]; hullPolygons[0] = hullPolygons[largestPolygon]; hullPolygons[largestPolygon] = movedPolygon; } } } const bool doValidation = desc.flags & PxConvexFlag::eDISABLE_MESH_VALIDATION ? false : true; if(!hullBuilder.init(desc.points.count, geometry, topology, desc.indices.count, desc.polygons.count, hullPolygons, doValidation, hullLib)) return outputError(__LINE__, "Gu::ConvexMesh::loadConvexHull: convex hull init failed!"); computeMassInfo(desc.flags & PxConvexFlag::eFAST_INERTIA_COMPUTATION); return true; } #if PX_VC #pragma warning(pop) #endif // compute polygons from given triangles. This is support function used in extensions. We do not accept triangles as an input for convex mesh desc. bool ConvexMeshBuilder::computeHullPolygons(const PxU32& nbVerts,const PxVec3* verts, const PxU32& nbTriangles, const PxU32* triangles, PxAllocatorCallback& inAllocator, PxU32& outNbVerts, PxVec3*& outVertices , PxU32& nbIndices, PxU32*& indices, PxU32& nbPolygons, PxHullPolygon*& polygons) { if(!hullBuilder.computeHullPolygons(nbVerts,verts,nbTriangles,triangles)) return outputError(__LINE__, "ConvexMeshBuilder::computeHullPolygons: compute convex hull polygons failed. Provided triangles dont form a convex hull."); outNbVerts = hullBuilder.mHull->mNbHullVertices; nbPolygons = hullBuilder.mHull->mNbPolygons; outVertices = reinterpret_cast(inAllocator.allocate(outNbVerts*sizeof(PxVec3),"PxVec3",__FILE__,__LINE__)); PxMemCopy(outVertices,hullBuilder.mHullDataHullVertices,outNbVerts*sizeof(PxVec3)); nbIndices = 0; for (PxU32 i = 0; i < nbPolygons; i++) { nbIndices += hullBuilder.mHullDataPolygons[i].mNbVerts; } indices = reinterpret_cast(inAllocator.allocate(nbIndices*sizeof(PxU32),"PxU32",__FILE__,__LINE__)); for (PxU32 i = 0; i < nbIndices; i++) { indices[i] = hullBuilder.mHullDataVertexData8[i]; } polygons = reinterpret_cast(inAllocator.allocate(nbPolygons*sizeof(PxHullPolygon),"PxHullPolygon",__FILE__,__LINE__)); for (PxU32 i = 0; i < nbPolygons; i++) { const Gu::HullPolygonData& polygonData = hullBuilder.mHullDataPolygons[i]; PxHullPolygon& outPolygon = polygons[i]; outPolygon.mPlane[0] = polygonData.mPlane.n.x; outPolygon.mPlane[1] = polygonData.mPlane.n.y; outPolygon.mPlane[2] = polygonData.mPlane.n.z; outPolygon.mPlane[3] = polygonData.mPlane.d; outPolygon.mNbVerts = polygonData.mNbVerts; outPolygon.mIndexBase = polygonData.mVRef8; for (PxU32 j = 0; j < polygonData.mNbVerts; j++) { PX_ASSERT(indices[outPolygon.mIndexBase + j] == hullBuilder.mHullDataVertexData8[polygonData.mVRef8+j]); } } return true; } // compute big convex data bool ConvexMeshBuilder::computeGaussMaps() { // The number of polygons is limited to 256 because the gaussmap encode 256 polys maximum PxU32 density = 16; // density = 64; // density = 8; // density = 2; PX_DELETE(mBigConvexData); PX_NEW_SERIALIZED(mBigConvexData,BigConvexData); BigConvexDataBuilder SVMB(&mHullData, mBigConvexData, hullBuilder.mHullDataHullVertices); // valencies we need to compute first, they are needed for min/max precompute SVMB.computeValencies(hullBuilder); SVMB.precompute(density); return true; } // TEST_INTERNAL_OBJECTS static void ComputeInternalExtent(Gu::ConvexHullData& data, const Gu::HullPolygonData* hullPolys) { const PxVec3 e = data.mAABB.getMax() - data.mAABB.getMin(); // PT: For that formula, see %SDKRoot%\InternalDocumentation\Cooking\InternalExtents.png const float r = data.mInternal.mInternalRadius / sqrtf(3.0f); const float epsilon = 1E-7f; const PxU32 largestExtent = PxLargestAxis(e); PxU32 e0 = PxGetNextIndex3(largestExtent); PxU32 e1 = PxGetNextIndex3(e0); if(e[e0] < e[e1]) PxSwap(e0,e1); PxVec3 internalExtents(FLT_MAX); // PT: the following code does ray-vs-plane raycasts. // find the largest box along the largest extent, with given internal radius for(PxU32 i = 0; i < data.mNbPolygons; i++) { // concurrent with search direction const float d = hullPolys[i].mPlane.n[largestExtent]; if((-epsilon < d && d < epsilon)) continue; const float numBase = -hullPolys[i].mPlane.d - hullPolys[i].mPlane.n.dot(data.mCenterOfMass); const float denBase = 1.0f/hullPolys[i].mPlane.n[largestExtent]; const float numn0 = r * hullPolys[i].mPlane.n[e0]; const float numn1 = r * hullPolys[i].mPlane.n[e1]; float num = numBase - numn0 - numn1; float ext = PxMax(fabsf(num*denBase), r); if(ext < internalExtents[largestExtent]) internalExtents[largestExtent] = ext; num = numBase - numn0 + numn1; ext = PxMax(fabsf(num *denBase), r); if(ext < internalExtents[largestExtent]) internalExtents[largestExtent] = ext; num = numBase + numn0 + numn1; ext = PxMax(fabsf(num *denBase), r); if(ext < internalExtents[largestExtent]) internalExtents[largestExtent] = ext; num = numBase + numn0 - numn1; ext = PxMax(fabsf(num *denBase), r); if(ext < internalExtents[largestExtent]) internalExtents[largestExtent] = ext; } // Refine the box along e0,e1 for(PxU32 i = 0; i < data.mNbPolygons; i++) { const float denumAdd = hullPolys[i].mPlane.n[e0] + hullPolys[i].mPlane.n[e1]; const float denumSub = hullPolys[i].mPlane.n[e0] - hullPolys[i].mPlane.n[e1]; const float numBase = -hullPolys[i].mPlane.d - hullPolys[i].mPlane.n.dot(data.mCenterOfMass); const float numn0 = internalExtents[largestExtent] * hullPolys[i].mPlane.n[largestExtent]; if(!(-epsilon < denumAdd && denumAdd < epsilon)) { float num = numBase - numn0; float ext = PxMax(fabsf(num/ denumAdd), r); if(ext < internalExtents[e0]) internalExtents[e0] = ext; num = numBase + numn0; ext = PxMax(fabsf(num / denumAdd), r); if(ext < internalExtents[e0]) internalExtents[e0] = ext; } if(!(-epsilon < denumSub && denumSub < epsilon)) { float num = numBase - numn0; float ext = PxMax(fabsf(num / denumSub), r); if(ext < internalExtents[e0]) internalExtents[e0] = ext; num = numBase + numn0; ext = PxMax(fabsf(num / denumSub), r); if(ext < internalExtents[e0]) internalExtents[e0] = ext; } } internalExtents[e1] = internalExtents[e0]; data.mInternal.mInternalExtents = internalExtents; } ////////////////////////////////////////////////////////////////////////// // compute internal objects, get the internal extent and radius void ConvexMeshBuilder::computeInternalObjects() { const Gu::HullPolygonData* hullPolys = hullBuilder.mHullDataPolygons; Gu::ConvexHullData& data = mHullData; // compute the internal radius float internalRadius = FLT_MAX; for(PxU32 i=0;i triangleIndice(numTotalTriangles * 3); PxU32 startIndex = 0; for (PxU32 i = 0; i < nbPolygons; ++i) { const Gu::HullPolygonData& polyData = hullPolys[i]; const PxU32 nbTriangles = polyData.mNbVerts - 2; const PxU8 vref0 = polygons[polyData.mVRef8]; for (PxU32 j = 0; j < nbTriangles; ++j) { const PxU32 index = startIndex + j * 3; const PxU32 vref1 = polygons[polyData.mVRef8 + 0 + j + 1]; const PxU32 vref2 = polygons[polyData.mVRef8 + 0 + j + 2]; triangleIndice[index + 0] = vref0; triangleIndice[index + 1] = vref1; triangleIndice[index + 2] = vref2; } startIndex += nbTriangles * 3; } PxArray sdfData; PxArray sdfDataSubgrids; PxArray sdfSubgridsStartSlots; PxTriangleMeshDesc triDesc; triDesc.points.count = nbVerts; triDesc.points.stride = sizeof(PxVec3); triDesc.points.data = verts; triDesc.triangles.count = numTotalTriangles; triDesc.triangles.stride = sizeof(PxU32) * 3; triDesc.triangles.data = triangleIndice.begin(); triDesc.flags &= (~PxMeshFlag::e16_BIT_INDICES); triDesc.sdfDesc = desc.sdfDesc; buildSDF(triDesc, sdfData, sdfDataSubgrids, sdfSubgridsStartSlots); PxSDFDesc& sdfDesc = *desc.sdfDesc; PxReal* sdf = mSdfData->allocateSdfs(sdfDesc.meshLower, sdfDesc.spacing, sdfDesc.dims.x, sdfDesc.dims.y, sdfDesc.dims.z, sdfDesc.subgridSize, sdfDesc.sdfSubgrids3DTexBlockDim.x, sdfDesc.sdfSubgrids3DTexBlockDim.y, sdfDesc.sdfSubgrids3DTexBlockDim.z, sdfDesc.subgridsMinSdfValue, sdfDesc.subgridsMaxSdfValue, sdfDesc.bitsPerSubgridPixel); if (sdfDesc.subgridSize > 0) { //Sparse sdf immediateCooking::gatherStrided(sdfDesc.sdf.data, sdf, sdfDesc.sdf.count, sizeof(PxReal), sdfDesc.sdf.stride); immediateCooking::gatherStrided(sdfDesc.sdfSubgrids.data, mSdfData->mSubgridSdf, sdfDesc.sdfSubgrids.count, sizeof(PxU8), sdfDesc.sdfSubgrids.stride); immediateCooking::gatherStrided(sdfDesc.sdfStartSlots.data, mSdfData->mSubgridStartSlots, sdfDesc.sdfStartSlots.count, sizeof(PxU32), sdfDesc.sdfStartSlots.stride); } else { //copy, and compact to get rid of strides: immediateCooking::gatherStrided(sdfDesc.sdf.data, sdf, sdfDesc.dims.x * sdfDesc.dims.y * sdfDesc.dims.z, sizeof(PxReal), sdfDesc.sdf.stride); } } //~TEST_INTERNAL_OBJECTS