// Copyright Epic Games, Inc. All Rights Reserved. #include "PhysXCooking.h" #include "Core/Public/Modules/ModuleManager.h" #if PHYSICS_INTERFACE_PHYSX #include "Serialization/MemoryWriter.h" #include "Modules/ModuleManager.h" #include "IPhysXCookingModule.h" #include "PhysicsPublicCore.h" #include "PhysXSupportCore.h" #include "PhysDerivedDataPublic.h" //only needed so that touching DDC will automatically re-link the cooking module #include "PhysicsInitialization.h" #include "Interface_CollisionDataProviderCore.h" static FName NAME_PhysXGeneric(TEXT("PhysXGeneric")); static FName NAME_PhysXPC(TEXT("PhysXPC")); bool GetPhysXCooking(FName InFormatName, PxPlatform::Enum& OutFormat) { if ((InFormatName == NAME_PhysXPC) || (InFormatName == NAME_PhysXGeneric)) { OutFormat = PxPlatform::ePC; } else { return false; } return true; } /** * Validates PhysX format name. */ bool CheckPhysXCooking(FName InFormatName) { PxPlatform::Enum Format = PxPlatform::ePC; return GetPhysXCooking(InFormatName, Format); } void UseBVH34IfSupported(FName Format, PxCookingParams& Params) { if((Format == NAME_PhysXPC)) { //TODO: can turn this on once bug is fixed with character movement //Params.midphaseDesc = PxMeshMidPhase::eBVH34; } } FPhysXCooking::FPhysXCooking() { #if WITH_PHYSX PxTolerancesScale PScale; PScale.length = CVarToleranceScaleLength.GetValueOnAnyThread(); PScale.speed = CVarToleranceScaleSpeed.GetValueOnAnyThread(); PxCookingParams PCookingParams(PScale); PCookingParams.meshWeldTolerance = 0.1f; // Weld to 1mm precision PCookingParams.meshPreprocessParams = PxMeshPreprocessingFlags(PxMeshPreprocessingFlag::eWELD_VERTICES); // Force any cooking in PhysX or APEX to use older incremental hull method // This is because the new 'quick hull' method can generate degenerate geometry in some cases (very thin meshes etc.) //PCookingParams.convexMeshCookingType = PxConvexMeshCookingType::eINFLATION_INCREMENTAL_HULL; PCookingParams.targetPlatform = PxPlatform::ePC; PCookingParams.midphaseDesc = PxMeshMidPhase::eBVH33; //PCookingParams.meshCookingHint = PxMeshCookingHint::eCOOKING_PERFORMANCE; //PCookingParams.meshSizePerformanceTradeOff = 0.0f; PhysXCooking = PxCreateCooking(PX_PHYSICS_VERSION, *GPhysXFoundation, PCookingParams); #endif } physx::PxCooking* FPhysXCooking::GetCooking() const { return PhysXCooking; } bool FPhysXCooking::AllowParallelBuild() const { return true; } uint16 FPhysXCooking::GetVersion(FName Format) const { check(CheckPhysXCooking(Format)); return UE_PHYSX_PC_VER; } void FPhysXCooking::GetSupportedFormats(TArray& OutFormats) const { OutFormats.Add(NAME_PhysXPC); OutFormats.Add(NAME_PhysXGeneric); } template EPhysXCookingResult FPhysXCooking::CookConvexImp(FName Format, EPhysXMeshCookFlags CookFlags, const TArray& SrcBuffer, TArray& OutBuffer, PxConvexMesh*& OutConvexMesh) const { EPhysXCookingResult CookResult = EPhysXCookingResult::Failed; OutConvexMesh = nullptr; #if WITH_PHYSX PxPlatform::Enum PhysXFormat = PxPlatform::ePC; bool bIsPhysXCookingValid = GetPhysXCooking(Format, PhysXFormat); check(bIsPhysXCookingValid); PxConvexMeshDesc PConvexMeshDesc; PConvexMeshDesc.points.data = SrcBuffer.GetData(); PConvexMeshDesc.points.count = SrcBuffer.Num(); PConvexMeshDesc.points.stride = sizeof(FVector); PConvexMeshDesc.flags = PxConvexFlag::eCOMPUTE_CONVEX | PxConvexFlag::eSHIFT_VERTICES; // Set up cooking const PxCookingParams CurrentParams = PhysXCooking->getParams(); PxCookingParams NewParams = CurrentParams; NewParams.targetPlatform = PhysXFormat; if (!!(CookFlags & EPhysXMeshCookFlags::SuppressFaceRemapTable)) { NewParams.suppressTriangleMeshRemapTable = true; } if (!!(CookFlags & EPhysXMeshCookFlags::DeformableMesh)) { // 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; } else { //For meshes that don't deform we can try to use BVH34 UseBVH34IfSupported(Format, NewParams); } // Do we want to do a 'fast' cook on this mesh, may slow down collision performance at runtime if (!!(CookFlags & EPhysXMeshCookFlags::FastCook)) { NewParams.meshCookingHint = PxMeshCookingHint::eCOOKING_PERFORMANCE; } PhysXCooking->setParams(NewParams); if (bUseBuffer) { // Cook the convex mesh to a temp buffer TArray 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; } } } if (CookedMeshBuffer.Num() == 0) { CookResult = EPhysXCookingResult::Failed; } if (CookResult != EPhysXCookingResult::Failed) { // Append the cooked data into cooked buffer OutBuffer.Append(CookedMeshBuffer); } } else { OutConvexMesh = PhysXCooking->createConvexMesh(PConvexMeshDesc, GPhysXSDK->getPhysicsInsertionCallback()); //NOTE: getPhysicsInsertionCallback probably not thread safe! if (OutConvexMesh) { 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; OutConvexMesh = PhysXCooking->createConvexMesh(PConvexMeshDesc, GPhysXSDK->getPhysicsInsertionCallback()); //NOTE: getPhysicsInsertionCallback probably not thread safe! if (OutConvexMesh) { CookResult = EPhysXCookingResult::SucceededWithInflation; } } } if (!OutConvexMesh) { CookResult = EPhysXCookingResult::Failed; } } // Return default cooking params to normal PhysXCooking->setParams(CurrentParams); #endif // WITH_PHYSX return CookResult; } EPhysXCookingResult FPhysXCooking::CookConvex(FName Format, EPhysXMeshCookFlags CookFlags, const TArray& SrcBuffer, TArray& OutBuffer) const { PxConvexMesh* JunkConvexMesh; return CookConvexImp(Format, CookFlags, SrcBuffer, OutBuffer, JunkConvexMesh); } EPhysXCookingResult FPhysXCooking::CreateConvex(FName Format, EPhysXMeshCookFlags CookFlags, const TArray& SrcBuffer, physx::PxConvexMesh*& OutConvexMesh) const { TArray JunkBuffer; return CookConvexImp(Format, CookFlags, SrcBuffer, JunkBuffer, OutConvexMesh); } template bool FPhysXCooking::CookTriMeshImp(FName Format, EPhysXMeshCookFlags CookFlags, const TArray& SrcVertices, const TArray& SrcIndices, const TArray& SrcMaterialIndices, const bool FlipNormals, TArray& OutBuffer, PxTriangleMesh*& OutTriangleMesh) const { OutTriangleMesh = nullptr; #if WITH_PHYSX PxPlatform::Enum PhysXFormat = PxPlatform::ePC; bool bIsPhysXCookingValid = GetPhysXCooking(Format, PhysXFormat); check(bIsPhysXCookingValid); PxTriangleMeshDesc PTriMeshDesc; PTriMeshDesc.points.data = SrcVertices.GetData(); PTriMeshDesc.points.count = SrcVertices.Num(); PTriMeshDesc.points.stride = sizeof(FVector); PTriMeshDesc.triangles.data = SrcIndices.GetData(); PTriMeshDesc.triangles.count = SrcIndices.Num(); PTriMeshDesc.triangles.stride = sizeof(FTriIndices); PTriMeshDesc.materialIndices.data = SrcMaterialIndices.GetData(); PTriMeshDesc.materialIndices.stride = sizeof(PxMaterialTableIndex); PTriMeshDesc.flags = FlipNormals ? PxMeshFlag::eFLIPNORMALS : static_cast(0); // Set up cooking const PxCookingParams CurrentParams = PhysXCooking->getParams(); PxCookingParams NewParams = CurrentParams; NewParams.targetPlatform = PhysXFormat; if (!!(CookFlags & EPhysXMeshCookFlags::SuppressFaceRemapTable)) { NewParams.suppressTriangleMeshRemapTable = true; } if (!!(CookFlags & EPhysXMeshCookFlags::DisableActiveEdgePrecompute)) { // Disable ActiveEdgePrecompute (This makes cooking faster, but will slow contact generation) NewParams.meshPreprocessParams |= PxMeshPreprocessingFlag::eDISABLE_ACTIVE_EDGES_PRECOMPUTE; } if (!!(CookFlags & EPhysXMeshCookFlags::DeformableMesh)) { // In the case of a deformable mesh, we have to change the cook params NewParams.meshPreprocessParams |= PxMeshPreprocessingFlag::eDISABLE_CLEAN_MESH; NewParams.meshPreprocessParams &= ~(PxMeshPreprocessingFlags(PxMeshPreprocessingFlag::eWELD_VERTICES)); // The default BVH34 midphase does not support refit NewParams.midphaseDesc = PxMeshMidPhase::eBVH33; } else { //For non deformable meshes we can try to use BVH34 UseBVH34IfSupported(Format, NewParams); } PhysXCooking->setParams(NewParams); bool bResult = false; // Cook TriMesh Data if (bUseBuffer) { FPhysXOutputStream Buffer(&OutBuffer); bResult = PhysXCooking->cookTriangleMesh(PTriMeshDesc, Buffer); } else { FPhysXOutputStream Buffer(&OutBuffer); OutTriangleMesh = PhysXCooking->createTriangleMesh(PTriMeshDesc, GPhysXSDK->getPhysicsInsertionCallback()); //NOTE: getPhysicsInsertionCallback probably not thread safe! bResult = !!OutTriangleMesh; } // Restore cooking params PhysXCooking->setParams(CurrentParams); return bResult; #else return false; #endif // WITH_PHYSX } bool FPhysXCooking::CookTriMesh(FName Format, EPhysXMeshCookFlags CookFlags, const TArray& SrcVertices, const TArray& SrcIndices, const TArray& SrcMaterialIndices, const bool FlipNormals, TArray& OutBuffer) const { PxTriangleMesh* JunkTriangleMesh = nullptr; return CookTriMeshImp(Format, CookFlags, SrcVertices, SrcIndices, SrcMaterialIndices, FlipNormals, OutBuffer, JunkTriangleMesh); } bool FPhysXCooking::CreateTriMesh(FName Format, EPhysXMeshCookFlags CookFlags, const TArray& SrcVertices, const TArray& SrcIndices, const TArray& SrcMaterialIndices, const bool FlipNormals, physx::PxTriangleMesh*& OutTriangleMesh) const { TArray JunkBuffer; return CookTriMeshImp(Format, CookFlags, SrcVertices, SrcIndices, SrcMaterialIndices, FlipNormals, JunkBuffer, OutTriangleMesh); } template bool FPhysXCooking::CookHeightFieldImp(FName Format, FIntPoint HFSize, const void* Samples, uint32 SamplesStride, TArray& OutBuffer, PxHeightField*& OutHeightField) const { OutHeightField = nullptr; #if WITH_PHYSX PxPlatform::Enum PhysXFormat = PxPlatform::ePC; bool bIsPhysXCookingValid = GetPhysXCooking(Format, PhysXFormat); check(bIsPhysXCookingValid); PxHeightFieldDesc HFDesc; HFDesc.format = PxHeightFieldFormat::eS16_TM; HFDesc.nbColumns = HFSize.X; HFDesc.nbRows = HFSize.Y; HFDesc.samples.data = Samples; HFDesc.samples.stride = SamplesStride; HFDesc.flags = PxHeightFieldFlag::eNO_BOUNDARY_EDGES; // Set up cooking const PxCookingParams& Params = PhysXCooking->getParams(); PxCookingParams NewParams = Params; NewParams.targetPlatform = PhysXFormat; UseBVH34IfSupported(Format, NewParams); PhysXCooking->setParams(NewParams); // Cook to a temp buffer TArray CookedBuffer; FPhysXOutputStream Buffer(&CookedBuffer); if (bUseBuffer) { if (PhysXCooking->cookHeightField(HFDesc, Buffer) && CookedBuffer.Num() > 0) { // Append the cooked data into cooked buffer OutBuffer.Append(CookedBuffer); return true; } } else { OutHeightField = PhysXCooking->createHeightField(HFDesc, GPhysXSDK->getPhysicsInsertionCallback()); //NOTE: getPhysicsInsertionCallback probably not thread safe! if (OutHeightField) { return true; } } return false; #else return false; #endif // WITH_PHYSX } bool FPhysXCooking::CookHeightField(FName Format, FIntPoint HFSize, const void* Samples, uint32 SamplesStride, TArray& OutBuffer) const { physx::PxHeightField* JunkHeightField = nullptr; return CookHeightFieldImp(Format, HFSize, Samples, SamplesStride, OutBuffer, JunkHeightField); } bool FPhysXCooking::CreateHeightField(FName Format, FIntPoint HFSize, const void* Samples, uint32 SamplesStride, physx::PxHeightField*& OutHeightField) const { TArray JunkBuffer; return CookHeightFieldImp(Format, HFSize, Samples, SamplesStride, JunkBuffer, OutHeightField); } FPhysXPlatformModule::FPhysXPlatformModule() { PhysXCookerTLS = FPlatformTLS::AllocTlsSlot(); } FPhysXPlatformModule::~FPhysXPlatformModule() { //NOTE: we don't bother cleaning up TLS, this is only closed during real shutdown } IPhysXCooking* FPhysXPlatformModule::GetPhysXCooking() { FPhysXCooking* PhysXCooking = (FPhysXCooking*)FPlatformTLS::GetTlsValue(PhysXCookerTLS); if (!PhysXCooking) { InitPhysXCooking(); PhysXCooking = new FPhysXCooking(); FPlatformTLS::SetTlsValue(PhysXCookerTLS, PhysXCooking); } return PhysXCooking; } physx::PxCooking* FPhysXPlatformModule::CreatePhysXCooker(uint32 version, physx::PxFoundation& foundation, const physx::PxCookingParams& params) { #if WITH_PHYSX return PxCreateCooking(PX_PHYSICS_VERSION, foundation, params); #endif return nullptr; } void FPhysXPlatformModule::Terminate() { ShutdownPhysXCooking(); } /** * Load the required modules for PhysX */ void FPhysXPlatformModule::InitPhysXCooking() { if (IsInGameThread()) { // Make sure PhysX libs are loaded PhysDLLHelper::LoadPhysXModules(/*bLoadCookingModule=*/true); } } void FPhysXPlatformModule::ShutdownPhysXCooking() { #if WITH_PHYSX if(IPhysXCooking* PhysXCookingWrapper = FPhysXPlatformModule::GetPhysXCooking()) { if (PxCooking* PhysXCooking = PhysXCookingWrapper->GetCooking()) { PhysXCooking->release(); PhysXCooking = nullptr; delete PhysXCookingWrapper; FPlatformTLS::SetTlsValue(PhysXCookerTLS, nullptr); } } #endif } IMPLEMENT_MODULE(FPhysXPlatformModule, PhysXCooking); #else class FPhysXPlatformModule : public IModuleInterface { }; IMPLEMENT_MODULE(FPhysXPlatformModule, PhysXCooking); #endif