Files
FlaxEngine/Source/Engine/Physics/CollisionCooking.cpp
2020-12-07 23:40:54 +01:00

322 lines
11 KiB
C++

// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_PHYSICS_COOKING
#include "CollisionCooking.h"
#include "Engine/Threading/Task.h"
#include "Engine/Graphics/Async/GPUTask.h"
#include "Engine/Core/Log.h"
#include "Physics.h"
#include <ThirdParty/PhysX/cooking/PxCooking.h>
#include <ThirdParty/PhysX/extensions/PxDefaultStreams.h>
#define ENSURE_CAN_COOK \
if (Physics::GetCooking() == nullptr) \
{ \
LOG(Warning, "Physics collisions cooking is disabled at runtime. Enable Physics Settigns option SupportCookingAtRuntime to use terrain generation at runtime."); \
return true; \
}
bool CollisionCooking::CookConvexMesh(CookingInput& input, BytesContainer& output)
{
ENSURE_CAN_COOK;
// Init options
PxConvexMeshDesc desc;
desc.points.count = input.VertexCount;
desc.points.stride = sizeof(Vector3);
desc.points.data = input.VertexData;
desc.flags = PxConvexFlag::eCOMPUTE_CONVEX;
desc.vertexLimit = input.ConvexVertexLimit;
if (input.ConvexFlags & ConvexMeshGenerationFlags::SkipValidation)
desc.flags |= PxConvexFlag::Enum::eDISABLE_MESH_VALIDATION;
if (input.ConvexFlags & ConvexMeshGenerationFlags::UsePlaneShifting)
desc.flags |= PxConvexFlag::Enum::ePLANE_SHIFTING;
if (input.ConvexFlags & ConvexMeshGenerationFlags::UseFastInteriaComputation)
desc.flags |= PxConvexFlag::Enum::eFAST_INERTIA_COMPUTATION;
if (input.ConvexFlags & ConvexMeshGenerationFlags::ShiftVertices)
desc.flags |= PxConvexFlag::Enum::eSHIFT_VERTICES;
// Perform cooking
PxDefaultMemoryOutputStream outputStream;
PxConvexMeshCookingResult::Enum result;
if (!Physics::GetCooking()->cookConvexMesh(desc, outputStream, &result))
{
LOG(Warning, "Convex Mesh cooking failed. Error code: {0}, Input vertices count: {1}", result, input.VertexCount);
return true;
}
// Copy result
output.Copy(outputStream.getData(), outputStream.getSize());
return false;
}
bool CollisionCooking::CookTriangleMesh(CookingInput& input, BytesContainer& output)
{
ENSURE_CAN_COOK;
// Init options
PxTriangleMeshDesc desc;
desc.points.count = input.VertexCount;
desc.points.stride = sizeof(Vector3);
desc.points.data = input.VertexData;
desc.triangles.count = input.IndexCount / 3;
desc.triangles.stride = 3 * (input.Is16bitIndexData ? sizeof(uint16) : sizeof(uint32));
desc.triangles.data = input.IndexData;
desc.flags = input.Is16bitIndexData ? PxMeshFlag::e16_BIT_INDICES : (PxMeshFlag::Enum)0;
// Perform cooking
PxDefaultMemoryOutputStream outputStream;
PxTriangleMeshCookingResult::Enum result;
if (!Physics::GetCooking()->cookTriangleMesh(desc, outputStream, &result))
{
LOG(Warning, "Triangle Mesh cooking failed. Error code: {0}, Input vertices count: {1}, indices count: {2}", result, input.VertexCount, input.IndexCount);
return true;
}
// Copy result
output.Copy(outputStream.getData(), outputStream.getSize());
return false;
}
bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::SerializedOptions& outputOptions, BytesContainer& outputData)
{
int32 convexVertexLimit = Math::Clamp(arg.ConvexVertexLimit, 8, 255);
if (arg.ConvexVertexLimit == 0)
convexVertexLimit = 255;
DataContainer<Vector3> finalVertexData;
DataContainer<uint32> finalIndexData;
const bool needIndexBuffer = arg.Type == CollisionDataType::TriangleMesh;
// Check if use custom model (specified in the argument, used for fast internal collision cooking by e.g. CSGBuilder)
if (arg.OverrideModelData)
{
// Validate model data
if (arg.OverrideModelData->LODs.IsEmpty())
{
LOG(Warning, "Missing model data.");
return true;
}
// Pick a proper model LOD
const int32 lodIndex = Math::Clamp(arg.ModelLodIndex, 0, arg.OverrideModelData->LODs.Count());
auto lod = &arg.OverrideModelData->LODs[lodIndex];
const int32 meshesCount = lod->Meshes.Count();
// Count vertex/index buffer sizes
int32 vCount = 0;
int32 iCount = 0;
for (int32 i = 0; i < meshesCount; i++)
{
const auto mesh = lod->Meshes[i];
vCount += mesh->Positions.Count();
if (needIndexBuffer)
{
iCount += mesh->Indices.Count() * 3;
}
}
if (meshesCount == 1)
{
// Link a single mesh
const auto mesh = lod->Meshes[0];
finalVertexData.Link(mesh->Positions);
if (needIndexBuffer)
{
finalIndexData.Link(mesh->Indices);
}
}
else
{
// Combine meshes into one
finalVertexData.Allocate(vCount);
finalIndexData.Allocate(iCount);
int32 vertexCounter = 0, indexCounter = 0;
for (int32 i = 0; i < meshesCount; i++)
{
const auto mesh = lod->Meshes[i];
const int32 firstVertexIndex = vertexCounter;
const int32 vertexCount = mesh->Positions.Count();
Platform::MemoryCopy(finalVertexData.Get() + firstVertexIndex, mesh->Positions.Get(), vertexCount * sizeof(Vector3));
vertexCounter += vertexCount;
if (needIndexBuffer)
{
auto dst = finalIndexData.Get() + indexCounter;
auto src = mesh->Indices.Get();
for (int32 j = 0; j < mesh->Indices.Count(); j++)
{
*dst++ = firstVertexIndex + *src++;
}
indexCounter += mesh->Indices.Count();
}
}
}
}
else
{
// Model data gather requires to use async tasks (main thread cannot stall)
// TODO: if asset is not virtual then maybe use the asset container and read raw bytes from file??
if (IsInMainThread())
{
LOG(Warning, "Cannot generate collision data on a main thread.");
return true;
}
// Ensure model is loaded
if (arg.Model == nullptr)
{
LOG(Warning, "Missing model.");
return true;
}
if (arg.Model->WaitForLoaded())
{
LOG(Warning, "Model loading failed.");
return true;
}
// Pick a proper model LOD
const int32 lodIndex = Math::Clamp(arg.ModelLodIndex, 0, arg.Model->LODs.Count());
auto lod = &arg.Model->LODs[lodIndex];
// Download model LOD data from the GPU.
// It's easier than reading internal, versioned mesh storage format.
// Also it works with virtual assets that have no dedicated storage.
// Note: request all meshes data at once and wait for the tasks to be done.
const int32 meshesCount = lod->Meshes.Count();
Array<BytesContainer> vertexBuffers;
Array<BytesContainer> indexBuffers;
vertexBuffers.Resize(meshesCount);
indexBuffers.Resize(needIndexBuffer ? meshesCount : 0);
// Start tasks
int32 vCount = 0;
int32 iCount = 0;
Array<Task*> tasks(meshesCount + meshesCount);
for (int32 i = 0; i < meshesCount; i++)
{
const auto& mesh = lod->Meshes[i];
auto task = mesh.ExtractDataAsync(MeshBufferType::Vertex0, vertexBuffers[i]);
if (task == nullptr)
return true;
task->Start();
tasks.Add(task);
vCount += mesh.GetVertexCount();
if (needIndexBuffer)
{
task = mesh.ExtractDataAsync(MeshBufferType::Index, indexBuffers[i]);
if (task == nullptr)
return true;
task->Start();
tasks.Add(task);
iCount += mesh.GetTriangleCount() * 3;
}
}
// Wait for tasks
if (Task::WaitAll(tasks))
return true;
tasks.Resize(0);
// Combine meshes into one
finalVertexData.Allocate(vCount);
finalIndexData.Allocate(iCount);
int32 vertexCounter = 0, indexCounter = 0;
for (int32 i = 0; i < meshesCount; i++)
{
const auto& mesh = lod->Meshes[i];
const auto& vData = vertexBuffers[i];
const int32 firstVertexIndex = vertexCounter;
const int32 vertexCount = mesh.GetVertexCount();
Platform::MemoryCopy(finalVertexData.Get() + firstVertexIndex, vData.Get(), vertexCount * sizeof(Vector3));
vertexCounter += vertexCount;
if (needIndexBuffer)
{
const auto& iData = indexBuffers[i];
const int32 indexCount = mesh.GetTriangleCount() * 3;
if (mesh.Use16BitIndexBuffer())
{
auto dst = finalIndexData.Get() + indexCounter;
auto src = (uint16*)iData.Get();
for (int32 j = 0; j < indexCount; j++)
{
*dst++ = firstVertexIndex + *src++;
}
indexCounter += indexCount;
}
else
{
auto dst = finalIndexData.Get() + indexCounter;
auto src = (uint32*)iData.Get();
for (int32 j = 0; j < indexCount; j++)
{
*dst++ = firstVertexIndex + *src++;
}
indexCounter += indexCount;
}
}
}
}
// Prepare cooking options
CookingInput cookingInput;
cookingInput.VertexCount = finalVertexData.Length();
cookingInput.VertexData = finalVertexData.Get();
cookingInput.IndexCount = finalIndexData.Length();
cookingInput.IndexData = finalIndexData.Get();
cookingInput.Is16bitIndexData = false;
cookingInput.ConvexFlags = arg.ConvexFlags;
cookingInput.ConvexVertexLimit = convexVertexLimit;
// Cook!
if (arg.Type == CollisionDataType::ConvexMesh)
{
if (CookConvexMesh(cookingInput, outputData))
return true;
}
else if (arg.Type == CollisionDataType::TriangleMesh)
{
if (CookTriangleMesh(cookingInput, outputData))
return true;
}
else
{
LOG(Warning, "Invalid collision data type.");
return true;
}
// Setup options
Platform::MemoryClear(&outputOptions, sizeof(outputOptions));
outputOptions.Type = arg.Type;
outputOptions.Model = arg.Model.GetID();
outputOptions.ModelLodIndex = arg.ModelLodIndex;
outputOptions.ConvexFlags = arg.ConvexFlags;
outputOptions.ConvexVertexLimit = arg.ConvexVertexLimit;
return false;
}
bool CollisionCooking::CookHeightField(const physx::PxHeightFieldDesc& desc, physx::PxOutputStream& stream)
{
ENSURE_CAN_COOK;
if (!Physics::GetCooking()->cookHeightField(desc, stream))
{
LOG(Warning, "Height Field collision cooking failed.");
return true;
}
return false;
}
#endif