Files
UnrealEngine/Engine/Source/Runtime/Experimental/Chaos/Private/GeometryCollection/GeometryCollectionCollisionStructureManager.cpp
2025-05-18 13:04:45 +08:00

522 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GeometryCollection/GeometryCollectionCollisionStructureManager.h"
#include "ChaosLog.h"
#include "Chaos/Box.h"
#include "Chaos/ErrorReporter.h"
#include "Chaos/ImplicitObjectUnion.h"
#include "Chaos/Levelset.h"
#include "Chaos/Particles.h"
#include "Chaos/Sphere.h"
#include "Chaos/Capsule.h"
#include "Chaos/Vector.h"
#include "HAL/IConsoleManager.h"
#include "Chaos/TriangleMesh.h"
#include "Chaos/PBDRigidClustering.h"
#include "Chaos/PBDRigidClusteringCollisionParticleAlgo.h"
DEFINE_LOG_CATEGORY_STATIC(GCS_Log, NoLogging, All);
FCollisionStructureManager::FCollisionStructureManager()
{
}
int32 bCollisionParticlesUseImplicitCulling = false;
FAutoConsoleVariableRef CVarCollisionParticlesUseImplicitCulling(TEXT("p.CollisionParticlesUseImplicitCulling"), bCollisionParticlesUseImplicitCulling, TEXT("Use the implicit to cull interior vertices."));
int32 CollisionParticlesSpatialDivision = 10;
FAutoConsoleVariableRef CVarCollisionParticlesSpatialDivision(TEXT("p.CollisionParticlesSpatialDivision"), CollisionParticlesSpatialDivision, TEXT("Spatial bucketing to cull collision particles."));
int32 CollisionParticlesMin = 10;
FAutoConsoleVariableRef CVarCollisionParticlesMin(TEXT("p.CollisionParticlesMin"), CollisionParticlesMin, TEXT("Minimum number of particles after simplicial pruning (assuming it started with more)"));
int32 CollisionParticlesMax = 2000;
FAutoConsoleVariableRef CVarCollisionParticlesMax(TEXT("p.CollisionParticlesMax"), CollisionParticlesMax, TEXT("Maximum number of particles after simplicial pruning"));
FCollisionStructureManager::FSimplicial*
FCollisionStructureManager::NewSimplicial(
const Chaos::FParticles& Vertices,
const Chaos::FTriangleMesh& TriMesh,
const Chaos::FImplicitObject* Implicit,
const int32 CollisionParticlesMaxInput)
{
FCollisionStructureManager::FSimplicial * Simplicial = new FCollisionStructureManager::FSimplicial();
if (Implicit || Vertices.Size())
{
Chaos::FReal Extent = 0;
int32 LSVCounter = 0;
TArray<int32> IndicesArray;
TriMesh.GetVertexSetAsArray(IndicesArray);
TArray<Chaos::FVec3> OutsideVertices;
bool bFullCopy = true;
int32 LocalCollisionParticlesMax = CollisionParticlesMaxInput > 0 ? FMath::Min(CollisionParticlesMaxInput, CollisionParticlesMax) : CollisionParticlesMax;
if (bCollisionParticlesUseImplicitCulling!=0 && Implicit && IndicesArray.Num()>LocalCollisionParticlesMax)
{
Extent = Implicit->HasBoundingBox() ? Implicit->BoundingBox().Extents().Size() : 1.f;
Chaos::FReal Threshold = Extent * 0.01f;
//
// Remove particles inside the levelset. (I think this is useless)
//
OutsideVertices.AddUninitialized(IndicesArray.Num());
for (int32 Idx : IndicesArray)
{
const Chaos::FVec3& SamplePoint = Vertices.GetX(Idx);
if (Implicit->SignedDistance(SamplePoint) > Threshold)
{
OutsideVertices[LSVCounter] = SamplePoint;
LSVCounter++;
}
}
OutsideVertices.SetNum(LSVCounter);
if (OutsideVertices.Num() > LocalCollisionParticlesMax)
bFullCopy = false;
}
if(bFullCopy)
{
FBox Bounds(ForceInitToZero);
OutsideVertices.AddUninitialized(IndicesArray.Num());
for (int32 Idx=0;Idx<IndicesArray.Num();Idx++)
{
Bounds += FVector(Vertices.GetX(IndicesArray[Idx]));
OutsideVertices[Idx] = Vertices.GetX(IndicesArray[Idx]);
}
Extent = Bounds.GetExtent().Size();
}
//
// Clean the particles based on distance
//
Chaos::FReal SnapThreshold = Extent / Chaos::FReal(CollisionParticlesSpatialDivision);
OutsideVertices = Chaos::CleanCollisionParticles(OutsideVertices, SnapThreshold);
int32 NumParticles = (OutsideVertices.Num() > LocalCollisionParticlesMax) ? LocalCollisionParticlesMax : OutsideVertices.Num();
if (NumParticles)
{
int32 VertexCounter = 0;
Simplicial->AddParticles(NumParticles);
for (int32 i = 0; i<NumParticles;i++)
{
if (!OutsideVertices[i].ContainsNaN())
{
Simplicial->SetX(i, OutsideVertices[i]);
VertexCounter++;
}
}
Simplicial->Resize(VertexCounter);
}
if(!Simplicial->Size())
{
Simplicial->AddParticles(1);
Simplicial->SetX(0, Chaos::FVec3(0));
}
Simplicial->UpdateAccelerationStructures();
UE_LOG(LogChaos, Log, TEXT("NewSimplicial: InitialSize: %d, ImplicitExterior: %d, FullCopy: %d, FinalSize: %d"), IndicesArray.Num(), LSVCounter, (int32)bFullCopy, NumParticles);
return Simplicial;
}
UE_LOG(LogChaos, Log, TEXT("NewSimplicial::Empty"));
return Simplicial;
}
FCollisionStructureManager::FSimplicial*
FCollisionStructureManager::NewSimplicial(
const Chaos::FParticles& AllParticles,
const TManagedArray<int32>& BoneMap,
const ECollisionTypeEnum CollisionType,
Chaos::FTriangleMesh& TriMesh,
const float CollisionParticlesFraction)
{
const bool bEnableCollisionParticles = (CollisionType == ECollisionTypeEnum::Chaos_Surface_Volumetric);
if (bEnableCollisionParticles || true)
{
// @todo : Clean collision particles need to operate on the collision mask from the DynamicCollection,
// then transfer only the good collision particles during the initialization. `
FCollisionStructureManager::FSimplicial * Simplicial = new FCollisionStructureManager::FSimplicial();
const TArrayView<const Chaos::FVec3> ArrayView(&AllParticles.GetX(0), AllParticles.Size());
const TArray<Chaos::FVec3>& Result = Chaos::CleanCollisionParticles(TriMesh, ArrayView, CollisionParticlesFraction);
if (Result.Num())
{
int32 VertexCounter = 0;
Simplicial->AddParticles(Result.Num());
for (int Index = Result.Num() - 1; 0 <= Index; Index--)
{
if (!Result[Index].ContainsNaN())
{
Simplicial->SetX(Index, Result[Index]);
VertexCounter++;
}
}
Simplicial->Resize(VertexCounter);
}
if (!Simplicial->Size())
{
Simplicial->AddParticles(1);
Simplicial->SetX(0, Chaos::FVec3(0));
}
Simplicial->UpdateAccelerationStructures();
return Simplicial;
}
return new FCollisionStructureManager::FSimplicial();
}
void FCollisionStructureManager::UpdateImplicitFlags(
FImplicit* Implicit,
const ECollisionTypeEnum CollisionType)
{
if (ensure(Implicit))
{
Implicit->SetCollisionType(CollisionType == ECollisionTypeEnum::Chaos_Surface_Volumetric ? Chaos::ImplicitObjectType::LevelSet : Implicit->GetType());
if (Implicit && (CollisionType == ECollisionTypeEnum::Chaos_Surface_Volumetric) && Implicit->GetType() == Chaos::ImplicitObjectType::LevelSet)
{
Implicit->SetDoCollide(false);
Implicit->SetConvex(false);
}
}
}
Chaos::FImplicitObjectRef
FCollisionStructureManager::NewImplicit(
Chaos::FErrorReporter ErrorReporter,
const Chaos::FParticles& MeshParticles,
const Chaos::FTriangleMesh& TriMesh,
const FBox& CollisionBounds,
const Chaos::FReal Radius,
const int32 MinRes,
const int32 MaxRes,
const float CollisionObjectReduction,
const ECollisionTypeEnum CollisionType,
const EImplicitTypeEnum ImplicitType)
{
if (ImplicitType == EImplicitTypeEnum::Chaos_Implicit_Box)
{
return NewImplicitBox(CollisionBounds, CollisionObjectReduction, CollisionType);
}
else if (ImplicitType == EImplicitTypeEnum::Chaos_Implicit_Sphere)
{
return NewImplicitSphere(Radius, CollisionObjectReduction, CollisionType);
}
else if (ImplicitType == EImplicitTypeEnum::Chaos_Implicit_LevelSet)
{
return NewImplicitLevelset(ErrorReporter, MeshParticles, TriMesh, CollisionBounds, MinRes, MaxRes, CollisionObjectReduction, CollisionType);
}
return nullptr;
}
Chaos::FImplicitObjectRef
FCollisionStructureManager::NewImplicitBox(
const FBox& CollisionBounds,
const float CollisionObjectReduction,
const ECollisionTypeEnum CollisionType)
{
const FVector HalfExtents = CollisionBounds.GetExtent() * (1 - CollisionObjectReduction / 100.f);
const FVector Center = CollisionBounds.GetCenter();
// Margin settings are in UPhysicsSettingsCore which we can't access here
// @todo(chaos): pass margin settings into the collision manager?
float CollisionMarginFraction = 0.1f;// FMath::Max(0.0f, UPhysicsSettingsCore::Get()->SolverOptions.CollisionMarginFraction);
float CollisionMarginMax = 10.0f;// FMath::Max(0.0f, UPhysicsSettingsCore::Get()->SolverOptions.CollisionMarginMax);
const Chaos::FReal Margin = FMath::Min(CollisionMarginFraction * 0.5f * HalfExtents.GetMin(), CollisionMarginMax);
Chaos::FImplicitObjectRef Implicit = new Chaos::TBox<Chaos::FReal, 3>(Center - HalfExtents, Center + HalfExtents, Margin);
UpdateImplicitFlags(Implicit, CollisionType);
return Implicit;
}
Chaos::FImplicitObjectRef
FCollisionStructureManager::NewImplicitSphere(
const Chaos::FReal Radius,
const float CollisionObjectReduction,
const ECollisionTypeEnum CollisionType)
{
Chaos::FImplicitObjectRef Implicit = new Chaos::FSphere(Chaos::FVec3(0), Radius * (1 - CollisionObjectReduction / 100.f));
UpdateImplicitFlags(Implicit, CollisionType);
return Implicit;
}
Chaos::FImplicitObjectRef
FCollisionStructureManager::NewImplicitConvex(
const TArray<int32>& ConvexIndices,
const TManagedArray<Chaos::FConvexPtr>* ConvexGeometry,
const ECollisionTypeEnum CollisionType,
const FTransform& MassTransform,
const Chaos::FReal CollisionMarginFraction,
const float CollisionObjectReduction)
{
using FConvexVec3 = Chaos::FConvex::FVec3Type;
if (ConvexIndices.Num())
{
TArray<Chaos::FImplicitObjectRef> Implicits;
for (auto& Index : ConvexIndices)
{
if((*ConvexGeometry)[Index])
{
TArray<FConvexVec3> ConvexVertices = (*ConvexGeometry)[Index]->GetVertices();
FConvexVec3 COM = MassTransform.InverseTransformPosition((*ConvexGeometry)[Index]->GetCenterOfMass());
FConvexVec3::FReal ScaleFactor = 1 - CollisionObjectReduction / 100.f;
for (int32 Idx = 0; Idx < ConvexVertices.Num(); Idx++)
{
ConvexVertices[Idx] = ((FConvexVec3)MassTransform.InverseTransformPosition(FVector(ConvexVertices[Idx])) - COM) * ScaleFactor + COM;
}
Chaos::FReal Margin = (Chaos::FReal)(*ConvexGeometry)[Index]->BoundingBox().Extents().Min() * CollisionMarginFraction;
Chaos::FConvex* MarginConvex = new Chaos::FConvex(ConvexVertices, Margin);
if (MarginConvex->NumVertices() > 0)
{
Chaos::FImplicitObject* Implicit = MarginConvex;
UpdateImplicitFlags(Implicit, CollisionType);
Implicits.Add(Implicit);
}
else
{
delete MarginConvex;
}
}
}
if (Implicits.Num() == 0)
{
return nullptr;
}
else if (Implicits.Num() == 1)
{
return Implicits[0];
}
else
{
TArray<Chaos::FImplicitObjectPtr> ImplicitsPtrs;
for(Chaos::FImplicitObjectRef ImplicitRef : Implicits)
{
Chaos::FImplicitObjectPtr ImplicitPtr(ImplicitRef);
ImplicitsPtrs.Add(ImplicitPtr);
}
return new Chaos::FImplicitObjectUnion(MoveTemp(ImplicitsPtrs));
}
}
return nullptr;
}
Chaos::FImplicitObjectRef
FCollisionStructureManager::NewImplicitCapsule(
const Chaos::FReal Radius,
const Chaos::FReal Length,
const float CollisionObjectReduction,
const ECollisionTypeEnum CollisionType)
{
if (Length < UE_SMALL_NUMBER)
{
// make a more optimized shape : sphere
return FCollisionStructureManager::NewImplicitSphere(Radius, CollisionObjectReduction, CollisionType);
}
const Chaos::FReal HalfLength = (Chaos::FReal)Length * (Chaos::FReal)0.5;
Chaos::FImplicitObjectRef Implicit = new Chaos::FCapsule(Chaos::FVec3(0, 0, -HalfLength), Chaos::FVec3(0, 0, +HalfLength), Radius * (1 - CollisionObjectReduction / 100.f));
UpdateImplicitFlags(Implicit, CollisionType);
return Implicit;
}
Chaos::FImplicitObjectRef
FCollisionStructureManager::NewImplicitCapsule(
const FBox& CollisionBounds,
const float CollisionObjectReduction,
const ECollisionTypeEnum CollisionType)
{
const FVector BBoxCenter = CollisionBounds.GetCenter();
const FVector BBoxExtent = CollisionBounds.GetExtent(); // FBox's extents are 1/2 (Max - Min)
const Chaos::FReal XExtent = FMath::Abs(BBoxExtent.X);
const Chaos::FReal YExtent = FMath::Abs(BBoxExtent.Y);
const Chaos::FReal ZExtent = FMath::Abs(BBoxExtent.Z);
Chaos::FVec3 HalfLengthVector;
Chaos::FReal Radius = 0;
if (XExtent > YExtent && XExtent > ZExtent)
{
Radius = FMath::Min(YExtent, ZExtent);
HalfLengthVector = Chaos::FVec3((XExtent - Radius), 0, 0);
}
else if (YExtent > XExtent && YExtent > ZExtent)
{
Radius = FMath::Min(XExtent, ZExtent);
HalfLengthVector = Chaos::FVec3(0, (YExtent - Radius), 0);
}
else
{
Radius = FMath::Min(XExtent, YExtent);
HalfLengthVector = Chaos::FVec3(0, 0, (ZExtent - Radius));
}
if (HalfLengthVector.Size() < UE_SMALL_NUMBER)
{
// make a more optimized shape : sphere
return FCollisionStructureManager::NewImplicitSphere(Radius, CollisionObjectReduction, CollisionType);
}
Chaos::FVec3 X1 = BBoxCenter - HalfLengthVector;
Chaos::FVec3 X2 = BBoxCenter + HalfLengthVector;
Chaos::FImplicitObjectRef Implicit = new Chaos::FCapsule(X1, X2, Radius * (1 - CollisionObjectReduction / 100.f));
UpdateImplicitFlags(Implicit, CollisionType);
return Implicit;
}
Chaos::FImplicitObjectRef
FCollisionStructureManager::NewImplicitLevelset(
Chaos::FErrorReporter ErrorReporter,
const Chaos::FParticles& MeshParticles,
const Chaos::FTriangleMesh& TriMesh,
const FBox& CollisionBounds,
const int32 MinRes,
const int32 MaxRes,
const float CollisionObjectReduction,
const ECollisionTypeEnum CollisionType)
{
FVector HalfExtents = CollisionBounds.GetExtent();
if (HalfExtents.GetAbsMin() < UE_KINDA_SMALL_NUMBER)
{
return nullptr;
}
Chaos::FLevelSetRef LevelSet = NewLevelset(ErrorReporter, MeshParticles, TriMesh, CollisionBounds, MinRes, MaxRes, CollisionType);
if (LevelSet)
{
const Chaos::FReal DomainVolume = LevelSet->BoundingBox().GetVolume();
const Chaos::FReal FilledVolume = LevelSet->ApproximateNegativeMaterial();
if (FilledVolume < DomainVolume * 0.05)
{
const Chaos::FVec3 Extent = LevelSet->BoundingBox().Extents();
ErrorReporter.ReportLog(
*FString::Printf(TEXT(
"Level set is small or empty:\n"
" domain extent: (%g %g %g) volume: %g\n"
" estimated level set volume: %g\n"
" percentage filled: %g%%"),
Extent[0], Extent[1], Extent[2], DomainVolume, FilledVolume, FilledVolume / DomainVolume * 100.0));
}
HalfExtents *= CollisionObjectReduction / 100.f;
const Chaos::FReal MinExtent = FMath::Min(HalfExtents[0], FMath::Min(HalfExtents[1], HalfExtents[2]));
if (MinExtent > 0)
{
LevelSet->Shrink(MinExtent);
}
}
else
{
ErrorReporter.ReportError(TEXT("Level set rasterization failed."));
}
return LevelSet;
}
Chaos::FLevelSetRef
FCollisionStructureManager::NewLevelset(
Chaos::FErrorReporter ErrorReporter,
const Chaos::FParticles& MeshParticles,
const Chaos::FTriangleMesh& TriMesh,
const FBox& CollisionBounds,
const int32 MinRes,
const int32 MaxRes,
const ECollisionTypeEnum CollisionType)
{
if(TriMesh.GetNumElements() == 0)
{
// Empty tri-mesh, can't create a new level set
return nullptr;
}
Chaos::TVector<int32, 3> Counts;
const FVector Extents = CollisionBounds.GetExtent();
if (Extents.X < Extents.Y && Extents.X < Extents.Z)
{
Counts.X = MinRes;
Counts.Y = MinRes * static_cast<int32>(Extents.Y / Extents.X);
Counts.Z = MinRes * static_cast<int32>(Extents.Z / Extents.X);
}
else if (Extents.Y < Extents.Z)
{
Counts.X = MinRes * static_cast<int32>(Extents.X / Extents.Y);
Counts.Y = MinRes;
Counts.Z = MinRes * static_cast<int32>(Extents.Z / Extents.Y);
}
else
{
Counts.X = MinRes * static_cast<int32>(Extents.X / Extents.Z);
Counts.Y = MinRes * static_cast<int32>(Extents.Y / Extents.Z);
Counts.Z = MinRes;
}
if (Counts.X > MaxRes)
{
Counts.X = MaxRes;
}
if (Counts.Y > MaxRes)
{
Counts.Y = MaxRes;
}
if (Counts.Z > MaxRes)
{
Counts.Z = MaxRes;
}
Chaos::TUniformGrid<Chaos::FReal, 3> Grid(CollisionBounds.Min, CollisionBounds.Max, Counts, 1);
Chaos::FLevelSetRef Implicit = new Chaos::FLevelSet(ErrorReporter, Grid, MeshParticles, TriMesh);
if (ErrorReporter.ContainsUnhandledError())
{
ErrorReporter.HandleLatestError(); //Allow future levelsets to attempt to cook
delete Implicit;
return nullptr;
}
UpdateImplicitFlags(Implicit, CollisionType);
return Implicit;
}
FVector
FCollisionStructureManager::CalculateUnitMassInertiaTensor(
const FBox& Bounds,
const Chaos::FReal Radius,
const EImplicitTypeEnum ImplicitType
)
{
FVector Tensor(1);
if (ImplicitType == EImplicitTypeEnum::Chaos_Implicit_Box)
{
const Chaos::FVec3 Size = Bounds.GetSize();
const Chaos::FMatrix33 I = Chaos::FAABB3::GetInertiaTensor(1.0, Size);
Tensor = FVector(I.M[0][0], I.M[1][1], I.M[2][2]);
}
else if (ImplicitType == EImplicitTypeEnum::Chaos_Implicit_Sphere)
{
Tensor = FVector(Chaos::FSphere::GetInertiaTensor(1.0, Radius, false).M[0][0]);
}
ensureMsgf(Tensor.X != 0.f && Tensor.Y != 0.f && Tensor.Z != 0.f, TEXT("Rigid bounds check failure."));
return Tensor;
}
Chaos::FReal
FCollisionStructureManager::CalculateVolume(
const FBox& Bounds,
const Chaos::FReal Radius,
const EImplicitTypeEnum ImplicitType
)
{
Chaos::FReal Volume = (Chaos::FReal)1.;
if (ImplicitType == EImplicitTypeEnum::Chaos_Implicit_Box)
{
Volume = Bounds.GetVolume();
}
else if (ImplicitType == EImplicitTypeEnum::Chaos_Implicit_Sphere)
{
Volume = Chaos::FSphere::GetVolume(Radius);
}
ensureMsgf(Volume != 0.f, TEXT("Rigid volume check failure."));
return Volume;
}