Files
UnrealEngine/Engine/Source/Developer/PhysicsUtilities/Private/LevelSetHelpers.cpp
2025-05-18 13:04:45 +08:00

189 lines
7.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LevelSetHelpers.h"
#include "Chaos/Levelset.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "Engine/SkeletalMesh.h"
#include "Implicit/SweepingMeshSDF.h"
#include "Implicit/Solidify.h"
#include "PhysicsEngine/BodySetup.h"
#include "PhysicsEngine/LevelSetElem.h"
// Copied from CollisionGeometryConversion.h (Plugins/Runtime/MeshModelingToolset)
static void CreateLevelSetElement(const FVector3d& GridOrigin, const UE::Geometry::FDenseGrid3f& Grid, float CellSize, FKLevelSetElem& LevelSetOut)
{
const UE::Geometry::FVector3i& GridDims = Grid.GetDimensions();
TArray<double> OutGridValues;
OutGridValues.Init(0.0, GridDims[0] * GridDims[1] * GridDims[2]);
for (int I = 0; I < GridDims[0]; ++I)
{
for (int J = 0; J < GridDims[1]; ++J)
{
for (int K = 0; K < GridDims[2]; ++K)
{
// account for different ordering in Geometry::TDenseGrid3 vs Chaos::TUniformGrid
const int InBufferIndex = I + GridDims[0] * (J + GridDims[1] * K);
const int OutBufferIndex = K + GridDims[2] * (J + GridDims[1] * I);
OutGridValues[OutBufferIndex] = Grid[InBufferIndex];
}
}
}
const FTransform3d GridTransform = FTransform(GridOrigin);
FTransform ChaosTransform = GridTransform;
ChaosTransform.AddToTranslation(-0.5 * CellSize * FVector::One()); // account for grid origin being at cell center vs cell corner
LevelSetOut.BuildLevelSet(ChaosTransform, OutGridValues, FIntVector(GridDims[0], GridDims[1], GridDims[2]), CellSize);
}
static void CreateLevelSetElement(const FVector3d& GridOrigin, const UE::Geometry::FDenseGrid3f& Grid, float CellSize, Chaos::FLevelSetPtr& LevelSetOut)
{
const UE::Geometry::FVector3i& GridDims = Grid.GetDimensions();
TArray<double> OutGridValues;
OutGridValues.Init(0.0, GridDims[0] * GridDims[1] * GridDims[2]);
for (int I = 0; I < GridDims[0]; ++I)
{
for (int J = 0; J < GridDims[1]; ++J)
{
for (int K = 0; K < GridDims[2]; ++K)
{
// account for different ordering in Geometry::TDenseGrid3 vs Chaos::TUniformGrid
const int InBufferIndex = I + GridDims[0] * (J + GridDims[1] * K);
const int OutBufferIndex = K + GridDims[2] * (J + GridDims[1] * I);
OutGridValues[OutBufferIndex] = Grid[InBufferIndex];
}
}
}
const FVector3d GridMin = GridOrigin - 0.5 * CellSize * FVector::One(); // account for grid origin being at cell center vs cell corner
const FVector3d GridMax = GridMin + CellSize * FVector3d((double)GridDims[0], (double)GridDims[1], (double)GridDims[2]);
const Chaos::TVec3<int32> ChaosLSGridDims(GridDims[0], GridDims[1], GridDims[2]);
Chaos::TUniformGrid<Chaos::FReal, 3> LevelSetGrid(GridMin, GridMax, ChaosLSGridDims);
Chaos::TArrayND<Chaos::FReal, 3> Phi(ChaosLSGridDims, OutGridValues);
LevelSetOut = Chaos::FLevelSetPtr( new Chaos::FLevelSet(MoveTemp(LevelSetGrid), MoveTemp(Phi), 0));
}
// Copied from FMeshSimpleShapeApproximation::Generate_LevelSets (Plugins/Runtime/GeometryProcessing)
template<typename FLevelSetElemType>
bool CreateLevelSetForMeshInternal(const UE::Geometry::FDynamicMesh3& InMesh, int32 InLevelSetGridResolution, FLevelSetElemType& OutElement)
{
using UE::Geometry::FDynamicMesh3;
using UE::Geometry::TSweepingMeshSDF;
constexpr int32 NumNarrowBandCells = 2;
constexpr int32 NumExpandCells = 1;
// Inside SDF.Compute(), extra grid cell are added to account for the narrow band ("NarrowBandMaxDistance") as well as an
// outer buffer ("ExpandBounds"). So here we adjust the cell size to make the final output resolution closer to the user-specified resolution.
const int32 LevelSetGridResolution = FMath::Max(1, InLevelSetGridResolution - 2 * (NumNarrowBandCells + NumExpandCells));
const UE::Geometry::FAxisAlignedBox3d Bounds = InMesh.GetBounds();
const double CellSize = Bounds.MaxDim() / LevelSetGridResolution;
const double ExpandBounds = NumExpandCells * CellSize;
UE::Geometry::TMeshAABBTree3<FDynamicMesh3> Spatial(&InMesh);
// Input mesh is likely open, so solidify it first
UE::Geometry::TFastWindingTree<FDynamicMesh3> FastWinding(&Spatial);
UE::Geometry::TImplicitSolidify<FDynamicMesh3> Solidify(&InMesh, &Spatial, &FastWinding);
Solidify.ExtendBounds = ExpandBounds;
Solidify.MeshCellSize = CellSize;
Solidify.WindingThreshold = 0.5;
Solidify.SurfaceSearchSteps = 3;
Solidify.bSolidAtBoundaries = true;
const UE::Geometry::FDynamicMesh3 SolidMesh = FDynamicMesh3(&Solidify.Generate());
Spatial.SetMesh(&SolidMesh, true);
TSweepingMeshSDF<FDynamicMesh3> SDF;
SDF.Mesh = &SolidMesh;
SDF.Spatial = &Spatial;
SDF.ComputeMode = TSweepingMeshSDF<FDynamicMesh3>::EComputeModes::FullGrid;
SDF.CellSize = (float)CellSize;
SDF.NarrowBandMaxDistance = NumNarrowBandCells * CellSize;
SDF.ExactBandWidth = FMath::CeilToInt32(SDF.NarrowBandMaxDistance / CellSize);
SDF.ExpandBounds = FVector3d(ExpandBounds);
if (SDF.Compute(Bounds))
{
CreateLevelSetElement((FVector3d)SDF.GridOrigin, SDF.Grid, SDF.CellSize, OutElement);
return true;
}
return false;
}
namespace LevelSetHelpers
{
bool CreateLevelSetForBone(UBodySetup* BodySetup, const TArray<FVector3f>& InVertices, const TArray<uint32>& InIndices, uint32 InResolution)
{
check(BodySetup != NULL);
// Validate input by checking bounding box
FBox VertBox(ForceInit);
for (const FVector3f& Vert : InVertices)
{
VertBox += (FVector)Vert;
}
// If box is invalid, or the largest dimension is less than 1 unit, or smallest is less than 0.1, skip trying to generate collision
if (VertBox.IsValid == 0 || VertBox.GetSize().GetMax() < 1.f || VertBox.GetSize().GetMin() < 0.1f || InIndices.Num() == 0)
{
return false;
}
TRACE_CPUPROFILER_EVENT_SCOPE(MeshToLevelSet)
// Clean out old hulls
BodySetup->RemoveSimpleCollision();
UE::Geometry::FDynamicMesh3 DynamicMesh;
CreateDynamicMesh(InVertices, InIndices, DynamicMesh);
FKLevelSetElem LevelSetElement;
const bool bOK = CreateLevelSetForMesh(DynamicMesh, InResolution, LevelSetElement);
if (!bOK)
{
return false;
}
BodySetup->AggGeom.LevelSetElems.Add(LevelSetElement);
BodySetup->InvalidatePhysicsData(); // update GUID
return true;
}
void CreateDynamicMesh(const TArray<FVector3f>& InVertices, const TArray<uint32>& InIndices, UE::Geometry::FDynamicMesh3& OutMesh)
{
OutMesh.Clear();
for (const FVector3f& Vertex : InVertices)
{
OutMesh.AppendVertex(FVector3d(Vertex));
}
check(InIndices.Num() % 3 == 0);
const int32 NumTriangles = InIndices.Num() / 3;
for (int32 TriangleID = 0; TriangleID < NumTriangles; ++TriangleID)
{
const UE::Geometry::FIndex3i Triangle(InIndices[3 * TriangleID], InIndices[3 * TriangleID + 1], InIndices[3 * TriangleID + 2]);
OutMesh.AppendTriangle(Triangle);
}
}
bool CreateLevelSetForMesh(const UE::Geometry::FDynamicMesh3& InMesh, int32 InLevelSetGridResolution, FKLevelSetElem& OutElement)
{
return CreateLevelSetForMeshInternal(InMesh, InLevelSetGridResolution, OutElement);
}
bool CreateLevelSetForMesh(const UE::Geometry::FDynamicMesh3& InMesh, int32 InLevelSetGridResolution, Chaos::FLevelSetPtr& OutElement)
{
return CreateLevelSetForMeshInternal(InMesh, InLevelSetGridResolution, OutElement);
}
} // namespace LevelSetHelpers