// 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 IndicesArray; TriMesh.GetVertexSetAsArray(IndicesArray); TArray 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 LocalCollisionParticlesMax) ? LocalCollisionParticlesMax : OutsideVertices.Num(); if (NumParticles) { int32 VertexCounter = 0; Simplicial->AddParticles(NumParticles); for (int32 i = 0; iSetX(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& 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 ArrayView(&AllParticles.GetX(0), AllParticles.Size()); const TArray& 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(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& ConvexIndices, const TManagedArray* ConvexGeometry, const ECollisionTypeEnum CollisionType, const FTransform& MassTransform, const Chaos::FReal CollisionMarginFraction, const float CollisionObjectReduction) { using FConvexVec3 = Chaos::FConvex::FVec3Type; if (ConvexIndices.Num()) { TArray Implicits; for (auto& Index : ConvexIndices) { if((*ConvexGeometry)[Index]) { TArray 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 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 Counts; const FVector Extents = CollisionBounds.GetExtent(); if (Extents.X < Extents.Y && Extents.X < Extents.Z) { Counts.X = MinRes; Counts.Y = MinRes * static_cast(Extents.Y / Extents.X); Counts.Z = MinRes * static_cast(Extents.Z / Extents.X); } else if (Extents.Y < Extents.Z) { Counts.X = MinRes * static_cast(Extents.X / Extents.Y); Counts.Y = MinRes; Counts.Z = MinRes * static_cast(Extents.Z / Extents.Y); } else { Counts.X = MinRes * static_cast(Extents.X / Extents.Z); Counts.Y = MinRes * static_cast(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 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; }