Files
UnrealEngine/Engine/Plugins/Runtime/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/MeshSimpleShapeApproximation.cpp
2025-05-18 13:04:45 +08:00

669 lines
20 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ShapeApproximation/MeshSimpleShapeApproximation.h"
#include "Async/ParallelFor.h"
#include "MinVolumeSphere3.h"
#include "MinVolumeBox3.h"
#include "FitCapsule3.h"
#include "CompGeom/ConvexDecomposition3.h"
//#include "DynamicMesh/DynamicMeshAABBTree3.h"
#include "Implicit/SweepingMeshSDF.h"
#include "ShapeApproximation/ShapeDetection3.h"
#include "MeshQueries.h"
#include "Operations/MeshConvexHull.h"
#include "Operations/MeshProjectionHull.h"
#include "Util/ProgressCancel.h"
#include "MeshSimplification.h"
#define LOCTEXT_NAMESPACE "MeshSimpleShapeApproximation"
using namespace UE::Geometry;
namespace FMeshSimpleShapeApproximationLocals
{
// Take the box, and while preserving its shape in space, swap its axes around so that they
// are pointed roughly toward the world x/y/z. This makes it behave more intuitively under
// transformations (for instance, scaling Z grows roughly in the Z direction rather than
// to the side if the original box was actually on its "side").
FOrientedBox3d ReparameterizeBoxCloserToWorldFrame(const FOrientedBox3d& Box)
{
FVector3d Axes[3]{ Box.AxisX(), Box.AxisY(), Box.AxisZ() };
int32 BestZ = FMath::Max3Index(
FMath::Abs(Axes[0].Z),
FMath::Abs(Axes[1].Z),
FMath::Abs(Axes[2].Z));
FVector3d NewZ = Axes[BestZ] * (Axes[BestZ].Z > 0 ? 1 : -1);
// Pick the best Y out of the two remaining
double DotY[3]{
Axes[0].Y,
Axes[1].Y,
Axes[2].Y
};
DotY[BestZ] = 0; // don't pick this one
int32 BestY = FMath::Max3Index(
FMath::Abs(DotY[0]),
FMath::Abs(DotY[1]),
FMath::Abs(DotY[2]));
FVector3d NewY = Axes[BestY] * (DotY[BestY] > 0 ? 1 : -1);
// Sum of BestY and BestZ will be either 0+1=1, 0+2=2, or 1+2=3, and the
// corresponding leftover index will be 2, 1, or 0.
int32 BestX = 3 - (BestY + BestZ);
// Static analyzer probably won't like that, so here's a clamp for safety
BestX = FMath::Clamp(BestX, 0, 2);
// Sanity check to make sure we picked unique axes
if (!ensure(BestX != BestY
&& BestY != BestZ
&& BestX != BestZ))
{
return Box;
}
return FOrientedBox3d (
FFrame3d(Box.Frame.Origin,
NewY.Cross(NewZ), // better to do this than risk picking the wrong direction for BestX
NewY,
NewZ),
FVector3d(Box.Extents[BestX], Box.Extents[BestY], Box.Extents[BestZ]));
}
}
void FMeshSimpleShapeApproximation::DetectAndCacheSimpleShapeType(const FDynamicMesh3* SourceMesh, FSourceMeshCache& CacheOut)
{
if (UE::Geometry::IsBoxMesh(*SourceMesh, CacheOut.DetectedBox))
{
CacheOut.DetectedType = EDetectedSimpleShapeType::Box;
}
else if (UE::Geometry::IsSphereMesh(*SourceMesh, CacheOut.DetectedSphere))
{
CacheOut.DetectedType = EDetectedSimpleShapeType::Sphere;
}
else if (UE::Geometry::IsCapsuleMesh(*SourceMesh, CacheOut.DetectedCapsule))
{
CacheOut.DetectedType = EDetectedSimpleShapeType::Capsule;
}
}
void FMeshSimpleShapeApproximation::InitializeSourceMeshes(const TArray<const FDynamicMesh3*>& InputMeshSet)
{
SourceMeshes = InputMeshSet;
SourceMeshCaches.Reset();
SourceMeshCaches.SetNum(SourceMeshes.Num());
ParallelFor(SourceMeshes.Num(), [&](int32 k) {
DetectAndCacheSimpleShapeType( SourceMeshes[k], SourceMeshCaches[k]);
});
}
bool FMeshSimpleShapeApproximation::GetDetectedSimpleShape(
const FSourceMeshCache& Cache,
FSimpleShapeSet3d& ShapeSetOut,
FCriticalSection& ShapeSetLock)
{
using namespace FMeshSimpleShapeApproximationLocals;
if (Cache.DetectedType == EDetectedSimpleShapeType::Sphere && bDetectSpheres)
{
ShapeSetLock.Lock();
ShapeSetOut.Spheres.Add(Cache.DetectedSphere);
ShapeSetLock.Unlock();
return true;
}
else if (Cache.DetectedType == EDetectedSimpleShapeType::Box && bDetectBoxes)
{
FOrientedBox3d BoxToUse = ReparameterizeBoxCloserToWorldFrame(Cache.DetectedBox);
ShapeSetLock.Lock();
ShapeSetOut.Boxes.Add(BoxToUse);
ShapeSetLock.Unlock();
return true;
}
else if (Cache.DetectedType == EDetectedSimpleShapeType::Capsule && bDetectCapsules)
{
ShapeSetLock.Lock();
ShapeSetOut.Capsules.Add(Cache.DetectedCapsule);
ShapeSetLock.Unlock();
return true;
}
return false;
}
namespace UE {
namespace Geometry {
struct FSimpleShapeFitsResult
{
bool bHaveSphere = false;
UE::Geometry::FSphere3d Sphere;
bool bHaveBox = false;
FOrientedBox3d Box;
bool bHaveCapsule = false;
FCapsule3d Capsule;
bool bHaveConvex = false;
FDynamicMesh3 Convex;
};
static void ComputeSimpleShapeFits(const FDynamicMesh3& Mesh,
bool bSphere, bool bBox, bool bCapsule, double MinDimension, bool bUseExactComputationForBox,
FSimpleShapeFitsResult& FitResult,
FProgressCancel* Progress = nullptr)
{
TArray<int32> ToLinear, FromLinear;
if (bSphere || bBox || bCapsule)
{
FromLinear.SetNum(Mesh.VertexCount());
int32 LinearIndex = 0;
for (int32 vid : Mesh.VertexIndicesItr())
{
FromLinear[LinearIndex++] = vid;
}
}
FitResult.bHaveBox = false;
if (bBox)
{
FMinVolumeBox3d MinBoxCalc;
bool bMinBoxOK = MinBoxCalc.Solve(FromLinear.Num(),
[&](int32 Index) { return Mesh.GetVertex(FromLinear[Index]); }, bUseExactComputationForBox, Progress);
if (bMinBoxOK && MinBoxCalc.IsSolutionAvailable())
{
FitResult.bHaveBox = true;
MinBoxCalc.GetResult(FitResult.Box);
double MinHalfDimension = MinDimension * .5;
for (int32 SubIdx = 0; SubIdx < 3; ++SubIdx)
{
FitResult.Box.Extents[SubIdx] = FMath::Max(MinHalfDimension, FitResult.Box.Extents[SubIdx]);
}
}
}
FitResult.bHaveSphere = false;
if (bSphere)
{
FMinVolumeSphere3d MinSphereCalc;
bool bMinSphereOK = MinSphereCalc.Solve(FromLinear.Num(),
[&](int32 Index) { return Mesh.GetVertex(FromLinear[Index]); });
if (bMinSphereOK && MinSphereCalc.IsSolutionAvailable())
{
FitResult.bHaveSphere = true;
MinSphereCalc.GetResult(FitResult.Sphere);
FitResult.Sphere.Radius = FMath::Max(MinDimension * .5, FitResult.Sphere.Radius);
}
}
FitResult.bHaveCapsule = false;
if (bCapsule)
{
FitResult.bHaveCapsule = TFitCapsule3<double>::Solve(FromLinear.Num(),
[&](int32 Index) { return Mesh.GetVertex(FromLinear[Index]); }, FitResult.Capsule);
if (FitResult.bHaveCapsule)
{
FitResult.Capsule.Radius = FMath::Max(MinDimension * .5, FitResult.Capsule.Radius);
// Note: No need to clamp Length based on MinDimension; a capsule's min dimension can't be along its length since a capsule w/ length of zero is still a sphere ...
}
}
}
}
}
void FMeshSimpleShapeApproximation::Generate_AlignedBoxes(FSimpleShapeSet3d& ShapeSetOut)
{
FCriticalSection GeometryLock;
ParallelFor(SourceMeshes.Num(), [&](int32 idx)
{
if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock))
{
return;
}
FAxisAlignedBox3d Bounds = SourceMeshes[idx]->GetBounds();
if (!Bounds.IsEmpty())
{
FBoxShape3d NewBox;
NewBox.Box = FOrientedBox3d(Bounds);
for (int32 SubIdx = 0; SubIdx < 3; ++SubIdx)
{
NewBox.Box.Extents[SubIdx] = FMath::Max(MinDimension * .5, NewBox.Box.Extents[SubIdx]);
}
GeometryLock.Lock();
ShapeSetOut.Boxes.Add(NewBox);
GeometryLock.Unlock();
}
});
}
void FMeshSimpleShapeApproximation::Generate_OrientedBoxes(FSimpleShapeSet3d& ShapeSetOut, FProgressCancel* Progress)
{
using namespace FMeshSimpleShapeApproximationLocals;
FCriticalSection GeometryLock;
ParallelFor(SourceMeshes.Num(), [&](int32 idx)
{
if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock))
{
return;
}
const FDynamicMesh3& SourceMesh = *SourceMeshes[idx];
FSimpleShapeFitsResult FitResult;
ComputeSimpleShapeFits(SourceMesh, false, true, false, MinDimension, bUseExactComputationForBox, FitResult, Progress);
if (Progress && Progress->Cancelled())
{
return;
}
if (FitResult.bHaveBox)
{
FOrientedBox3d BoxToUse = ReparameterizeBoxCloserToWorldFrame(FitResult.Box);
GeometryLock.Lock();
ShapeSetOut.Boxes.Add(FBoxShape3d(BoxToUse));
GeometryLock.Unlock();
}
});
}
void FMeshSimpleShapeApproximation::Generate_MinimalSpheres(FSimpleShapeSet3d& ShapeSetOut)
{
FCriticalSection GeometryLock;
ParallelFor(SourceMeshes.Num(), [&](int32 idx)
{
if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock))
{
return;
}
const FDynamicMesh3& SourceMesh = *SourceMeshes[idx];
FSimpleShapeFitsResult FitResult;
ComputeSimpleShapeFits(SourceMesh, true, false, false, MinDimension, bUseExactComputationForBox, FitResult);
if (FitResult.bHaveSphere)
{
GeometryLock.Lock();
ShapeSetOut.Spheres.Add(FSphereShape3d(FitResult.Sphere));
GeometryLock.Unlock();
}
});
}
void FMeshSimpleShapeApproximation::Generate_Capsules(FSimpleShapeSet3d& ShapeSetOut)
{
FCriticalSection GeometryLock;
ParallelFor(SourceMeshes.Num(), [&](int32 idx)
{
if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock))
{
return;
}
const FDynamicMesh3& SourceMesh = *SourceMeshes[idx];
FSimpleShapeFitsResult FitResult;
ComputeSimpleShapeFits(SourceMesh, false, false, true, MinDimension, bUseExactComputationForBox, FitResult);
if (FitResult.bHaveCapsule)
{
GeometryLock.Lock();
ShapeSetOut.Capsules.Add(UE::Geometry::FCapsuleShape3d(FitResult.Capsule));
GeometryLock.Unlock();
}
});
}
void FMeshSimpleShapeApproximation::Generate_ConvexHulls(FSimpleShapeSet3d& ShapeSetOut, FProgressCancel* Progress)
{
FCriticalSection GeometryLock;
ParallelFor(SourceMeshes.Num(), [&](int32 idx)
{
if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock))
{
return;
}
const FDynamicMesh3& SourceMesh = *SourceMeshes[idx];
FMeshConvexHull Hull(&SourceMesh);
Hull.bPostSimplify = bSimplifyHulls;
Hull.MaxTargetFaceCount = HullTargetFaceCount;
Hull.MinDimension = MinDimension;
if (Hull.Compute(Progress))
{
GeometryLock.Lock();
ShapeSetOut.Convexes.Emplace(MoveTemp(Hull.ConvexHull));
GeometryLock.Unlock();
}
});
}
void FMeshSimpleShapeApproximation::Generate_ConvexHullDecompositions(FSimpleShapeSet3d& ShapeSetOut, FProgressCancel* Progress)
{
FCriticalSection GeometryLock;
ParallelFor(SourceMeshes.Num(), [&](int32 idx)
{
if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock))
{
return;
}
if (Progress && Progress->Cancelled())
{
return;
}
const FDynamicMesh3& SourceMesh = *SourceMeshes[idx];
FConvexDecomposition3::FPreprocessMeshOptions PreprocessOptions;
PreprocessOptions.bMergeEdges = true;
// Hull thickening allows us to compute decompositions planar inputs
PreprocessOptions.ThickenInputAfterHullFailure = ConvexDecompositionMinPartThickness;
// If protecting negative space, we also clamp thickening to at most half of the tolerance value so that we keep within the tolerance distance of the input shape
if (bConvexDecompositionProtectNegativeSpace)
{
PreprocessOptions.ThickenInputAfterHullFailure = FMath::Min(PreprocessOptions.ThickenInputAfterHullFailure, NegativeSpaceTolerance * .5);
}
PreprocessOptions.ThickenInputAfterHullFailure = FMath::Max(PreprocessOptions.ThickenInputAfterHullFailure, FMathd::ZeroTolerance);
PreprocessOptions.CustomPreprocess = [this](FDynamicMesh3& ProcessMesh, const FAxisAlignedBox3d& Bounds) -> void
{
// for solid inputs, flip orientation if the initial volume is negative
if (ProcessMesh.IsClosed())
{
double InitialVolume = TMeshQueries<FDynamicMesh3>::GetVolumeArea(ProcessMesh).X;
if (InitialVolume < 0)
{
ProcessMesh.ReverseOrientation();
}
}
if (bDecompositionPreSimplifyWithEdgeLength)
{
// Run pre-simplification to the target edge length
UE::Geometry::FVolPresMeshSimplification Simplifier(&ProcessMesh);
Simplifier.CollapseMode = UE::Geometry::FVolPresMeshSimplification::ESimplificationCollapseModes::MinimalExistingVertexError;
Simplifier.SimplifyToEdgeLength(DecompositionPreSimplifyEdgeLength);
}
};
FConvexDecomposition3 Decomposition(SourceMesh, PreprocessOptions);
const bool bIsSolid = Decomposition.IsInputSolid();
Decomposition.bTreatAsSolid = bIsSolid;
if (bConvexDecompositionProtectNegativeSpace)
{
// Use settings tuned for navigation-driven decomposition
FNegativeSpaceSampleSettings Settings;
Settings.MarchingCubesGridScale = 1.0;
Settings.MaxVoxelsPerDim = 1024;
Settings.MinSpacing = 0.0;
Settings.TargetNumSamples = 0;
Settings.VoxelExpandBoundsFactor = UE_DOUBLE_KINDA_SMALL_NUMBER;
Settings.bOnlyConnectedToHull = bIgnoreInternalNegativeSpace;
Settings.MinRadius = NegativeSpaceMinRadius;
Settings.ReduceRadiusMargin = NegativeSpaceTolerance;
Settings.MinRadius = FMath::Max(1, (NegativeSpaceMinRadius + NegativeSpaceTolerance) * .5);
Settings.SampleMethod = FNegativeSpaceSampleSettings::ESampleMethod::NavigableVoxelSearch;
Settings.bRequireSearchSampleCoverage = true;
Settings.TargetNumSamples = 0; // let the sample coverage determine the number of spheres to place
Settings.bAllowSamplesInsideMesh = !bIsSolid;
Decomposition.MaxConvexEdgePlanes = 4;
Decomposition.bSplitDisconnectedComponents = false;
Decomposition.ConvexEdgeAngleMoreSamplesThreshold = 180;
Decomposition.ThickenAfterHullFailure = PreprocessOptions.ThickenInputAfterHullFailure;
Decomposition.InitializeNegativeSpace(Settings);
constexpr int32 MaxAllowedSplits = 1000000; // more parts than any expected / reasonable decomposition
int32 TargetNumSplits = bUseConvexDecompositionMaxPieces ? ConvexDecompositionMaxPieces - 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 = bUseConvexDecompositionMaxPieces ? ConvexDecompositionMaxPieces : -1;
Decomposition.MergeBest(TargetNumPieces, 0, ConvexDecompositionMinPartThickness, true);
}
else
{
int32 NumAdditionalSplits = FMath::FloorToInt32(float(ConvexDecompositionMaxPieces) * ConvexDecompositionSearchFactor);
Decomposition.Compute(ConvexDecompositionMaxPieces, NumAdditionalSplits, ConvexDecompositionErrorTolerance, ConvexDecompositionMinPartThickness);
}
for (int32 HullIdx = 0; HullIdx < Decomposition.NumHulls(); HullIdx++)
{
FDynamicMesh3 HullMesh = Decomposition.GetHullMesh(HullIdx);
if (bSimplifyHulls && FMeshConvexHull::SimplifyHull(HullMesh, HullTargetFaceCount, Progress) == false)
{
return;
}
GeometryLock.Lock();
ShapeSetOut.Convexes.Emplace(MoveTemp(HullMesh));
GeometryLock.Unlock();
}
});
}
void FMeshSimpleShapeApproximation::Generate_ProjectedHulls(FSimpleShapeSet3d& ShapeSetOut, EProjectedHullAxisMode AxisMode)
{
FCriticalSection GeometryLock;
ParallelFor(SourceMeshes.Num(), [&](int32 idx)
{
if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock))
{
return;
}
const FDynamicMesh3& Mesh = *SourceMeshes[idx];
FFrame3d ProjectionPlane(FVector3d::Zero(), FVector3d::UnitY());
if (AxisMode == EProjectedHullAxisMode::SmallestBoxDimension)
{
FAxisAlignedBox3d Bounds = Mesh.GetBounds();
int32 AxisIndex = MinAbsElementIndex(Bounds.Diagonal());
check(MinAbsElement(Bounds.Diagonal()) == Bounds.Diagonal()[AxisIndex]);
ProjectionPlane = FFrame3d(FVector3d::Zero(), MakeUnitVector3<double>(AxisIndex));
}
else if (AxisMode == EProjectedHullAxisMode::SmallestVolume)
{
FMeshProjectionHull HullX(&Mesh);
HullX.ProjectionFrame = FFrame3d(FVector3d::Zero(), FVector3d::UnitX());
HullX.MinThickness = FMathd::Max(MinDimension, 0);
bool bHaveX = HullX.Compute();
FMeshProjectionHull HullY(&Mesh);
HullY.ProjectionFrame = FFrame3d(FVector3d::Zero(), FVector3d::UnitY());
HullY.MinThickness = FMathd::Max(MinDimension, 0);
bool bHaveY = HullY.Compute();
FMeshProjectionHull HullZ(&Mesh);
HullZ.ProjectionFrame = FFrame3d(FVector3d::Zero(), FVector3d::UnitZ());
HullZ.MinThickness = FMathd::Max(MinDimension, 0);
bool bHaveZ = HullZ.Compute();
int32 MinIdx = FMathd::Min3Index(
(bHaveX) ? TMeshQueries<FDynamicMesh3>::GetVolumeArea(HullX.ConvexHull3D).X : TNumericLimits<double>::Max(),
(bHaveY) ? TMeshQueries<FDynamicMesh3>::GetVolumeArea(HullY.ConvexHull3D).X : TNumericLimits<double>::Max(),
(bHaveZ) ? TMeshQueries<FDynamicMesh3>::GetVolumeArea(HullZ.ConvexHull3D).X : TNumericLimits<double>::Max());
ProjectionPlane = (MinIdx == 0) ? HullX.ProjectionFrame : ((MinIdx == 1) ? HullY.ProjectionFrame : HullZ.ProjectionFrame);
}
else
{
ProjectionPlane = FFrame3d(FVector3d::Zero(), MakeUnitVector3<double>((int32)AxisMode));
}
FMeshProjectionHull Hull(&Mesh);
Hull.ProjectionFrame = ProjectionPlane;
Hull.MinThickness = FMathd::Max(MinDimension, 0);
Hull.bSimplifyPolygon = bSimplifyHulls;
Hull.MinEdgeLength = HullSimplifyTolerance;
Hull.DeviationTolerance = HullSimplifyTolerance;
if (Hull.Compute())
{
FConvexShape3d NewConvex;
NewConvex.Mesh = MoveTemp(Hull.ConvexHull3D);
GeometryLock.Lock();
ShapeSetOut.Convexes.Add(NewConvex);
GeometryLock.Unlock();
}
});
}
void FMeshSimpleShapeApproximation::Generate_LevelSets(FSimpleShapeSet3d& ShapeSetOut, FProgressCancel* Progress)
{
FCriticalSection GeometryLock;
ParallelFor(SourceMeshes.Num(), [&](int32 MeshIndex)
{
if (GetDetectedSimpleShape(SourceMeshCaches[MeshIndex], ShapeSetOut, GeometryLock))
{
return;
}
const FAxisAlignedBox3d Bounds = SourceMeshes[MeshIndex]->GetBounds();
const double CellSize = Bounds.MaxDim() / LevelSetGridResolution;
TMeshAABBTree3<FDynamicMesh3> Spatial(SourceMeshes[MeshIndex]);
TSweepingMeshSDF<FDynamicMesh3> SDF;
SDF.Mesh = SourceMeshes[MeshIndex];
SDF.Spatial = &Spatial;
SDF.ComputeMode = TSweepingMeshSDF<FDynamicMesh3>::EComputeModes::NarrowBand_SpatialFloodFill;
SDF.CellSize = (float)CellSize;
SDF.NarrowBandMaxDistance = 2.0 * CellSize;
SDF.ExactBandWidth = FMath::CeilToInt32(SDF.NarrowBandMaxDistance / CellSize);
SDF.ExpandBounds = 2.0 * CellSize * FVector3d::One();
if (SDF.Compute(Bounds))
{
FLevelSetShape3d NewLevelSet;
NewLevelSet.GridTransform = FTransform((FVector3d)SDF.GridOrigin);
NewLevelSet.Grid = MoveTemp(SDF.Grid);
NewLevelSet.CellSize = SDF.CellSize;
GeometryLock.Lock();
ShapeSetOut.LevelSets.Add(MoveTemp(NewLevelSet));
GeometryLock.Unlock();
}
else if (Progress)
{
GeometryLock.Lock();
Progress->AddWarning(LOCTEXT("Generate_LevelSets_Failed", "Generating a new Level Set failed"), FProgressCancel::EMessageLevel::UserWarning);
GeometryLock.Unlock();
}
});
}
void FMeshSimpleShapeApproximation::Generate_MinVolume(FSimpleShapeSet3d& ShapeSetOut)
{
FCriticalSection GeometryLock;
ParallelFor(SourceMeshes.Num(), [&](int32 idx)
{
if (GetDetectedSimpleShape(SourceMeshCaches[idx], ShapeSetOut, GeometryLock))
{
return;
}
const FDynamicMesh3& SourceMesh = *SourceMeshes[idx];
FOrientedBox3d AlignedBox = FOrientedBox3d(SourceMesh.GetBounds());
double MinHalfDimension = MinDimension * .5;
for (int32 SubIdx = 0; SubIdx < 3; ++SubIdx)
{
AlignedBox.Extents[SubIdx] = FMath::Max(MinHalfDimension, AlignedBox.Extents[SubIdx]);
}
FSimpleShapeFitsResult FitResult;
ComputeSimpleShapeFits(SourceMesh, true, true, true, MinDimension, bUseExactComputationForBox, FitResult);
double Volumes[4];
Volumes[0] = AlignedBox.Volume();
Volumes[1] = (FitResult.bHaveBox) ? FitResult.Box.Volume() : TNumericLimits<double>::Max();
Volumes[2] = (FitResult.bHaveSphere) ? FitResult.Sphere.Volume() : TNumericLimits<double>::Max();
Volumes[3] = (FitResult.bHaveCapsule) ? FitResult.Capsule.Volume() : TNumericLimits<double>::Max();
int32 MinVolIndex = 0;
for (int32 k = 1; k < 4; ++k)
{
if (Volumes[k] < Volumes[MinVolIndex])
{
MinVolIndex = k;
}
}
if (Volumes[MinVolIndex] < TNumericLimits<double>::Max())
{
GeometryLock.Lock();
switch (MinVolIndex)
{
case 0:
ShapeSetOut.Boxes.Add(FBoxShape3d(AlignedBox));
break;
case 1:
ShapeSetOut.Boxes.Add(FBoxShape3d(FitResult.Box));
break;
case 2:
ShapeSetOut.Spheres.Add(FSphereShape3d(FitResult.Sphere));
break;
case 3:
ShapeSetOut.Capsules.Add(UE::Geometry::FCapsuleShape3d(FitResult.Capsule));
break;
}
GeometryLock.Unlock();
}
});
}
#undef LOCTEXT_NAMESPACE