3720 lines
136 KiB
C++
3720 lines
136 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "GeometryCollection/GeometryCollectionConvexUtility.h"
|
|
#include "Chaos/Convex.h"
|
|
#include "Chaos/GJK.h"
|
|
#include "GeometryCollection/GeometryCollection.h"
|
|
#include "GeometryCollection/GeometryCollectionAlgo.h"
|
|
#include "GeometryCollection/GeometryCollectionProximityUtility.h"
|
|
#include "GeometryCollection/Facades/CollectionTransformFacade.h"
|
|
#include "GeometryCollection/Facades/CollectionHierarchyFacade.h"
|
|
#include "GeometryCollection/Facades/CollectionMeshFacade.h"
|
|
#include "CompGeom/ConvexHull3.h"
|
|
#include "Templates/Sorting.h"
|
|
#include "Spatial/PointHashGrid3.h"
|
|
#include "Operations/MeshBoolean.h"
|
|
#include "Operations/MeshSelfUnion.h"
|
|
#include "MeshQueries.h"
|
|
#include "GeometryCollection/GeometryCollectionClusteringUtility.h"
|
|
|
|
bool UseVolumeToComputeRelativeSize = false;
|
|
FAutoConsoleVariableRef CVarUseVolumeToComputeRelativeSize(TEXT("p.gc.UseVolumeToComputeRelativeSize"), UseVolumeToComputeRelativeSize, TEXT("Use Volume To Compute RelativeSize instead of the side of the cubic volume (def: false)"));
|
|
|
|
bool UseLargestClusterToComputeRelativeSize = false;
|
|
FAutoConsoleVariableRef CVarUseMaxClusterToComputeRelativeSize(TEXT("p.gc.UseLargestClusterToComputeRelativeSize"), UseVolumeToComputeRelativeSize, TEXT("Use the largest Cluster as reference for the releative size instead of the largest child (def: false)"));
|
|
|
|
static const Chaos::FVec3f IcoSphere_Subdiv0[] =
|
|
{
|
|
{ 0.000000f, 0.000000f, -1.000000f },
|
|
{ 0.525720f, 0.723600f, -0.447215f },
|
|
{ 0.850640f, -0.276385f, -0.447215f },
|
|
{ 0.000000f, -0.894425f, -0.447215f },
|
|
{ -0.850640f, -0.276385f, -0.447215f },
|
|
{ -0.525720f, 0.723600f, -0.447215f },
|
|
{ 0.850640f, 0.276385f, 0.447215f },
|
|
{ 0.525720f, -0.723600f, 0.447215f },
|
|
{ -0.525720f, -0.723600f, 0.447215f },
|
|
{ -0.850640f, 0.276385f, 0.447215f },
|
|
{ 0.000000f, 0.894425f, 0.447215f },
|
|
{ 0.000000f, 0.000000f, 1.000000f },
|
|
};
|
|
constexpr int32 IcoSphere_Subdiv0_Num = sizeof(IcoSphere_Subdiv0) / sizeof(Chaos::FVec3f);
|
|
|
|
static const Chaos::FVec3f IcoSphere_Subdiv1[] =
|
|
{
|
|
{ 0.000000f, 0.000000f, -1.000000f },
|
|
{ 0.525725f, 0.723607f, -0.447220f },
|
|
{ 0.850649f, -0.276388f, -0.447220f },
|
|
{ 0.000000f, -0.894426f, -0.447216f },
|
|
{ -0.850649f, -0.276388f, -0.447220f },
|
|
{ -0.525725f, 0.723607f, -0.447220f },
|
|
{ 0.850649f, 0.276388f, 0.447220f },
|
|
{ 0.525725f, -0.723607f, 0.447220f },
|
|
{ -0.525725f, -0.723607f, 0.447220f },
|
|
{ -0.850649f, 0.276388f, 0.447220f },
|
|
{ 0.000000f, 0.894426f, 0.447216f },
|
|
{ 0.000000f, 0.000000f, 1.000000f },
|
|
{ 0.499995f, -0.162456f, -0.850654f },
|
|
{ 0.309011f, 0.425323f, -0.850654f },
|
|
{ 0.809012f, 0.262869f, -0.525738f },
|
|
{ 0.000000f, 0.850648f, -0.525736f },
|
|
{ -0.309011f, 0.425323f, -0.850654f },
|
|
{ 0.000000f, -0.525730f, -0.850652f },
|
|
{ 0.499997f, -0.688189f, -0.525736f },
|
|
{ -0.499995f, -0.162456f, -0.850654f },
|
|
{ -0.499997f, -0.688189f, -0.525736f },
|
|
{ -0.809012f, 0.262869f, -0.525738f },
|
|
{ 0.309013f, 0.951058f, 0.000000f },
|
|
{ -0.309013f, 0.951058f, 0.000000f },
|
|
{ 1.000000f, 0.000000f, 0.000000f },
|
|
{ 0.809017f, 0.587786f, 0.000000f },
|
|
{ 0.309013f, -0.951058f, 0.000000f },
|
|
{ 0.809017f, -0.587786f, 0.000000f },
|
|
{ -0.809017f, -0.587786f, 0.000000f },
|
|
{ -0.309013f, -0.951058f, 0.000000f },
|
|
{ -0.809017f, 0.587786f, 0.000000f },
|
|
{ -1.000000f, 0.000000f, 0.000000f },
|
|
{ 0.499997f, 0.688189f, 0.525736f },
|
|
{ 0.809012f, -0.262869f, 0.525738f },
|
|
{ 0.000000f, -0.850648f, 0.525736f },
|
|
{ -0.809012f, -0.262869f, 0.525738f },
|
|
{ -0.499997f, 0.688189f, 0.525736f },
|
|
{ 0.499995f, 0.162456f, 0.850654f },
|
|
{ 0.000000f, 0.525730f, 0.850652f },
|
|
{ 0.309011f, -0.425323f, 0.850654f },
|
|
{ -0.309011f, -0.425323f, 0.850654f },
|
|
{ -0.499995f, 0.162456f, 0.850654f },
|
|
};
|
|
constexpr int32 IcoSphere_Subdiv1_Num = sizeof(IcoSphere_Subdiv1) / sizeof(Chaos::FVec3f);
|
|
|
|
static const Chaos::FVec3f IcoHemisphere_Subdiv1[] =
|
|
{
|
|
{ 0.850649f, 0.27638f, 0.447220f },
|
|
{ 0.525725f, -0.72360f, 0.447220f },
|
|
{ -0.525725f, -0.72360f, 0.447220f },
|
|
{ -0.850649f, 0.27638f, 0.447220f },
|
|
{ 0.000000f, 0.89442f, 0.447216f },
|
|
{ 0.000000f, 0.00000f, 1.000000f },
|
|
{ 0.309013f, 0.95105f, 0.000000f },
|
|
{ -0.309013f, 0.95105f, 0.000000f },
|
|
{ 1.000000f, 0.00000f, 0.000000f },
|
|
{ 0.809017f, 0.58778f, 0.000000f },
|
|
{ 0.309013f, -0.95105f, 0.000000f },
|
|
{ 0.809017f, -0.58778f, 0.000000f },
|
|
{ -0.809017f, -0.58778f, 0.000000f },
|
|
{ -0.309013f, -0.95105f, 0.000000f },
|
|
{ -0.809017f, 0.58778f, 0.000000f },
|
|
{ -1.000000f, 0.00000f, 0.000000f },
|
|
{ 0.499997f, 0.68818f, 0.525736f },
|
|
{ 0.809012f, -0.26286f, 0.525738f },
|
|
{ 0.000000f, -0.85064f, 0.525736f },
|
|
{ -0.809012f, -0.26286f, 0.525738f },
|
|
{ -0.499997f, 0.68818f, 0.525736f },
|
|
{ 0.499995f, 0.16245f, 0.850654f },
|
|
{ 0.000000f, 0.52573f, 0.850652f },
|
|
{ 0.309011f, -0.42532f, 0.850654f },
|
|
{ -0.309011f, -0.42532f, 0.850654f },
|
|
{ -0.499995f, 0.16245f, 0.850654f },
|
|
};
|
|
constexpr int32 IcoHemisphere_Subdiv1_Num = sizeof(IcoHemisphere_Subdiv1) / sizeof(Chaos::FVec3f);
|
|
|
|
|
|
TOptional<FGeometryCollectionConvexUtility::FGeometryCollectionConvexData> FGeometryCollectionConvexUtility::GetConvexHullDataIfPresent(FManagedArrayCollection* GeometryCollection)
|
|
{
|
|
check(GeometryCollection);
|
|
|
|
if (!GeometryCollection->HasAttribute("TransformToConvexIndices", FTransformCollection::TransformGroup) ||
|
|
!GeometryCollection->HasAttribute(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup))
|
|
{
|
|
return TOptional<FGeometryCollectionConvexUtility::FGeometryCollectionConvexData>();
|
|
}
|
|
|
|
FGeometryCollectionConvexUtility::FGeometryCollectionConvexData ConvexData{
|
|
GeometryCollection->ModifyAttribute<TSet<int32>>("TransformToConvexIndices", FTransformCollection::TransformGroup),
|
|
GeometryCollection->ModifyAttribute<Chaos::FConvexPtr>(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup)
|
|
};
|
|
return TOptional<FGeometryCollectionConvexUtility::FGeometryCollectionConvexData>(ConvexData);
|
|
}
|
|
|
|
bool FGeometryCollectionConvexUtility::HasConvexHullData(const FManagedArrayCollection* Collection)
|
|
{
|
|
return Collection->HasAttribute("TransformToConvexIndices", FTransformCollection::TransformGroup) && Collection->HasAttribute(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup);
|
|
}
|
|
|
|
FGeometryCollectionConvexUtility::FGeometryCollectionConvexData FGeometryCollectionConvexUtility::GetValidConvexHullData(FGeometryCollection* GeometryCollection)
|
|
{
|
|
check(GeometryCollection)
|
|
|
|
CreateConvexHullAttributesIfNeeded(*GeometryCollection);
|
|
|
|
// Check for correct population. Make sure all rigid nodes should have a convex associated; leave convex hulls for transform nodes alone for now
|
|
const TManagedArray<int32>& SimulationType = GeometryCollection->GetAttribute<int32>("SimulationType", FTransformCollection::TransformGroup);
|
|
const TManagedArray<int32>& TransformToGeometryIndex = GeometryCollection->GetAttribute<int32>("TransformToGeometryIndex", FTransformCollection::TransformGroup);
|
|
TManagedArray<TSet<int32>>& TransformToConvexIndices = GeometryCollection->ModifyAttribute<TSet<int32>>("TransformToConvexIndices", FTransformCollection::TransformGroup);
|
|
TManagedArray<Chaos::FConvexPtr>& ConvexHull = GeometryCollection->ModifyAttribute<Chaos::FConvexPtr>(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup);
|
|
|
|
TArray<int32> ProduceConvexHulls;
|
|
ProduceConvexHulls.Reserve(SimulationType.Num());
|
|
|
|
for (int32 Idx = 0; Idx < SimulationType.Num(); ++Idx)
|
|
{
|
|
if ((SimulationType[Idx] == FGeometryCollection::ESimulationTypes::FST_Rigid) && (TransformToConvexIndices[Idx].Num() == 0))
|
|
{
|
|
ProduceConvexHulls.Add(Idx);
|
|
}
|
|
}
|
|
|
|
if (ProduceConvexHulls.Num())
|
|
{
|
|
int32 NewConvexIndexStart = GeometryCollection->AddElements(ProduceConvexHulls.Num(), FGeometryCollection::ConvexGroup);
|
|
for (int32 Idx = 0; Idx < ProduceConvexHulls.Num(); ++Idx)
|
|
{
|
|
int32 GeometryIdx = TransformToGeometryIndex[ProduceConvexHulls[Idx]];
|
|
ConvexHull[NewConvexIndexStart + Idx] = GetConvexHull(GeometryCollection, GeometryIdx);
|
|
TransformToConvexIndices[ProduceConvexHulls[Idx]].Reset();
|
|
TransformToConvexIndices[ProduceConvexHulls[Idx]].Add(NewConvexIndexStart + Idx);
|
|
}
|
|
}
|
|
|
|
return { TransformToConvexIndices, ConvexHull };
|
|
}
|
|
|
|
|
|
|
|
namespace
|
|
{
|
|
|
|
typedef Chaos::TPlaneConcrete<Chaos::FReal, 3> FChaosPlane;
|
|
|
|
// filter points s.t. they are spaced at least more than SimplificationDistanceThreshold apart (after starting with the 4 'extreme' points to ensure we cover a volume)
|
|
void FilterHullPoints(const TArray<Chaos::FConvex::FVec3Type>& InPts, TArray<Chaos::FConvex::FVec3Type>& OutPts, double SimplificationDistanceThreshold)
|
|
{
|
|
if (SimplificationDistanceThreshold > 0 && InPts.Num() > 0)
|
|
{
|
|
OutPts.Reset();
|
|
|
|
int32 NumPts = InPts.Num();
|
|
TArray<Chaos::FReal> DistSq;
|
|
DistSq.SetNumUninitialized(NumPts);
|
|
TArray<int32> PointOrder;
|
|
PointOrder.SetNumUninitialized(NumPts);
|
|
UE::Geometry::TPointHashGrid3<int32, Chaos::FReal> Spatial((Chaos::FReal)SimplificationDistanceThreshold, INDEX_NONE);
|
|
Chaos::FAABB3 Bounds;
|
|
for (int32 VIdx = 0; VIdx < NumPts; VIdx++)
|
|
{
|
|
Bounds.GrowToInclude(InPts[VIdx]);
|
|
PointOrder[VIdx] = VIdx;
|
|
}
|
|
|
|
// Rank points by squared distance from center
|
|
Chaos::FVec3 Center = Bounds.Center();
|
|
for (int i = 0; i < NumPts; i++)
|
|
{
|
|
DistSq[i] = (InPts[i] - Center).SizeSquared();
|
|
}
|
|
|
|
// Start by picking the 'extreme' points to ensure we cover the volume reasonably (otherwise it's too easy to end up with a degenerate hull pieces)
|
|
UE::Geometry::TExtremePoints3<Chaos::FReal> ExtremePoints(NumPts,
|
|
[&InPts](int32 Idx)->UE::Math::TVector<Chaos::FReal>
|
|
{
|
|
return (UE::Math::TVector<Chaos::FReal>)InPts[Idx];
|
|
});
|
|
for (int32 ExtremeIdx = 0; ExtremeIdx < ExtremePoints.Dimension + 1; ExtremeIdx++)
|
|
{
|
|
int32 ExtremePtIdx = ExtremePoints.Extreme[ExtremeIdx];
|
|
if (!InPts.IsValidIndex(ExtremePtIdx))
|
|
{
|
|
break;
|
|
}
|
|
Chaos::FVec3 ExtremePt = InPts[ExtremePtIdx];
|
|
OutPts.Add(ExtremePt);
|
|
Spatial.InsertPointUnsafe(ExtremePtIdx, ExtremePt);
|
|
DistSq[ExtremePtIdx] = -1; // remove these points from the distance ranking
|
|
}
|
|
|
|
// Sort in descending order
|
|
Algo::Sort(PointOrder, [&DistSq](int32 i, int32 j)
|
|
{
|
|
return DistSq[i] > DistSq[j];
|
|
});
|
|
|
|
// Filter to include only w/ no other too-close points, prioritizing the farthest points
|
|
for (int32 OrdIdx = 0; OrdIdx < NumPts; OrdIdx++)
|
|
{
|
|
int32 PtIdx = PointOrder[OrdIdx];
|
|
if (DistSq[PtIdx] < 0)
|
|
{
|
|
break; // we've reached the extreme points that were already selected
|
|
}
|
|
Chaos::FVec3 Pt = InPts[PtIdx];
|
|
TPair<int32, Chaos::FReal> NearestIdx = Spatial.FindNearestInRadius(Pt, (Chaos::FReal)SimplificationDistanceThreshold,
|
|
[&InPts, &Pt](int32 Idx)
|
|
{
|
|
return (Pt - InPts[Idx]).SizeSquared();
|
|
});
|
|
if (NearestIdx.Key == INDEX_NONE)
|
|
{
|
|
Spatial.InsertPointUnsafe(PtIdx, Pt);
|
|
OutPts.Add(Pt);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No filtering requested -- in practice, we don't call this function in these cases below
|
|
OutPts = InPts;
|
|
}
|
|
}
|
|
|
|
void FilterHullPoints(TArray<Chaos::FConvex::FVec3Type>& InOutPts, double SimplificationDistanceThreshold)
|
|
{
|
|
if (SimplificationDistanceThreshold > 0)
|
|
{
|
|
TArray<Chaos::FConvex::FVec3Type> FilteredPts;
|
|
FilterHullPoints(InOutPts, FilteredPts, SimplificationDistanceThreshold);
|
|
InOutPts = MoveTemp(FilteredPts);
|
|
}
|
|
}
|
|
|
|
FVector ScaleHullPoints(TArray<Chaos::FConvex::FVec3Type>& InOutPts, double ShrinkPercentage)
|
|
{
|
|
FVector Pivot = FVector::ZeroVector;
|
|
if (ShrinkPercentage == 0.0 || InOutPts.IsEmpty())
|
|
{
|
|
return Pivot;
|
|
}
|
|
|
|
double ScaleFactor = 1.0 - ShrinkPercentage / 100.0;
|
|
ensure(ScaleFactor != 0.0);
|
|
|
|
FVector Sum(0,0,0);
|
|
for (Chaos::FConvex::FVec3Type& Pt : InOutPts)
|
|
{
|
|
Sum += (FVector)Pt;
|
|
}
|
|
|
|
Pivot = Sum / (double)InOutPts.Num();
|
|
for (Chaos::FConvex::FVec3Type& Pt : InOutPts)
|
|
{
|
|
Pt = Chaos::FConvex::FVec3Type((FVector(Pt) - Pivot) * ScaleFactor + Pivot);
|
|
}
|
|
|
|
return Pivot;
|
|
}
|
|
|
|
void AddUnscaledPts(TArray<Chaos::FConvex::FVec3Type>& OutHullPts, const TArray<Chaos::FConvex::FVec3Type>& Vertices, const FVector& Pivot, double ShrinkPercentage)
|
|
{
|
|
double ScaleFactor = 1.0 - ShrinkPercentage / 100.0;
|
|
double InvScaleFactor = 1.0;
|
|
if (ensure(ScaleFactor != 0.0))
|
|
{
|
|
InvScaleFactor = 1.0 / ScaleFactor;
|
|
}
|
|
|
|
int32 StartIdx = OutHullPts.AddUninitialized(Vertices.Num());
|
|
for (int32 VertIdx = 0, Num = Vertices.Num(); VertIdx < Num; VertIdx++)
|
|
{
|
|
FVector Vert = (FVector)Vertices[VertIdx];
|
|
OutHullPts[StartIdx + VertIdx] = Chaos::FConvex::FVec3Type((Vert - Pivot) * InvScaleFactor + Pivot);
|
|
}
|
|
}
|
|
|
|
Chaos::FConvex MakeHull(const TArray<Chaos::FConvex::FVec3Type>& Pts, double SimplificationDistanceThreshold)
|
|
{
|
|
if (SimplificationDistanceThreshold > 0)
|
|
{
|
|
TArray<Chaos::FConvex::FVec3Type> FilteredPts;
|
|
FilterHullPoints(Pts, FilteredPts, SimplificationDistanceThreshold);
|
|
|
|
return Chaos::FConvex(FilteredPts, UE_KINDA_SMALL_NUMBER);
|
|
}
|
|
else
|
|
{
|
|
return Chaos::FConvex(Pts, UE_KINDA_SMALL_NUMBER);
|
|
}
|
|
}
|
|
|
|
/// Cut hull with plane, generating the point set of a new hull
|
|
/// @return false if plane did not cut any points on the hull
|
|
bool CutHull(const Chaos::FConvex& HullIn, FChaosPlane Plane, bool KeepSide, TArray<Chaos::FConvex::FVec3Type>& HullPtsOut)
|
|
{
|
|
const TArray<Chaos::FConvex::FVec3Type>& Vertices = HullIn.GetVertices();
|
|
const Chaos::FConvexStructureData& HullData = HullIn.GetStructureData();
|
|
bool bHasOutside = false;
|
|
for (int VertIdx = 0; VertIdx < Vertices.Num(); VertIdx++)
|
|
{
|
|
const Chaos::FVec3& V = Vertices[VertIdx];
|
|
if ((Plane.SignedDistance(V) < 0) == KeepSide)
|
|
{
|
|
HullPtsOut.Add(V);
|
|
}
|
|
else
|
|
{
|
|
bHasOutside = true;
|
|
}
|
|
}
|
|
|
|
if (!bHasOutside)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int32 NumPlanes = HullIn.NumPlanes();
|
|
for (int PlaneIdx = 0; PlaneIdx < NumPlanes; PlaneIdx++)
|
|
{
|
|
int32 NumPlaneVerts = HullData.NumPlaneVertices(PlaneIdx);
|
|
for (int32 PlaneVertexIdx = 0; PlaneVertexIdx < NumPlaneVerts; PlaneVertexIdx++)
|
|
{
|
|
int32 NextVertIdx = (PlaneVertexIdx + 1) % NumPlaneVerts;
|
|
const Chaos::FVec3& V0 = Vertices[HullData.GetPlaneVertex(PlaneIdx, PlaneVertexIdx)];
|
|
const Chaos::FVec3& V1 = Vertices[HullData.GetPlaneVertex(PlaneIdx, NextVertIdx)];
|
|
if ((Plane.SignedDistance(V0) < 0) != (Plane.SignedDistance(V1) < 0))
|
|
{
|
|
Chaos::Pair<Chaos::FVec3, bool> Res = Plane.FindClosestIntersection(V0, V1, 0);
|
|
if (Res.Second)
|
|
{
|
|
HullPtsOut.Add(Res.First);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// Cut hull with plane, keeping both sides and generating the point set of both new hulls
|
|
/// @return false if plane did not cut any points on the hull
|
|
bool SplitHull(const Chaos::FConvex& HullIn, FChaosPlane Plane, bool KeepSide, TArray<Chaos::FVec3>& InsidePtsOut, TArray<Chaos::FVec3>& OutsidePtsOut)
|
|
{
|
|
const TArray<Chaos::FConvex::FVec3Type>& Vertices = HullIn.GetVertices();
|
|
const Chaos::FConvexStructureData& HullData = HullIn.GetStructureData();
|
|
bool bHasOutside = false;
|
|
for (int VertIdx = 0; VertIdx < Vertices.Num(); VertIdx++)
|
|
{
|
|
const Chaos::FVec3& V = Vertices[VertIdx];
|
|
if ((Plane.SignedDistance(V) < 0) == KeepSide)
|
|
{
|
|
InsidePtsOut.Add(V);
|
|
}
|
|
else
|
|
{
|
|
OutsidePtsOut.Add(V);
|
|
bHasOutside = true;
|
|
}
|
|
}
|
|
|
|
if (!bHasOutside)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int32 NumPlanes = HullIn.NumPlanes();
|
|
for (int PlaneIdx = 0; PlaneIdx < NumPlanes; PlaneIdx++)
|
|
{
|
|
int32 NumPlaneVerts = HullData.NumPlaneVertices(PlaneIdx);
|
|
for (int32 PlaneVertexIdx = 0; PlaneVertexIdx < NumPlaneVerts; PlaneVertexIdx++)
|
|
{
|
|
int32 NextVertIdx = (PlaneVertexIdx + 1) % NumPlaneVerts;
|
|
const Chaos::FVec3 V0 = Vertices[HullData.GetPlaneVertex(PlaneIdx, PlaneVertexIdx)];
|
|
const Chaos::FVec3 V1 = Vertices[HullData.GetPlaneVertex(PlaneIdx, NextVertIdx)];
|
|
if ((Plane.SignedDistance(V0) < 0) != (Plane.SignedDistance(V1) < 0))
|
|
{
|
|
Chaos::Pair<Chaos::FVec3, bool> Res = Plane.FindClosestIntersection(V0, V1, 0);
|
|
if (Res.Second)
|
|
{
|
|
InsidePtsOut.Add(Res.First);
|
|
OutsidePtsOut.Add(Res.First);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Add a pivot and verify (in debug) that the pivot array and convexes array are in sync
|
|
void AddPivot(TArray<Chaos::FConvexPtr>& Convexes, TArray<FVector>& ConvexPivots, FVector Pivot)
|
|
{
|
|
ConvexPivots.Add(Pivot);
|
|
checkSlow(Convexes.Num() == ConvexPivots.Num());
|
|
}
|
|
|
|
/// Assumptions:
|
|
/// Convexes is initialized to one convex hull for each leaf geometry in a SHARED coordinate space
|
|
/// TransformToConvexIndices is initialized to point to the existing convex hulls
|
|
/// Parents, GeoProximity, and GeometryToTransformIndex are all initialized from the geometry collection
|
|
void CreateNonoverlappingConvexHulls(
|
|
TArray<Chaos::FConvexPtr>& Convexes,
|
|
TArray<FVector>& ConvexPivots,
|
|
TArray<TSet<int32>>& TransformToConvexIndices,
|
|
TFunctionRef<bool(int32)> HasCustomConvexFn,
|
|
const TManagedArray<int32>& SimulationType,
|
|
int32 LeafType,
|
|
int32 SkipType,
|
|
const TManagedArray<int32>& Parents,
|
|
const TManagedArray<TSet<int32>>* GeoProximity,
|
|
const TManagedArray<int32>& GeometryToTransformIndex,
|
|
const TManagedArray<float>* Volume,
|
|
double FracAllowRemove,
|
|
double SimplificationDistanceThreshold,
|
|
double CanExceedFraction,
|
|
EConvexOverlapRemoval OverlapRemovalMethod,
|
|
double ShrinkPercentage
|
|
)
|
|
{
|
|
bool bRemoveOverlaps = OverlapRemovalMethod != EConvexOverlapRemoval::None;
|
|
bool bRemoveLeafOverlaps = OverlapRemovalMethod == EConvexOverlapRemoval::All;
|
|
|
|
double ScaleFactor = 1.0 - ShrinkPercentage / 100.0;
|
|
ensure(ScaleFactor != 0.0);
|
|
|
|
int32 NumBones = TransformToConvexIndices.Num();
|
|
check(Parents.Num() == NumBones);
|
|
|
|
auto SkipBone = [&SimulationType, SkipType](int32 Bone) -> bool
|
|
{
|
|
return SimulationType[Bone] == SkipType;
|
|
};
|
|
|
|
auto OnlyConvex = [&TransformToConvexIndices](int32 Bone) -> int32
|
|
{
|
|
ensure(TransformToConvexIndices[Bone].Num() <= 1);
|
|
if (auto It = TransformToConvexIndices[Bone].CreateConstIterator(); It)
|
|
{
|
|
const int32 ConvexIndex = *It;
|
|
return ConvexIndex;
|
|
}
|
|
return INDEX_NONE;
|
|
};
|
|
|
|
TArray<TSet<int32>> LeafProximity;
|
|
LeafProximity.SetNum(NumBones);
|
|
if (ensure(GeoProximity))
|
|
{
|
|
for (int32 GeomIdx = 0; GeomIdx < GeoProximity->Num(); GeomIdx++)
|
|
{
|
|
int32 TransformIdx = GeometryToTransformIndex[GeomIdx];
|
|
for (int32 NbrGeomIdx : (*GeoProximity)[GeomIdx])
|
|
{
|
|
LeafProximity[TransformIdx].Add(GeometryToTransformIndex[NbrGeomIdx]);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto IsColliding = [&Convexes](int32 ConvexA, int32 ConvexB)
|
|
{
|
|
if (ConvexA == -1 || ConvexB == -1 || Convexes[ConvexA]->NumVertices() == 0 || Convexes[ConvexB]->NumVertices() == 0)
|
|
{
|
|
// at least one of the convex hulls was empty, so cannot be colliding
|
|
return false;
|
|
}
|
|
const Chaos::TRigidTransform<Chaos::FReal, 3> IdentityTransform = Chaos::TRigidTransform<Chaos::FReal, 3>::Identity;
|
|
return GJKIntersection(*Convexes[ConvexA], *Convexes[ConvexB], IdentityTransform);
|
|
};
|
|
|
|
auto IsBoneColliding = [&TransformToConvexIndices, &Convexes, &IsColliding](int32 BoneA, int32 BoneB)
|
|
{
|
|
for (int32 ConvexA : TransformToConvexIndices[BoneA])
|
|
{
|
|
for (int32 ConvexB : TransformToConvexIndices[BoneB])
|
|
{
|
|
if (IsColliding(ConvexA, ConvexB))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
auto GetConvexSpan = [](const Chaos::FConvex& Convex, const Chaos::FVec3& Center, const Chaos::FVec3& Normal) -> Chaos::FVec2
|
|
{
|
|
int32 NumVertices = Convex.NumVertices();
|
|
if (NumVertices == 0)
|
|
{
|
|
return Chaos::FVec2(0, 0);
|
|
}
|
|
Chaos::FReal AlongFirst = (Convex.GetVertex(0) - Center).Dot(FVector3f(Normal));
|
|
Chaos::FVec2 Range(AlongFirst, AlongFirst);
|
|
for (int Idx = 1; Idx < NumVertices; Idx++)
|
|
{
|
|
const float Along = static_cast<float>((Convex.GetVertex(Idx) - Center).Dot(FVector3f(Normal)));
|
|
if (Along < Range.X)
|
|
{
|
|
Range.X = Along;
|
|
}
|
|
else if (Along > Range.Y)
|
|
{
|
|
Range.Y = Along;
|
|
}
|
|
}
|
|
return Range;
|
|
};
|
|
|
|
// Score separating plane direction based on how well it separates (lower is better)
|
|
// and also compute new center for plane + normal. Normal is either the input normal or flipped.
|
|
auto ScoreCutPlane = [&GetConvexSpan](
|
|
const Chaos::FConvex& A, const Chaos::FConvex& B,
|
|
const FChaosPlane& Plane, bool bOneSidedCut,
|
|
Chaos::FVec3& OutCenter, Chaos::FVec3& OutNormal) -> Chaos::FReal
|
|
{
|
|
bool bRangeAValid = false, bRangeBValid = false;
|
|
Chaos::FVec2 RangeA = GetConvexSpan(A, Plane.X(), Plane.Normal());
|
|
Chaos::FVec2 RangeB = GetConvexSpan(B, Plane.X(), Plane.Normal());
|
|
Chaos::FVec2 Union(FMath::Min(RangeA.X, RangeB.X), FMath::Max(RangeA.Y, RangeB.Y));
|
|
// no intersection -- cut plane is separating, this is ideal!
|
|
if (RangeA.X > RangeB.Y || RangeA.Y < RangeB.X)
|
|
{
|
|
if (RangeA.X > RangeB.Y)
|
|
{
|
|
OutCenter = Plane.X() + Plane.Normal() * ((RangeA.X + RangeB.Y) * (Chaos::FReal).5);
|
|
OutNormal = -Plane.Normal();
|
|
}
|
|
else
|
|
{
|
|
OutCenter = Plane.X() + Plane.Normal() * ((RangeA.Y + RangeB.X) * (Chaos::FReal).5);
|
|
OutNormal = Plane.Normal();
|
|
}
|
|
return 0;
|
|
}
|
|
// there was an intersection; find the actual mid plane-center and score it
|
|
Chaos::FVec2 Intersection(FMath::Max(RangeA.X, RangeB.X), FMath::Min(RangeA.Y, RangeB.Y));
|
|
Chaos::FReal IntersectionMid = (Intersection.X + Intersection.Y) * (Chaos::FReal).5;
|
|
|
|
// Decide which side of the plane is kept/removed
|
|
Chaos::FVec2 BiggerRange = RangeA;
|
|
Chaos::FReal Sign = 1;
|
|
if (RangeA.Y - RangeA.X < RangeB.Y - RangeB.X)
|
|
{
|
|
BiggerRange = RangeB;
|
|
Sign = -1;
|
|
}
|
|
if (IntersectionMid - BiggerRange.X < BiggerRange.Y - IntersectionMid)
|
|
{
|
|
Sign *= -1;
|
|
}
|
|
OutNormal = Sign * Plane.Normal();
|
|
|
|
Chaos::FReal IntersectionCut = IntersectionMid;
|
|
if (bOneSidedCut) // if cut is one-sided, move the plane to the far end of Convex B (which it should not cut)
|
|
{
|
|
// which end depends on which way the output cut plane is oriented
|
|
if (Sign > 0)
|
|
{
|
|
IntersectionCut = RangeB.X;
|
|
}
|
|
else
|
|
{
|
|
IntersectionCut = RangeB.Y;
|
|
}
|
|
}
|
|
OutCenter = Plane.X() + Plane.Normal() * IntersectionCut;
|
|
|
|
// Simple score favors small intersection span relative to union span
|
|
// TODO: consider other metrics; e.g. something more directly ~ percent cut away
|
|
return (Intersection.Y - Intersection.X) / (Union.Y - Union.X);
|
|
};
|
|
|
|
// Search cut plane options for the most promising one --
|
|
// Usually GJK gives a good cut plane, but it can fail badly so we also test a simple 'difference between centers' plane
|
|
// TODO: consider adding more plane options to the search
|
|
auto FindCutPlane = [&ScoreCutPlane](const Chaos::FConvex& A, const Chaos::FConvex& B,
|
|
Chaos::FVec3 CloseA, Chaos::FVec3 CloseB, Chaos::FVec3 Normal,
|
|
bool bOneSidedCut,
|
|
Chaos::FVec3& OutCenter, Chaos::FVec3& OutNormal) -> bool
|
|
{
|
|
OutCenter = (CloseA + CloseB) * .5;
|
|
OutNormal = Normal;
|
|
Chaos::FReal BestScore = ScoreCutPlane(A, B, FChaosPlane(OutCenter, OutNormal), bOneSidedCut, OutCenter, OutNormal);
|
|
Chaos::FVec3 MassSepNormal = (B.GetCenterOfMass() - A.GetCenterOfMass());
|
|
if (MassSepNormal.Normalize() && BestScore > 0)
|
|
{
|
|
Chaos::FVec3 MassSepCenter = (A.GetCenterOfMass() + B.GetCenterOfMass()) * .5;
|
|
Chaos::FReal ScoreB = ScoreCutPlane(A, B,
|
|
FChaosPlane(MassSepCenter, MassSepNormal), bOneSidedCut, MassSepCenter, MassSepNormal);
|
|
if (ScoreB < BestScore)
|
|
{
|
|
BestScore = ScoreB;
|
|
OutCenter = MassSepCenter;
|
|
OutNormal = MassSepNormal;
|
|
}
|
|
}
|
|
if (BestScore == 0)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
auto FixCollisionWithCut = [&Convexes, &FindCutPlane, &SimplificationDistanceThreshold, &ScaleFactor](int32 ConvexA, int32 ConvexB)
|
|
{
|
|
if (Convexes[ConvexA]->NumVertices() == 0 || Convexes[ConvexB]->NumVertices() == 0)
|
|
{
|
|
// at least one of the convex hulls was empty, so cannot be colliding
|
|
return false;
|
|
}
|
|
Chaos::FReal Depth;
|
|
Chaos::FVec3 CloseA, CloseB, Normal;
|
|
int32 OutIdxA, OutIdxB;
|
|
const Chaos::TRigidTransform<Chaos::FReal, 3> IdentityTransform = Chaos::TRigidTransform<Chaos::FReal, 3>::Identity;
|
|
bool bCollide = Chaos::GJKPenetration(*Convexes[ConvexA], *Convexes[ConvexB], IdentityTransform, Depth, CloseA, CloseB, Normal, OutIdxA, OutIdxB);
|
|
if (bCollide)
|
|
{
|
|
Chaos::FVec3 IdealCenter, IdealNormal;
|
|
if (!FindCutPlane(*Convexes[ConvexA], *Convexes[ConvexB], CloseA, CloseB, Normal, false, IdealCenter, IdealNormal))
|
|
{
|
|
return false;
|
|
}
|
|
FChaosPlane CutPlane(IdealCenter, IdealNormal);
|
|
TArray<Chaos::FConvex::FVec3Type> CutHullPts;
|
|
if (CutHull(*Convexes[ConvexA], CutPlane, true, CutHullPts))
|
|
{
|
|
*Convexes[ConvexA] = MakeHull(CutHullPts, SimplificationDistanceThreshold * ScaleFactor);
|
|
}
|
|
CutHullPts.Reset();
|
|
if (CutHull(*Convexes[ConvexB], CutPlane, false, CutHullPts))
|
|
{
|
|
*Convexes[ConvexB] = MakeHull(CutHullPts, SimplificationDistanceThreshold * ScaleFactor);
|
|
}
|
|
}
|
|
return bCollide;
|
|
};
|
|
|
|
// Initialize Children and Depths of tree
|
|
// Fix collisions between input hulls using the input proximity relationships
|
|
|
|
int32 MaxDepth = 0;
|
|
TArray<int32> Depths;
|
|
TArray<TArray<int32>> Children;
|
|
Children.SetNum(NumBones);
|
|
Depths.SetNumZeroed(NumBones);
|
|
for (int HullIdx = 0; HullIdx < NumBones; HullIdx++)
|
|
{
|
|
if (SimulationType[HullIdx] == SkipType) // Skip any 'SkipType' elements (generally embedded geometry)
|
|
{
|
|
Depths[HullIdx] = -1;
|
|
continue;
|
|
}
|
|
if (Parents[HullIdx] != INDEX_NONE)
|
|
{
|
|
if (SimulationType[Parents[HullIdx]] != LeafType)
|
|
{
|
|
Children[Parents[HullIdx]].Add(HullIdx);
|
|
}
|
|
else
|
|
{
|
|
Depths[HullIdx] = -1; // child-of-leaf == embedded geometry, just ignore it
|
|
continue;
|
|
}
|
|
}
|
|
int32 Depth = 0, WalkParent = HullIdx;
|
|
while (Parents[WalkParent] != INDEX_NONE)
|
|
{
|
|
Depth++;
|
|
WalkParent = Parents[WalkParent];
|
|
}
|
|
Depths[HullIdx] = Depth;
|
|
MaxDepth = FMath::Max(Depth, MaxDepth);
|
|
|
|
if (bRemoveOverlaps && bRemoveLeafOverlaps && !HasCustomConvexFn(HullIdx) && TransformToConvexIndices[HullIdx].Num() > 0)
|
|
{
|
|
const TSet<int32>& Neighbors = LeafProximity[HullIdx];
|
|
for (int32 NbrIdx : Neighbors)
|
|
{
|
|
if (!HasCustomConvexFn(NbrIdx) && NbrIdx < HullIdx && TransformToConvexIndices[NbrIdx].Num() > 0)
|
|
{
|
|
// TODO: consider a one-sided cut if one of the bones has custom convexes
|
|
FixCollisionWithCut(OnlyConvex(HullIdx), OnlyConvex(NbrIdx));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<int32> ByDepthOrder;
|
|
ByDepthOrder.Reserve(NumBones);
|
|
for (int32 ProcessDepth = MaxDepth; ProcessDepth >= 0; --ProcessDepth)
|
|
{
|
|
for (int32 Bone = 0; Bone < NumBones; Bone++)
|
|
{
|
|
if (Depths[Bone] == ProcessDepth)
|
|
{
|
|
ByDepthOrder.Add(Bone);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto AddLeaves = [&Children, &SimulationType, LeafType](int32 Bone, TArray<int32>& Leaves)
|
|
{
|
|
TArray<int32> ToExpand;
|
|
ToExpand.Add(Bone);
|
|
while (ToExpand.Num() > 0)
|
|
{
|
|
int32 ToProcess = ToExpand.Pop(EAllowShrinking::No);
|
|
if (SimulationType[ToProcess] == LeafType)
|
|
{
|
|
Leaves.Add(ToProcess);
|
|
}
|
|
else if (Children[ToProcess].Num() > 0)
|
|
{
|
|
ToExpand.Append(Children[ToProcess]);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Fill OutChildren with the shallowest descendents of Bone that have convex(es) (i.e., the direct children if they all have convexes, otherwise descend further to grandchildren searching for convex hulls)
|
|
auto AddDescendentsWithHulls = [&Children, &TransformToConvexIndices, &SimulationType, LeafType](int32 Bone, TArray<int32>& OutChildren)
|
|
{
|
|
TArray<int32> ToExpand = Children[Bone];
|
|
while (ToExpand.Num() > 0)
|
|
{
|
|
int32 ToProcess = ToExpand.Pop(EAllowShrinking::No);
|
|
if (TransformToConvexIndices[ToProcess].Num() > 0)
|
|
{
|
|
OutChildren.Add(ToProcess);
|
|
}
|
|
else if (Children[ToProcess].Num() > 0)
|
|
{
|
|
ToExpand.Append(Children[ToProcess]);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Use initial leaf proximity to compute which cluster bones are in proximity to which neighbors at the same level of the bone hierarchy
|
|
|
|
TArray<TSet<int32>> SameDepthClusterProximity;
|
|
if (bRemoveOverlaps)
|
|
{
|
|
SameDepthClusterProximity.SetNum(NumBones);
|
|
for (int32 Bone = 0; Bone < NumBones; Bone++)
|
|
{
|
|
for (int32 NbrBone : LeafProximity[Bone])
|
|
{
|
|
if (Bone > NbrBone)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto FindCommonParent = [&Parents](int32 BoneA, int32 BoneB)
|
|
{
|
|
int AParent = Parents[BoneA];
|
|
int BParent = Parents[BoneB];
|
|
if (AParent == BParent) // early out if they're in the same cluster
|
|
{
|
|
return AParent;
|
|
}
|
|
|
|
TSet<int32> AParents;
|
|
while (AParent != INDEX_NONE)
|
|
{
|
|
AParents.Add(AParent);
|
|
AParent = Parents[AParent];
|
|
}
|
|
|
|
while (BParent != INDEX_NONE && !AParents.Contains(BParent))
|
|
{
|
|
BParent = Parents[BParent];
|
|
}
|
|
return BParent;
|
|
};
|
|
int32 CommonParent = FindCommonParent(Bone, NbrBone);
|
|
|
|
auto ConnectAtMatchingDepth = [&SameDepthClusterProximity, &Parents, CommonParent, &Depths](int32 BoneA, int32 BoneToWalkParents)
|
|
{
|
|
if (BoneA == INDEX_NONE || BoneToWalkParents == INDEX_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 DepthA = Depths[BoneA];
|
|
int32 DepthB = Depths[BoneToWalkParents];
|
|
while (BoneToWalkParents != INDEX_NONE && BoneToWalkParents != CommonParent && DepthB >= DepthA)
|
|
{
|
|
if (DepthB == DepthA)
|
|
{
|
|
bool bWasInSet = false;
|
|
SameDepthClusterProximity[BoneToWalkParents].Add(BoneA, &bWasInSet);
|
|
if (bWasInSet)
|
|
{
|
|
break;
|
|
}
|
|
SameDepthClusterProximity[BoneA].Add(BoneToWalkParents);
|
|
}
|
|
|
|
BoneToWalkParents = Parents[BoneToWalkParents];
|
|
DepthB--;
|
|
}
|
|
};
|
|
|
|
// connect bone to neighboring bone's clusters
|
|
ConnectAtMatchingDepth(Bone, Parents[NbrBone]);
|
|
|
|
// walk chain of parents of bone and connect each to the chain of neighboring bone + its parents
|
|
int32 BoneParent = Parents[Bone];
|
|
while (BoneParent != INDEX_NONE && BoneParent != CommonParent)
|
|
{
|
|
ConnectAtMatchingDepth(BoneParent, NbrBone);
|
|
BoneParent = Parents[BoneParent];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute initial hulls at all levels and filter out any that are too large relative to the geometry they contain
|
|
|
|
TArray<TSet<int32>> ClusterProximity;
|
|
if (bRemoveOverlaps)
|
|
{
|
|
ClusterProximity.SetNum(NumBones);
|
|
}
|
|
TArrayView<int32> DepthOrderView(ByDepthOrder);
|
|
|
|
for (int32 Idx = 0, SliceEndIdx = Idx + 1; Idx < ByDepthOrder.Num(); Idx = SliceEndIdx)
|
|
{
|
|
FCriticalSection ConvexesCS;
|
|
|
|
int32 ProcessDepth = Depths[ByDepthOrder[Idx]];
|
|
for (SliceEndIdx = Idx + 1; SliceEndIdx < ByDepthOrder.Num(); SliceEndIdx++)
|
|
{
|
|
if (Depths[ByDepthOrder[SliceEndIdx]] != ProcessDepth)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
TArrayView<int32> SameDepthSlice = DepthOrderView.Slice(Idx, SliceEndIdx - Idx);
|
|
|
|
ParallelFor(SameDepthSlice.Num(), [&](int32 SliceIdx)
|
|
{
|
|
int32 Bone = SameDepthSlice[SliceIdx];
|
|
checkSlow(Depths[Bone] == ProcessDepth);
|
|
if (!SkipBone(Bone))
|
|
{
|
|
if (TransformToConvexIndices[Bone].Num() == 0 && !HasCustomConvexFn(Bone))
|
|
{
|
|
TArray<Chaos::FConvex::FVec3Type> JoinedHullPts;
|
|
TArray<int32> ChildrenWithHulls;
|
|
AddDescendentsWithHulls(Bone, ChildrenWithHulls);
|
|
for (int32 Child : ChildrenWithHulls)
|
|
{
|
|
for (int32 ConvexIdx : TransformToConvexIndices[Child])
|
|
{
|
|
FVector Pivot;
|
|
ConvexesCS.Lock();
|
|
Chaos::FConvex* Convex = Convexes[ConvexIdx].GetReference();
|
|
Pivot = ConvexPivots[ConvexIdx];
|
|
ConvexesCS.Unlock();
|
|
if (ShrinkPercentage != 0.0)
|
|
{
|
|
AddUnscaledPts(JoinedHullPts, Convex->GetVertices(), Pivot, ShrinkPercentage);
|
|
}
|
|
else
|
|
{
|
|
JoinedHullPts.Append(Convex->GetVertices());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (JoinedHullPts.Num() > 0)
|
|
{
|
|
FilterHullPoints(JoinedHullPts, SimplificationDistanceThreshold);
|
|
FVector Pivot = ScaleHullPoints(JoinedHullPts, ShrinkPercentage);
|
|
Chaos::FConvex* Hull = new Chaos::FConvex(JoinedHullPts, UE_KINDA_SMALL_NUMBER);
|
|
Chaos::FConvexPtr HullPtr(Hull);
|
|
bool bIsTooBig = false;
|
|
if (Volume)
|
|
{
|
|
Chaos::FReal HullVolume = Hull->GetVolume();
|
|
if (HullVolume > Chaos::FReal((*Volume)[Bone]) * (1.0 + CanExceedFraction))
|
|
{
|
|
bIsTooBig = true;
|
|
}
|
|
}
|
|
if (!bIsTooBig)
|
|
{
|
|
ConvexesCS.Lock();
|
|
int32 ConvexIdx = Convexes.Add(MoveTemp(HullPtr));
|
|
AddPivot(Convexes, ConvexPivots, Pivot);
|
|
ConvexesCS.Unlock();
|
|
TransformToConvexIndices[Bone].Add(ConvexIdx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!bRemoveOverlaps)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Compute cluster proximity
|
|
for (int32 BoneA : SameDepthSlice)
|
|
{
|
|
if (SkipBone(BoneA) || TransformToConvexIndices[BoneA].Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
for (int32 BoneB : SameDepthClusterProximity[BoneA])
|
|
{
|
|
if (BoneB < BoneA && IsBoneColliding(BoneA, BoneB))
|
|
{
|
|
int32 Bones[2]{ BoneA, BoneB };
|
|
for (int32 BoneIdx = 0; BoneIdx < 2; BoneIdx++)
|
|
{
|
|
int32 ParentBone = Bones[BoneIdx];
|
|
int32 OtherBone = Bones[1 - BoneIdx];
|
|
TArray<int32> TraverseBones;
|
|
TraverseBones.Append(Children[ParentBone]);
|
|
while (TraverseBones.Num() > 0)
|
|
{
|
|
int32 ToProc = TraverseBones.Pop(EAllowShrinking::No);
|
|
if (IsBoneColliding(OtherBone, ToProc))
|
|
{
|
|
ClusterProximity[OtherBone].Add(ToProc);
|
|
ClusterProximity[ToProc].Add(OtherBone);
|
|
TraverseBones.Append(Children[ToProc]);
|
|
}
|
|
}
|
|
}
|
|
|
|
ClusterProximity[BoneA].Add(BoneB);
|
|
ClusterProximity[BoneB].Add(BoneA);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bRemoveOverlaps)
|
|
{
|
|
return; // rest of function is just for removing overlaps
|
|
}
|
|
|
|
|
|
// Compute all initial non-leaf hull volumes
|
|
|
|
TArray<double> NonLeafVolumes; // Original volumes of non-leaf hulls (to compare against progressively cut-down volume as intersections are removed)
|
|
NonLeafVolumes.SetNumZeroed(Convexes.Num());
|
|
ParallelFor(NumBones, [&](int32 Bone)
|
|
{
|
|
bool bCustom = HasCustomConvexFn(Bone);
|
|
if (!bCustom && // if we need an automatic hull
|
|
Children[Bone].Num() > 0 && !SkipBone(Bone) && // and we have children && aren't embedded geo
|
|
TransformToConvexIndices[Bone].Num() == 1 // and hull wasn't already ruled out by CanExceedFraction
|
|
)
|
|
{
|
|
int32 ConvexIdx = OnlyConvex(Bone);
|
|
checkSlow(ConvexIdx > -1); // safe to assume because the if-statement verified "TransformToConvexIndices[Bone].Num() == 1"
|
|
NonLeafVolumes[ConvexIdx] = Convexes[ConvexIdx]->GetVolume();
|
|
}
|
|
});
|
|
|
|
// if bOneSidedCut, then only ConvexA is cut; ConvexB is left unchanged
|
|
auto CutIfOk = [&Convexes, &NonLeafVolumes, &FindCutPlane, &FracAllowRemove, &SimplificationDistanceThreshold, &ScaleFactor](bool bOneSidedCut, int32 ConvexA, int32 ConvexB) -> bool
|
|
{
|
|
Chaos::FReal Depth;
|
|
Chaos::FVec3 CloseA, CloseB, Normal;
|
|
int32 OutIdxA, OutIdxB;
|
|
const Chaos::TRigidTransform<Chaos::FReal, 3> IdentityTransform = Chaos::TRigidTransform<Chaos::FReal, 3>::Identity;
|
|
bool bCollide = Chaos::GJKPenetration(*Convexes[ConvexA], *Convexes[ConvexB], IdentityTransform, Depth, CloseA, CloseB, Normal, OutIdxA, OutIdxB);
|
|
if (bCollide)
|
|
{
|
|
Chaos::FVec3 IdealCenter, IdealNormal;
|
|
FindCutPlane(*Convexes[ConvexA], *Convexes[ConvexB], CloseA, CloseB, Normal, bOneSidedCut, IdealCenter, IdealNormal);
|
|
FChaosPlane CutPlane(IdealCenter, IdealNormal);
|
|
|
|
// Tentatively create the clipped hulls
|
|
Chaos::FConvex CutHullA, CutHullB;
|
|
bool bCreatedA = false, bCreatedB = false;
|
|
TArray<Chaos::FConvex::FVec3Type> CutHullPts;
|
|
if (CutHull(*Convexes[ConvexA], CutPlane, true, CutHullPts))
|
|
{
|
|
if (CutHullPts.Num() < 4) // immediate reject zero-volume results
|
|
{
|
|
return false;
|
|
}
|
|
CutHullA = MakeHull(CutHullPts, SimplificationDistanceThreshold * ScaleFactor);
|
|
bCreatedA = true;
|
|
}
|
|
if (!bOneSidedCut)
|
|
{
|
|
CutHullPts.Reset();
|
|
if (CutHull(*Convexes[ConvexB], CutPlane, false, CutHullPts))
|
|
{
|
|
CutHullB = MakeHull(CutHullPts, SimplificationDistanceThreshold * ScaleFactor);
|
|
bCreatedB = true;
|
|
}
|
|
}
|
|
|
|
// Test if the clipped hulls have become too small vs the original volumes
|
|
if (ensure(ConvexA < NonLeafVolumes.Num()) && NonLeafVolumes[ConvexA] > 0 && bCreatedA && CutHullA.GetVolume() / NonLeafVolumes[ConvexA] < 1 - FracAllowRemove)
|
|
{
|
|
return false;
|
|
}
|
|
if (!bOneSidedCut)
|
|
{
|
|
if (ensure(ConvexB < NonLeafVolumes.Num()) && NonLeafVolumes[ConvexB] > 0 && bCreatedB && CutHullB.GetVolume() / NonLeafVolumes[ConvexB] < 1 - FracAllowRemove)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If the clipped hulls were large enough, go ahead and set them as the new hulls
|
|
if (bCreatedA)
|
|
{
|
|
*Convexes[ConvexA] = MoveTemp(CutHullA);
|
|
}
|
|
if (bCreatedB)
|
|
{
|
|
*Convexes[ConvexB] = MoveTemp(CutHullB);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return true; // no cut needed, so was ok
|
|
}
|
|
};
|
|
|
|
// re-process all non-leaf bones
|
|
for (int32 Idx = 0, SliceEndIdx = Idx + 1; Idx < DepthOrderView.Num(); Idx = SliceEndIdx)
|
|
{
|
|
int32 ProcessDepth = Depths[DepthOrderView[Idx]];
|
|
for (SliceEndIdx = Idx + 1; SliceEndIdx < DepthOrderView.Num(); SliceEndIdx++)
|
|
{
|
|
if (Depths[DepthOrderView[SliceEndIdx]] != ProcessDepth)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
TArrayView<int32> SameDepthSlice = DepthOrderView.Slice(Idx, SliceEndIdx - Idx);
|
|
|
|
TSet<int32> WasNotOk;
|
|
|
|
for (int32 Bone : SameDepthSlice)
|
|
{
|
|
bool bCustom = HasCustomConvexFn(Bone);
|
|
if (bCustom || Children[Bone].Num() == 0 || TransformToConvexIndices[Bone].Num() == 0 || WasNotOk.Contains(Bone))
|
|
{
|
|
continue;
|
|
}
|
|
for (int32 Nbr : ClusterProximity[Bone])
|
|
{
|
|
if (WasNotOk.Contains(Nbr) || TransformToConvexIndices[Nbr].Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
bool bNbrCustom = HasCustomConvexFn(Nbr);
|
|
|
|
// if the neighbor is less deep and not a leaf, skip processing this to favor processing the neighbor instead
|
|
if (Depths[Bone] > Depths[Nbr] && Children[Nbr].Num() > 0)
|
|
{
|
|
continue;
|
|
}
|
|
// If we only consider cluster-vs-cluster overlap, and the neighbor is a leaf, do not consider it
|
|
if (OverlapRemovalMethod == EConvexOverlapRemoval::OnlyClustersVsClusters && Children[Nbr].Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool bAllOk = true;
|
|
for (int32 ConvexBone : TransformToConvexIndices[Bone])
|
|
{
|
|
for (int32 ConvexNbr : TransformToConvexIndices[Nbr])
|
|
{
|
|
bool bOneSidedCut = Depths[Bone] != Depths[Nbr] || Children[Nbr].Num() == 0 || bNbrCustom;
|
|
|
|
bool bWasOk = CutIfOk(bOneSidedCut, ConvexBone, ConvexNbr);
|
|
|
|
// cut would have removed too much; just fall back to using the hulls of children
|
|
// TODO: attempt splitting hulls before fully falling back to this
|
|
if (!bWasOk)
|
|
{
|
|
bAllOk = false;
|
|
auto ResetBoneHulls = [&WasNotOk, &TransformToConvexIndices, &Convexes](int32 ToReset)
|
|
{
|
|
WasNotOk.Add(ToReset);
|
|
for (int32 ConvexIdx : TransformToConvexIndices[ToReset])
|
|
{
|
|
// we just leave these hulls in as null, without any references here
|
|
// and come through and clear them later after everything is set up in the geometry collection
|
|
// (because that has the built-in machinery to update the TransformToConvexIndices indices accordingly)
|
|
Convexes[ConvexIdx].SafeRelease();
|
|
}
|
|
TransformToConvexIndices[ToReset].Reset();
|
|
};
|
|
ResetBoneHulls(Bone);
|
|
if (!bOneSidedCut)
|
|
{
|
|
ResetBoneHulls(Nbr);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!bAllOk)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
// helper to compute the volume of an individual piece of geometry
|
|
double ComputeGeometryVolume(
|
|
const FManagedArrayCollection* Collection,
|
|
int32 GeometryIdx,
|
|
const FTransform& GlobalTransform,
|
|
double ScalePerDimension
|
|
)
|
|
{
|
|
GeometryCollection::Facades::FCollectionMeshFacade MeshFacade(*Collection);
|
|
const TManagedArray<int32>& VertexStart = MeshFacade.VertexStartAttribute.Get();
|
|
const TManagedArray<int32>& VertexCount = MeshFacade.VertexCountAttribute.Get();
|
|
const TManagedArray<FVector3f>& Vertex = MeshFacade.VertexAttribute.Get();
|
|
const TManagedArray<int32>& FaceStart = MeshFacade.FaceStartAttribute.Get();
|
|
const TManagedArray<int32>& FaceCount = MeshFacade.FaceCountAttribute.Get();
|
|
const TManagedArray<FIntVector>& Indices = MeshFacade.IndicesAttribute.Get();
|
|
int32 VStart = VertexStart[GeometryIdx];
|
|
int32 VEnd = VStart + VertexCount[GeometryIdx];
|
|
if (VStart == VEnd)
|
|
{
|
|
return 0.0;
|
|
}
|
|
FVector3d Center = FVector::ZeroVector;
|
|
for (int32 VIdx = VStart; VIdx < VEnd; VIdx++)
|
|
{
|
|
FVector Pos = GlobalTransform.TransformPosition((FVector)Vertex[VIdx]);
|
|
Center += (FVector3d)Pos;
|
|
}
|
|
Center /= double(VEnd - VStart);
|
|
int32 FStart = FaceStart[GeometryIdx];
|
|
int32 FEnd = FStart + FaceCount[GeometryIdx];
|
|
double VolOut = 0;
|
|
for (int32 FIdx = FStart; FIdx < FEnd; FIdx++)
|
|
{
|
|
FIntVector Tri = Indices[FIdx];
|
|
FVector3d V0 = (FVector3d)GlobalTransform.TransformPosition((FVector)Vertex[Tri.X]);
|
|
FVector3d V1 = (FVector3d)GlobalTransform.TransformPosition((FVector)Vertex[Tri.Y]);
|
|
FVector3d V2 = (FVector3d)GlobalTransform.TransformPosition((FVector)Vertex[Tri.Z]);
|
|
|
|
// add volume of the tetrahedron formed by the triangles and the reference point
|
|
FVector3d V1mRef = (V1 - Center) * ScalePerDimension;
|
|
FVector3d V2mRef = (V2 - Center) * ScalePerDimension;
|
|
FVector3d N = V2mRef.Cross(V1mRef);
|
|
|
|
VolOut += ((V0 - Center) * ScalePerDimension).Dot(N) / 6.0;
|
|
}
|
|
return VolOut;
|
|
}
|
|
|
|
// Helper to append the triangles of a convex hull to a dynamic mesh
|
|
static void AddConvexHullToCompactDynamicMesh(const ::Chaos::FConvex* InConvexHull, UE::Geometry::FDynamicMesh3& Mesh, const FTransform* OptionalTransform = nullptr, bool bInvertFaces = true)
|
|
{
|
|
check(Mesh.IsCompact());
|
|
|
|
const ::Chaos::FConvexStructureData& ConvexStructure = InConvexHull->GetStructureData();
|
|
const int32 NumV = InConvexHull->NumVertices();
|
|
const int32 NumP = InConvexHull->NumPlanes();
|
|
int32 StartV = Mesh.MaxVertexID();
|
|
for (int32 VIdx = 0; VIdx < NumV; ++VIdx)
|
|
{
|
|
FVector3d V = (FVector3d)InConvexHull->GetVertex(VIdx);
|
|
if (OptionalTransform)
|
|
{
|
|
V = OptionalTransform->TransformPosition(V);
|
|
}
|
|
int32 MeshVIdx = Mesh.AppendVertex(V);
|
|
checkSlow(MeshVIdx == VIdx + StartV); // Must be true because the mesh is compact
|
|
}
|
|
for (int32 PIdx = 0; PIdx < NumP; ++PIdx)
|
|
{
|
|
const int32 NumFaceV = ConvexStructure.NumPlaneVertices(PIdx);
|
|
const int32 V0 = StartV + ConvexStructure.GetPlaneVertex(PIdx, 0);
|
|
for (int32 SubIdx = 1; SubIdx + 1 < NumFaceV; ++SubIdx)
|
|
{
|
|
int32 V1 = StartV + ConvexStructure.GetPlaneVertex(PIdx, SubIdx);
|
|
int32 V2 = StartV + ConvexStructure.GetPlaneVertex(PIdx, SubIdx + 1);
|
|
if (bInvertFaces)
|
|
{
|
|
Swap(V1, V2);
|
|
}
|
|
int32 ResultTID = Mesh.AppendTriangle(UE::Geometry::FIndex3i(V0, V1, V2));
|
|
constexpr bool bFixNonmanifoldWithDuplicates = true;
|
|
if (bFixNonmanifoldWithDuplicates && ResultTID == UE::Geometry::FDynamicMesh3::NonManifoldID)
|
|
{
|
|
// failed to append due to a non-manifold triangle; try adding all the vertices independently so we at least capture the shape
|
|
// note: this should not happen for normal convex hulls, but the current convex hull algorithm does some aggressive face merging that sometimes creates weird geometry
|
|
UE::Geometry::FIndex3i DuplicateVerts(
|
|
Mesh.AppendVertex(Mesh.GetVertex(V0)),
|
|
Mesh.AppendVertex(Mesh.GetVertex(V1)),
|
|
Mesh.AppendVertex(Mesh.GetVertex(V2))
|
|
);
|
|
Mesh.AppendTriangle(DuplicateVerts);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper to convert a geometry collection's geometry to a dynamic mesh
|
|
static UE::Geometry::FDynamicMesh3 GeometryToDynamicMesh(const FGeometryCollection& Geometry, int32 GeomIdx, FTransform* OptionalTransform)
|
|
{
|
|
UE::Geometry::FDynamicMesh3 Mesh;
|
|
|
|
int32 VStart = Geometry.VertexStart[GeomIdx];
|
|
int32 VCount = Geometry.VertexCount[GeomIdx];
|
|
int32 VEnd = VStart + VCount;
|
|
|
|
for (int32 Idx = VStart; Idx < VEnd; ++Idx)
|
|
{
|
|
FVector3d V = (FVector3d)Geometry.Vertex[Idx];
|
|
if (OptionalTransform)
|
|
{
|
|
V = OptionalTransform->TransformPosition(V);
|
|
}
|
|
Mesh.AppendVertex(V);
|
|
}
|
|
|
|
int32 FStart = Geometry.FaceStart[GeomIdx];
|
|
int32 FCount = Geometry.FaceCount[GeomIdx];
|
|
int32 FEnd = FStart + FCount;
|
|
for (int32 Idx = FStart; Idx < FEnd; ++Idx)
|
|
{
|
|
FIntVector Face = Geometry.Indices[Idx];
|
|
Mesh.AppendTriangle(Face.X - VStart, Face.Y - VStart, Face.Z - VStart);
|
|
}
|
|
|
|
return Mesh;
|
|
}
|
|
|
|
/// Helper to get convex hulls from a geometry collection in the format required by CreateNonoverlappingConvexHulls
|
|
void HullsFromGeometry(
|
|
FGeometryCollection& Geometry,
|
|
const TArray<FTransform>& GlobalTransformArray,
|
|
TFunctionRef<bool(int32)> HasCustomConvexFn,
|
|
TArray<Chaos::FConvexPtr>& Convexes,
|
|
TArray<FVector>& ConvexPivots,
|
|
TArray<TSet<int32>>& TransformToConvexIndices,
|
|
const TManagedArray<int32>& SimulationType,
|
|
int32 RigidType,
|
|
double SimplificationDistanceThreshold,
|
|
double OverlapRemovalShrinkPercent,
|
|
TFunction<bool(int32)> SkipBoneFn = nullptr,
|
|
const FGeometryCollectionConvexUtility::FConvexDecompositionSettings* OptionalDecompositionSettings = nullptr,
|
|
const TArray<FGeometryCollectionConvexUtility::FTransformedConvex>* OptionalIntersectConvexHulls = nullptr,
|
|
const TArray<TSet<int32>>* OptionalTransformToIntersectHulls = nullptr,
|
|
TArray<FGeometryCollectionConvexUtility::FSphereCoveringInfo>* OutComputedNavigableSpheres = nullptr
|
|
)
|
|
{
|
|
TArray<FVector> GlobalVertices;
|
|
|
|
TOptional<FGeometryCollectionConvexUtility::FGeometryCollectionConvexData> OrigConvexData = FGeometryCollectionConvexUtility::GetConvexHullDataIfPresent(&Geometry);
|
|
|
|
GlobalVertices.SetNum(Geometry.Vertex.Num());
|
|
for (int32 Idx = 0; Idx < GlobalVertices.Num(); Idx++)
|
|
{
|
|
GlobalVertices[Idx] = GlobalTransformArray[Geometry.BoneMap[Idx]].TransformPosition(FVector(Geometry.Vertex[Idx]));
|
|
}
|
|
|
|
double ScaleFactor = 1 - OverlapRemovalShrinkPercent / 100.0;
|
|
|
|
int32 NumBones = Geometry.TransformToGeometryIndex.Num();
|
|
TransformToConvexIndices.SetNum(NumBones);
|
|
FCriticalSection HullCS;
|
|
FCriticalSection NavSpheresCS;
|
|
ParallelFor(NumBones, [&](int32 Idx)
|
|
{
|
|
if (SkipBoneFn && !SkipBoneFn(Idx))
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 GeomIdx = Geometry.TransformToGeometryIndex[Idx];
|
|
if (OrigConvexData.IsSet() && HasCustomConvexFn(Idx))
|
|
{
|
|
// custom convex hulls are kept, but transformed to global space + copied to the new data structure
|
|
const FTransform& Transform = GlobalTransformArray[Idx];
|
|
for (int32 OrigConvexIdx : OrigConvexData->TransformToConvexIndices[Idx])
|
|
{
|
|
TArray<Chaos::FConvex::FVec3Type> HullPts;
|
|
// As we already have a hull, use the center of mass as the pivot
|
|
FVector COM = (FVector)OrigConvexData->ConvexHull[OrigConvexIdx]->GetCenterOfMass();
|
|
HullPts.Reserve(OrigConvexData->ConvexHull[OrigConvexIdx]->NumVertices());
|
|
for (const Chaos::FConvex::FVec3Type& P : OrigConvexData->ConvexHull[OrigConvexIdx]->GetVertices())
|
|
{
|
|
HullPts.Add(Chaos::FConvex::FVec3Type(Transform.TransformPosition( // transform to the global space
|
|
(FVector(P) - COM) * ScaleFactor + COM // scale in the original coordinate frame
|
|
)));
|
|
}
|
|
// Do not simplify hulls when we're just trying to transform them
|
|
Chaos::FConvexPtr Hull( new Chaos::FConvex(HullPts, UE_KINDA_SMALL_NUMBER));
|
|
HullCS.Lock();
|
|
int32 NewConvexIdx = Convexes.Add(MoveTemp(Hull));
|
|
AddPivot(Convexes, ConvexPivots, COM);
|
|
HullCS.Unlock();
|
|
TransformToConvexIndices[Idx].Add(NewConvexIdx);
|
|
}
|
|
}
|
|
else if (SimulationType[Idx] == RigidType && GeomIdx != INDEX_NONE)
|
|
{
|
|
// If external intersection is requested, do the merge + intersection for the given bone here so it can be used by ComputeHull and/or the decomp
|
|
bool bHasExternal = OptionalIntersectConvexHulls && OptionalTransformToIntersectHulls;
|
|
TUniquePtr<UE::Geometry::FDynamicMesh3> GeometryIntersectedWithExternalHull = nullptr;
|
|
if (bHasExternal)
|
|
{
|
|
const TSet<int32>& HullInds = (*OptionalTransformToIntersectHulls)[Idx];
|
|
UE::Geometry::FDynamicMesh3 MergedHullMesh;
|
|
for (int32 HullIdx : HullInds)
|
|
{
|
|
// Append transformed external hull mesh
|
|
AddConvexHullToCompactDynamicMesh((*OptionalIntersectConvexHulls)[HullIdx].Convex.GetReference(), MergedHullMesh, &(*OptionalIntersectConvexHulls)[HullIdx].Transform);
|
|
}
|
|
if (HullInds.Num() > 1)
|
|
{
|
|
// Self-union the merged hull mesh -- mainly needed to help ensure the intersection volume accuracy
|
|
// i.e., in the (bAttemptDecomposition && OptionalDecompositionSettings->MaxGeoToHullVolumeRatioToDecompose < 1.0) case below
|
|
UE::Geometry::FMeshSelfUnion Union(&MergedHullMesh);
|
|
Union.Compute();
|
|
}
|
|
if (MergedHullMesh.TriangleCount() > 0)
|
|
{
|
|
UE::Geometry::FDynamicMesh3 GeometryMesh = GeometryToDynamicMesh(Geometry, GeomIdx, nullptr);
|
|
UE::Geometry::FMeshBoolean Boolean(&GeometryMesh, GlobalTransformArray[Idx], &MergedHullMesh, GlobalTransformArray[Idx],
|
|
&GeometryMesh, UE::Geometry::FMeshBoolean::EBooleanOp::Intersect);
|
|
|
|
Boolean.Compute();
|
|
if (GeometryMesh.VertexCount() > 0)
|
|
{
|
|
GeometryIntersectedWithExternalHull = MakeUnique<UE::Geometry::FDynamicMesh3>();
|
|
*GeometryIntersectedWithExternalHull = MoveTemp(GeometryMesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto ComputeHull = [&Geometry, &GlobalVertices, SimplificationDistanceThreshold, OverlapRemovalShrinkPercent, GeomIdx,
|
|
&GeometryIntersectedWithExternalHull](FVector& PivotOut) -> ::Chaos::FConvexPtr
|
|
{
|
|
TArray<Chaos::FConvex::FVec3Type> HullPts;
|
|
if (GeometryIntersectedWithExternalHull)
|
|
{
|
|
HullPts.Reserve(GeometryIntersectedWithExternalHull->VertexCount());
|
|
for (FVector3d Vertex : GeometryIntersectedWithExternalHull->VerticesItr())
|
|
{
|
|
HullPts.Add((Chaos::FConvex::FVec3Type)Vertex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 VStart = Geometry.VertexStart[GeomIdx];
|
|
int32 VCount = Geometry.VertexCount[GeomIdx];
|
|
int32 VEnd = VStart + VCount;
|
|
|
|
HullPts.Reserve(VCount);
|
|
for (int32 VIdx = VStart; VIdx < VEnd; VIdx++)
|
|
{
|
|
HullPts.Add(GlobalVertices[VIdx]);
|
|
}
|
|
}
|
|
ensure(HullPts.Num() > 0);
|
|
FilterHullPoints(HullPts, SimplificationDistanceThreshold);
|
|
PivotOut = ScaleHullPoints(HullPts, OverlapRemovalShrinkPercent);
|
|
return Chaos::FConvexPtr(new Chaos::FConvex(HullPts, UE_KINDA_SMALL_NUMBER));
|
|
};
|
|
Chaos::FConvexPtr Hull = nullptr;
|
|
FVector HullPivot = FVector::ZeroVector;
|
|
|
|
if (OptionalDecompositionSettings)
|
|
{
|
|
bool bAttemptDecomposition = OptionalDecompositionSettings->MaxHullsPerGeometry > 1 || OptionalDecompositionSettings->ErrorTolerance > 0.0 || OptionalDecompositionSettings->bProtectNegativeSpace;
|
|
double InitialGeoVolume = 0.0, InitialHullVolume = 0.0;
|
|
if (bAttemptDecomposition && (OptionalDecompositionSettings->MinGeoVolumeToDecompose > 0.0 || OptionalDecompositionSettings->MaxGeoToHullVolumeRatioToDecompose < 1.0))
|
|
{
|
|
if (GeometryIntersectedWithExternalHull)
|
|
{
|
|
InitialGeoVolume = UE::Geometry::TMeshQueries<UE::Geometry::FDynamicMesh3>::GetVolumeNonWatertight(*GeometryIntersectedWithExternalHull);
|
|
}
|
|
else
|
|
{
|
|
InitialGeoVolume = ComputeGeometryVolume(&Geometry, GeomIdx, GlobalTransformArray[Idx], 1.0);
|
|
}
|
|
}
|
|
if (bAttemptDecomposition && InitialGeoVolume < OptionalDecompositionSettings->MinGeoVolumeToDecompose)
|
|
{
|
|
bAttemptDecomposition = false; // too small to consider for decomposition
|
|
}
|
|
if (bAttemptDecomposition && OptionalDecompositionSettings->MaxGeoToHullVolumeRatioToDecompose < 1.0)
|
|
{
|
|
Hull = ComputeHull(HullPivot);
|
|
InitialHullVolume = (double)Hull->GetVolume();
|
|
// Hull was scaled down by the overlap removal shrink percent, so to compute a correct ratio we need to adjust the geo volume in the same way
|
|
double VolumeScale = ScaleFactor * ScaleFactor * ScaleFactor;
|
|
if ((VolumeScale * InitialGeoVolume) / InitialHullVolume >= OptionalDecompositionSettings->MaxGeoToHullVolumeRatioToDecompose)
|
|
{
|
|
bAttemptDecomposition = false;
|
|
}
|
|
}
|
|
|
|
if (bAttemptDecomposition)
|
|
{
|
|
UE::Geometry::FConvexDecomposition3 Decomposition;
|
|
UE::Geometry::FConvexDecomposition3::FPreprocessMeshOptions PreprocessOptions;
|
|
PreprocessOptions.bMergeEdges = true;
|
|
PreprocessOptions.ThickenInputAfterHullFailure = OptionalDecompositionSettings->ThickenHullOnFailure;
|
|
|
|
if (GeometryIntersectedWithExternalHull)
|
|
{
|
|
Decomposition.InitializeFromMesh(*GeometryIntersectedWithExternalHull, PreprocessOptions);
|
|
}
|
|
else
|
|
{
|
|
int32 VertexStart = Geometry.VertexStart[GeomIdx];
|
|
TArrayView<const FVector3f> VerticesView(Geometry.Vertex.GetData() + VertexStart, Geometry.VertexCount[GeomIdx]);
|
|
TArrayView<const FIntVector3> FacesView(Geometry.Indices.GetData() + Geometry.FaceStart[GeomIdx], Geometry.FaceCount[GeomIdx]);
|
|
Decomposition.InitializeFromIndexMesh(VerticesView, FacesView, PreprocessOptions, -VertexStart);
|
|
}
|
|
|
|
const bool bIsSolid = Decomposition.IsInputSolid();
|
|
Decomposition.bTreatAsSolid = bIsSolid;
|
|
|
|
if (OptionalDecompositionSettings->bProtectNegativeSpace)
|
|
{
|
|
// Use settings tuned for navigation-driven decomposition
|
|
|
|
UE::Geometry::FNegativeSpaceSampleSettings Settings;
|
|
Settings.ApplyDefaults();
|
|
Settings.bOnlyConnectedToHull = OptionalDecompositionSettings->bOnlyConnectedToHull;
|
|
Settings.MinRadius = OptionalDecompositionSettings->NegativeSpaceMinRadius;
|
|
Settings.ReduceRadiusMargin = OptionalDecompositionSettings->NegativeSpaceTolerance;
|
|
Settings.bRequireSearchSampleCoverage = true;
|
|
Settings.bAllowSamplesInsideMesh = !bIsSolid;
|
|
|
|
Decomposition.MaxConvexEdgePlanes = 4;
|
|
Decomposition.bSplitDisconnectedComponents = false;
|
|
Decomposition.ConvexEdgeAngleMoreSamplesThreshold = 180;
|
|
Decomposition.ThickenAfterHullFailure = PreprocessOptions.ThickenInputAfterHullFailure;
|
|
|
|
Decomposition.InitializeNegativeSpace(Settings);
|
|
if (OutComputedNavigableSpheres)
|
|
{
|
|
FGeometryCollectionConvexUtility::FSphereCoveringInfo SphereInfo
|
|
{
|
|
.Transform = GlobalTransformArray[Idx],
|
|
.SourceBoneIndex = Idx,
|
|
.SphereCovering = Decomposition.GetNegativeSpace()
|
|
};
|
|
SphereInfo.SphereCovering.ApplyTransform(Decomposition.ResultTransform);
|
|
NavSpheresCS.Lock();
|
|
OutComputedNavigableSpheres->Add(MoveTemp(SphereInfo));
|
|
NavSpheresCS.Unlock();
|
|
}
|
|
|
|
constexpr int32 MaxAllowedSplits = 1000000; // more parts than any expected / reasonable decomposition
|
|
int32 TargetNumSplits = OptionalDecompositionSettings->MaxHullsPerGeometry > 0 ? OptionalDecompositionSettings->MaxHullsPerGeometry - 1 : MaxAllowedSplits + 1;
|
|
for (int32 Split = 0; Split < TargetNumSplits; Split++)
|
|
{
|
|
int32 NumSplit = Decomposition.SplitWorst(false, -1, true, Settings.ReduceRadiusMargin * .5);
|
|
|
|
if (NumSplit == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!ensureMsgf(Split < MaxAllowedSplits, TEXT("Convex decomposition split the input %d times; likely stuck in a loop"), Split))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Decomposition.FixHullOverlapsInNegativeSpace();
|
|
int32 TargetNumPieces = OptionalDecompositionSettings->MaxHullsPerGeometry > 0 ? OptionalDecompositionSettings->MaxHullsPerGeometry : -1;
|
|
Decomposition.MergeBest(TargetNumPieces, 0, OptionalDecompositionSettings->MinThicknessTolerance, true);
|
|
}
|
|
else
|
|
{
|
|
Decomposition.Compute(OptionalDecompositionSettings->MaxHullsPerGeometry, OptionalDecompositionSettings->NumAdditionalSplits,
|
|
OptionalDecompositionSettings->ErrorTolerance, OptionalDecompositionSettings->MinThicknessTolerance, OptionalDecompositionSettings->MaxHullsPerGeometry);
|
|
}
|
|
|
|
int32 NumHulls = Decomposition.NumHulls();
|
|
if ((NumHulls > 0 && !Hull) || NumHulls > 1)
|
|
{
|
|
TArray<Chaos::FConvexPtr> DecompHulls; DecompHulls.Reserve(NumHulls);
|
|
TArray<FVector> Pivots; Pivots.Reserve(NumHulls);
|
|
for (int32 HullIdx = 0; HullIdx < NumHulls; ++HullIdx)
|
|
{
|
|
TArray<FVector3f> OrigDecompVerts = Decomposition.GetVertices<float>(HullIdx);
|
|
// convert to the vector type expected by Chaos::FConvex
|
|
TArray<::Chaos::FConvex::FVec3Type> DecompVerts;
|
|
DecompVerts.SetNumUninitialized(OrigDecompVerts.Num());
|
|
for (int32 PtIdx = 0; PtIdx < OrigDecompVerts.Num(); ++PtIdx)
|
|
{
|
|
DecompVerts[PtIdx] = OrigDecompVerts[PtIdx];
|
|
}
|
|
FilterHullPoints(DecompVerts, SimplificationDistanceThreshold);
|
|
FVector Pivot = ScaleHullPoints(DecompVerts, OverlapRemovalShrinkPercent);
|
|
Pivots.Add(Pivot);
|
|
DecompHulls.Add(Chaos::FConvexPtr(new Chaos::FConvex(DecompVerts, UE_KINDA_SMALL_NUMBER)));
|
|
}
|
|
HullCS.Lock();
|
|
for (int32 HullIdx = 0; HullIdx < DecompHulls.Num(); ++HullIdx)
|
|
{
|
|
int32 ConvexIdx = Convexes.Add(MoveTemp(DecompHulls[HullIdx]));
|
|
AddPivot(Convexes, ConvexPivots, Pivots[HullIdx]);
|
|
TransformToConvexIndices[Idx].Add(ConvexIdx);
|
|
}
|
|
HullCS.Unlock();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// We don't need a convex decomposition
|
|
if (!Hull)
|
|
{
|
|
Hull = ComputeHull(HullPivot);
|
|
}
|
|
HullCS.Lock();
|
|
int32 ConvexIdx = Convexes.Add(MoveTemp(Hull));
|
|
AddPivot(Convexes, ConvexPivots, HullPivot);
|
|
HullCS.Unlock();
|
|
TransformToConvexIndices[Idx].Add(ConvexIdx);
|
|
}
|
|
});
|
|
}
|
|
|
|
void TransformHullsToLocal(
|
|
TArray<FTransform>& GlobalTransformArray,
|
|
TArray<Chaos::FConvexPtr>& Convexes,
|
|
TArray<FVector>& ConvexPivots,
|
|
TArray<TSet<int32>>& TransformToConvexIndices,
|
|
double OverlapRemovalShrinkPercent
|
|
)
|
|
{
|
|
checkSlow((OverlapRemovalShrinkPercent == 0.0 && ConvexPivots.Num() == 0) || Convexes.Num() == ConvexPivots.Num());
|
|
|
|
double ScaleFactor = 1.0 - OverlapRemovalShrinkPercent / 100.0;
|
|
double InvScaleFactor = 1.0;
|
|
if (ensure(ScaleFactor != 0.0))
|
|
{
|
|
InvScaleFactor = 1.0 / ScaleFactor;
|
|
}
|
|
|
|
ParallelFor(TransformToConvexIndices.Num(), [&](int32 Bone)
|
|
{
|
|
FTransform& Transform = GlobalTransformArray[Bone];
|
|
TArray<Chaos::FConvex::FVec3Type> HullPts;
|
|
for (int32 ConvexIdx : TransformToConvexIndices[Bone])
|
|
{
|
|
HullPts.Reset();
|
|
for (const Chaos::FConvex::FVec3Type& P : Convexes[ConvexIdx]->GetVertices())
|
|
{
|
|
FVector PVec(P);
|
|
if (OverlapRemovalShrinkPercent != 0.0)
|
|
{
|
|
|
|
PVec = (PVec - ConvexPivots[ConvexIdx]) * InvScaleFactor + ConvexPivots[ConvexIdx];
|
|
}
|
|
HullPts.Add(FVector(Transform.InverseTransformPosition(PVec)));
|
|
}
|
|
// Do not simplify hulls when we're just trying to transform them
|
|
*Convexes[ConvexIdx] = Chaos::FConvex(HullPts, UE_KINDA_SMALL_NUMBER);
|
|
}
|
|
});
|
|
}
|
|
|
|
// copy hulls from InBone to OutBone
|
|
// Store the results in a temporary array, to be added in bulk later
|
|
bool CopyHulls(
|
|
const TArray<FTransform>& InGlobalTransformArray,
|
|
const TManagedArray<Chaos::FConvexPtr>& InConvexes,
|
|
const TManagedArray<TSet<int32>>& InTransformToConvexIndices,
|
|
int32 InBone,
|
|
const TArray<FTransform>& OutGlobalTransformArray,
|
|
TArray<Chaos::FConvexPtr>& OutConvexes,
|
|
TArray<TSet<int32>>& OutTransformToConvexIndices,
|
|
int32 OutBone
|
|
)
|
|
{
|
|
const FTransform& InTransform = InGlobalTransformArray[InBone];
|
|
const FTransform& OutTransform = InGlobalTransformArray[OutBone];
|
|
TArray<Chaos::FConvex::FVec3Type> HullPts;
|
|
for (int32 ConvexIdx : InTransformToConvexIndices[InBone])
|
|
{
|
|
HullPts.Reset();
|
|
for (const Chaos::FConvex::FVec3Type& P : InConvexes[ConvexIdx]->GetVertices())
|
|
{
|
|
HullPts.Add(OutTransform.InverseTransformPosition(InTransform.TransformPosition(FVector(P))));
|
|
}
|
|
// Do not simplify hulls when we're just trying to transform them
|
|
int32 OutIdx = OutConvexes.Add(Chaos::FConvexPtr(new Chaos::FConvex(HullPts, UE_KINDA_SMALL_NUMBER)));
|
|
OutTransformToConvexIndices[OutBone].Add(OutIdx);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// enum to pass to AddHullProximity's GetTransformBFn to indicate that all hulls use the same constant FTransform, so we can skip transforms and keep hulls in the shared space
|
|
enum class EUseConstantTransform
|
|
{
|
|
ConstantTransform
|
|
};
|
|
|
|
// Helper to add hull proximities for one hull to a set of neighboring hulls
|
|
template<typename GetOtherTransformFn, typename HullIndexMapContainer = TArray<int32>>
|
|
static void AddHullProximity(TArray<TPair<int32, int32>>& HullProximity, const EConvexHullProximityFilter ProximityFilter, const float ProximityDistanceThreshold,
|
|
TFunctionRef<const Chaos::FConvex* (int32)> GetConvex, const FTransform& TransformA, int32 ConvexA,
|
|
GetOtherTransformFn GetTransformBFn, int32 ConvexBIdxStart, int32 ConvexBIdxEnd, const HullIndexMapContainer* ConvexBIdxMap = nullptr)
|
|
{
|
|
Chaos::FConvex::FAABB3Type BoxA;
|
|
if (ProximityFilter == EConvexHullProximityFilter::BoundingBox)
|
|
{
|
|
BoxA = GetConvex(ConvexA)->GetLocalBoundingBox();
|
|
if constexpr (!std::is_same_v<GetOtherTransformFn, EUseConstantTransform>)
|
|
{
|
|
BoxA = BoxA.TransformedAABB(TransformA);
|
|
BoxA.Thicken(ProximityDistanceThreshold);
|
|
}
|
|
else
|
|
{
|
|
float ThickenScaleFactor = 1.0f / FMath::Max(FMathf::ZeroTolerance, (float)TransformA.GetScale3D().GetAbsMax());
|
|
BoxA.Thicken(ProximityDistanceThreshold * ThickenScaleFactor);
|
|
}
|
|
}
|
|
for (int32 ConvexBIdx = ConvexBIdxStart; ConvexBIdx < ConvexBIdxEnd; ++ConvexBIdx)
|
|
{
|
|
const int32 ConvexB = ConvexBIdxMap ? (*ConvexBIdxMap)[ConvexBIdx] : ConvexBIdx;
|
|
if (ProximityFilter == EConvexHullProximityFilter::BoundingBox)
|
|
{
|
|
Chaos::FConvex::FAABB3Type BoxB = GetConvex(ConvexB)->GetLocalBoundingBox();
|
|
if constexpr (!std::is_same_v<GetOtherTransformFn, EUseConstantTransform>)
|
|
{
|
|
BoxB = BoxB.TransformedAABB(GetTransformBFn(ConvexB));
|
|
}
|
|
if (!BoxA.Intersects(BoxB))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
HullProximity.Emplace(ConvexA, ConvexB);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void FGeometryCollectionConvexUtility::CreateConvexHullAttributesIfNeeded(FManagedArrayCollection& Collection)
|
|
{
|
|
if (!Collection.HasGroup(FGeometryCollection::ConvexGroup))
|
|
{
|
|
Collection.AddGroup(FGeometryCollection::ConvexGroup);
|
|
}
|
|
|
|
if (!Collection.HasAttribute("TransformToConvexIndices", FTransformCollection::TransformGroup))
|
|
{
|
|
FManagedArrayCollection::FConstructionParameters ConvexDependency(FGeometryCollection::ConvexGroup);
|
|
Collection.AddAttribute<TSet<int32>>("TransformToConvexIndices", FTransformCollection::TransformGroup, ConvexDependency);
|
|
}
|
|
|
|
if (!Collection.HasAttribute(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup))
|
|
{
|
|
Collection.AddAttribute<Chaos::FConvexPtr>(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup);
|
|
}
|
|
}
|
|
|
|
UE::GeometryCollectionConvexUtility::FConvexHulls
|
|
FGeometryCollectionConvexUtility::ComputeLeafHulls(FGeometryCollection* GeometryCollection, const TArray<FTransform>& GlobalTransformArray, double SimplificationDistanceThreshold, double OverlapRemovalShrinkPercent,
|
|
TFunction<bool(int32)> SkipBoneFn, const FConvexDecompositionSettings* OptionalDecompositionSettings,
|
|
const TArray<FTransformedConvex>* OptionalIntersectConvexHulls,
|
|
const TArray<TSet<int32>>* OptionalTransformToIntersectHulls,
|
|
TArray<FGeometryCollectionConvexUtility::FSphereCoveringInfo>* OutComputedNavigableSpheres)
|
|
{
|
|
check(GeometryCollection);
|
|
|
|
|
|
UE::GeometryCollectionConvexUtility::FConvexHulls Hulls;
|
|
|
|
// Prevent ~100% shrink percentage, because it would not be reversible ...
|
|
// Note: Could alternatively just disable convex overlap removal in this case
|
|
Hulls.OverlapRemovalShrinkPercent = FMath::Min(99.9, OverlapRemovalShrinkPercent);
|
|
|
|
TManagedArray<int32>* CustomConvexFlags = GetCustomConvexFlags(GeometryCollection, false);
|
|
auto ConvexFlagsAlwaysFalse = [](int32) -> bool { return false; };
|
|
auto ConvexFlagsFromArray = [&CustomConvexFlags](int32 TransformIdx) -> bool { return (bool)(*CustomConvexFlags)[TransformIdx]; };
|
|
TFunctionRef<bool(int32)> HasCustomConvexFn = (CustomConvexFlags != nullptr) ? (TFunctionRef<bool(int32)>)ConvexFlagsFromArray : (TFunctionRef<bool(int32)>)ConvexFlagsAlwaysFalse;
|
|
|
|
HullsFromGeometry(*GeometryCollection, GlobalTransformArray, HasCustomConvexFn, Hulls.Hulls, Hulls.Pivots, Hulls.TransformToHullsIndices, GeometryCollection->SimulationType,
|
|
FGeometryCollection::ESimulationTypes::FST_Rigid, SimplificationDistanceThreshold, Hulls.OverlapRemovalShrinkPercent, SkipBoneFn, OptionalDecompositionSettings,
|
|
OptionalIntersectConvexHulls, OptionalTransformToIntersectHulls, OutComputedNavigableSpheres
|
|
);
|
|
|
|
return Hulls;
|
|
}
|
|
|
|
FGeometryCollectionConvexUtility::FGeometryCollectionConvexData FGeometryCollectionConvexUtility::CreateNonOverlappingConvexHullData(
|
|
FGeometryCollection* GeometryCollection, double FracAllowRemove, double SimplificationDistanceThreshold, double CanExceedFraction, EConvexOverlapRemoval OverlapRemovalMethod,
|
|
double OverlapRemovalShrinkPercentIn, UE::GeometryCollectionConvexUtility::FConvexHulls* PrecomputedLeafHulls)
|
|
{
|
|
check(GeometryCollection);
|
|
|
|
TManagedArray<int32>* CustomConvexFlags = GetCustomConvexFlags(GeometryCollection, false);
|
|
auto ConvexFlagsAlwaysFalse = [](int32) -> bool { return false; };
|
|
auto ConvexFlagsFromArray = [&CustomConvexFlags](int32 TransformIdx) -> bool { return (bool)(*CustomConvexFlags)[TransformIdx]; };
|
|
TFunctionRef<bool(int32)> HasCustomConvexFn = (CustomConvexFlags != nullptr) ? (TFunctionRef<bool(int32)>)ConvexFlagsFromArray : (TFunctionRef<bool(int32)>)ConvexFlagsAlwaysFalse;
|
|
|
|
TArray<FTransform> GlobalTransformArray;
|
|
GeometryCollectionAlgo::GlobalMatrices(GeometryCollection->Transform, GeometryCollection->Parent, GlobalTransformArray);
|
|
|
|
UE::GeometryCollectionConvexUtility::FConvexHulls* UseLeafHulls = PrecomputedLeafHulls;
|
|
UE::GeometryCollectionConvexUtility::FConvexHulls LocalLeafHullsStorage;
|
|
if (!UseLeafHulls)
|
|
{
|
|
LocalLeafHullsStorage = ComputeLeafHulls(GeometryCollection, GlobalTransformArray, SimplificationDistanceThreshold, OverlapRemovalShrinkPercentIn);
|
|
UseLeafHulls = &LocalLeafHullsStorage;
|
|
}
|
|
|
|
FGeometryCollectionProximityUtility ProximityUtility(GeometryCollection);
|
|
ProximityUtility.RequireProximity(UseLeafHulls);
|
|
SetVolumeAttributes(GeometryCollection);
|
|
|
|
const TManagedArray<TSet<int32>>* GCProximity = GeometryCollection->FindAttribute<TSet<int32>>("Proximity", FGeometryCollection::GeometryGroup);
|
|
const TManagedArray<float>* Volume = GeometryCollection->FindAttribute<float>("Volume", FGeometryCollection::TransformGroup);
|
|
|
|
CreateNonoverlappingConvexHulls(UseLeafHulls->Hulls, UseLeafHulls->Pivots, UseLeafHulls->TransformToHullsIndices, HasCustomConvexFn, GeometryCollection->SimulationType, FGeometryCollection::ESimulationTypes::FST_Rigid, FGeometryCollection::ESimulationTypes::FST_None,
|
|
GeometryCollection->Parent, GCProximity, GeometryCollection->TransformIndex, Volume, FracAllowRemove, SimplificationDistanceThreshold, CanExceedFraction, OverlapRemovalMethod, UseLeafHulls->OverlapRemovalShrinkPercent);
|
|
|
|
TransformHullsToLocal(GlobalTransformArray, UseLeafHulls->Hulls, UseLeafHulls->Pivots, UseLeafHulls->TransformToHullsIndices, UseLeafHulls->OverlapRemovalShrinkPercent);
|
|
|
|
CreateConvexHullAttributesIfNeeded(*GeometryCollection);
|
|
|
|
TManagedArray<TSet<int32>>& TransformToConvexIndices = GeometryCollection->ModifyAttribute<TSet<int32>>("TransformToConvexIndices", FTransformCollection::TransformGroup);
|
|
TManagedArray<Chaos::FConvexPtr>& ConvexHull = GeometryCollection->ModifyAttribute<Chaos::FConvexPtr>(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup);
|
|
TransformToConvexIndices = MoveTemp(UseLeafHulls->TransformToHullsIndices);
|
|
GeometryCollection->EmptyGroup(FGeometryCollection::ConvexGroup);
|
|
GeometryCollection->Resize(UseLeafHulls->Hulls.Num(), FGeometryCollection::ConvexGroup);
|
|
ConvexHull = MoveTemp(UseLeafHulls->Hulls);
|
|
|
|
// clear all null and empty hulls
|
|
RemoveEmptyConvexHulls(*GeometryCollection);
|
|
|
|
checkSlow(FGeometryCollectionConvexUtility::ValidateConvexData(GeometryCollection));
|
|
|
|
return { TransformToConvexIndices, ConvexHull };
|
|
}
|
|
|
|
void FGeometryCollectionConvexUtility::GenerateLeafConvexHulls(
|
|
FGeometryCollection& Collection, bool bRestrictToSelection,
|
|
const TArrayView<const int32> TransformSubset, const FLeafConvexHullSettings& Settings,
|
|
TArray<FSphereCoveringInfo>* OutComputedNavigableSpheres
|
|
|
|
)
|
|
{
|
|
int32 NumTransforms = Collection.NumElements(FGeometryCollection::TransformGroup);
|
|
|
|
const TManagedArrayAccessor<Chaos::FImplicitObjectPtr> ExternalCollisionAttribute(Collection, FGeometryCollection::ExternalCollisionsAttribute, FGeometryCollection::TransformGroup);
|
|
bool bHasExternalCollision = ExternalCollisionAttribute.IsValid();
|
|
if (bHasExternalCollision)
|
|
{
|
|
bool bAnyValid = false;
|
|
for (int32 TransformIdx = 0; TransformIdx < NumTransforms; ++TransformIdx)
|
|
{
|
|
bAnyValid = bAnyValid || ExternalCollisionAttribute[TransformIdx].IsValid();
|
|
}
|
|
if (!bAnyValid)
|
|
{
|
|
bHasExternalCollision = false;
|
|
}
|
|
}
|
|
bool bUseExternal = bHasExternalCollision && (Settings.GenerateMethod == EGenerateConvexMethod::ExternalCollision || Settings.GenerateMethod == EGenerateConvexMethod::IntersectExternalWithComputed);
|
|
bool bUseGenerated = !bUseExternal || Settings.GenerateMethod == EGenerateConvexMethod::IntersectExternalWithComputed;
|
|
bool bUseIntersect = bUseExternal && Settings.GenerateMethod == EGenerateConvexMethod::IntersectExternalWithComputed;
|
|
|
|
UE::GeometryCollectionConvexUtility::FConvexHulls ComputedHulls;
|
|
TArray<FTransformedConvex> ExternalHulls;
|
|
TArray<TSet<int32>> TransformToExternalHullsIndices;
|
|
TransformToExternalHullsIndices.SetNum(NumTransforms);
|
|
|
|
CreateConvexHullAttributesIfNeeded(Collection);
|
|
|
|
auto ComputeTransformedHull = [](const Chaos::FConvex& HullIn, const FTransform& TransformIn) -> Chaos::FConvexPtr
|
|
{
|
|
FTransform3f Transform = (FTransform3f)TransformIn;
|
|
TArray<Chaos::FConvex::FVec3Type> HullPts;
|
|
for (const Chaos::FConvex::FVec3Type& P : HullIn.GetVertices())
|
|
{
|
|
HullPts.Add(Transform.TransformPosition(P));
|
|
}
|
|
return Chaos::FConvexPtr(new Chaos::FConvex(HullPts, UE_KINDA_SMALL_NUMBER));
|
|
};
|
|
|
|
TArray<int32> LocalTransformIndices;
|
|
if (!bRestrictToSelection)
|
|
{
|
|
LocalTransformIndices.Reserve(NumTransforms);
|
|
for (int32 Idx = 0; Idx < NumTransforms; ++Idx)
|
|
{
|
|
if (Collection.SimulationType[Idx] == FGeometryCollection::ESimulationTypes::FST_Rigid)
|
|
{
|
|
LocalTransformIndices.Add(Idx);
|
|
}
|
|
}
|
|
}
|
|
const TArrayView<const int32> UseTransforms = bRestrictToSelection ? TransformSubset : TArrayView<const int32>(LocalTransformIndices);
|
|
if (UseTransforms.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TManagedArray<Chaos::FConvexPtr>& ConvexHullAttrib = Collection.ModifyAttribute<Chaos::FConvexPtr>(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup);
|
|
TManagedArray<TSet<int32>>& TransformToConvexIndicesAttrib = Collection.ModifyAttribute<TSet<int32>>("TransformToConvexIndices", FTransformCollection::TransformGroup);
|
|
|
|
// Remove all convex hulls from bones that we have selected for re-compute
|
|
for (int32 TransformIdx : UseTransforms)
|
|
{
|
|
if (Collection.SimulationType[TransformIdx] != FGeometryCollection::ESimulationTypes::FST_Rigid)
|
|
{
|
|
continue;
|
|
}
|
|
TransformToConvexIndicesAttrib[TransformIdx].Empty();
|
|
}
|
|
// Remove all convex hulls that are no longer referenced by any transforms
|
|
{
|
|
TArray<bool> ConvexHullsUsed; ConvexHullsUsed.SetNumZeroed(ConvexHullAttrib.Num());
|
|
for (int32 TransformIdx = 0; TransformIdx < TransformToConvexIndicesAttrib.Num(); ++TransformIdx)
|
|
{
|
|
for (int32 ConvexIdx : TransformToConvexIndicesAttrib[TransformIdx])
|
|
{
|
|
ConvexHullsUsed[ConvexIdx] = true;
|
|
}
|
|
}
|
|
for (int32 ConvexIdx = 0; ConvexIdx < ConvexHullAttrib.Num(); ++ConvexIdx)
|
|
{
|
|
if (!ConvexHullsUsed[ConvexIdx])
|
|
{
|
|
ConvexHullAttrib[ConvexIdx].SafeRelease();
|
|
}
|
|
}
|
|
RemoveEmptyConvexHulls(Collection);
|
|
}
|
|
|
|
auto AddComputedHullsToCollection = [&Collection, &ConvexHullAttrib, &TransformToConvexIndicesAttrib](TArray<Chaos::FConvexPtr>& Hulls, TArray<TSet<int32>>& TransformToHullsIndices)
|
|
{
|
|
int32 InitialNum = ConvexHullAttrib.Num();
|
|
Collection.Resize(InitialNum + Hulls.Num(), FGeometryCollection::ConvexGroup);
|
|
for (int32 Idx = 0; Idx < Hulls.Num(); ++Idx)
|
|
{
|
|
ConvexHullAttrib[InitialNum + Idx] = MoveTemp(Hulls[Idx]);
|
|
}
|
|
for (int32 TransformIdx = 0; TransformIdx < TransformToHullsIndices.Num(); ++TransformIdx)
|
|
{
|
|
for (int32 ConvexIdx : TransformToHullsIndices[TransformIdx])
|
|
{
|
|
TransformToConvexIndicesAttrib[TransformIdx].Add(ConvexIdx + InitialNum);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (bUseExternal)
|
|
{
|
|
for (int32 SourceTransformIdx : UseTransforms)
|
|
{
|
|
if (Collection.SimulationType[SourceTransformIdx] != FGeometryCollection::ESimulationTypes::FST_Rigid)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// convert the external collisions to convex hulls
|
|
const Chaos::FImplicitObjectPtr ExternalCollision = ExternalCollisionAttribute[SourceTransformIdx];
|
|
if (!ExternalCollision.IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int32 ExternalHullsStart = ExternalHulls.Num();
|
|
ConvertImplicitToConvexArray(*ExternalCollision, FTransform::Identity, ExternalHulls);
|
|
for (int32 HullIndex = ExternalHullsStart; HullIndex < ExternalHulls.Num(); HullIndex++)
|
|
{
|
|
TransformToExternalHullsIndices[SourceTransformIdx].Add(HullIndex);
|
|
}
|
|
}
|
|
|
|
if (!bUseIntersect)
|
|
{
|
|
TArray<Chaos::FConvexPtr> FinalHulls;
|
|
FinalHulls.SetNum(ExternalHulls.Num());
|
|
|
|
// Transform the hulls to the local space of the transform
|
|
ParallelFor(ExternalHulls.Num(), [&](int32 HullIdx)
|
|
{
|
|
FinalHulls[HullIdx] = ComputeTransformedHull(*ExternalHulls[HullIdx].Convex, ExternalHulls[HullIdx].Transform);
|
|
});
|
|
|
|
AddComputedHullsToCollection(FinalHulls, TransformToExternalHullsIndices);
|
|
}
|
|
}
|
|
|
|
if (bUseGenerated)
|
|
{
|
|
// Identity Transform Array allows us to leave leaf hulls in their original local coordinate spaces
|
|
TArray<FTransform> IdentityTransformArray;
|
|
IdentityTransformArray.Init(FTransform::Identity, NumTransforms);
|
|
TFunction<bool(int32)> SkipBoneFn = nullptr;
|
|
TSet<int32> SelectionSet;
|
|
if (bRestrictToSelection)
|
|
{
|
|
SelectionSet.Append(UseTransforms);
|
|
SkipBoneFn = [SelectionSet](int32 BoneIdx) -> bool
|
|
{
|
|
return SelectionSet.Contains(BoneIdx);
|
|
};
|
|
}
|
|
ComputedHulls = ComputeLeafHulls(&Collection, IdentityTransformArray, Settings.SimplificationDistanceThreshold, 0, SkipBoneFn, &Settings.DecompositionSettings,
|
|
Settings.bComputeIntersectionsBeforeHull ? &ExternalHulls : nullptr,
|
|
Settings.bComputeIntersectionsBeforeHull ? &TransformToExternalHullsIndices : nullptr,
|
|
OutComputedNavigableSpheres
|
|
);
|
|
|
|
if (!bUseIntersect)
|
|
{
|
|
AddComputedHullsToCollection(ComputedHulls.Hulls, ComputedHulls.TransformToHullsIndices);
|
|
}
|
|
}
|
|
|
|
if (bUseIntersect)
|
|
{
|
|
TArray<Chaos::FConvexPtr> FinalHulls;
|
|
int32 HullIndexOffset = ConvexHullAttrib.Num();
|
|
for (int32 SourceTransformIdx : UseTransforms)
|
|
{
|
|
if (Collection.SimulationType[SourceTransformIdx] != FGeometryCollection::ESimulationTypes::FST_Rigid)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSet<int32>& GeoHulls = ComputedHulls.TransformToHullsIndices[SourceTransformIdx];
|
|
|
|
if (Settings.IntersectFilters.OnlyIntersectIfComputedIsSmallerFactor < 1.0 || Settings.IntersectFilters.MinExternalVolumeToIntersect > 0.0)
|
|
{
|
|
double ComputedVolSum = 0.0;
|
|
for (int32 GeoHullIdx : GeoHulls)
|
|
{
|
|
ComputedVolSum += (double)ComputedHulls.Hulls[GeoHullIdx]->GetVolume();
|
|
}
|
|
double ExternalVolSum = 0.0;
|
|
for (int32 ExtHull : TransformToExternalHullsIndices[SourceTransformIdx])
|
|
{
|
|
ExternalVolSum += (double)ExternalHulls[ExtHull].Convex->GetVolume();
|
|
}
|
|
if (
|
|
(Settings.IntersectFilters.OnlyIntersectIfComputedIsSmallerFactor < 1.0 && ComputedVolSum >= ExternalVolSum * Settings.IntersectFilters.OnlyIntersectIfComputedIsSmallerFactor) ||
|
|
(ExternalVolSum < Settings.IntersectFilters.MinExternalVolumeToIntersect)
|
|
)
|
|
{
|
|
// Filters allow us to skip computing the intersection and just use the external hulls
|
|
for (int32 ExtHull : TransformToExternalHullsIndices[SourceTransformIdx])
|
|
{
|
|
int32 HullIdx = FinalHulls.Add(ComputeTransformedHull(*ExternalHulls[ExtHull].Convex, ExternalHulls[ExtHull].Transform));
|
|
TransformToConvexIndicesAttrib[SourceTransformIdx].Add(HullIndexOffset + HullIdx);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// We've already computed the hulls w/ external geo intersection; just move it to the output
|
|
if (Settings.bComputeIntersectionsBeforeHull)
|
|
{
|
|
for (int32 GeoHullIdx : GeoHulls)
|
|
{
|
|
int32 HullIdx = FinalHulls.Add(MoveTemp(ComputedHulls.Hulls[GeoHullIdx]));
|
|
TransformToConvexIndicesAttrib[SourceTransformIdx].Add(HullIdx + HullIndexOffset);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// The !bComputeIntersectionsBeforeHull path
|
|
checkSlow(!Settings.bComputeIntersectionsBeforeHull);
|
|
for (int32 GeoHullIdx : GeoHulls)
|
|
{
|
|
if (TransformToExternalHullsIndices.IsEmpty())
|
|
{
|
|
TransformToConvexIndicesAttrib[SourceTransformIdx].Add(HullIndexOffset + FinalHulls.Add(MoveTemp(ComputedHulls.Hulls[GeoHullIdx])));
|
|
|
|
continue;
|
|
}
|
|
for (int32 ExtHull : TransformToExternalHullsIndices[SourceTransformIdx])
|
|
{
|
|
int32 HullIdx = FinalHulls.Add(Chaos::FConvexPtr(new Chaos::FConvex()));
|
|
UE::GeometryCollectionConvexUtility::IntersectConvexHulls(FinalHulls[HullIdx].GetReference(),
|
|
ComputedHulls.Hulls[GeoHullIdx].GetReference(), 0.0f, ExternalHulls[ExtHull].Convex.GetReference(),
|
|
nullptr, &ExternalHulls[ExtHull].Transform, &ExternalHulls[ExtHull].Transform, Settings.SimplificationDistanceThreshold);
|
|
TransformToConvexIndicesAttrib[SourceTransformIdx].Add(HullIdx + HullIndexOffset);
|
|
}
|
|
}
|
|
}
|
|
// copy intersected hulls into the output hull attrib
|
|
Collection.Resize(HullIndexOffset + FinalHulls.Num(), FGeometryCollection::ConvexGroup);
|
|
for (int32 Idx = 0; Idx < FinalHulls.Num(); ++Idx)
|
|
{
|
|
ConvexHullAttrib[HullIndexOffset + Idx] = MoveTemp(FinalHulls[Idx]);
|
|
}
|
|
}
|
|
|
|
RemoveEmptyConvexHulls(Collection);
|
|
}
|
|
|
|
void FGeometryCollectionConvexUtility::GenerateClusterConvexHullsFromChildrenHulls(FGeometryCollection& Collection, const FClusterConvexHullSettings& Settings, const TArrayView<const int32> TransformSubset)
|
|
{
|
|
GenerateClusterConvexHullsFromLeafOrChildrenHullsInternal(Collection, Settings, true, true/*bUseDirectChildren*/, TransformSubset);
|
|
}
|
|
void FGeometryCollectionConvexUtility::GenerateClusterConvexHullsFromChildrenHulls(FGeometryCollection& Collection, const FClusterConvexHullSettings& Settings)
|
|
{
|
|
GenerateClusterConvexHullsFromLeafOrChildrenHullsInternal(Collection, Settings, false, true/*bUseDirectChildren*/, TArrayView<const int32>());
|
|
}
|
|
void FGeometryCollectionConvexUtility::GenerateClusterConvexHullsFromLeafHulls(FGeometryCollection& Collection, const FClusterConvexHullSettings& Settings, const TArrayView<const int32> TransformSubset)
|
|
{
|
|
GenerateClusterConvexHullsFromLeafOrChildrenHullsInternal(Collection, Settings, true, false/*bUseDirectChildren*/, TransformSubset);
|
|
}
|
|
void FGeometryCollectionConvexUtility::GenerateClusterConvexHullsFromLeafHulls(FGeometryCollection& Collection, const FClusterConvexHullSettings& Settings)
|
|
{
|
|
GenerateClusterConvexHullsFromLeafOrChildrenHullsInternal(Collection, Settings, false, false/*bUseDirectChildren*/, TArrayView<const int32>());
|
|
}
|
|
|
|
void FGeometryCollectionConvexUtility::GenerateClusterConvexHullsFromLeafOrChildrenHullsInternal(FGeometryCollection& Collection, const FClusterConvexHullSettings& Settings, bool bOnlySubset, bool bUseDirectChildren, const TArrayView<const int32> TransformSubset)
|
|
{
|
|
static FName ConvexGroupName = FGeometryCollection::ConvexGroup;
|
|
static FName ConvexHullAttributeName = FGeometryCollection::ConvexHullAttribute;
|
|
static FName TransformToConvexIndicesName("TransformToConvexIndices");
|
|
|
|
CreateConvexHullAttributesIfNeeded(Collection);
|
|
|
|
TManagedArrayAccessor<Chaos::FConvexPtr> ConvexHullAttribute(Collection, ConvexHullAttributeName, ConvexGroupName);
|
|
TManagedArrayAccessor<TSet<int32>> TransformToConvexIndicesAttribute(Collection, TransformToConvexIndicesName, FGeometryCollection::TransformGroup);
|
|
const TManagedArrayAccessor<Chaos::FImplicitObjectPtr> ExternalCollisionAttribute(Collection, FGeometryCollection::ExternalCollisionsAttribute, FGeometryCollection::TransformGroup);
|
|
|
|
GeometryCollection::Facades::FCollectionTransformFacade TransformFacade(Collection);
|
|
Chaos::Facades::FCollectionHierarchyFacade HierarchyFacade(Collection);
|
|
|
|
FGeometryCollectionProximityUtility ProximityUtility(&Collection);
|
|
ProximityUtility.RequireProximity();
|
|
const TManagedArray<TSet<int32>>& Proximity = Collection.GetAttribute<TSet<int32>>("Proximity", FGeometryCollection::GeometryGroup);
|
|
|
|
// Whether to consider directly merging hulls from the same child/leaf transform; without this, these hulls can still be merged if they are also merged to a neighboring hull
|
|
// TODO: decide whether to always-enable this, or expose this option to the user, or leave it disabled
|
|
constexpr bool bConsiderIntraTransformConvexMerges = false;
|
|
|
|
const TArray<FTransform> GlobalTransforms = TransformFacade.ComputeCollectionSpaceTransforms();
|
|
const TManagedArrayAccessor<int32> SimulationTypeAttribute(Collection, FGeometryCollection::SimulationTypeAttribute, FTransformCollection::TransformGroup);
|
|
const TArray<int32> DepthFirstTransformIndices = HierarchyFacade.GetTransformArrayInDepthFirstOrder();
|
|
TArray<int32> TransformsToProcess;
|
|
if (bOnlySubset)
|
|
{
|
|
if (TransformSubset.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bUseDirectChildren || Settings.bAllowMergingLeafHulls)
|
|
{
|
|
// If using direct children, or merging leaf hulls, order matters -- use depth-first order
|
|
TSet<int32> ToProcessSet;
|
|
ToProcessSet.Append(TransformSubset);
|
|
TransformsToProcess.Reserve(TransformSubset.Num());
|
|
for (int32 TransformIdx : DepthFirstTransformIndices)
|
|
{
|
|
if (ToProcessSet.Contains(TransformIdx))
|
|
{
|
|
TransformsToProcess.Add(TransformIdx);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Order doesn't matter
|
|
TransformsToProcess.Append(TransformSubset);
|
|
}
|
|
}
|
|
else // process all
|
|
{
|
|
TransformsToProcess = DepthFirstTransformIndices;
|
|
}
|
|
|
|
// Simulation types must be present
|
|
if (!SimulationTypeAttribute.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int32 TransformIndex : TransformsToProcess)
|
|
{
|
|
if (TransformIndex < 0 || TransformIndex >= SimulationTypeAttribute.Num())
|
|
{
|
|
UE_LOG(LogChaos, Warning, TEXT("Transform Index %d out of bounds [0,%d)"), TransformIndex, SimulationTypeAttribute.Num());
|
|
continue;
|
|
}
|
|
// only do this for clusters, unless we allow merging for leaf hulls too
|
|
const bool bIsCluster = (SimulationTypeAttribute.Get()[TransformIndex] == FGeometryCollection::ESimulationTypes::FST_Clustered);
|
|
if (bIsCluster || Settings.bAllowMergingLeafHulls)
|
|
{
|
|
TArray<int32> SourceTransformIndices;
|
|
if (!bIsCluster)
|
|
{
|
|
SourceTransformIndices.Add(TransformIndex);
|
|
}
|
|
else if (bUseDirectChildren)
|
|
{
|
|
SourceTransformIndices = HierarchyFacade.GetChildrenAsArray(TransformIndex);
|
|
// TODO: consider expanding this recursively to children for any nodes without convex hulls, until we hit a leaf/rigid node
|
|
}
|
|
else
|
|
{
|
|
FGeometryCollectionClusteringUtility::GetLeafBones(&Collection, TransformIndex, true, SourceTransformIndices);
|
|
}
|
|
|
|
if (SourceTransformIndices.Num() > 0)
|
|
{
|
|
const FTransform& ParentTransform = GlobalTransforms[TransformIndex];
|
|
|
|
TManagedArray<TSet<int32>>& TransformToConvexIndices = TransformToConvexIndicesAttribute.Modify();
|
|
TManagedArray<Chaos::FConvexPtr>& ConvexHull = ConvexHullAttribute.Modify();
|
|
|
|
struct FHullInfo
|
|
{
|
|
Chaos::FConvex* Convex = nullptr;
|
|
FTransform Transform;
|
|
};
|
|
|
|
// build information required for merging hulls
|
|
TArray<FHullInfo> Hulls;
|
|
TArray<FTransformedConvex> ExternalHulls;
|
|
TMap<int32, TArray<int32>> TransformToHullIdx;
|
|
TArray<TPair<int32, int32>> HullProximity;
|
|
|
|
for (int32 SourceTransformIndex : SourceTransformIndices)
|
|
{
|
|
const FTransform InnerTransform = GlobalTransforms[SourceTransformIndex];
|
|
FTransform ChildToParentTransform = InnerTransform.GetRelativeTransform(ParentTransform);
|
|
|
|
const Chaos::FImplicitObjectPtr ExternalCollision = (ExternalCollisionAttribute.IsValid()) ? ExternalCollisionAttribute.Get()[SourceTransformIndex] : Chaos::FImplicitObjectPtr();
|
|
if (ExternalCollision && Settings.bUseExternalCollisionIfAvailable)
|
|
{
|
|
const int32 ExternalHullsStart = ExternalHulls.Num();
|
|
ConvertImplicitToConvexArray(*ExternalCollision, FTransform::Identity, ExternalHulls);
|
|
for (int32 HullIndex = ExternalHullsStart; HullIndex < ExternalHulls.Num(); HullIndex++)
|
|
{
|
|
const FTransformedConvex& ExternalHull = ExternalHulls[HullIndex];
|
|
|
|
FHullInfo HullInfo;
|
|
HullInfo.Convex = ExternalHull.Convex.GetReference();
|
|
HullInfo.Transform = ExternalHull.Transform * ChildToParentTransform;
|
|
const int32 HullIdx = Hulls.Emplace(HullInfo);
|
|
TransformToHullIdx.FindOrAdd(SourceTransformIndex).Add(HullIdx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int32 SourceConvexIdx : TransformToConvexIndices[SourceTransformIndex])
|
|
{
|
|
FHullInfo HullInfo;
|
|
HullInfo.Convex = ConvexHull[SourceConvexIdx].GetReference();
|
|
HullInfo.Transform = ChildToParentTransform;
|
|
const int32 HullIdx = Hulls.Emplace(HullInfo);
|
|
TransformToHullIdx.FindOrAdd(SourceTransformIndex).Add(HullIdx);
|
|
}
|
|
}
|
|
}
|
|
// Set HullProximity for convex merges -- the connections between convex hulls that can be merged
|
|
if (!bIsCluster || bUseDirectChildren || Settings.AllowMergesMethod == EAllowConvexMergeMethod::Any)
|
|
{
|
|
// With MergeMethod of Any, any pair of convex hulls can be merged
|
|
// Note: Currently bUseDirectChildren only supports this method
|
|
// This is likely to be slower and may consider additional merges that the proximity method cannot
|
|
for (int32 IndexA = 0; IndexA < SourceTransformIndices.Num(); IndexA++)
|
|
{
|
|
const int32 TransformIndexA = SourceTransformIndices[IndexA];
|
|
if (const TArray<int32>* HullIndicesA = TransformToHullIdx.Find(TransformIndexA))
|
|
{
|
|
// Consider merges between convex hulls from separate transforms
|
|
for (int32 IndexB = IndexA + 1; IndexB < SourceTransformIndices.Num(); IndexB++)
|
|
{
|
|
const int32 TransformIndexB = SourceTransformIndices[IndexB];
|
|
if (const TArray<int32>* HullIndicesB = TransformToHullIdx.Find(TransformIndexB))
|
|
{
|
|
for (int32 SourceHullIdxA : (*HullIndicesA))
|
|
{
|
|
|
|
AddHullProximity(HullProximity, Settings.ProximityFilter, Settings.ProximityDistanceThreshold,
|
|
[&Hulls](int32 Idx) -> const Chaos::FConvex* { return Hulls[Idx].Convex; }, Hulls[SourceHullIdxA].Transform,
|
|
SourceHullIdxA, [&Hulls](int32 Idx) -> const FTransform& { return Hulls[Idx].Transform; },
|
|
0, HullIndicesB->Num(), HullIndicesB);
|
|
}
|
|
}
|
|
}
|
|
// Consider merges between convex hulls within a transform
|
|
if (!bIsCluster || bConsiderIntraTransformConvexMerges)
|
|
{
|
|
for (int32 HullSubIdx = 0; HullSubIdx + 1 < HullIndicesA->Num(); ++HullSubIdx)
|
|
{
|
|
const int32 HullA = (*HullIndicesA)[HullSubIdx];
|
|
AddHullProximity(HullProximity, Settings.ProximityFilter, Settings.ProximityDistanceThreshold,
|
|
[&Hulls](int32 Idx) -> const Chaos::FConvex* { return Hulls[Idx].Convex; }, Hulls[HullA].Transform,
|
|
HullA, EUseConstantTransform::ConstantTransform, HullSubIdx + 1, HullIndicesA->Num(), HullIndicesA);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // Settings.MergeMethod == EAllowConvexMergeMethod::ByProximity
|
|
{
|
|
for (int32 SourceTransformIndex : SourceTransformIndices)
|
|
{
|
|
const TArray<int32, TInlineAllocator<8>> SourceHullIndices{ TransformToHullIdx.FindOrAdd(SourceTransformIndex) };
|
|
const int32 GeoIdx = Collection.TransformToGeometryIndex[SourceTransformIndex];
|
|
if (GeoIdx > INDEX_NONE)
|
|
{
|
|
// Consider merges between convex hulls from separate transforms
|
|
for (int32 NeighborGeoIndex : Proximity[GeoIdx])
|
|
{
|
|
const int32 NeighborTransformIdx = Collection.TransformIndex[NeighborGeoIndex];
|
|
if (const TArray<int32>* NeighborHullIndices = TransformToHullIdx.Find(NeighborTransformIdx))
|
|
{
|
|
for (int32 SourceHullIdx : SourceHullIndices)
|
|
{
|
|
AddHullProximity(HullProximity, Settings.ProximityFilter, Settings.ProximityDistanceThreshold,
|
|
[&Hulls](int32 Idx) -> const Chaos::FConvex* { return Hulls[Idx].Convex; }, Hulls[SourceHullIdx].Transform,
|
|
SourceHullIdx, [&Hulls](int32 Idx) -> const FTransform& { return Hulls[Idx].Transform; },
|
|
0, NeighborHullIndices->Num(), NeighborHullIndices);
|
|
|
|
}
|
|
}
|
|
}
|
|
// Consider merges between convex hulls within a transform
|
|
if (bConsiderIntraTransformConvexMerges)
|
|
{
|
|
for (int32 HullSubIdx = 0; HullSubIdx + 1 < SourceHullIndices.Num(); ++HullSubIdx)
|
|
{
|
|
const int32 HullA = SourceHullIndices[HullSubIdx];
|
|
AddHullProximity(HullProximity, Settings.ProximityFilter, Settings.ProximityDistanceThreshold,
|
|
[&Hulls](int32 Idx) -> const Chaos::FConvex* { return Hulls[Idx].Convex; }, Hulls[HullA].Transform,
|
|
HullA, EUseConstantTransform::ConstantTransform, HullSubIdx + 1, SourceHullIndices.Num(), &SourceHullIndices);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// try to merge the leaf convex into a simpler set of convex
|
|
const auto GetHullVolume = [&Hulls](int32 Idx) { return (double)Hulls[Idx].Convex->GetVolume(); };
|
|
const auto GetHullNumVertices = [&Hulls](int32 Idx) { return Hulls[Idx].Convex->NumVertices(); };
|
|
const auto GetHullVertex = [&Hulls](int32 Hull, int32 V) {
|
|
const FVector3d LocalVertex(Hulls[Hull].Convex->GetVertex(V));
|
|
return Hulls[Hull].Transform.TransformPosition(LocalVertex);
|
|
};
|
|
UE::Geometry::FConvexDecomposition3 ConvexDecomposition;
|
|
ConvexDecomposition.InitializeFromHulls(Hulls.Num(), GetHullVolume, GetHullNumVertices, GetHullVertex, HullProximity);
|
|
ConvexDecomposition.MergeBest(Settings.ConvexCount, Settings.ErrorToleranceInCm, 0, true /*bAllowCompact*/, false /*bRequireHullTriangles*/, -1 /*MaxHulls*/, Settings.EmptySpace, &ParentTransform);
|
|
|
|
// reset existing hulls for this transform index
|
|
for (int32 ConvexIndex : TransformToConvexIndices[TransformIndex])
|
|
{
|
|
ConvexHull[ConvexIndex] = nullptr;
|
|
}
|
|
TransformToConvexIndices[TransformIndex].Reset();
|
|
|
|
// add new computed ones
|
|
for (int32 DecompIndex = 0; DecompIndex < ConvexDecomposition.Decomposition.Num(); DecompIndex++)
|
|
{
|
|
// Make Implicit Convex of a decomposed element
|
|
TArray<Chaos::FConvex::FVec3Type> Particles;
|
|
const TArray<FVector> Points = ConvexDecomposition.GetVertices<double>(DecompIndex);
|
|
Particles.SetNum(Points.Num());
|
|
for (int32 PointIndex = 0; PointIndex < Points.Num(); PointIndex++)
|
|
{
|
|
Particles[PointIndex] = Points[PointIndex];
|
|
}
|
|
Chaos::FConvexPtr ImplicitConvex( new Chaos::FConvex(MoveTemp(Particles), 0.0f));
|
|
|
|
// Add the the element to the union
|
|
const int32 NewConvexIndex = Collection.AddElements(1, ConvexGroupName);
|
|
ConvexHull[NewConvexIndex] = MoveTemp(ImplicitConvex);
|
|
TransformToConvexIndices[TransformIndex].Add(NewConvexIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove empty or null convex hulls
|
|
RemoveEmptyConvexHulls(Collection);
|
|
|
|
checkSlow(FGeometryCollectionConvexUtility::ValidateConvexData(&Collection));
|
|
|
|
}
|
|
|
|
void FGeometryCollectionConvexUtility::MergeHullsOnTransforms(FManagedArrayCollection& Collection, const FGeometryCollectionConvexUtility::FMergeConvexHullSettings& Settings, bool bRestrictToSelection,
|
|
const TArrayView<const int32> OptionalTransformSelection, UE::Geometry::FSphereCovering* OptionalSphereCoveringOut)
|
|
{
|
|
static FName ConvexGroupName = FGeometryCollection::ConvexGroup;
|
|
|
|
TOptional<FGeometryCollectionConvexUtility::FGeometryCollectionConvexData> ConvexData = GetConvexHullDataIfPresent(&Collection);
|
|
if (!ConvexData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TManagedArray<TSet<int32>>& TransformToConvexIndices = ConvexData->TransformToConvexIndices;
|
|
TManagedArray<Chaos::FConvexPtr>& ConvexHull = ConvexData->ConvexHull;
|
|
|
|
GeometryCollection::Facades::FCollectionTransformFacade TransformFacade(Collection);
|
|
|
|
const TArray<FTransform> GlobalTransforms = TransformFacade.ComputeCollectionSpaceTransforms();
|
|
TArray<int32> TransformsToProcess;
|
|
if (bRestrictToSelection)
|
|
{
|
|
TransformsToProcess.Append(OptionalTransformSelection);
|
|
}
|
|
else // process all
|
|
{
|
|
TransformsToProcess.Reserve(TransformToConvexIndices.Num());
|
|
for (int32 Idx = 0; Idx < TransformToConvexIndices.Num(); ++Idx)
|
|
{
|
|
TransformsToProcess.Add(Idx);
|
|
}
|
|
}
|
|
|
|
if (OptionalSphereCoveringOut)
|
|
{
|
|
OptionalSphereCoveringOut->Reset();
|
|
}
|
|
if (OptionalSphereCoveringOut && Settings.EmptySpace)
|
|
{
|
|
OptionalSphereCoveringOut->Append(*Settings.EmptySpace);
|
|
}
|
|
|
|
for (int32 TransformIndex : TransformsToProcess)
|
|
{
|
|
const int32 InitialNumConvex = TransformToConvexIndices[TransformIndex].Num();
|
|
if (InitialNumConvex <= 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TArray<Chaos::FConvex*> Hulls;
|
|
for (int32 ConvexIdx : TransformToConvexIndices[TransformIndex])
|
|
{
|
|
Hulls.Add(ConvexHull[ConvexIdx].GetReference());
|
|
}
|
|
TArray<TPair<int32, int32>> HullProximity;
|
|
for (int32 ConvexA = 0; ConvexA < InitialNumConvex; ++ConvexA)
|
|
{
|
|
AddHullProximity(HullProximity, Settings.ProximityFilter, Settings.ProximityDistanceThreshold,
|
|
[&Hulls](int32 Idx) -> const Chaos::FConvex* { return Hulls[Idx]; }, GlobalTransforms[TransformIndex],
|
|
ConvexA, EUseConstantTransform::ConstantTransform, ConvexA + 1, InitialNumConvex);
|
|
}
|
|
|
|
// try to merge the leaf convex into a simpler set of convex
|
|
const auto GetHullVolume = [&Hulls](int32 Idx) { return (double)Hulls[Idx]->GetVolume(); };
|
|
const auto GetHullNumVertices = [&Hulls](int32 Idx) { return Hulls[Idx]->NumVertices(); };
|
|
const auto GetHullVertex = [&Hulls](int32 Hull, int32 V) -> FVector3d {
|
|
const FVector3d LocalVertex(Hulls[Hull]->GetVertex(V));
|
|
return LocalVertex;
|
|
};
|
|
UE::Geometry::FConvexDecomposition3 ConvexDecomposition;
|
|
ConvexDecomposition.InitializeFromHulls(Hulls.Num(), GetHullVolume, GetHullNumVertices, GetHullVertex, HullProximity);
|
|
UE::Geometry::FSphereCovering LocalCovering;
|
|
UE::Geometry::FSphereCovering* UseCovering = Settings.EmptySpace;
|
|
if (Settings.ComputeEmptySpacePerBoneSettings)
|
|
{
|
|
UseCovering = &LocalCovering;
|
|
UE::Geometry::FDynamicMesh3 HullsMesh;
|
|
for (const ::Chaos::FConvex* Hull : Hulls)
|
|
{
|
|
constexpr bool bInvertFaces = false; // don't invert faces, to match orientation expected by AddNegativeSpace below
|
|
AddConvexHullToCompactDynamicMesh(Hull, HullsMesh, &GlobalTransforms[TransformIndex], bInvertFaces);
|
|
}
|
|
UE::Geometry::FDynamicMeshAABBTree3 HullsTree(&HullsMesh, true);
|
|
UE::Geometry::TFastWindingTree<UE::Geometry::FDynamicMesh3> HullsWinding(&HullsTree, true);
|
|
LocalCovering.AddNegativeSpace(HullsWinding, *Settings.ComputeEmptySpacePerBoneSettings, true);
|
|
|
|
if (Settings.EmptySpace)
|
|
{
|
|
LocalCovering.Append(*Settings.EmptySpace);
|
|
}
|
|
|
|
if (OptionalSphereCoveringOut)
|
|
{
|
|
OptionalSphereCoveringOut->Append(LocalCovering);
|
|
}
|
|
}
|
|
ConvexDecomposition.MergeBest(Settings.MaxConvexCount, Settings.ErrorToleranceInCm, 0, true /*bAllowCompact*/, false /*bRequireHullTriangles*/, Settings.MaxConvexCount /*MaxHulls*/, UseCovering, &GlobalTransforms[TransformIndex]);
|
|
|
|
if (!ensure(ConvexDecomposition.Decomposition.Num() > 0))
|
|
{
|
|
// Empty convex decomposition cannot be used / should not happen
|
|
continue;
|
|
}
|
|
if (InitialNumConvex <= ConvexDecomposition.Decomposition.Num())
|
|
{
|
|
// no need to update hulls if the merge didn't change the number of hulls
|
|
continue;
|
|
}
|
|
|
|
// reset existing hulls for this transform index
|
|
for (int32 ConvexIndex : TransformToConvexIndices[TransformIndex])
|
|
{
|
|
ConvexHull[ConvexIndex] = nullptr;
|
|
}
|
|
TransformToConvexIndices[TransformIndex].Reset();
|
|
|
|
// add new computed ones
|
|
for (int32 DecompIndex = 0; DecompIndex < ConvexDecomposition.Decomposition.Num(); DecompIndex++)
|
|
{
|
|
// Make Implicit Convex of a decomposed element
|
|
TArray<Chaos::FConvex::FVec3Type> Particles;
|
|
const TArray<FVector> Points = ConvexDecomposition.GetVertices<double>(DecompIndex);
|
|
Particles.SetNum(Points.Num());
|
|
for (int32 PointIndex = 0; PointIndex < Points.Num(); PointIndex++)
|
|
{
|
|
Particles[PointIndex] = Points[PointIndex];
|
|
}
|
|
Chaos::FConvexPtr ImplicitConvex( new Chaos::FConvex(MoveTemp(Particles), 0.0f));
|
|
|
|
// Add the the element to the union
|
|
const int32 NewConvexIndex = Collection.AddElements(1, ConvexGroupName);
|
|
ConvexHull[NewConvexIndex] = MoveTemp(ImplicitConvex);
|
|
TransformToConvexIndices[TransformIndex].Add(NewConvexIndex);
|
|
}
|
|
}
|
|
|
|
// remove empty or null convex hulls
|
|
RemoveEmptyConvexHulls(Collection);
|
|
|
|
checkSlow(FGeometryCollectionConvexUtility::ValidateConvexData(&Collection));
|
|
}
|
|
|
|
bool FGeometryCollectionConvexUtility::ValidateConvexData(const FManagedArrayCollection* GeometryCollection)
|
|
{
|
|
if (!GeometryCollection->HasAttribute(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup) || !GeometryCollection->HasAttribute("TransformToConvexIndices", FTransformCollection::TransformGroup))
|
|
{
|
|
return false;
|
|
}
|
|
const TManagedArray<Chaos::FConvexPtr>& ConvexHull = GeometryCollection->GetAttribute<Chaos::FConvexPtr>(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup);
|
|
for (int32 ConvexIdx = 0; ConvexIdx < ConvexHull.Num(); ConvexIdx++)
|
|
{
|
|
if (!ConvexHull[ConvexIdx].IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
const TManagedArray<TSet<int32>>& TransformToConvexIndices = GeometryCollection->GetAttribute<TSet<int32>>("TransformToConvexIndices", FTransformCollection::TransformGroup);
|
|
for (int32 TransformIdx = 0; TransformIdx < TransformToConvexIndices.Num(); TransformIdx++)
|
|
{
|
|
for (int32 ConvexIdx : TransformToConvexIndices[TransformIdx])
|
|
{
|
|
if (ConvexIdx < 0 || ConvexIdx >= ConvexHull.Num())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Chaos::FConvexPtr FGeometryCollectionConvexUtility::GetConvexHull(const FGeometryCollection* GeometryCollection, int32 GeometryIndex)
|
|
{
|
|
check(GeometryCollection);
|
|
|
|
int32 VertexCount = GeometryCollection->VertexCount[GeometryIndex];
|
|
int32 VertexStart = GeometryCollection->VertexStart[GeometryIndex];
|
|
|
|
TArray<Chaos::FConvex::FVec3Type> Vertices;
|
|
Vertices.SetNum(VertexCount);
|
|
for (int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex)
|
|
{
|
|
Vertices[VertexIndex] = GeometryCollection->Vertex[VertexStart+VertexIndex];
|
|
}
|
|
|
|
return Chaos::FConvexPtr( new Chaos::FConvex(Vertices, 0.0f));
|
|
}
|
|
|
|
|
|
bool FGeometryCollectionConvexUtility::CopyConvexHulls(FManagedArrayCollection& Collection, const TArray<int32>& TransformIndices,
|
|
const FManagedArrayCollection& SourceCollection, const TArray<int32>& SourceTransformIndices, bool bSkipIfEmpty)
|
|
{
|
|
static FName ConvexGroupName = FGeometryCollection::ConvexGroup;
|
|
static FName ConvexHullAttributeName = FGeometryCollection::ConvexHullAttribute;
|
|
static FName TransformToConvexIndicesName("TransformToConvexIndices");
|
|
|
|
if (TransformIndices.Num() != SourceTransformIndices.Num())
|
|
{
|
|
UE_LOG(LogChaos, Warning,
|
|
TEXT("Attempted to copy convex hulls failed because the source and target transform indices array did not have the same length (%d vs %d)"),
|
|
TransformIndices.Num(), SourceTransformIndices.Num());
|
|
return false;
|
|
}
|
|
|
|
CreateConvexHullAttributesIfNeeded(Collection);
|
|
|
|
TManagedArrayAccessor<Chaos::FConvexPtr> ToConvexHullAttribute(Collection, ConvexHullAttributeName, ConvexGroupName);
|
|
TManagedArrayAccessor<Chaos::FConvexPtr> FromConvexHullAttribute(SourceCollection, ConvexHullAttributeName, ConvexGroupName);
|
|
TManagedArrayAccessor<TSet<int32>> ToTransformToConvexIndicesAttribute(Collection, TransformToConvexIndicesName, FTransformCollection::TransformGroup);
|
|
TManagedArrayAccessor<TSet<int32>> FromTransformToConvexIndicesAttribute(SourceCollection, TransformToConvexIndicesName, FTransformCollection::TransformGroup);
|
|
|
|
if (!FromConvexHullAttribute.IsValid() || !FromTransformToConvexIndicesAttribute.IsValid())
|
|
{
|
|
UE_LOG(LogChaos, Warning,
|
|
TEXT("Failed to copy convex hulls: Hull attributes not found on source collection"));
|
|
return false;
|
|
}
|
|
|
|
TManagedArray<TSet<int32>>& ToTransformToConvexIndices = ToTransformToConvexIndicesAttribute.Modify();
|
|
TManagedArray<Chaos::FConvexPtr>& ToConvexHull = ToConvexHullAttribute.Modify();
|
|
|
|
TSet<int32> ConvexToRemove;
|
|
for (int32 ToTransferIdx = 0; ToTransferIdx < TransformIndices.Num(); ++ToTransferIdx)
|
|
{
|
|
int32 FromTransformIdx = SourceTransformIndices[ToTransferIdx];
|
|
int32 ToTransformIdx = TransformIndices[ToTransferIdx];
|
|
if (!ToTransformToConvexIndices.IsValidIndex(ToTransformIdx))
|
|
{
|
|
UE_LOG(LogChaos, Warning,
|
|
TEXT("Invalid transform index: %d"),
|
|
ToTransformIdx);
|
|
continue;
|
|
}
|
|
if (!FromTransformToConvexIndicesAttribute.Get().IsValidIndex(FromTransformIdx))
|
|
{
|
|
UE_LOG(LogChaos, Warning,
|
|
TEXT("Invalid source transform index: %d"),
|
|
FromTransformIdx);
|
|
continue;
|
|
}
|
|
const TSet<int32>& FromIndices = FromTransformToConvexIndicesAttribute[FromTransformIdx];
|
|
|
|
if (bSkipIfEmpty && FromIndices.IsEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ConvexToRemove.Append(ToTransformToConvexIndices[ToTransformIdx]);
|
|
TSet<int32>& ToConvexIndices = ToTransformToConvexIndices[ToTransformIdx];
|
|
ToConvexIndices.Reset();
|
|
|
|
int32 NewConvexIndexStart = Collection.AddElements(FromIndices.Num(), FGeometryCollection::ConvexGroup);
|
|
for (int32 FromConvexIdx : FromIndices)
|
|
{
|
|
int32 ToConvexIdx = NewConvexIndexStart++;
|
|
ToConvexIndices.Add(ToConvexIdx);
|
|
ToConvexHull[ToConvexIdx] = FromConvexHullAttribute[FromConvexIdx];
|
|
}
|
|
}
|
|
TArray<int32> ToRemoveArr = ConvexToRemove.Array();
|
|
if (!ToRemoveArr.IsEmpty())
|
|
{
|
|
ToRemoveArr.Sort();
|
|
FManagedArrayCollection::FProcessingParameters Params;
|
|
Params.bDoValidation = false; // for perf reasons
|
|
Collection.RemoveElements(FGeometryCollection::ConvexGroup, ToRemoveArr, Params);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void FGeometryCollectionConvexUtility::RemoveConvexHulls(FManagedArrayCollection* GeometryCollection, const TArray<int32>& TransformsToClearHullsFrom)
|
|
{
|
|
if (GeometryCollection->HasGroup(FGeometryCollection::ConvexGroup) && GeometryCollection->HasAttribute("TransformToConvexIndices", FTransformCollection::TransformGroup))
|
|
{
|
|
TManagedArray<TSet<int32>>& TransformToConvexIndices = GeometryCollection->ModifyAttribute<TSet<int32>>("TransformToConvexIndices", FTransformCollection::TransformGroup);
|
|
TArray<int32> ConvexIndices;
|
|
for (int32 TransformIdx : TransformsToClearHullsFrom)
|
|
{
|
|
if (TransformToConvexIndices[TransformIdx].Num() > 0)
|
|
{
|
|
for (int32 ConvexIdx : TransformToConvexIndices[TransformIdx])
|
|
{
|
|
ConvexIndices.Add(ConvexIdx);
|
|
}
|
|
TransformToConvexIndices[TransformIdx].Empty();
|
|
}
|
|
}
|
|
|
|
if (ConvexIndices.Num())
|
|
{
|
|
ConvexIndices.Sort();
|
|
FManagedArrayCollection::FProcessingParameters Params;
|
|
Params.bDoValidation = false; // for perf reasons
|
|
GeometryCollection->RemoveElements(FGeometryCollection::ConvexGroup, ConvexIndices, Params);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Delete the convex hulls that are null */
|
|
void FGeometryCollectionConvexUtility::RemoveEmptyConvexHulls(FManagedArrayCollection& Collection)
|
|
{
|
|
const FName ConvexGroupName = FGeometryCollection::ConvexGroup;
|
|
const FName ConvexAttributeName = FGeometryCollection::ConvexHullAttribute;
|
|
|
|
TManagedArrayAccessor<Chaos::FConvexPtr> ConvexHullAttribute(Collection, ConvexAttributeName, ConvexGroupName);
|
|
if (ConvexHullAttribute.IsValid())
|
|
{
|
|
TManagedArray<Chaos::FConvexPtr>& ConvexHull = ConvexHullAttribute.Modify();
|
|
|
|
// clear all null and empty hulls
|
|
TArray<int32> EmptyConvex;
|
|
for (int32 ConvexIdx = 0; ConvexIdx < ConvexHull.Num(); ConvexIdx++)
|
|
{
|
|
if (ConvexHull[ConvexIdx].IsValid())
|
|
{
|
|
if (ConvexHull[ConvexIdx]->NumVertices() == 0)
|
|
{
|
|
ConvexHull[ConvexIdx].SafeRelease();
|
|
EmptyConvex.Add(ConvexIdx);
|
|
}
|
|
}
|
|
else // (!ConvexHull[ConvexIdx].IsValid())
|
|
{
|
|
EmptyConvex.Add(ConvexIdx);
|
|
}
|
|
}
|
|
|
|
FManagedArrayCollection::FProcessingParameters Params;
|
|
Params.bDoValidation = false; // for perf reasons
|
|
Collection.RemoveElements(ConvexGroupName, EmptyConvex, Params);
|
|
}
|
|
}
|
|
|
|
void FGeometryCollectionConvexUtility::SetDefaults(FGeometryCollection* GeometryCollection, FName Group, uint32 StartSize, uint32 NumElements)
|
|
{
|
|
}
|
|
|
|
|
|
TManagedArray<int32>* FGeometryCollectionConvexUtility::GetCustomConvexFlags(FGeometryCollection* GeometryCollection, bool bAddIfMissing)
|
|
{
|
|
if (!GeometryCollection->HasAttribute("HasCustomConvex", FTransformCollection::TransformGroup))
|
|
{
|
|
if (!bAddIfMissing)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return &GeometryCollection->AddAttribute<int32>("HasCustomConvex", FTransformCollection::TransformGroup);
|
|
}
|
|
return &GeometryCollection->ModifyAttribute<int32>("HasCustomConvex", FTransformCollection::TransformGroup);
|
|
}
|
|
|
|
|
|
void FGeometryCollectionConvexUtility::CopyChildConvexes(const FGeometryCollection* FromCollection, const TArrayView<const int32>& FromTransformIdx, FGeometryCollection* ToCollection, const TArrayView<const int32>& ToTransformIdx, bool bLeafOnly)
|
|
{
|
|
const TManagedArray<TSet<int32>>* InTransformToConvexIndices = FromCollection->FindAttribute<TSet<int32>>("TransformToConvexIndices", FTransformCollection::TransformGroup);
|
|
const TManagedArray<Chaos::FConvexPtr>* InConvexHull = FromCollection->FindAttribute<Chaos::FConvexPtr>(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup);
|
|
TOptional<FGeometryCollectionConvexUtility::FGeometryCollectionConvexData> OutConvex = FGeometryCollectionConvexUtility::GetConvexHullDataIfPresent(ToCollection);
|
|
TManagedArray<int32>* OutCustomFlags = GetCustomConvexFlags(ToCollection, true);
|
|
|
|
if (!ensure(InTransformToConvexIndices != nullptr && InConvexHull != nullptr) || !ensure(OutConvex.IsSet())) // missing convex data on collection(s); cannot copy
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!ensure(FromTransformIdx.Num() == ToTransformIdx.Num())) // these arrays must be matched up
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 InNumBones = FromCollection->NumElements(FGeometryCollection::TransformGroup);
|
|
int32 LeafType = FGeometryCollection::ESimulationTypes::FST_Rigid;
|
|
TArray<TArray<int32>> Children;
|
|
Children.SetNum(InNumBones);
|
|
for (int InBone = 0; InBone < InNumBones; InBone++)
|
|
{
|
|
if (FromCollection->SimulationType[InBone] == FGeometryCollection::ESimulationTypes::FST_None)
|
|
{
|
|
continue; // skip embedded geo
|
|
}
|
|
|
|
int32 ParentBone = FromCollection->Parent[InBone];
|
|
if (ParentBone != INDEX_NONE)
|
|
{
|
|
if (FromCollection->SimulationType[ParentBone] != LeafType)
|
|
{
|
|
Children[ParentBone].Add(InBone);
|
|
}
|
|
else
|
|
{
|
|
// parent is leaf (should only happen for embedded geo, which we've already skipped above)
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto GetChildrenWithConvex = [&FromCollection, bLeafOnly, &InTransformToConvexIndices, LeafType, &Children](int32 Bone, TArray<int32>& OutChildren)
|
|
{
|
|
TArray<int32> ToExpand = Children[Bone];
|
|
if (ToExpand.IsEmpty()) // if no children, fall back to copying the convex on the bone itself
|
|
{
|
|
ToExpand.Add(Bone);
|
|
}
|
|
while (ToExpand.Num() > 0)
|
|
{
|
|
int32 ToProcess = ToExpand.Pop();
|
|
bool bIsLeaf = FromCollection->SimulationType[ToProcess] == LeafType;
|
|
if (bIsLeaf || (!bLeafOnly && (*InTransformToConvexIndices)[ToProcess].Num() > 0))
|
|
{
|
|
OutChildren.Add(ToProcess);
|
|
}
|
|
else if (Children[ToProcess].Num() > 0)
|
|
{
|
|
ToExpand.Append(Children[ToProcess]);
|
|
}
|
|
}
|
|
};
|
|
|
|
TArray<FTransform> InGlobalTransformArray, OutGlobalTransformArray;
|
|
GeometryCollectionAlgo::GlobalMatrices(FromCollection->Transform, FromCollection->Parent, InGlobalTransformArray);
|
|
if (FromCollection == ToCollection)
|
|
{
|
|
OutGlobalTransformArray = InGlobalTransformArray;
|
|
}
|
|
else
|
|
{
|
|
GeometryCollectionAlgo::GlobalMatrices(ToCollection->Transform, ToCollection->Parent, OutGlobalTransformArray);
|
|
}
|
|
|
|
// build the new convex data in separate arrays (which support incremental add)
|
|
TArray<Chaos::FConvexPtr> ConvexToAdd;
|
|
TArray<TSet<int32>> OutTransformToConvexIndices;
|
|
OutTransformToConvexIndices.SetNum(OutConvex->TransformToConvexIndices.Num());
|
|
TSet<int32> ToRemove;
|
|
|
|
for (int32 i = 0; i < ToTransformIdx.Num(); i++)
|
|
{
|
|
int32 InBone = FromTransformIdx[i];
|
|
int32 OutBone = ToTransformIdx[i];
|
|
TArray<int32> BonesWithHulls;
|
|
GetChildrenWithConvex(InBone, BonesWithHulls);
|
|
|
|
ToRemove.Add(OutBone);
|
|
(*OutCustomFlags)[OutBone] = true;
|
|
OutTransformToConvexIndices[OutBone].Reset();
|
|
for (int32 InBoneWithHulls : BonesWithHulls)
|
|
{
|
|
CopyHulls(
|
|
InGlobalTransformArray, *InConvexHull, *InTransformToConvexIndices, InBoneWithHulls,
|
|
OutGlobalTransformArray, ConvexToAdd, OutTransformToConvexIndices, OutBone);
|
|
}
|
|
}
|
|
|
|
TArray<int32> ToRemoveArr = ToRemove.Array();
|
|
ToRemoveArr.Sort();
|
|
if (ToRemoveArr.Num() > 0)
|
|
{
|
|
RemoveConvexHulls(ToCollection, ToRemoveArr);
|
|
}
|
|
|
|
int32 NewNumConvex = ToCollection->NumElements(FGeometryCollection::ConvexGroup);
|
|
|
|
for (int32 OutBone : ToTransformIdx)
|
|
{
|
|
OutConvex->TransformToConvexIndices[OutBone] = MoveTemp(OutTransformToConvexIndices[OutBone]);
|
|
for (int32& ConvexInd : OutConvex->TransformToConvexIndices[OutBone])
|
|
{
|
|
ConvexInd += NewNumConvex;
|
|
}
|
|
}
|
|
|
|
ToCollection->Resize(NewNumConvex + ConvexToAdd.Num(), FGeometryCollection::ConvexGroup);
|
|
for (int32 i = 0; i < ConvexToAdd.Num(); i++)
|
|
{
|
|
OutConvex->ConvexHull[NewNumConvex + i] = MoveTemp(ConvexToAdd[i]);
|
|
}
|
|
}
|
|
|
|
static float GetRelativeSizeDimensionFromVolume(const float Volume)
|
|
{
|
|
if (UseVolumeToComputeRelativeSize)
|
|
{
|
|
return Volume;
|
|
}
|
|
return FGenericPlatformMath::Pow(Volume, 1.0f / 3.0f);
|
|
}
|
|
|
|
void FGeometryCollectionConvexUtility::SetVolumeAttributes(FManagedArrayCollection* Collection)
|
|
{
|
|
TManagedArray<float>& Volumes = Collection->AddAttribute<float>("Volume", FTransformCollection::TransformGroup);
|
|
TManagedArray<float>& Sizes = Collection->AddAttribute<float>("Size", FTransformCollection::TransformGroup);
|
|
|
|
GeometryCollection::Facades::FCollectionMeshFacade MeshFacade(*Collection);
|
|
GeometryCollection::Facades::FCollectionTransformFacade TransformFacade(*Collection);
|
|
const TManagedArray<int32>* FoundSimulationType = Collection->FindAttributeTyped<int32>("SimulationType", FTransformCollection::TransformGroup);
|
|
if (!MeshFacade.IsValid() || !TransformFacade.IsValid() || !FoundSimulationType)
|
|
{
|
|
ensureMsgf(false, TEXT("Cannot compute Volume and Size attributes for Collection: Missing required attributes"));
|
|
return;
|
|
}
|
|
|
|
const TManagedArray<int32>& SimulationType = *FoundSimulationType;
|
|
const TManagedArray<int32>& TransformToGeometryIndex = MeshFacade.TransformToGeometryIndexAttribute.Get();
|
|
const TManagedArray<int32>& Parent = *TransformFacade.GetParents();
|
|
|
|
TArray<FTransform> Transforms;
|
|
GeometryCollectionAlgo::GlobalMatrices(*TransformFacade.FindTransforms(), Parent, Transforms);
|
|
|
|
TArray<float> GeoVolumes;
|
|
GeoVolumes.SetNumZeroed(Collection->NumElements(FGeometryCollection::GeometryGroup));
|
|
ParallelFor(GeoVolumes.Num(), [&](int32 GeoIdx)
|
|
{
|
|
int32 Bone = MeshFacade.TransformIndexAttribute[GeoIdx];
|
|
if (SimulationType[Bone] == FGeometryCollection::ESimulationTypes::FST_Rigid)
|
|
{
|
|
GeoVolumes[GeoIdx] = (float)ComputeGeometryVolume(Collection, GeoIdx, Transforms[Bone], 1.0);
|
|
}
|
|
});
|
|
|
|
float MaxGeoVolume = 0.0f;
|
|
TArray<int32> RecursiveOrder = GeometryCollectionAlgo::ComputeRecursiveOrder(*Collection);
|
|
Volumes.Fill(0.0f);
|
|
for (const int32 Bone : RecursiveOrder)
|
|
{
|
|
if (SimulationType[Bone] == FGeometryCollection::ESimulationTypes::FST_Rigid)
|
|
{
|
|
int32 GeoIdx = TransformToGeometryIndex[Bone];
|
|
if (GeoIdx == INDEX_NONE)
|
|
{
|
|
Volumes[Bone] = 0.0f;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
const float GeoVolume = GeoVolumes[GeoIdx];
|
|
MaxGeoVolume = FMath::Max(MaxGeoVolume, GeoVolume);
|
|
Volumes[Bone] = GeoVolume;
|
|
}
|
|
}
|
|
const int32 ParentIdx = Parent[Bone];
|
|
if (ParentIdx != INDEX_NONE)
|
|
{
|
|
Volumes[ParentIdx] += Volumes[Bone];
|
|
}
|
|
}
|
|
|
|
if (UseLargestClusterToComputeRelativeSize)
|
|
{
|
|
// just go over all the bones as the largest clusters will be naturally larger in volume than any of the children
|
|
for (int32 BoneIdx = 0; BoneIdx < Volumes.Num(); BoneIdx++)
|
|
{
|
|
MaxGeoVolume = FMath::Max(MaxGeoVolume, Volumes[BoneIdx]);
|
|
}
|
|
}
|
|
|
|
const float ReferenceSize = GetRelativeSizeDimensionFromVolume(MaxGeoVolume);
|
|
const float OneOverReferenceSize = MaxGeoVolume > 0 ? (1.0f / ReferenceSize) : 1.0f;
|
|
for (int32 BoneIdx = 0; BoneIdx < Volumes.Num(); BoneIdx++)
|
|
{
|
|
const float ActualSize = GetRelativeSizeDimensionFromVolume(Volumes[BoneIdx]);
|
|
Sizes[BoneIdx] = ActualSize * OneOverReferenceSize;
|
|
}
|
|
}
|
|
|
|
void FGeometryCollectionConvexUtility::ConvertImplicitToConvexArray(const Chaos::FImplicitObject& InImplicit, const FTransform& Transform, TArray<FTransformedConvex>& InOutConvex)
|
|
{
|
|
const Chaos::EImplicitObjectType PackedType = InImplicit.GetType(); // Type includes scaling and instancing data
|
|
const Chaos::EImplicitObjectType InnerType = Chaos::GetInnerType(InImplicit.GetType());
|
|
|
|
// Unwrap the wrapper/aggregating shapes
|
|
if (Chaos::IsScaled(PackedType))
|
|
{
|
|
ConvertScaledImplicitToConvexArray(InImplicit, Transform, Chaos::IsInstanced(PackedType), InOutConvex);
|
|
return;
|
|
}
|
|
|
|
if (Chaos::IsInstanced(PackedType))
|
|
{
|
|
ConvertInstancedImplicitToConvexArray(InImplicit, Transform, InOutConvex);
|
|
return;
|
|
}
|
|
|
|
if (InnerType == Chaos::ImplicitObjectType::Transformed)
|
|
{
|
|
const Chaos::TImplicitObjectTransformed<Chaos::FReal, 3>* Transformed = InImplicit.template GetObject<Chaos::TImplicitObjectTransformed<Chaos::FReal, 3>>();
|
|
const Chaos::FRigidTransform3 ImplicitTransform(
|
|
Transform.TransformPosition(Transformed->GetTransform().GetLocation()),
|
|
Transform.GetRotation() * Transformed->GetTransform().GetRotation()
|
|
);
|
|
if (const Chaos::FImplicitObject* TransformedImplicit = Transformed->GetTransformedObject())
|
|
{
|
|
ConvertImplicitToConvexArray(*TransformedImplicit, ImplicitTransform, InOutConvex);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (InnerType == Chaos::ImplicitObjectType::Union)
|
|
{
|
|
const Chaos::FImplicitObjectUnion* Union = InImplicit.template GetObject<Chaos::FImplicitObjectUnion>();
|
|
int32 UnionIdx = 0;
|
|
for (const Chaos::FImplicitObjectPtr& UnionImplicit : Union->GetObjects())
|
|
{
|
|
if (UnionImplicit)
|
|
{
|
|
ConvertImplicitToConvexArray(*UnionImplicit, Transform, InOutConvex);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (InnerType == Chaos::ImplicitObjectType::UnionClustered)
|
|
{
|
|
// unsupported - quiet exit
|
|
return;
|
|
}
|
|
|
|
// If we get here, we have an actual shape to render
|
|
switch (InnerType)
|
|
{
|
|
case Chaos::ImplicitObjectType::Sphere:
|
|
{
|
|
if (const Chaos::FSphere*Sphere = InImplicit.template GetObject<Chaos::FSphere>())
|
|
{
|
|
const FTransform SphereTransform(FQuat::Identity, FVector(Sphere->GetCenterf()), FVector(Sphere->GetRadiusf()));
|
|
const TArray<Chaos::FConvex::FVec3Type> Vertices(IcoSphere_Subdiv1, IcoSphere_Subdiv1_Num);
|
|
|
|
FTransformedConvex& TransformedConvex = InOutConvex.Emplace_GetRef();
|
|
TransformedConvex.Convex = Chaos::FConvexPtr( new Chaos::FConvex(Vertices, 0));
|
|
TransformedConvex.Transform = SphereTransform * Transform;
|
|
}
|
|
break;
|
|
}
|
|
case Chaos::ImplicitObjectType::Box:
|
|
{
|
|
if (const Chaos::TBox<Chaos::FReal, 3>*Box = InImplicit.template GetObject<Chaos::TBox<Chaos::FReal, 3>>())
|
|
{
|
|
Chaos::FVec3f A(Box->Min());
|
|
Chaos::FVec3f B(Box->Max());
|
|
TArray<Chaos::FConvex::FVec3Type> Vertices;
|
|
Vertices.Add({ A.X, A.Y, A.Z });
|
|
Vertices.Add({ B.X, A.Y, A.Z });
|
|
Vertices.Add({ A.X, B.Y, A.Z });
|
|
Vertices.Add({ B.X, B.Y, A.Z });
|
|
Vertices.Add({ A.X, A.Y, B.Z });
|
|
Vertices.Add({ B.X, A.Y, B.Z });
|
|
Vertices.Add({ A.X, B.Y, B.Z });
|
|
Vertices.Add({ B.X, B.Y, B.Z });
|
|
|
|
FTransformedConvex& TransformedConvex = InOutConvex.Emplace_GetRef();
|
|
TransformedConvex.Convex = Chaos::FConvexPtr( new Chaos::FConvex(Vertices, 0));
|
|
TransformedConvex.Transform = Transform;
|
|
}
|
|
break;
|
|
}
|
|
case Chaos::ImplicitObjectType::Capsule:
|
|
{
|
|
if (const Chaos::FCapsule* Capsule = InImplicit.template GetObject<Chaos::FCapsule>())
|
|
{
|
|
const FTransform CapsuleTransform(FRotationMatrix::MakeFromZ(FVector3d(Capsule->GetAxisf())).ToQuat(), FVector3d(Capsule->GetCenterf()));
|
|
|
|
const FVector3f HalfHeightOffset(0, 0, Capsule->GetHeightf() * 0.5f);
|
|
|
|
TArray<Chaos::FConvex::FVec3Type> Vertices;
|
|
for (int32 VtxIndex = 0; VtxIndex < IcoHemisphere_Subdiv1_Num; VtxIndex++)
|
|
{
|
|
const float CapsuleRadius = Capsule->GetRadiusf();
|
|
Vertices.Add(IcoHemisphere_Subdiv1[VtxIndex] * CapsuleRadius + HalfHeightOffset); // top hemisphere
|
|
Vertices.Add(IcoHemisphere_Subdiv1[VtxIndex] * -CapsuleRadius - HalfHeightOffset); // bottom hemisphere
|
|
}
|
|
|
|
FTransformedConvex& TransformedConvex = InOutConvex.Emplace_GetRef();
|
|
TransformedConvex.Convex = Chaos::FConvexPtr( new Chaos::FConvex(Vertices, 0));
|
|
TransformedConvex.Transform = CapsuleTransform * Transform;
|
|
}
|
|
break;
|
|
}
|
|
case Chaos::ImplicitObjectType::Convex:
|
|
{
|
|
if (const Chaos::FConvex* Convex = InImplicit.template GetObject<Chaos::FConvex>())
|
|
{
|
|
FTransformedConvex& TransformedConvex = InOutConvex.Emplace_GetRef();
|
|
TransformedConvex.Convex = Chaos::FConvexPtr(Convex->RawCopyAsConvex());
|
|
TransformedConvex.Transform = Transform;
|
|
}
|
|
break;
|
|
}
|
|
// Unsuported shape types
|
|
case Chaos::ImplicitObjectType::Plane:
|
|
case Chaos::ImplicitObjectType::LevelSet:
|
|
case Chaos::ImplicitObjectType::TaperedCylinder:
|
|
case Chaos::ImplicitObjectType::Cylinder:
|
|
case Chaos::ImplicitObjectType::TriangleMesh:
|
|
case Chaos::ImplicitObjectType::HeightField:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FGeometryCollectionConvexUtility::ConvertScaledImplicitToConvexArray(
|
|
const Chaos::FImplicitObject& Implicit,
|
|
const FTransform& WorldSpaceTransform, bool bInstanced,
|
|
TArray<FTransformedConvex>& InOutConvex)
|
|
{
|
|
const Chaos::EImplicitObjectType InnerType = Chaos::GetInnerType(Implicit.GetType());
|
|
switch (InnerType)
|
|
{
|
|
// we only support scaled / instanced convex
|
|
case Chaos::ImplicitObjectType::Convex:
|
|
{
|
|
Chaos::FRigidTransform3 ScaleTM{ Chaos::FRigidTransform3::Identity };
|
|
const Chaos::FConvex* Convex = nullptr;
|
|
if (bInstanced)
|
|
{
|
|
if (const auto ScaledInstancedConvex = Implicit.template GetObject<Chaos::TImplicitObjectScaled<Chaos::FConvex, true>>())
|
|
{
|
|
ScaleTM.SetScale3D(ScaledInstancedConvex->GetScale());
|
|
Convex = ScaledInstancedConvex->GetUnscaledObject();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (const auto ScaledConvex = Implicit.template GetObject<Chaos::TImplicitObjectScaled<Chaos::FConvex, false>>())
|
|
{
|
|
ScaleTM.SetScale3D(ScaledConvex->GetScale());
|
|
Convex = ScaledConvex->GetUnscaledObject();
|
|
}
|
|
}
|
|
if (Convex)
|
|
{
|
|
FTransformedConvex& TransformedConvex = InOutConvex.Emplace_GetRef();
|
|
TransformedConvex.Convex = Chaos::FConvexPtr(Convex->RawCopyAsConvex());
|
|
TransformedConvex.Transform = ScaleTM * WorldSpaceTransform;
|
|
}
|
|
break;
|
|
}
|
|
// unsupported types for scaled implicit
|
|
case Chaos::ImplicitObjectType::Sphere:
|
|
case Chaos::ImplicitObjectType::Box:
|
|
case Chaos::ImplicitObjectType::Plane:
|
|
case Chaos::ImplicitObjectType::Capsule:
|
|
case Chaos::ImplicitObjectType::Transformed:
|
|
case Chaos::ImplicitObjectType::Union:
|
|
case Chaos::ImplicitObjectType::LevelSet:
|
|
case Chaos::ImplicitObjectType::Unknown:
|
|
case Chaos::ImplicitObjectType::TaperedCylinder:
|
|
case Chaos::ImplicitObjectType::Cylinder:
|
|
case Chaos::ImplicitObjectType::TriangleMesh:
|
|
case Chaos::ImplicitObjectType::HeightField:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FGeometryCollectionConvexUtility::ConvertInstancedImplicitToConvexArray(
|
|
const Chaos::FImplicitObject& Implicit,
|
|
const FTransform& Transform,
|
|
TArray<FTransformedConvex>& InOutConvex)
|
|
{
|
|
const Chaos::EImplicitObjectType InnerType = Chaos::GetInnerType(Implicit.GetType());
|
|
switch (InnerType)
|
|
{
|
|
// we only support instanced convex
|
|
case Chaos::ImplicitObjectType::Convex:
|
|
{
|
|
const Chaos::TImplicitObjectInstanced<Chaos::FConvex>* Instanced = Implicit.template GetObject< Chaos::TImplicitObjectInstanced< Chaos::FConvex>>();
|
|
if (const Chaos::FConvex* Convex = Instanced->GetInstancedObject())
|
|
{
|
|
FTransformedConvex& TransformedConvex = InOutConvex.Emplace_GetRef();
|
|
TransformedConvex.Convex = Chaos::FConvexPtr(Convex->RawCopyAsConvex());
|
|
TransformedConvex.Transform = Transform;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// unsupported types for scaled implicit
|
|
case Chaos::ImplicitObjectType::Sphere:
|
|
case Chaos::ImplicitObjectType::Box:
|
|
case Chaos::ImplicitObjectType::Plane:
|
|
case Chaos::ImplicitObjectType::Capsule:
|
|
case Chaos::ImplicitObjectType::Transformed:
|
|
case Chaos::ImplicitObjectType::Union:
|
|
case Chaos::ImplicitObjectType::LevelSet:
|
|
case Chaos::ImplicitObjectType::Unknown:
|
|
case Chaos::ImplicitObjectType::TaperedCylinder:
|
|
case Chaos::ImplicitObjectType::Cylinder:
|
|
case Chaos::ImplicitObjectType::TriangleMesh:
|
|
case Chaos::ImplicitObjectType::HeightField:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// local (static) helpers for the below Convex Utility code
|
|
namespace
|
|
{
|
|
// Helpful struct to represent a convex hull with a local editable representation, to perform intersections / clipping on it
|
|
struct FHullPolygons
|
|
{
|
|
// simple packed representation for convex hull faces, where each polygon's indices are listed sequentially,
|
|
// and negative values indicate the number of vertices in the next polygon. If no negative value is listed, polygon is a triangle.
|
|
TArray<int32> PackedPolygons;
|
|
|
|
// Copy of hull vertices, to be refined through plane cuts
|
|
TArray<Chaos::FVec3f> Vertices;
|
|
Chaos::FAABB3f Bounds;
|
|
|
|
void Reset()
|
|
{
|
|
Vertices.Reset();
|
|
PackedPolygons.Reset();
|
|
Bounds = Chaos::FAABB3f::EmptyAABB();
|
|
}
|
|
|
|
FHullPolygons() = default;
|
|
|
|
FHullPolygons(const Chaos::FConvex& HullIn)
|
|
{
|
|
Init(HullIn);
|
|
}
|
|
|
|
void Init(const Chaos::FConvex& HullIn)
|
|
{
|
|
Reset();
|
|
Vertices = HullIn.GetVertices();
|
|
const Chaos::FConvexStructureData& HullData = HullIn.GetStructureData();
|
|
int32 NumPlanes = HullIn.NumPlanes();
|
|
PackedPolygons.Reserve(NumPlanes * 3);
|
|
for (int PlaneIdx = 0; PlaneIdx < NumPlanes; PlaneIdx++)
|
|
{
|
|
int32 NumPlaneVerts = HullData.NumPlaneVertices(PlaneIdx);
|
|
if (NumPlaneVerts > 3)
|
|
{
|
|
PackedPolygons.Add(-NumPlaneVerts);
|
|
}
|
|
for (int32 PlaneVertexIdx = 0; PlaneVertexIdx < NumPlaneVerts; PlaneVertexIdx++)
|
|
{
|
|
PackedPolygons.Add(HullData.GetPlaneVertex(PlaneIdx, PlaneVertexIdx));
|
|
}
|
|
}
|
|
Bounds = HullIn.GetLocalBoundingBox();
|
|
}
|
|
|
|
void Intersect(const Chaos::FConvex& OtherHull, float ExpandAmount)
|
|
{
|
|
// Arrays to store intermediate plane cut data
|
|
TArray<int32> NewPolygons;
|
|
TMap<FIntVector2, int> NewVertices; // mapping from edges to new vertices
|
|
TArray<float> SignedDist; // signed distance from vertices to a cutting plane
|
|
TArray<int32> VertexRemap;
|
|
TMap<int32, int32> OpenEdgeVertMap;
|
|
|
|
// Cut by the plane through PlanePt with PlaneNormal. Note: Must pre-apply ExpandAmount; we do not assume the plane is shifted here.
|
|
// Uses the intermediate data above as temp storage
|
|
auto PlaneCut = [&NewPolygons, &NewVertices, &SignedDist, &VertexRemap, &OpenEdgeVertMap, this](Chaos::FVec3f PlanePt, Chaos::FVec3f PlaneNormal)
|
|
{
|
|
NewPolygons.Reset(PackedPolygons.Num());
|
|
NewVertices.Reset();
|
|
OpenEdgeVertMap.Reset();
|
|
SignedDist.SetNum(Vertices.Num(), EAllowShrinking::No);
|
|
VertexRemap.SetNum(Vertices.Num(), EAllowShrinking::No);
|
|
int32 OpenEdgeStart = -1;
|
|
|
|
// Possible optimization: if many vertices, check plane vs AABB corners first?
|
|
|
|
int32 OutTotal = 0;
|
|
for (int32 VertIdx = 0; VertIdx < Vertices.Num(); ++VertIdx)
|
|
{
|
|
float SD = (float)(Vertices[VertIdx] - PlanePt).Dot(PlaneNormal);
|
|
SignedDist[VertIdx] = SD;
|
|
OutTotal += int32(SD > 0);
|
|
}
|
|
// hull is fully outside plane's half-space
|
|
if (OutTotal == Vertices.Num())
|
|
{
|
|
Vertices.Empty();
|
|
PackedPolygons.Empty();
|
|
return;
|
|
}
|
|
// hull is fully inside plane's half-space
|
|
if (OutTotal == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int32 Idx = 0, PolyLen = 3; Idx < PackedPolygons.Num(); Idx += PolyLen)
|
|
{
|
|
// extract length of current polygon
|
|
PolyLen = 3;
|
|
int32 OrigStart = Idx;
|
|
if (PackedPolygons[Idx] < 0)
|
|
{
|
|
PolyLen = -PackedPolygons[Idx];
|
|
Idx++;
|
|
}
|
|
int32 Start = Idx;
|
|
|
|
// helper to convert index within polygon to index within vertices array
|
|
auto ToV = [this, Start, PolyLen](int32 SubIdx) -> int32
|
|
{
|
|
checkSlow(SubIdx >= 0 && SubIdx < PolyLen);
|
|
int32 VertIdx = PackedPolygons[Start + SubIdx];
|
|
checkSlow(VertIdx >= 0);
|
|
return VertIdx;
|
|
};
|
|
// track where the polygon crosses the plane to decide what to do with it
|
|
int32 OutCount = 0;
|
|
int32 FirstIn = -1, FirstOut = -1, LastIn = -1;
|
|
for (int32 SubIdx = 0; SubIdx < PolyLen; ++SubIdx)
|
|
{
|
|
float SD = SignedDist[ToV(SubIdx)];
|
|
bool IsOut = SD > 0;
|
|
if (FirstIn == -1)
|
|
{
|
|
if (!IsOut)
|
|
{
|
|
FirstIn = SubIdx;
|
|
}
|
|
}
|
|
else if (FirstOut == -1 && IsOut)
|
|
{
|
|
LastIn = SubIdx - 1;
|
|
FirstOut = SubIdx;
|
|
}
|
|
OutCount += int32(IsOut);
|
|
}
|
|
if (FirstOut == -1)
|
|
{
|
|
FirstOut = 0;
|
|
LastIn = PolyLen - 1;
|
|
}
|
|
if (OutCount == PolyLen)
|
|
{
|
|
continue;
|
|
}
|
|
if (OutCount == 0)
|
|
{
|
|
// copy original polygon data
|
|
for (int32 CopyIdx = OrigStart; CopyIdx < Start + PolyLen; ++CopyIdx)
|
|
{
|
|
NewPolygons.Add(PackedPolygons[CopyIdx]);
|
|
}
|
|
continue;
|
|
}
|
|
int32 NewPolyLen = LastIn + 1 - FirstIn;
|
|
if (FirstIn == 0)
|
|
{
|
|
int32 WalkBack = PolyLen - 1;
|
|
while (WalkBack > 0 && SignedDist[ToV(WalkBack)] <= 0)
|
|
{
|
|
FirstIn = WalkBack--;
|
|
NewPolyLen++;
|
|
}
|
|
}
|
|
auto GetCrossVertIdx = [&NewVertices, &SignedDist, this](int32 InsideVertIdx, int32 OutsideVertIdx) -> int32
|
|
{
|
|
checkSlow(InsideVertIdx != OutsideVertIdx);
|
|
// If within zero tolerance of plane, snap to plane
|
|
float InsideSD = SignedDist[InsideVertIdx];
|
|
checkSlow(InsideSD <= 0);
|
|
if (InsideSD > -FMathf::ZeroTolerance)
|
|
{
|
|
return -1;
|
|
}
|
|
FIntVector2 Key(InsideVertIdx, OutsideVertIdx);
|
|
int32* FoundVert = NewVertices.Find(Key);
|
|
if (!FoundVert)
|
|
{
|
|
float OutsideSD = SignedDist[OutsideVertIdx];
|
|
checkSlow(OutsideSD >= 0);
|
|
Chaos::FVec3f NewVert = FMath::Lerp(Vertices[InsideVertIdx], Vertices[OutsideVertIdx], InsideSD / (InsideSD - OutsideSD));
|
|
int32 NewVertIdx = Vertices.Add(NewVert);
|
|
NewVertices.Add(Key, NewVertIdx);
|
|
return NewVertIdx;
|
|
}
|
|
return *FoundVert;
|
|
};
|
|
int32 FirstCross = GetCrossVertIdx(ToV(FirstIn), ToV((FirstIn + PolyLen - 1) % PolyLen));
|
|
int32 LastCross = GetCrossVertIdx(ToV(LastIn), ToV(FirstOut));
|
|
int32 OpenPlaneEdgeVA = FirstCross;
|
|
int32 OpenPlaneEdgeVB = LastCross;
|
|
if (FirstCross != -1)
|
|
{
|
|
NewPolyLen++;
|
|
}
|
|
else
|
|
{
|
|
OpenPlaneEdgeVA = ToV(FirstIn);
|
|
}
|
|
if (LastCross != -1)
|
|
{
|
|
NewPolyLen++;
|
|
}
|
|
else
|
|
{
|
|
OpenPlaneEdgeVB = ToV(LastIn);
|
|
}
|
|
|
|
if (NewPolyLen < 2) // single co-incident vertex; no open edge here
|
|
{
|
|
continue;
|
|
}
|
|
OpenEdgeStart = OpenPlaneEdgeVB;
|
|
OpenEdgeVertMap.Add(OpenPlaneEdgeVA, OpenPlaneEdgeVB);
|
|
if (NewPolyLen == 2)
|
|
{
|
|
continue;
|
|
}
|
|
if (NewPolyLen > 3)
|
|
{
|
|
NewPolygons.Add(-NewPolyLen);
|
|
}
|
|
int32 NewPolygonStart = NewPolygons.Num();
|
|
if (FirstCross != -1)
|
|
{
|
|
NewPolygons.Add(FirstCross);
|
|
}
|
|
int32 AddStart = FirstIn;
|
|
if (FirstIn > LastIn)
|
|
{
|
|
for (int32 SubIdx = FirstIn; SubIdx < PolyLen; ++SubIdx)
|
|
{
|
|
NewPolygons.Add(ToV(SubIdx));
|
|
}
|
|
AddStart = 0;
|
|
}
|
|
for (int32 SubIdx = AddStart; SubIdx <= LastIn; ++SubIdx)
|
|
{
|
|
NewPolygons.Add(ToV(SubIdx));
|
|
}
|
|
if (LastCross != -1)
|
|
{
|
|
NewPolygons.Add(LastCross);
|
|
}
|
|
|
|
check(NewPolygons.Num() - NewPolygonStart == NewPolyLen);
|
|
}
|
|
|
|
// add the closing polygon
|
|
if (OpenEdgeStart != -1 && OpenEdgeVertMap.Num() > 2)
|
|
{
|
|
int32 OrigEnd = NewPolygons.Num();
|
|
int32 PolyEdges = OpenEdgeVertMap.Num();
|
|
if (PolyEdges > 3)
|
|
{
|
|
NewPolygons.Add(-PolyEdges);
|
|
}
|
|
int32 TraverseIdx = OpenEdgeStart;
|
|
int32 Added = 0;
|
|
do
|
|
{
|
|
NewPolygons.Add(TraverseIdx);
|
|
int32* FoundNext = OpenEdgeVertMap.Find(TraverseIdx);
|
|
if (!FoundNext)
|
|
{
|
|
break;
|
|
}
|
|
TraverseIdx = *FoundNext;
|
|
Added++;
|
|
} while (TraverseIdx != OpenEdgeStart && Added < PolyEdges);
|
|
if (Added != PolyEdges || TraverseIdx != OpenEdgeStart)
|
|
{
|
|
// failsafe if we didn't find a closed loop covering all edges:
|
|
// add a triangle fan closing off the edges that we did find
|
|
NewPolygons.SetNum(OrigEnd, EAllowShrinking::No);
|
|
Chaos::FVec3f Center(0, 0, 0);
|
|
float CenterWt = 0;
|
|
int32 CenterIdx = Vertices.Num();
|
|
for (TPair<int32, int32> KV : OpenEdgeVertMap)
|
|
{
|
|
NewPolygons.Add(KV.Key);
|
|
NewPolygons.Add(KV.Value);
|
|
NewPolygons.Add(CenterIdx);
|
|
Center += Vertices[KV.Key];
|
|
CenterWt += 1.f;
|
|
}
|
|
Center /= CenterWt;
|
|
Vertices.Add(Center);
|
|
}
|
|
}
|
|
|
|
// NewPolygons now contains the updated polygon data
|
|
Swap(PackedPolygons, NewPolygons);
|
|
// Compress the vertex array to only include the vertices that weren't outside
|
|
// and track how the indices were remapped
|
|
int32 NumKept = 0;
|
|
const int32 OldVertCount = SignedDist.Num();
|
|
for (int32 OldV = 0; OldV < OldVertCount; ++OldV)
|
|
{
|
|
if (SignedDist[OldV] <= 0)
|
|
{
|
|
int32 UseNewV = NumKept++;
|
|
VertexRemap[OldV] = UseNewV;
|
|
checkSlow(OldV >= UseNewV);
|
|
Vertices[UseNewV] = Vertices[OldV];
|
|
}
|
|
}
|
|
// Translate back the new vertices
|
|
if (NumKept < OldVertCount)
|
|
{
|
|
for (int32 OldIdx = OldVertCount, AddedIdx = 0; OldIdx < Vertices.Num(); ++OldIdx, ++AddedIdx)
|
|
{
|
|
Vertices[NumKept + AddedIdx] = Vertices[OldIdx];
|
|
}
|
|
int32 NumNew = Vertices.Num() - OldVertCount;
|
|
Vertices.SetNum(NumKept + NumNew, EAllowShrinking::No);
|
|
}
|
|
// Update the polygons w/ the compressed vertex indices
|
|
for (int32& VIdx : PackedPolygons)
|
|
{
|
|
if (VIdx >= 0) // Only remap vertices, not polygon sizes
|
|
{
|
|
if (VIdx < OldVertCount)
|
|
{
|
|
VIdx = VertexRemap[VIdx];
|
|
}
|
|
else // newly-created vertices are kept in the same order at the end of the array
|
|
{
|
|
VIdx = NumKept + (VIdx - OldVertCount);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// TODO: For performance, consider also pre-cutting with (some of) OtherHull's (expanded) bounding box planes
|
|
//Chaos::FConvex::FAABB3Type OtherBounds = OtherHull.GetLocalBoundingBox();
|
|
//OtherBounds.Thicken(ExpandAmount);
|
|
|
|
// Cut with each convex plane
|
|
const int32 NumPlanes = OtherHull.NumPlanes();
|
|
for (int32 PlaneIdx = 0; PlaneIdx < NumPlanes; ++PlaneIdx)
|
|
{
|
|
Chaos::TPlaneConcrete<float, 3> Plane = OtherHull.GetPlaneRaw(PlaneIdx);
|
|
Chaos::FVec3f N = Plane.Normal();
|
|
Chaos::FVec3f X = Plane.X();
|
|
X += N * ExpandAmount;
|
|
PlaneCut(X, N);
|
|
}
|
|
|
|
// When ExpandAmount is positive, also clip the hull at offsets of average edge planes for 'sharp' edges
|
|
if (ExpandAmount > 0)
|
|
{
|
|
const int32 NumEdges = OtherHull.NumEdges();
|
|
for (int32 EdgeIdx = 0; EdgeIdx < NumEdges; ++EdgeIdx)
|
|
{
|
|
Chaos::TPlaneConcrete<float, 3> EPlane0 = OtherHull.GetPlaneRaw(OtherHull.GetEdgePlane(EdgeIdx, 0));
|
|
Chaos::TPlaneConcrete<float, 3> EPlane1 = OtherHull.GetPlaneRaw(OtherHull.GetEdgePlane(EdgeIdx, 1));
|
|
float NormalDot = EPlane0.Normal().Dot(EPlane1.Normal());
|
|
if (NormalDot < -.1) // add an extra plane when not doing so would leave ~1.5x more space than the expected offset across from the edge, due to the miter
|
|
{
|
|
Chaos::FVec3f AvgNormal = EPlane0.Normal() + EPlane1.Normal();
|
|
if (AvgNormal.Normalize())
|
|
{
|
|
Chaos::FVec3f EdgeVert = OtherHull.GetVertex(OtherHull.GetEdgeVertex(EdgeIdx, 0));
|
|
PlaneCut(EdgeVert + AvgNormal * ExpandAmount, AvgNormal);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
float ComputeArea()
|
|
{
|
|
float Area = 0;
|
|
for (int32 Idx = 0, PolyLen = 3; Idx < PackedPolygons.Num(); Idx += PolyLen)
|
|
{
|
|
// extract length of current polygon
|
|
PolyLen = 3;
|
|
if (PackedPolygons[Idx] < 0)
|
|
{
|
|
PolyLen = -PackedPolygons[Idx];
|
|
Idx++;
|
|
}
|
|
int32 Start = Idx;
|
|
|
|
// Add area of triangle fan covering the polygon
|
|
Chaos::FVec3f V0 = Vertices[PackedPolygons[Start]];
|
|
for (int32 SubIdx = 1; SubIdx + 1 < PolyLen; ++SubIdx)
|
|
{
|
|
Chaos::FVec3f V1 = Vertices[PackedPolygons[Start + SubIdx]];
|
|
Chaos::FVec3f V2 = Vertices[PackedPolygons[Start + SubIdx + 1]];
|
|
Area += UE::Geometry::VectorUtil::Area<float>(V0, V1, V2);
|
|
}
|
|
}
|
|
return Area;
|
|
}
|
|
|
|
void EstimateSharpContact(const Chaos::FConvex* HullA, const Chaos::FConvex* HullB, float& OutSharpContact, float& OutMaxSharpContact)
|
|
{
|
|
UE::Geometry::FExtremePoints3f ExtremePts(Vertices.Num(), [this](int32 Idx) {return Vertices[Idx];});
|
|
if (ExtremePts.Dimension < 1)
|
|
{
|
|
OutSharpContact = 0;
|
|
OutMaxSharpContact = 1;
|
|
return; // degenerate/empty contact
|
|
}
|
|
UE::Geometry::FInterval1f IntersectionIntervals[2];
|
|
UE::Geometry::FInterval1f HullAIntervals[2], HullBIntervals[2];
|
|
if (ExtremePts.Dimension > 1)
|
|
{
|
|
auto SetIntervals = [&ExtremePts](const TArray<Chaos::FVec3f>& UseVertices, UE::Geometry::FInterval1f* Intervals) -> void
|
|
{
|
|
for (FVector3f Vertex : UseVertices)
|
|
{
|
|
Intervals[0].Contain(Vertex.Dot(ExtremePts.Basis[1]));
|
|
if (ExtremePts.Dimension > 2)
|
|
{
|
|
Intervals[1].Contain(Vertex.Dot(ExtremePts.Basis[2]));
|
|
}
|
|
}
|
|
};
|
|
SetIntervals(Vertices, IntersectionIntervals);
|
|
SetIntervals(HullA->GetVertices(), HullAIntervals);
|
|
SetIntervals(HullB->GetVertices(), HullBIntervals);
|
|
}
|
|
auto IntervalsMaxLen = [](UE::Geometry::FInterval1f* Intervals)
|
|
{
|
|
return FMath::Max(Intervals[0].Length(), Intervals[1].Length());
|
|
};
|
|
OutSharpContact = IntervalsMaxLen(IntersectionIntervals);
|
|
OutMaxSharpContact = FMath::Min(IntervalsMaxLen(HullAIntervals), IntervalsMaxLen(HullBIntervals));
|
|
}
|
|
};
|
|
|
|
static float ComputeHullArea(const Chaos::FConvex& Hull)
|
|
{
|
|
float Area = 0;
|
|
const Chaos::FConvexStructureData& HullData = Hull.GetStructureData();
|
|
int32 NumPlanes = Hull.NumPlanes();
|
|
for (int PlaneIdx = 0; PlaneIdx < NumPlanes; PlaneIdx++)
|
|
{
|
|
int32 NumPlaneVerts = HullData.NumPlaneVertices(PlaneIdx);
|
|
Chaos::FVec3f V0 = Hull.GetVertex(HullData.GetPlaneVertex(PlaneIdx, 0));
|
|
for (int32 PlaneVertexIdx = 1; PlaneVertexIdx + 1 < NumPlaneVerts; PlaneVertexIdx++)
|
|
{
|
|
Chaos::FVec3f V1 = Hull.GetVertex(HullData.GetPlaneVertex(PlaneIdx, PlaneVertexIdx));
|
|
Chaos::FVec3f V2 = Hull.GetVertex(HullData.GetPlaneVertex(PlaneIdx, PlaneVertexIdx + 1));
|
|
Area += UE::Geometry::VectorUtil::Area<float>(V0, V1, V2);
|
|
}
|
|
}
|
|
return Area;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
namespace UE::GeometryCollectionConvexUtility
|
|
{
|
|
|
|
|
|
void HullIntersectionStats(const Chaos::FConvex* HullA, const Chaos::FConvex* HullB, float HullBExpansion, float& OutArea, float& OutMaxArea, float& OutSharpContact, float& OutMaxSharpContact)
|
|
{
|
|
FHullPolygons HullPolygons(*HullA);
|
|
HullPolygons.Intersect(*HullB, HullBExpansion);
|
|
OutArea = HullPolygons.ComputeArea();
|
|
// The maximum intersection area is ~ the minimum of the two hull areas
|
|
float MaxIntersectionArea = FMath::Min(ComputeHullArea(*HullA), ComputeHullArea(*HullB));
|
|
OutMaxArea = MaxIntersectionArea;
|
|
HullPolygons.EstimateSharpContact(HullA, HullB, OutSharpContact, OutMaxSharpContact);
|
|
}
|
|
|
|
void IntersectConvexHulls(Chaos::FConvex* ResultHull, const Chaos::FConvex* ClipHull, float ClipHullOffset, const Chaos::FConvex* UpdateHull, const FTransform* ClipHullTransform, const FTransform* UpdateHullTransform, const FTransform* ResultTransform, double SimplificationDistanceThreshold)
|
|
{
|
|
FHullPolygons HullPolygons(*ClipHull);
|
|
bool bNeedRecomputeBounds = false;
|
|
if (ClipHullTransform)
|
|
{
|
|
bNeedRecomputeBounds = true;
|
|
for (Chaos::FVec3f& V : HullPolygons.Vertices)
|
|
{
|
|
V = (Chaos::FVec3f)ClipHullTransform->TransformPosition((FVector)V);
|
|
}
|
|
}
|
|
if (UpdateHullTransform)
|
|
{
|
|
bNeedRecomputeBounds = true;
|
|
for (Chaos::FVec3f& V : HullPolygons.Vertices)
|
|
{
|
|
V = (Chaos::FVec3f)UpdateHullTransform->InverseTransformPosition((FVector)V);
|
|
}
|
|
}
|
|
if (bNeedRecomputeBounds)
|
|
{
|
|
HullPolygons.Bounds = Chaos::FAABB3f::EmptyAABB();
|
|
for (Chaos::FVec3f& V : HullPolygons.Vertices)
|
|
{
|
|
HullPolygons.Bounds.GrowToInclude(V);
|
|
}
|
|
}
|
|
HullPolygons.Intersect(*UpdateHull, ClipHullOffset);
|
|
if (ResultTransform)
|
|
{
|
|
for (Chaos::FVec3f& V : HullPolygons.Vertices)
|
|
{
|
|
V = (Chaos::FVec3f)ResultTransform->TransformPosition((FVector)V);
|
|
}
|
|
}
|
|
FilterHullPoints(HullPolygons.Vertices, SimplificationDistanceThreshold);
|
|
*ResultHull = Chaos::FConvex(HullPolygons.Vertices, UpdateHull->GetMargin());
|
|
}
|
|
|
|
bool CHAOS_API GetExistingConvexHullsInSharedSpace(const FManagedArrayCollection* Collection, FConvexHulls& OutConvexHulls, bool bLeafOnly)
|
|
{
|
|
if (!Collection->HasAttribute("TransformToConvexIndices", FTransformCollection::TransformGroup) ||
|
|
!Collection->HasAttribute(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const TManagedArray<TSet<int32>>& OrigTransformToConvexIndices = Collection->GetAttribute<TSet<int32>>("TransformToConvexIndices", FTransformCollection::TransformGroup);
|
|
const TManagedArray<Chaos::FConvexPtr>& OrigConvexHulls = Collection->GetAttribute<Chaos::FConvexPtr>(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup);
|
|
const TManagedArray<int32>* SimulationType = Collection->FindAttribute<int32>("SimulationType", FTransformCollection::TransformGroup);
|
|
if (bLeafOnly && !SimulationType)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const int32 NumTransform = OrigTransformToConvexIndices.Num();
|
|
OutConvexHulls.TransformToHullsIndices.SetNum(NumTransform);
|
|
|
|
GeometryCollection::Facades::FCollectionTransformFacade TransformFacade(*Collection);
|
|
TArray<FTransform> GlobalTransformArray = TransformFacade.ComputeCollectionSpaceTransforms();
|
|
|
|
TArray<int32> NewConvexToTransformIndices;
|
|
TArray<int32> NewConvexToOrigConvexIndices;
|
|
NewConvexToTransformIndices.Reserve(OrigConvexHulls.Num());
|
|
NewConvexToOrigConvexIndices.Reserve(OrigConvexHulls.Num());
|
|
int32 NumNewConvex = 0;
|
|
for (int32 TransformIdx = 0; TransformIdx < OrigTransformToConvexIndices.Num(); ++TransformIdx)
|
|
{
|
|
if (bLeafOnly && (*SimulationType)[TransformIdx] != FGeometryCollection::ESimulationTypes::FST_Rigid)
|
|
{
|
|
continue;
|
|
}
|
|
for (int32 OrigConvexIdx : OrigTransformToConvexIndices[TransformIdx])
|
|
{
|
|
int32 NewConvexIdx = NumNewConvex++;
|
|
NewConvexToTransformIndices.Add(TransformIdx);
|
|
NewConvexToOrigConvexIndices.Add(OrigConvexIdx);
|
|
checkSlow(NewConvexToTransformIndices.Num() == NumNewConvex);
|
|
checkSlow(NewConvexToOrigConvexIndices.Num() == NumNewConvex);
|
|
OutConvexHulls.TransformToHullsIndices[TransformIdx].Add(NewConvexIdx);
|
|
}
|
|
}
|
|
|
|
OutConvexHulls.Hulls.SetNum(NumNewConvex);
|
|
OutConvexHulls.OverlapRemovalShrinkPercent = 0;
|
|
OutConvexHulls.Pivots.Reset(); // Note: No scaling is applied so pivots are not used
|
|
|
|
ParallelFor(NewConvexToOrigConvexIndices.Num(), [&](int32 NewConvexIdx)
|
|
{
|
|
int32 TransformIdx = NewConvexToTransformIndices[NewConvexIdx];
|
|
FTransform Transform = GlobalTransformArray[TransformIdx];
|
|
int32 OrigConvexIdx = NewConvexToOrigConvexIndices[NewConvexIdx];
|
|
TArray<Chaos::FConvex::FVec3Type> HullPts;
|
|
HullPts.Reserve(OrigConvexHulls[OrigConvexIdx]->GetVertices().Num());
|
|
for (const Chaos::FConvex::FVec3Type& P : OrigConvexHulls[OrigConvexIdx]->GetVertices())
|
|
{
|
|
FVector PVec(P);
|
|
HullPts.Add((Chaos::FConvex::FVec3Type)(Transform.TransformPosition(PVec)));
|
|
}
|
|
OutConvexHulls.Hulls[NewConvexIdx] = new Chaos::FConvex(HullPts, UE_KINDA_SMALL_NUMBER);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
}
|