// // 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 "foundation/PxErrorCallback.h" #include "common/PxPhysicsInsertionCallback.h" #include "geometry/PxSimpleTriangleMesh.h" #include "geometry/PxHeightFieldDesc.h" #include "cooking/PxTriangleMeshDesc.h" #include "cooking/PxConvexMeshDesc.h" #include "cooking/PxCooking.h" #include "PxPhysXConfig.h" #include "PsFoundation.h" #include "PsUtilities.h" #include "PsFPU.h" #include "CmPhysXCommon.h" #include "CmIO.h" #include "CmUtils.h" #include "Cooking.h" #include "mesh/TriangleMeshBuilder.h" #include "GuConvexMesh.h" #include "GuHeightField.h" #include "ConvexMeshBuilder.h" #include "BVHStructureBuilder.h" #include "QuickHullConvexHullLib.h" #include "HeightFieldCooking.h" /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// using namespace physx; using namespace Gu; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void Cooking::setParams(const PxCookingParams& params) { mParams = params; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const PxCookingParams& Cooking::getParams() const { return mParams; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool Cooking::platformMismatch() const { // Get current endianness (the one for the platform where cooking is performed) const PxI8 currentEndian = Ps::littleEndian(); const bool mismatch = currentEndian!=1; // The files must be little endian - we don't have big endian platforms anymore. return mismatch; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void Cooking::release() { delete this; Ps::Foundation::decRefCount(); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool Cooking::validateTriangleMesh(const PxTriangleMeshDesc& desc) const { // cooking code does lots of float bitwise reinterpretation that generates exceptions PX_FPU_GUARD; if(!desc.isValid()) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "Cooking::validateTriangleMesh: user-provided triangle mesh descriptor is invalid!"); return false; } // PT: validation code doesn't look at midphase data, so ideally we wouldn't build the midphase structure at all here. BV4TriangleMeshBuilder builder(mParams); return builder.loadFromDesc(desc, NULL, true /*doValidate*/); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool Cooking::cookTriangleMesh(TriangleMeshBuilder& builder, const PxTriangleMeshDesc& desc, PxOutputStream& stream, PxTriangleMeshCookingResult::Enum* condition) const { // cooking code does lots of float bitwise reinterpretation that generates exceptions PX_FPU_GUARD; if (condition) *condition = PxTriangleMeshCookingResult::eSUCCESS; if(!builder.loadFromDesc(desc, condition, false)) { return false; } builder.save(stream, platformMismatch(), mParams); return true; } bool Cooking::cookTriangleMesh(const PxTriangleMeshDesc& desc, PxOutputStream& stream, PxTriangleMeshCookingResult::Enum* condition) const { if(mParams.midphaseDesc.getType() == PxMeshMidPhase::eBVH33) { RTreeTriangleMeshBuilder builder(mParams); return cookTriangleMesh(builder, desc, stream, condition); } else { BV4TriangleMeshBuilder builder(mParams); return cookTriangleMesh(builder, desc, stream, condition); } } PxTriangleMesh* Cooking::createTriangleMesh(TriangleMeshBuilder& builder, const PxTriangleMeshDesc& desc, PxPhysicsInsertionCallback& insertionCallback, PxTriangleMeshCookingResult::Enum* condition) const { // cooking code does lots of float bitwise reinterpretation that generates exceptions PX_FPU_GUARD; if (condition) *condition = PxTriangleMeshCookingResult::eSUCCESS; if(!builder.loadFromDesc(desc, condition, false)) { return NULL; } // check if the indices can be moved from 32bits to 16bits if(!(mParams.meshPreprocessParams & PxMeshPreprocessingFlag::eFORCE_32BIT_INDICES)) builder.checkMeshIndicesSize(); PxConcreteType::Enum type; if(builder.getMidphaseID()==PxMeshMidPhase::eBVH33) type = PxConcreteType::eTRIANGLE_MESH_BVH33; else type = PxConcreteType::eTRIANGLE_MESH_BVH34; return static_cast(insertionCallback.buildObjectFromData(type, &builder.getMeshData())); } PxTriangleMesh* Cooking::createTriangleMesh(const PxTriangleMeshDesc& desc, PxPhysicsInsertionCallback& insertionCallback, PxTriangleMeshCookingResult::Enum* condition) const { if(mParams.midphaseDesc.getType() == PxMeshMidPhase::eBVH33) { RTreeTriangleMeshBuilder builder(mParams); return createTriangleMesh(builder, desc, insertionCallback, condition); } else { BV4TriangleMeshBuilder builder(mParams); return createTriangleMesh(builder, desc, insertionCallback, condition); } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cook convex mesh from given desc, internal function to be shared between create/cook convex mesh bool Cooking::cookConvexMeshInternal(const PxConvexMeshDesc& desc_, ConvexMeshBuilder& meshBuilder, ConvexHullLib* hullLib, PxConvexMeshCookingResult::Enum* condition) const { if (condition) *condition = PxConvexMeshCookingResult::eFAILURE; if (!desc_.isValid()) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "Cooking::cookConvexMesh: user-provided convex mesh descriptor is invalid!"); return false; } if (mParams.areaTestEpsilon <= 0.0f) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "Cooking::cookConvexMesh: provided cooking parameter areaTestEpsilon is invalid!"); return false; } if(mParams.planeTolerance < 0.0f) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "Cooking::cookConvexMesh: provided cooking parameter planeTolerance is invalid!"); return false; } PxConvexMeshDesc desc = desc_; bool polygonsLimitReached = false; // the convex will be cooked from provided points if (desc_.flags & PxConvexFlag::eCOMPUTE_CONVEX) { PX_ASSERT(hullLib); // clean up the indices information, it could have been set by accident desc.flags &= ~PxConvexFlag::e16_BIT_INDICES; desc.indices.count = 0; desc.indices.data = NULL; desc.indices.stride = 0; desc.polygons.count = 0; desc.polygons.data = NULL; desc.polygons.stride = 0; PxConvexMeshCookingResult::Enum res = hullLib->createConvexHull(); if (res == PxConvexMeshCookingResult::eSUCCESS || res == PxConvexMeshCookingResult::ePOLYGONS_LIMIT_REACHED) { if (res == PxConvexMeshCookingResult::ePOLYGONS_LIMIT_REACHED) polygonsLimitReached = true; hullLib->fillConvexMeshDesc(desc); } else { if (res == PxConvexMeshCookingResult::eZERO_AREA_TEST_FAILED) { *condition = PxConvexMeshCookingResult::eZERO_AREA_TEST_FAILED; } return false; } } if (desc.points.count >= 256) { Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, "Cooking::cookConvexMesh: user-provided hull must have less than 256 vertices!"); return false; } if (!meshBuilder.build(desc, mParams.gaussMapLimit, false, hullLib)) { return false; } if (condition) { *condition = polygonsLimitReached ? PxConvexMeshCookingResult::ePOLYGONS_LIMIT_REACHED : PxConvexMeshCookingResult::eSUCCESS; } return true; } ////////////////////////////////////////////////////////////////////////// // cook convex mesh from given desc, save the results into stream bool Cooking::cookConvexMesh(const PxConvexMeshDesc& desc_, PxOutputStream& stream, PxConvexMeshCookingResult::Enum* condition) const { PX_FPU_GUARD; // choose cooking library if needed ConvexHullLib* hullLib = NULL; PxConvexMeshDesc desc = desc_; if(desc_.flags & PxConvexFlag::eCOMPUTE_CONVEX) { const PxU16 gpuMaxVertsLimit = 64; // GRB supports 64 verts max if(desc_.flags & PxConvexFlag::eGPU_COMPATIBLE) { desc.vertexLimit = PxMin(desc.vertexLimit, gpuMaxVertsLimit); } hullLib = PX_NEW(QuickHullConvexHullLib) (desc, mParams); } ConvexMeshBuilder meshBuilder(mParams.buildGPUData); if(!cookConvexMeshInternal(desc,meshBuilder,hullLib , condition)) { if(hullLib) PX_DELETE(hullLib); return false; } // save the cooked results into stream if(!meshBuilder.save(stream, platformMismatch())) { if (condition) { *condition = PxConvexMeshCookingResult::eFAILURE; } if(hullLib) PX_DELETE(hullLib); return false; } if(hullLib) PX_DELETE(hullLib); return true; } ////////////////////////////////////////////////////////////////////////// // cook convex mesh from given desc, copy the results into internal convex mesh // and insert the mesh into PxPhysics PxConvexMesh* Cooking::createConvexMesh(const PxConvexMeshDesc& desc_, PxPhysicsInsertionCallback& insertionCallback, PxConvexMeshCookingResult::Enum* condition) const { PX_FPU_GUARD; // choose cooking library if needed ConvexHullLib* hullLib = NULL; PxConvexMeshDesc desc = desc_; if(desc.flags & PxConvexFlag::eCOMPUTE_CONVEX) { const PxU16 gpuMaxVertsLimit = 64; // GRB supports 64 verts max if(desc_.flags & PxConvexFlag::eGPU_COMPATIBLE) { desc.vertexLimit = PxMin(desc.vertexLimit, gpuMaxVertsLimit); } hullLib = PX_NEW(QuickHullConvexHullLib) (desc, mParams); } // cook the mesh ConvexMeshBuilder meshBuilder(mParams.buildGPUData); if (!cookConvexMeshInternal(desc, meshBuilder, hullLib, condition)) { if(hullLib) PX_DELETE(hullLib); return NULL; } // copy the constructed data into the new mesh Gu::ConvexHullInitData meshData; meshBuilder.copy(meshData); // insert into physics PxConvexMesh* convexMesh = static_cast(insertionCallback.buildObjectFromData(PxConcreteType::eCONVEX_MESH, &meshData)); if (!convexMesh) { if(condition) *condition = PxConvexMeshCookingResult::eFAILURE; if (hullLib) PX_DELETE(hullLib); return NULL; } if(hullLib) PX_DELETE(hullLib); return convexMesh; } ////////////////////////////////////////////////////////////////////////// bool Cooking::validateConvexMesh(const PxConvexMeshDesc& desc) const { ConvexMeshBuilder mesh(mParams.buildGPUData); return mesh.build(desc, mParams.gaussMapLimit, true); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool Cooking::computeHullPolygons(const PxSimpleTriangleMesh& mesh, PxAllocatorCallback& inCallback,PxU32& nbVerts, PxVec3*& vertices, PxU32& nbIndices, PxU32*& indices, PxU32& nbPolygons, PxHullPolygon*& hullPolygons) const { PxVec3* geometry = reinterpret_cast(PxAlloca(sizeof(PxVec3)*mesh.points.count)); Cooking::gatherStrided(mesh.points.data, geometry, mesh.points.count, sizeof(PxVec3), mesh.points.stride); PxU32* topology = reinterpret_cast(PxAlloca(sizeof(PxU32)*3*mesh.triangles.count)); if (mesh.flags & PxMeshFlag::e16_BIT_INDICES) { // conversion; 16 bit index -> 32 bit index & stride PxU32* dest = topology; const PxU32* pastLastDest = topology + 3*mesh.triangles.count; const PxU8* source = reinterpret_cast(mesh.triangles.data); while (dest < pastLastDest) { const PxU16 * trig16 = reinterpret_cast(source); *dest++ = trig16[0]; *dest++ = trig16[1]; *dest++ = trig16[2]; source += mesh.triangles.stride; } } else { Cooking::gatherStrided(mesh.triangles.data, topology, mesh.triangles.count, sizeof(PxU32) * 3, mesh.triangles.stride); } ConvexMeshBuilder meshBuilder(mParams.buildGPUData); if(!meshBuilder.computeHullPolygons(mesh.points.count,geometry,mesh.triangles.count,topology,inCallback, nbVerts, vertices,nbIndices,indices,nbPolygons,hullPolygons)) return false; return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool Cooking::cookHeightField(const PxHeightFieldDesc& desc, PxOutputStream& stream) const { PX_FPU_GUARD; if(!desc.isValid()) { #if PX_CHECKED Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "Cooking::createHeightField: user-provided heightfield descriptor is invalid!"); #endif return false; } Gu::HeightField hf(NULL); if(!hf.loadFromDesc(desc)) { hf.releaseMemory(); return false; } if (!saveHeightField(hf, stream, platformMismatch())) { hf.releaseMemory(); return false; } hf.releaseMemory(); return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PxHeightField* Cooking::createHeightField(const PxHeightFieldDesc& desc, PxPhysicsInsertionCallback& insertionCallback) const { PX_FPU_GUARD; if(!desc.isValid()) { #if PX_CHECKED Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "Cooking::createHeightField: user-provided heightfield descriptor is invalid!"); #endif return NULL; } Gu::HeightField* hf; PX_NEW_SERIALIZED(hf, Gu::HeightField)(NULL); if(!hf->loadFromDesc(desc)) { PX_DELETE(hf); return NULL; } // create heightfield and set the HF data Gu::HeightField* heightField = static_cast(insertionCallback.buildObjectFromData(PxConcreteType::eHEIGHTFIELD, &hf->mData)); if(!heightField) { PX_DELETE(hf); return NULL; } // copy the Gu::HeightField variables heightField->mSampleStride = hf->mSampleStride; heightField->mNbSamples = hf->mNbSamples; heightField->mMinHeight = hf->mMinHeight; heightField->mMaxHeight = hf->mMaxHeight; heightField->mModifyCount = hf->mModifyCount; PX_DELETE(hf); return heightField; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool Cooking::cookBVHStructure(const PxBVHStructureDesc& desc, PxOutputStream& stream) const { PX_FPU_GUARD; if(!desc.isValid()) { #if PX_CHECKED Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "Cooking::createHeightField: user-provided heightfield descriptor is invalid!"); #endif return false; } BVHStructureBuilder builder; if(!builder.loadFromDesc(desc)) { return false; } builder.save(stream, platformMismatch()); return true; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PxBVHStructure* Cooking::createBVHStructure(const PxBVHStructureDesc& desc, PxPhysicsInsertionCallback& insertionCallback) const { PX_FPU_GUARD; if(!desc.isValid()) { #if PX_CHECKED Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "Cooking::createHeightField: user-provided heightfield descriptor is invalid!"); #endif return NULL; } BVHStructureBuilder builder; if(!builder.loadFromDesc(desc)) { return NULL; } Gu::BVHStructureData bvhData; builder.moveData(bvhData); Gu::BVHStructure* bvhStructure = static_cast(insertionCallback.buildObjectFromData(PxConcreteType::eBVH_STRUCTURE, &bvhData)); return bvhStructure; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PxCooking* PxCreateCooking(PxU32 /*version*/, PxFoundation& foundation, const PxCookingParams& params) { PX_ASSERT(static_cast(&foundation) == &Ps::Foundation::getInstance()); PX_UNUSED(foundation); Ps::Foundation::incRefCount(); return PX_NEW(Cooking)(params); }