virtual bool CookConvex(FName Format, const TArray<FVector>& SrcBuffer, TArray<uint8>& OutBuffer, bool bDeformableMesh = false) const override { #if WITH_PHYSX PxPlatform::Enum PhysXFormat = PxPlatform::ePC; bool bIsPhysXFormatValid = GetPhysXFormat(Format, PhysXFormat); check(bIsPhysXFormatValid); PxConvexMeshDesc PConvexMeshDesc; PConvexMeshDesc.points.data = SrcBuffer.GetData(); PConvexMeshDesc.points.count = SrcBuffer.Num(); PConvexMeshDesc.points.stride = sizeof(FVector); if (bDeformableMesh) { PConvexMeshDesc.flags = PxConvexFlag::eCOMPUTE_CONVEX | PxConvexFlag::eINFLATE_CONVEX; } else { PConvexMeshDesc.flags = PxConvexFlag::eCOMPUTE_CONVEX; } // Set up cooking const PxCookingParams Params = PhysXCooking->getParams(); PxCookingParams NewParams = Params; NewParams.targetPlatform = PhysXFormat; if (bDeformableMesh) { // Meshes which can be deformed need different cooking parameters to inhibit vertex welding and add an extra skin around the collision mesh for safety. // We need to set the meshWeldTolerance to zero, even when disabling 'clean mesh' as PhysX will attempt to perform mesh cleaning anyway according to this meshWeldTolerance // if the convex hull is not well formed. // Set the skin thickness as a proportion of the overall size of the mesh as PhysX's internal tolerances also use the overall size to calculate the epsilon used. const FBox Bounds(SrcBuffer); const float MaxExtent = (Bounds.Max - Bounds.Min).Size(); NewParams.skinWidth = MaxExtent / 512.0f; NewParams.meshPreprocessParams = PxMeshPreprocessingFlags(PxMeshPreprocessingFlag::eDISABLE_CLEAN_MESH); NewParams.areaTestEpsilon = 0.0f; NewParams.meshWeldTolerance = 0.0f; PhysXCooking->setParams(NewParams); } // Cook the convex mesh to a temp buffer TArray<uint8> CookedMeshBuffer; FPhysXOutputStream Buffer(&CookedMeshBuffer); bool Result = PhysXCooking->cookConvexMesh(PConvexMeshDesc, Buffer); // Return default cooking params to normal if (bDeformableMesh) { PhysXCooking->setParams(Params); } if( Result && CookedMeshBuffer.Num() > 0 ) { // Append the cooked data into cooked buffer OutBuffer.Append( CookedMeshBuffer ); return true; } #endif // WITH_PHYSX return false; }
void ConvertFbxFile(string inFilename, string outFilename, vector<AnimClip>& animClips, CollisionGeneration generateCollision) { Mesh mesh; { //Get mesh from FileReader FbxFileReader fbxFile(inFilename); mesh = fbxFile.GetMesh(); std::cout << "\nProcessing FBX file " << inFilename << "...\n\n"; //Extract vertex attributes and skeleton mesh.ExtractData(); std::cout << "Done.\nOptimizing vertex attributes... "; //Remove duplicates and use indirect arrays mesh.Optimize(); std::cout << "Done.\nExtracting bone transforms... "; //Get bone transforms for(auto& animClip : animClips) for(auto& transformAtTime : animClip.TransformsAtTimeStamps) transformAtTime.second = mesh.GetBoneTransforms(transformAtTime.first); } std::cout << "Done.\nChecking if vertices are linked to more than 4 bones... "; //Make sure none of the vertices is skinned to more than 4 bones for(auto& elem : mesh.BlendInformation.data) while(elem.BlendIndices.size() > 4) { elem.BlendIndices.pop_back(); elem.BlendWeights.pop_back(); } std::cout << "Done.\nBuilding vertex- and indexbuffers... "; // Construct vertexbuffer/indexBuffer vector<Vertex> vertexBuffer; vector<unsigned int> indexBuffer; for(unsigned int i=0; i < mesh.Positions.indices.size(); ++i){ Vertex newVert; newVert.iPosition = mesh.Positions.indices[i]; newVert.iTexCoord = mesh.TexCoords.indices.empty() ? 0 : mesh.TexCoords.indices[i]; newVert.iNormal = mesh.Normals.indices.empty() ? 0 : mesh.Normals.indices[i]; newVert.iTangent = mesh.Tangents.indices.empty() ? 0 : mesh.Tangents.indices[i]; newVert.iBinormal = mesh.Binormals.indices.empty() ? 0 : mesh.Binormals.indices[i]; newVert.iVertexColor = mesh.Colors.indices.empty() ? 0 : mesh.Colors.indices[i]; newVert.iAnimData = mesh.BlendInformation.indices.empty() ? 0 : mesh.BlendInformation.indices[i]; auto it = find(vertexBuffer.begin(), vertexBuffer.end(), newVert); if(it==vertexBuffer.end()){ vertexBuffer.push_back(newVert); it = vertexBuffer.end() - 1; } indexBuffer.push_back(it-vertexBuffer.begin()); } std::cout << "Done.\nWriting mesh data... "; //Write a binary file containing all of the mesh & skeleton data unsigned int version=1, nrOfUVChannels=mesh.Normals.data.empty() ?0:1, vertexFormat=0; //Build vertex format vertexFormat |= mesh.Normals.data.empty() ? 0 : 1 << 0; vertexFormat |= mesh.Tangents.data.empty() ? 0 : 1 << 1; vertexFormat |= mesh.Binormals.data.empty() ? 0 : 1 << 2; vertexFormat |= mesh.Colors.data.empty() ? 0 : 1 << 3; vertexFormat |= mesh.BlendInformation.data.empty() ? 0 : 1 << 4; //Create an output file BinaryWriter oFile(outFilename + ".ttmesh"); oFile.Write<unsigned short>(version); //version number oFile.Write<unsigned char>(vertexFormat); // vertex format oFile.Write<unsigned char>(nrOfUVChannels); // nr of texcoord channels oFile.Write<unsigned int>(mesh.Positions.data.size()); // nr of positions // nr of texcoords if(nrOfUVChannels > 0) oFile.Write<unsigned int>(mesh.TexCoords.data.size()); // nr of normals if(vertexFormat & 1 << 0) oFile.Write<unsigned int>(mesh.Normals.data.size()); // nr of tangents if(vertexFormat & 1 << 1) oFile.Write<unsigned int>(mesh.Tangents.data.size()); // nr of binormals if(vertexFormat & 1 << 2) oFile.Write<unsigned int>(mesh.Binormals.data.size()); // nr of vertex colors if(vertexFormat & 1 << 3) oFile.Write<unsigned int>(mesh.Colors.data.size()); //nr of blendweights/-indices if(vertexFormat & 1 << 4) oFile.Write<unsigned int>(mesh.BlendInformation.data.size()); oFile.Write<unsigned int>(vertexBuffer.size()); // nr of vertices oFile.Write<unsigned int>(indexBuffer.size()); // nr of indices for(auto& elem : mesh.Positions.data) //positions oFile.Write<FbxVector4>(elem); for(auto& elem : mesh.TexCoords.data) //texCoords oFile.Write<FbxVector2>(FbxVector2(elem.mData[0],1-elem.mData[1])); for(auto& elem : mesh.Normals.data) //normals oFile.Write<FbxVector4>(elem); for(auto& elem : mesh.Tangents.data) //tangents oFile.Write<FbxVector4>(elem); for(auto& elem : mesh.Binormals.data) //binormals oFile.Write<FbxVector4>(elem); for(auto& elem : mesh.Colors.data) //vertex colors oFile.Write<FbxColor>(elem); for(auto& elem : mesh.BlendInformation.data){ //blend indices oFile.Write<unsigned int>(elem.BlendIndices.size() ); for(auto index : elem.BlendIndices) oFile.Write<unsigned int>(index); //blend weights for(auto weight : elem.BlendWeights) oFile.Write<float>(static_cast<float>(weight) ); } //Vertex buffer for(auto& vertex : vertexBuffer){ oFile.Write<unsigned int>(vertex.iPosition); if(nrOfUVChannels > 0) oFile.Write<unsigned int>(vertex.iTexCoord); if(vertexFormat & 1 << 0) oFile.Write<unsigned int>(vertex.iNormal); if(vertexFormat & 1 << 1) oFile.Write<unsigned int>(vertex.iTangent); if(vertexFormat & 1 << 2) oFile.Write<unsigned int>(vertex.iBinormal); if(vertexFormat & 1 << 3) oFile.Write<unsigned int>(vertex.iVertexColor); if(vertexFormat & 1 << 4) oFile.Write<unsigned int>(vertex.iAnimData); } //Index buffer for(auto index : indexBuffer) oFile.Write<unsigned int>(index); std::cout << "Done.\nWriting skeleton data... "; //Bones (#, names, bindposes) oFile.Write<unsigned int>( mesh.Skeleton.size() ); for(auto& bone : mesh.Skeleton){ oFile.Write<std::string>(bone.Name); oFile.Write<FbxAMatrix>(bone.BindPose); } std::cout << "Done.\nWriting bone animations... "; //animClips (#, name, fps, nrOfKeys, keyTime0, boneTransforms0, keyTime1, boneTransforms1...) oFile.Write<unsigned int>( animClips.size() ); for(auto& animClip : animClips){ oFile.Write<std::string>(animClip.Name); oFile.Write<float>(animClip.FramesPerSecond); oFile.Write<unsigned int>( animClip.TransformsAtTimeStamps.size() ); for(auto& animKey : animClip.TransformsAtTimeStamps){ oFile.Write<float>(static_cast<float>(animKey.first) ); for(auto& boneTransform : animKey.second) oFile.Write<FbxAMatrix>(boneTransform); } } //Check if we need to generate a collision mesh if(generateCollision == CollisionGeneration::None) { std::cout << "Done.\n\nOperation succeeded!\n\n"; return; } std::cout << "Done.\nWriting PhysX data... "; //Initialize cooker PxCookingParams params{ PxTolerancesScale() }; PxCooking* pCooker = PxCreateCooking(PX_PHYSICS_VERSION, PxGetFoundation(), params); //Build a vertex buffer for PhysX (containing only vertex positions), also copy index buffer, casting to PxU32 unsigned int nrOfVerts = mesh.Positions.data.size(); unsigned int nrOfIndices = mesh.Positions.indices.size(); PxVec3* pVertices = new PxVec3[nrOfVerts]; PxU32* pIndices = new PxU32[nrOfIndices]; for(unsigned int i=0; i<nrOfVerts; ++i){ auto pos = mesh.Positions.data[i]; pVertices[i] = PxVec3( static_cast<PxReal>(pos.mData[0]), static_cast<PxReal>(pos.mData[1]), static_cast<PxReal>(pos.mData[2]) ); } for(unsigned int i=0; i<nrOfIndices; ++i) pIndices[i] = static_cast<PxU32>(mesh.Positions.indices[i]); PxTriangleMeshDesc triMeshDesc; PxConvexMeshDesc convexMeshDesc; //Build physical model switch(generateCollision){ case CollisionGeneration::Concave: //Fill desc triMeshDesc.points.count = nrOfVerts; triMeshDesc.triangles.count = nrOfIndices / 3; triMeshDesc.points.stride = sizeof(PxVec3); triMeshDesc.triangles.stride = 3 * sizeof(PxU32); triMeshDesc.points.data = pVertices; triMeshDesc.triangles.data = pIndices; //Cook pCooker->cookTriangleMesh(triMeshDesc, UserStream((outFilename + ".ttcol").c_str(), false)); break; case CollisionGeneration::Convex: //Fill desc convexMeshDesc.points.count = nrOfVerts; convexMeshDesc.triangles.count = nrOfIndices / 3; convexMeshDesc.points.stride = sizeof(PxVec3); convexMeshDesc.triangles.stride = 3*sizeof(PxU32); convexMeshDesc.points.data = pVertices; convexMeshDesc.triangles.data = pIndices; convexMeshDesc.flags.set(PxConvexFlag::Enum::eCOMPUTE_CONVEX); //Cook pCooker->cookConvexMesh(convexMeshDesc, UserStream( (outFilename + ".ttcol").c_str(), false)); break; }; //Stop cooking pCooker->release(); std::cout << "Done.\n\nOperation succeeded!\n\n"; }
virtual EPhysXCookingResult CookConvex(FName Format, int32 RuntimeCookFlags, const TArray<FVector>& SrcBuffer, TArray<uint8>& OutBuffer, bool bDeformableMesh = false) const override { EPhysXCookingResult CookResult = EPhysXCookingResult::Failed; #if WITH_PHYSX PxPlatform::Enum PhysXFormat = PxPlatform::ePC; bool bIsPhysXFormatValid = GetPhysXFormat(Format, PhysXFormat); check(bIsPhysXFormatValid); PxConvexMeshDesc PConvexMeshDesc; PConvexMeshDesc.points.data = SrcBuffer.GetData(); PConvexMeshDesc.points.count = SrcBuffer.Num(); PConvexMeshDesc.points.stride = sizeof(FVector); PConvexMeshDesc.flags = PxConvexFlag::eCOMPUTE_CONVEX; // Set up cooking const PxCookingParams Params = PhysXCooking->getParams(); PxCookingParams NewParams = Params; NewParams.targetPlatform = PhysXFormat; if(RuntimeCookFlags & ERuntimePhysxCookOptimizationFlags::SuppressFaceRemapTable) { NewParams.suppressTriangleMeshRemapTable = true; } if (bDeformableMesh) { // Meshes which can be deformed need different cooking parameters to inhibit vertex welding and add an extra skin around the collision mesh for safety. // We need to set the meshWeldTolerance to zero, even when disabling 'clean mesh' as PhysX will attempt to perform mesh cleaning anyway according to this meshWeldTolerance // if the convex hull is not well formed. // Set the skin thickness as a proportion of the overall size of the mesh as PhysX's internal tolerances also use the overall size to calculate the epsilon used. const FBox Bounds(SrcBuffer); const float MaxExtent = (Bounds.Max - Bounds.Min).Size(); NewParams.meshPreprocessParams = PxMeshPreprocessingFlags(PxMeshPreprocessingFlag::eDISABLE_CLEAN_MESH); NewParams.meshWeldTolerance = 0.0f; } PhysXCooking->setParams(NewParams); // Cook the convex mesh to a temp buffer TArray<uint8> CookedMeshBuffer; FPhysXOutputStream Buffer(&CookedMeshBuffer); if (PhysXCooking->cookConvexMesh(PConvexMeshDesc, Buffer)) { CookResult = EPhysXCookingResult::Succeeded; } else { if (!(PConvexMeshDesc.flags & PxConvexFlag::eINFLATE_CONVEX)) { // We failed to cook without inflating convex. Let's try again with inflation //This is not ideal since it makes the collision less accurate. It's needed if given verts are extremely close. PConvexMeshDesc.flags |= PxConvexFlag::eINFLATE_CONVEX; if (PhysXCooking->cookConvexMesh(PConvexMeshDesc, Buffer)) { CookResult = EPhysXCookingResult::SucceededWithInflation; } } } // Return default cooking params to normal if (bDeformableMesh) { PhysXCooking->setParams(Params); } if (CookedMeshBuffer.Num() == 0) { CookResult = EPhysXCookingResult::Failed; } if (CookResult != EPhysXCookingResult::Failed) { // Append the cooked data into cooked buffer OutBuffer.Append( CookedMeshBuffer ); } #endif // WITH_PHYSX return CookResult; }