963 lines
32 KiB
C++
963 lines
32 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DataprepGeometryOperations.h"
|
|
|
|
#include "DataprepOperationsLibraryUtil.h"
|
|
#include "DataprepAssetUserData.h"
|
|
#include "DynamicMesh/DynamicMeshAABBTree3.h"
|
|
#include "DynamicMeshToMeshDescription.h"
|
|
#include "DynamicMeshEditor.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "CuttingOps/PlaneCutOp.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Engine/StaticMeshActor.h"
|
|
#include "IDataprepProgressReporter.h"
|
|
#include "IMeshReductionInterfaces.h"
|
|
#include "IMeshReductionManagerModule.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "MeshAdapterTransforms.h"
|
|
#include "MeshDescriptionAdapter.h"
|
|
#include "MeshDescriptionToDynamicMesh.h"
|
|
#include "DynamicMesh/Operations/MergeCoincidentMeshEdges.h"
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogDataprepGeometryOperations);
|
|
|
|
#define LOCTEXT_NAMESPACE "DatasmithEditingOperationsExperimental"
|
|
|
|
#ifdef LOG_TIME
|
|
namespace DataprepGeometryOperationsTime
|
|
{
|
|
typedef TFunction<void(FText)> FLogFunc;
|
|
|
|
class FTimeLogger
|
|
{
|
|
public:
|
|
FTimeLogger(const FString& InText, FLogFunc&& InLogFunc)
|
|
: StartTime( FPlatformTime::Cycles64() )
|
|
, Text( InText )
|
|
, LogFunc(MoveTemp(InLogFunc))
|
|
{
|
|
UE_LOG( LogDataprep, Log, TEXT("%s ..."), *Text );
|
|
}
|
|
|
|
~FTimeLogger()
|
|
{
|
|
// Log time spent to import incoming file in minutes and seconds
|
|
double ElapsedSeconds = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - StartTime);
|
|
|
|
int ElapsedMin = int(ElapsedSeconds / 60.0);
|
|
ElapsedSeconds -= 60.0 * (double)ElapsedMin;
|
|
FText Msg = FText::Format( LOCTEXT("DataprepOperation_LogTime", "{0} took {1} min {2} s."), FText::FromString( Text ), ElapsedMin, FText::FromString( FString::Printf( TEXT("%.3f"), ElapsedSeconds ) ) );
|
|
LogFunc( Msg );
|
|
}
|
|
|
|
private:
|
|
uint64 StartTime;
|
|
FString Text;
|
|
FLogFunc LogFunc;
|
|
};
|
|
}
|
|
#endif
|
|
|
|
void UDataprepRemeshOperation::OnExecution_Implementation(const FDataprepContext& InContext)
|
|
{
|
|
#ifdef LOG_TIME
|
|
DataprepGeometryOperationsTime::FTimeLogger TimeLogger( TEXT("RemeshMesh"), [&]( FText Text) { this->LogInfo( Text ); });
|
|
#endif
|
|
|
|
TArray<UObject*> ModifiedStaticMeshes;
|
|
|
|
|
|
TSet<UStaticMesh*> SelectedMeshes = DataprepOperationsLibraryUtil::GetSelectedMeshes(InContext.Objects);
|
|
|
|
// Apply remesher
|
|
|
|
for (UStaticMesh* StaticMesh : SelectedMeshes)
|
|
{
|
|
if (!StaticMesh)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(0);
|
|
|
|
TSharedPtr<UE::Geometry::FDynamicMesh3, ESPMode::ThreadSafe> OriginalMesh = MakeShared<UE::Geometry::FDynamicMesh3, ESPMode::ThreadSafe>();
|
|
FMeshDescriptionToDynamicMesh MeshDescriptionToDynamicMesh;
|
|
MeshDescriptionToDynamicMesh.Convert(MeshDescription, *OriginalMesh);
|
|
|
|
UE::Geometry::FRemeshMeshOp Op;
|
|
|
|
Op.RemeshType = RemeshType;
|
|
Op.bCollapses = true;
|
|
Op.bDiscardAttributes = bDiscardAttributes;
|
|
Op.bFlips = true;
|
|
Op.bPreserveSharpEdges = true;
|
|
Op.MeshBoundaryConstraint = (EEdgeRefineFlags)MeshBoundaryConstraint;
|
|
Op.GroupBoundaryConstraint = (EEdgeRefineFlags)GroupBoundaryConstraint;
|
|
Op.MaterialBoundaryConstraint = (EEdgeRefineFlags)MaterialBoundaryConstraint;
|
|
Op.bPreventNormalFlips = true;
|
|
Op.bReproject = true;
|
|
Op.bSplits = true;
|
|
Op.RemeshIterations = RemeshIterations;
|
|
Op.SmoothingStrength = SmoothingStrength;
|
|
Op.SmoothingType = ERemeshSmoothingType::MeanValue;
|
|
|
|
TSharedPtr<UE::Geometry::FDynamicMeshAABBTree3, ESPMode::ThreadSafe> OriginalMeshSpatial = MakeShared<UE::Geometry::FDynamicMeshAABBTree3, ESPMode::ThreadSafe>(OriginalMesh.Get(), true);
|
|
|
|
double InitialMeshArea = 0;
|
|
for (int tid : OriginalMesh->TriangleIndicesItr())
|
|
{
|
|
InitialMeshArea += OriginalMesh->GetTriArea(tid);
|
|
}
|
|
|
|
double TargetTriArea = InitialMeshArea / (double)TargetTriangleCount;
|
|
double EdgeLen = UE::Geometry::TriangleUtil::EquilateralEdgeLengthForArea(TargetTriArea);
|
|
Op.TargetEdgeLength = (double)FMath::RoundToInt(EdgeLen*100.0) / 100.0;
|
|
|
|
Op.OriginalMesh = OriginalMesh;
|
|
Op.OriginalMeshSpatial = OriginalMeshSpatial;
|
|
|
|
FProgressCancel Progress;
|
|
Op.CalculateResult(&Progress);
|
|
|
|
// Update the static mesh with result
|
|
|
|
FDynamicMeshToMeshDescription DynamicMeshToMeshDescription;
|
|
|
|
// full conversion if normal topology changed or faces were inverted
|
|
TUniquePtr<UE::Geometry::FDynamicMesh3> ResultMesh = Op.ExtractResult();
|
|
DynamicMeshToMeshDescription.Convert(ResultMesh.Get(), *MeshDescription);
|
|
|
|
UStaticMesh::FCommitMeshDescriptionParams Params;
|
|
Params.bMarkPackageDirty = false;
|
|
Params.bUseHashAsGuid = true;
|
|
StaticMesh->CommitMeshDescription(0, Params);
|
|
|
|
ModifiedStaticMeshes.Add( StaticMesh );
|
|
}
|
|
|
|
if(ModifiedStaticMeshes.Num() > 0)
|
|
{
|
|
AssetsModified( MoveTemp( ModifiedStaticMeshes ) );
|
|
}
|
|
}
|
|
|
|
void UDataprepSimplifyMeshOperation::OnExecution_Implementation(const FDataprepContext& InContext)
|
|
{
|
|
#ifdef LOG_TIME
|
|
DataprepGeometryOperationsTime::FTimeLogger TimeLogger( TEXT("SimplifyMesh"), [&]( FText Text) { this->LogInfo( Text ); });
|
|
#endif
|
|
|
|
// Execute operation
|
|
TArray<UObject*> ModifiedStaticMeshes;
|
|
|
|
TSet<UStaticMesh*> SelectedMeshes = DataprepOperationsLibraryUtil::GetSelectedMeshes(InContext.Objects);
|
|
|
|
IMeshReductionManagerModule& MeshReductionModule = FModuleManager::Get().LoadModuleChecked<IMeshReductionManagerModule>("MeshReductionInterface");
|
|
|
|
for (UStaticMesh* StaticMesh : SelectedMeshes)
|
|
{
|
|
if (!StaticMesh)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(0);
|
|
|
|
TSharedPtr<FDynamicMesh3, ESPMode::ThreadSafe> OriginalMesh = MakeShared<FDynamicMesh3, ESPMode::ThreadSafe>();
|
|
FMeshDescriptionToDynamicMesh MeshDescriptionToDynamicMesh;
|
|
MeshDescriptionToDynamicMesh.Convert(MeshDescription, *OriginalMesh);
|
|
|
|
TSharedPtr<UE::Geometry::FDynamicMeshAABBTree3, ESPMode::ThreadSafe> OriginalMeshSpatial = MakeShared<UE::Geometry::FDynamicMeshAABBTree3, ESPMode::ThreadSafe>(OriginalMesh.Get(), true);
|
|
|
|
TSharedPtr<FMeshDescription, ESPMode::ThreadSafe> OriginalMeshDescription = MakeShared<FMeshDescription, ESPMode::ThreadSafe>(*StaticMesh->GetMeshDescription(0));
|
|
|
|
UE::Geometry::FSimplifyMeshOp Op;
|
|
|
|
Op.bDiscardAttributes = bDiscardAttributes;
|
|
Op.bPreventNormalFlips = true;
|
|
Op.bPreserveSharpEdges = true;
|
|
Op.bReproject = false;
|
|
Op.SimplifierType = ESimplifyType::UEStandard;
|
|
// Op.TargetCount = TargetCount;
|
|
// Op.TargetEdgeLength = TargetEdgeLength;
|
|
Op.TargetMode = ESimplifyTargetType::Percentage;
|
|
Op.TargetPercentage = TargetPercentage;
|
|
Op.MeshBoundaryConstraint = (EEdgeRefineFlags)MeshBoundaryConstraint;
|
|
Op.GroupBoundaryConstraint = (EEdgeRefineFlags)GroupBoundaryConstraint;
|
|
Op.MaterialBoundaryConstraint = (EEdgeRefineFlags)MaterialBoundaryConstraint;
|
|
Op.OriginalMeshDescription = OriginalMeshDescription;
|
|
Op.OriginalMesh = OriginalMesh;
|
|
Op.OriginalMeshSpatial = OriginalMeshSpatial;
|
|
|
|
Op.MeshReduction = MeshReductionModule.GetStaticMeshReductionInterface();
|
|
|
|
FProgressCancel Progress;
|
|
Op.CalculateResult(&Progress);
|
|
|
|
// Update the static mesh with result
|
|
|
|
FDynamicMeshToMeshDescription DynamicMeshToMeshDescription;
|
|
|
|
// Convert back to mesh description
|
|
TUniquePtr<UE::Geometry::FDynamicMesh3> ResultMesh = Op.ExtractResult();
|
|
DynamicMeshToMeshDescription.Convert(ResultMesh.Get(), *MeshDescription);
|
|
|
|
UStaticMesh::FCommitMeshDescriptionParams Params;
|
|
Params.bMarkPackageDirty = false;
|
|
Params.bUseHashAsGuid = true;
|
|
StaticMesh->CommitMeshDescription(0, Params);
|
|
|
|
ModifiedStaticMeshes.Add( StaticMesh );
|
|
}
|
|
|
|
if(ModifiedStaticMeshes.Num() > 0)
|
|
{
|
|
AssetsModified( MoveTemp( ModifiedStaticMeshes ) );
|
|
}
|
|
}
|
|
|
|
void UDataprepBakeTransformOperation::OnExecution_Implementation(const FDataprepContext& InContext)
|
|
{
|
|
#ifdef LOG_TIME
|
|
DataprepGeometryOperationsTime::FTimeLogger TimeLogger( TEXT("BakeTransform"), [&]( FText Text) { this->LogInfo( Text ); });
|
|
#endif
|
|
|
|
TArray<UObject*> ModifiedStaticMeshes;
|
|
|
|
TArray<UStaticMeshComponent*> StaticMeshComponents;
|
|
TArray<AActor*> ComponentActors;
|
|
|
|
for (UObject* Object : InContext.Objects)
|
|
{
|
|
if (AActor* Actor = Cast< AActor >(Object))
|
|
{
|
|
TInlineComponentArray<UStaticMeshComponent*> Components(Actor);
|
|
for (UStaticMeshComponent* Component : Components)
|
|
{
|
|
StaticMeshComponents.Add(Component);
|
|
ComponentActors.Add(Actor);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bSharesSources = false;
|
|
TArray<int> MapToFirstOccurrences;
|
|
MapToFirstOccurrences.SetNumUninitialized(StaticMeshComponents.Num());
|
|
for (int32 ComponentIdx = 0; ComponentIdx < StaticMeshComponents.Num(); ComponentIdx++)
|
|
{
|
|
MapToFirstOccurrences[ComponentIdx] = -1;
|
|
}
|
|
for (int32 ComponentIdx = 0; ComponentIdx < StaticMeshComponents.Num(); ComponentIdx++)
|
|
{
|
|
if (MapToFirstOccurrences[ComponentIdx] >= 0) // already mapped
|
|
{
|
|
continue;
|
|
}
|
|
|
|
MapToFirstOccurrences[ComponentIdx] = ComponentIdx;
|
|
|
|
UStaticMeshComponent* Component = StaticMeshComponents[ComponentIdx];
|
|
for (int32 VsIdx = ComponentIdx + 1; VsIdx < StaticMeshComponents.Num(); VsIdx++)
|
|
{
|
|
UStaticMeshComponent* OtherComponent = StaticMeshComponents[VsIdx];
|
|
|
|
const UStaticMesh* StaticMesh = Component->GetStaticMesh();
|
|
const UStaticMesh* OtherStaticMesh = OtherComponent->GetStaticMesh();
|
|
if ( StaticMesh && StaticMesh == OtherStaticMesh )
|
|
{
|
|
bSharesSources = true;
|
|
MapToFirstOccurrences[VsIdx] = ComponentIdx;
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<UE::Geometry::FTransformSRT3d> BakedTransforms;
|
|
for (int32 ComponentIdx = 0; ComponentIdx < StaticMeshComponents.Num(); ComponentIdx++)
|
|
{
|
|
UE::Geometry::FTransformSRT3d ComponentToWorld(StaticMeshComponents[ComponentIdx]->GetComponentTransform());
|
|
UE::Geometry::FTransformSRT3d ToBakePart = UE::Geometry::FTransformSRT3d::Identity();
|
|
UE::Geometry::FTransformSRT3d NewWorldPart = ComponentToWorld;
|
|
|
|
if (MapToFirstOccurrences[ComponentIdx] < ComponentIdx)
|
|
{
|
|
ToBakePart = BakedTransforms[MapToFirstOccurrences[ComponentIdx]];
|
|
BakedTransforms.Add(ToBakePart);
|
|
// Try to invert baked transform
|
|
NewWorldPart = UE::Geometry::FTransformSRT3d(
|
|
NewWorldPart.GetRotation() * ToBakePart.GetRotation().Inverse(),
|
|
NewWorldPart.GetTranslation(),
|
|
NewWorldPart.GetScale() * UE::Geometry::FTransformSRT3d::GetSafeScaleReciprocal(ToBakePart.GetScale())
|
|
);
|
|
NewWorldPart.SetTranslation(NewWorldPart.GetTranslation() - NewWorldPart.TransformVector(ToBakePart.GetTranslation()));
|
|
}
|
|
else
|
|
{
|
|
if (bBakeRotation)
|
|
{
|
|
ToBakePart.SetRotation(ComponentToWorld.GetRotation());
|
|
NewWorldPart.SetRotation(UE::Geometry::FQuaterniond::Identity());
|
|
}
|
|
FVector3d ScaleVec = ComponentToWorld.GetScale();
|
|
|
|
FVector3d AbsScales(FMathd::Abs(ScaleVec.X), FMathd::Abs(ScaleVec.Y), FMathd::Abs(ScaleVec.Z));
|
|
double RemainingUniformScale = AbsScales.X;
|
|
{
|
|
FVector3d Dists;
|
|
for (int SubIdx = 0; SubIdx < 3; SubIdx++)
|
|
{
|
|
int OtherA = (SubIdx + 1) % 3;
|
|
int OtherB = (SubIdx + 2) % 3;
|
|
Dists[SubIdx] = FMathd::Abs(AbsScales[SubIdx] - AbsScales[OtherA]) + FMathd::Abs(AbsScales[SubIdx] - AbsScales[OtherB]);
|
|
}
|
|
int BestSubIdx = 0;
|
|
for (int CompareSubIdx = 1; CompareSubIdx < 3; CompareSubIdx++)
|
|
{
|
|
if (Dists[CompareSubIdx] < Dists[BestSubIdx])
|
|
{
|
|
BestSubIdx = CompareSubIdx;
|
|
}
|
|
}
|
|
RemainingUniformScale = AbsScales[BestSubIdx];
|
|
if (RemainingUniformScale <= FLT_MIN)
|
|
{
|
|
RemainingUniformScale = UE::Geometry::MaxAbsElement(AbsScales);
|
|
}
|
|
}
|
|
switch (BakeScale)
|
|
{
|
|
case EBakeScaleMethod::BakeFullScale:
|
|
ToBakePart.SetScale(ScaleVec);
|
|
NewWorldPart.SetScale(FVector3d::One());
|
|
break;
|
|
case EBakeScaleMethod::BakeNonuniformScale:
|
|
check(RemainingUniformScale > FLT_MIN); // avoid baking a ~zero scale
|
|
ToBakePart.SetScale(ScaleVec / RemainingUniformScale);
|
|
NewWorldPart.SetScale(FVector3d(RemainingUniformScale, RemainingUniformScale, RemainingUniformScale));
|
|
break;
|
|
case EBakeScaleMethod::DoNotBakeScale:
|
|
break;
|
|
default:
|
|
check(false); // must explicitly handle all cases
|
|
}
|
|
|
|
UStaticMeshComponent* Component = StaticMeshComponents[ComponentIdx];
|
|
|
|
UStaticMesh* StaticMesh = Component->GetStaticMesh();
|
|
|
|
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(0);
|
|
|
|
FMeshDescriptionEditableTriangleMeshAdapter EditableMeshDescAdapter(MeshDescription);
|
|
|
|
// Do this part within the commit because we have the MeshDescription already computed
|
|
if (bRecenterPivot)
|
|
{
|
|
FBox BBox = MeshDescription->ComputeBoundingBox();
|
|
FVector3d Center(BBox.GetCenter());
|
|
UE::Geometry::FFrame3d LocalFrame(Center);
|
|
ToBakePart.SetTranslation(ToBakePart.GetTranslation() - Center);
|
|
NewWorldPart.SetTranslation(NewWorldPart.GetTranslation() + NewWorldPart.TransformVector(Center));
|
|
}
|
|
|
|
MeshAdapterTransforms::ApplyTransform(EditableMeshDescAdapter, ToBakePart);
|
|
|
|
ScaleVec = ToBakePart.GetScale();
|
|
if (ScaleVec.X * ScaleVec.Y * ScaleVec.Z < 0)
|
|
{
|
|
MeshDescription->ReverseAllPolygonFacing();
|
|
}
|
|
|
|
UStaticMesh::FCommitMeshDescriptionParams Params;
|
|
Params.bMarkPackageDirty = false;
|
|
Params.bUseHashAsGuid = true;
|
|
StaticMesh->CommitMeshDescription(0, Params);
|
|
|
|
ModifiedStaticMeshes.Add(StaticMesh);
|
|
|
|
BakedTransforms.Add(ToBakePart);
|
|
}
|
|
|
|
UStaticMeshComponent* Component = StaticMeshComponents[ComponentIdx];
|
|
Component->SetWorldTransform((FTransform)NewWorldPart);
|
|
ComponentActors[ComponentIdx]->MarkComponentsRenderStateDirty();
|
|
}
|
|
}
|
|
|
|
void UDataprepWeldEdgesOperation::OnExecution_Implementation(const FDataprepContext& InContext)
|
|
{
|
|
#ifdef LOG_TIME
|
|
DataprepGeometryOperationsTime::FTimeLogger TimeLogger( TEXT("WeldEdges"), [&]( FText Text) { this->LogInfo( Text ); });
|
|
#endif
|
|
|
|
TArray<UObject*> ModifiedStaticMeshes;
|
|
|
|
TSet<UStaticMesh*> SelectedMeshes = DataprepOperationsLibraryUtil::GetSelectedMeshes(InContext.Objects);
|
|
|
|
for (UStaticMesh* StaticMesh : SelectedMeshes)
|
|
{
|
|
if (!StaticMesh)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(0);
|
|
|
|
TSharedPtr<FDynamicMesh3, ESPMode::ThreadSafe> TargetMesh = MakeShared<FDynamicMesh3, ESPMode::ThreadSafe>();
|
|
FMeshDescriptionToDynamicMesh MeshDescriptionToDynamicMesh;
|
|
MeshDescriptionToDynamicMesh.Convert(MeshDescription, *TargetMesh);
|
|
|
|
UE::Geometry::FMergeCoincidentMeshEdges Merger(TargetMesh.Get());
|
|
Merger.MergeSearchTolerance = Tolerance;
|
|
Merger.OnlyUniquePairs = bOnlyUnique;
|
|
|
|
if (Merger.Apply() == false)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (TargetMesh->CheckValidity(true, UE::Geometry::EValidityCheckFailMode::ReturnOnly) == false)
|
|
{
|
|
continue; // Target mesh is invalid
|
|
}
|
|
|
|
FDynamicMeshToMeshDescription DynamicMeshToMeshDescription;
|
|
|
|
// full conversion if normal topology changed or faces were inverted
|
|
DynamicMeshToMeshDescription.Convert(TargetMesh.Get(), *MeshDescription);
|
|
|
|
UStaticMesh::FCommitMeshDescriptionParams Params;
|
|
Params.bMarkPackageDirty = false;
|
|
Params.bUseHashAsGuid = true;
|
|
StaticMesh->CommitMeshDescription(0, Params);
|
|
|
|
ModifiedStaticMeshes.Add(StaticMesh);
|
|
}
|
|
|
|
if(ModifiedStaticMeshes.Num() > 0)
|
|
{
|
|
AssetsModified( MoveTemp( ModifiedStaticMeshes ) );
|
|
}
|
|
}
|
|
|
|
void UDataprepPlaneCutOperation::OnExecution_Implementation(const FDataprepContext& InContext)
|
|
{
|
|
#ifdef LOG_TIME
|
|
DataprepEditingOperationTime::FTimeLogger TimeLogger(TEXT("PlaneCutOperation"), [&](FText Text) { this->LogInfo(Text); });
|
|
#endif
|
|
|
|
TArray<UStaticMeshComponent*> StaticMeshComponents;
|
|
TArray<AActor*> ComponentActors;
|
|
|
|
TSet<UStaticMesh*> StaticMeshesSet;
|
|
|
|
for (UObject* Object : InContext.Objects)
|
|
{
|
|
if (AActor* Actor = Cast< AActor >(Object))
|
|
{
|
|
TInlineComponentArray<UStaticMeshComponent*> Components(Actor);
|
|
for (UStaticMeshComponent* Component : Components)
|
|
{
|
|
if (Component && Component->GetStaticMesh())
|
|
{
|
|
StaticMeshComponents.Add(Component);
|
|
ComponentActors.Add(Actor);
|
|
}
|
|
}
|
|
}
|
|
else if (UStaticMesh* StaticMesh = Cast< UStaticMesh >(Object))
|
|
{
|
|
StaticMeshesSet.Add(StaticMesh);
|
|
}
|
|
}
|
|
|
|
// Remove the static meshes that are also referenced in the static mesh components (components take precedence)
|
|
for (UStaticMeshComponent* MeshComp : StaticMeshComponents)
|
|
{
|
|
if (UStaticMesh* StaticMesh = MeshComp->GetStaticMesh())
|
|
{
|
|
if (StaticMeshesSet.Contains(StaticMesh))
|
|
{
|
|
StaticMeshesSet.Remove(StaticMesh);
|
|
|
|
if (StaticMeshesSet.Num() == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<UObject*> ModifiedStaticMeshes;
|
|
|
|
TArray<UStaticMeshComponent*> CutawayStaticMeshes;
|
|
|
|
// Cut static meshes
|
|
|
|
if (StaticMeshesSet.Num() > 0)
|
|
{
|
|
TArray<UStaticMesh*> StaticMeshes = StaticMeshesSet.Array();
|
|
TArray<TArray<UStaticMeshComponent*>> ReferencingComponentsToUpdate;
|
|
TArray<FTransform> CutPlaneTransforms;
|
|
|
|
ReferencingComponentsToUpdate.Init(TArray<UStaticMeshComponent*>(), StaticMeshes.Num());
|
|
|
|
// Use identity transforms when cutting static meshes
|
|
CutPlaneTransforms.Init(FTransform(), StaticMeshes.Num());
|
|
|
|
for (TObjectIterator<UStaticMeshComponent> It; It; ++It)
|
|
{
|
|
if (UStaticMesh* StaticMesh = It->GetStaticMesh())
|
|
{
|
|
if (StaticMeshesSet.Contains(StaticMesh))
|
|
{
|
|
const int32 MeshIndex = StaticMeshes.Find(StaticMesh);
|
|
check(MeshIndex != INDEX_NONE);
|
|
ReferencingComponentsToUpdate[MeshIndex].Add(*It);
|
|
}
|
|
}
|
|
}
|
|
|
|
PerformCutting(false, StaticMeshes, CutPlaneTransforms, ReferencingComponentsToUpdate, ModifiedStaticMeshes, CutawayStaticMeshes);
|
|
}
|
|
|
|
// Cut static mesh components
|
|
|
|
if (StaticMeshComponents.Num() > 0)
|
|
{
|
|
TArray<UStaticMesh*> StaticMeshes;
|
|
TArray<TArray<UStaticMeshComponent*>> ReferencingComponentsToUpdate;
|
|
TArray<FTransform> CutPlaneTransforms;
|
|
|
|
StaticMeshes.Reserve(StaticMeshComponents.Num());
|
|
ReferencingComponentsToUpdate.Reserve(StaticMeshComponents.Num());
|
|
CutPlaneTransforms.Reserve(StaticMeshComponents.Num());
|
|
|
|
for (UStaticMeshComponent* Component : StaticMeshComponents)
|
|
{
|
|
if (UStaticMesh* StaticMesh = Component->GetStaticMesh())
|
|
{
|
|
StaticMeshes.Add(StaticMesh);
|
|
ReferencingComponentsToUpdate.AddDefaulted_GetRef().Add(Component);
|
|
CutPlaneTransforms.Add(Component->GetComponentTransform());
|
|
}
|
|
}
|
|
|
|
PerformCutting(true, StaticMeshes, CutPlaneTransforms, ReferencingComponentsToUpdate, ModifiedStaticMeshes, CutawayStaticMeshes);
|
|
}
|
|
|
|
// Remove actors that have no valid static mesh as a result of the cutting
|
|
TSet<UObject*> ActorsToRemove;
|
|
for (UStaticMeshComponent* CutoffComponent : CutawayStaticMeshes)
|
|
{
|
|
if (AActor* Owner = CutoffComponent->GetOwner())
|
|
{
|
|
TInlineComponentArray<UStaticMeshComponent*> ComponentArray;
|
|
Owner->GetComponents(ComponentArray, true);
|
|
|
|
bool bMeshActorIsValid = false;
|
|
|
|
for(UStaticMeshComponent* MeshComponent : ComponentArray)
|
|
{
|
|
if (!MeshComponent || MeshComponent == CutoffComponent)
|
|
{
|
|
continue; // We know this component does not have a mesh
|
|
}
|
|
|
|
if (MeshComponent->GetStaticMesh() && MeshComponent->GetStaticMesh()->GetSourceModels().Num() > 0)
|
|
{
|
|
bMeshActorIsValid = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bMeshActorIsValid)
|
|
{
|
|
ActorsToRemove.Add(Owner);
|
|
}
|
|
else
|
|
{
|
|
CutoffComponent->DestroyComponent();
|
|
}
|
|
}
|
|
}
|
|
|
|
DeleteObjects(ActorsToRemove.Array());
|
|
|
|
if (ModifiedStaticMeshes.Num() > 0)
|
|
{
|
|
AssetsModified(MoveTemp(ModifiedStaticMeshes));
|
|
}
|
|
}
|
|
|
|
TUniquePtr<FDynamicMesh3> UDataprepPlaneCutOperation::CutStaticMesh(const FTransform& InTransform, const UStaticMesh* InStaticMesh)
|
|
{
|
|
const FMeshDescription* MeshDescription = InStaticMesh->GetMeshDescription(0);
|
|
|
|
TSharedPtr<FDynamicMesh3, ESPMode::ThreadSafe> OriginalMesh = MakeShared<FDynamicMesh3, ESPMode::ThreadSafe>();
|
|
FMeshDescriptionToDynamicMesh MeshDescriptionToDynamicMesh;
|
|
MeshDescriptionToDynamicMesh.Convert(MeshDescription, *OriginalMesh);
|
|
|
|
OriginalMesh->EnableAttributes();
|
|
UE::Geometry::TDynamicMeshScalarTriangleAttribute<int>* SubObjectIDs = new UE::Geometry::TDynamicMeshScalarTriangleAttribute<int>(OriginalMesh.Get());
|
|
SubObjectIDs->Initialize(0);
|
|
OriginalMesh->Attributes()->AttachAttribute(UE::Geometry::FPlaneCutOp::ObjectIndexAttribute, SubObjectIDs);
|
|
|
|
// Store a UV scale based on the original mesh bounds (we don't want to recompute this between cuts b/c we want consistent UV scale)
|
|
const float MeshUVScaleFactor = 1.0 / OriginalMesh->GetBounds().MaxDim();
|
|
|
|
TUniquePtr<UE::Geometry::FDynamicMeshOperator> CutOp = MakeNewOperator(InTransform, OriginalMesh, MeshUVScaleFactor);
|
|
|
|
FProgressCancel Progress;
|
|
CutOp->CalculateResult(&Progress);
|
|
|
|
return CutOp->ExtractResult();
|
|
}
|
|
|
|
void UDataprepPlaneCutOperation::PerformCutting(
|
|
bool bKeepOriginalMesh,
|
|
TArray<UStaticMesh*>& InStaticMeshes,
|
|
const TArray<FTransform>& InCutPlaneTransforms,
|
|
TArray<TArray<UStaticMeshComponent*>>& InReferencingComponentsToUpdate,
|
|
TArray<UObject*>& OutModifiedStaticMeshes,
|
|
TArray<UStaticMeshComponent*>& OutCutawayMeshes)
|
|
{
|
|
TArray<TUniquePtr<FDynamicMesh3>> Results;
|
|
Results.SetNum(InStaticMeshes.Num());
|
|
|
|
ParallelFor( Results.Num(), [this, &Results, &InCutPlaneTransforms, &InStaticMeshes](int32 Index)
|
|
{
|
|
Results[Index] = CutStaticMesh(InCutPlaneTransforms[Index], InStaticMeshes[Index]);
|
|
});
|
|
|
|
TArray<TArray<FDynamicMesh3>> AllSplitMeshes;
|
|
AllSplitMeshes.SetNum(InStaticMeshes.Num());
|
|
|
|
for (int OrigMeshIdx = 0; OrigMeshIdx < InStaticMeshes.Num(); OrigMeshIdx++)
|
|
{
|
|
FDynamicMesh3* UseMesh = Results[OrigMeshIdx].Get();
|
|
check(UseMesh != nullptr);
|
|
|
|
// Check if mesh was entirely cut away
|
|
if (UseMesh->TriangleCount() == 0)
|
|
{
|
|
TArray<UStaticMeshComponent*>& ComponentsToUpdate = InReferencingComponentsToUpdate[OrigMeshIdx];
|
|
|
|
for (UStaticMeshComponent* Component : ComponentsToUpdate)
|
|
{
|
|
Component->SetStaticMesh(nullptr);
|
|
Component->MarkRenderStateDirty();
|
|
OutCutawayMeshes.Add(Component);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
UStaticMesh* StaticMesh = InStaticMeshes[OrigMeshIdx];
|
|
|
|
if (bExportSeparatePieces)
|
|
{
|
|
// Export separated pieces as new mesh assets
|
|
|
|
UE::Geometry::TDynamicMeshScalarTriangleAttribute<int>* SubMeshIDs =
|
|
static_cast<UE::Geometry::TDynamicMeshScalarTriangleAttribute<int>*>(UseMesh->Attributes()->GetAttachedAttribute(
|
|
UE::Geometry::FPlaneCutOp::ObjectIndexAttribute));
|
|
TArray<UE::Geometry::FDynamicMesh3>& SplitMeshes = AllSplitMeshes[OrigMeshIdx];
|
|
bool bWasSplit = UE::Geometry::FDynamicMeshEditor::SplitMesh(UseMesh, SplitMeshes, [SubMeshIDs](int TID)
|
|
{
|
|
return SubMeshIDs->GetValue(TID);
|
|
});
|
|
if (bWasSplit)
|
|
{
|
|
// Split mesh did something but has no meshes in the output array??
|
|
if (!ensure(SplitMeshes.Num() > 0))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (SplitMeshes.Num() > 1)
|
|
{
|
|
TArray<UStaticMeshComponent*>& ComponentsToUpdate = InReferencingComponentsToUpdate[OrigMeshIdx];
|
|
|
|
for (UStaticMeshComponent* ComponentTarget : ComponentsToUpdate)
|
|
{
|
|
// Build array of materials from the original
|
|
TArray<UMaterialInterface*> Materials;
|
|
for (int MaterialIdx = 0, NumMaterials = ComponentTarget->GetNumMaterials(); MaterialIdx < StaticMesh->GetStaticMaterials().Num(); MaterialIdx++)
|
|
{
|
|
Materials.Add(ComponentTarget->GetMaterial(MaterialIdx));
|
|
}
|
|
|
|
// Add all the additional meshes
|
|
for (int AddMeshIdx = 1; AddMeshIdx < SplitMeshes.Num(); AddMeshIdx++)
|
|
{
|
|
FTransform Transform = ComponentTarget->GetComponentTransform();
|
|
|
|
FDynamicMesh3* Mesh = &SplitMeshes[AddMeshIdx];
|
|
|
|
TUniquePtr<FMeshDescription> MeshDescription = MakeUnique<FMeshDescription>();
|
|
FStaticMeshAttributes Attributes(*MeshDescription);
|
|
Attributes.Register();
|
|
|
|
FDynamicMeshToMeshDescription Converter;
|
|
Converter.Convert(Mesh, *MeshDescription);
|
|
|
|
// Add new actor
|
|
AStaticMeshActor* NewActor = Cast<AStaticMeshActor>(CreateActor(AStaticMeshActor::StaticClass(), FString()));
|
|
check(NewActor);
|
|
|
|
AActor* OriginalActor = ComponentTarget->GetOwner<AActor>();
|
|
check(OriginalActor);
|
|
|
|
NewActor->SetActorLabel(OriginalActor->GetActorLabel() + "_Below");
|
|
|
|
const FString NewMeshName = StaticMesh->GetName() + "_Below";
|
|
|
|
// Create new mesh component and set as root of NewActor.
|
|
UStaticMeshComponent* NewMeshComponent = FinalizeStaticMeshActor(NewActor, NewMeshName, MeshDescription.Get(), Materials.Num(), StaticMesh);
|
|
|
|
NewMeshComponent->SetMobility(ComponentTarget->Mobility);
|
|
NewMeshComponent->SetVisibility(ComponentTarget->IsVisible());
|
|
NewMeshComponent->ComponentTags = ComponentTarget->ComponentTags;
|
|
|
|
// Add dataprep user data
|
|
if (ComponentTarget->GetClass()->ImplementsInterface(UInterface_AssetUserData::StaticClass()) &&
|
|
NewMeshComponent->GetClass()->ImplementsInterface(UInterface_AssetUserData::StaticClass()))
|
|
{
|
|
IInterface_AssetUserData* OriginalAssetUserDataInterface = Cast< IInterface_AssetUserData >(ComponentTarget);
|
|
IInterface_AssetUserData* NewAssetUserDataInterface = Cast< IInterface_AssetUserData >(NewMeshComponent);
|
|
|
|
if (OriginalAssetUserDataInterface && NewAssetUserDataInterface)
|
|
{
|
|
if (const TArray<UAssetUserData*>* UserDataArray = OriginalAssetUserDataInterface->GetAssetUserDataArray())
|
|
{
|
|
for ( UAssetUserData* UserData : *UserDataArray )
|
|
{
|
|
if (UserData)
|
|
{
|
|
UObject* NewUserDataOuter = (UserData->GetOuter() == ComponentTarget) ? NewMeshComponent : UserData->GetOuter();
|
|
UAssetUserData* NewUserData = DuplicateObject<UAssetUserData>(UserData, NewUserDataOuter);
|
|
NewAssetUserDataInterface->AddAssetUserData(NewUserData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NewActor->Tags = OriginalActor->Tags;
|
|
NewActor->Layers = OriginalActor->Layers;
|
|
|
|
// Keep the newly created actor at the same level in hierarchy as the original one
|
|
AActor* Parent = OriginalActor->GetAttachParentActor();
|
|
if (Parent != nullptr)
|
|
{
|
|
NewActor->AttachToActor(Parent, FAttachmentTransformRules::KeepWorldTransform);
|
|
}
|
|
|
|
// Configure transform and materials of new component
|
|
NewMeshComponent->SetWorldTransform((FTransform)Transform);
|
|
for (int MatIdx = 0, NumMats = Materials.Num(); MatIdx < NumMats; MatIdx++)
|
|
{
|
|
NewMeshComponent->SetMaterial(MatIdx, Materials[MatIdx]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
UseMesh = &SplitMeshes[0];
|
|
}
|
|
}
|
|
|
|
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(0);
|
|
|
|
if (bKeepOriginalMesh)
|
|
{
|
|
// Create new mesh in order to preserve the original
|
|
|
|
EObjectFlags flags = EObjectFlags::RF_Public | EObjectFlags::RF_Standalone;
|
|
UStaticMesh* NewStaticMesh = Cast<UStaticMesh>(CreateAsset(UStaticMesh::StaticClass(), StaticMesh->GetName() + "_PlaneCut"));
|
|
|
|
// Initialize the LOD 0 MeshDescription
|
|
NewStaticMesh->SetNumSourceModels(1);
|
|
NewStaticMesh->GetSourceModel(0).BuildSettings.bRecomputeNormals = false;
|
|
NewStaticMesh->GetSourceModel(0).BuildSettings.bRecomputeTangents = true;
|
|
|
|
MeshDescription = NewStaticMesh->CreateMeshDescription(0);
|
|
|
|
if (StaticMesh->GetBodySetup())
|
|
{
|
|
if (NewStaticMesh->GetBodySetup() == nullptr)
|
|
{
|
|
NewStaticMesh->CreateBodySetup();
|
|
}
|
|
|
|
NewStaticMesh->GetBodySetup()->CollisionTraceFlag = StaticMesh->GetBodySetup()->CollisionTraceFlag.GetValue();
|
|
}
|
|
|
|
const TArray<FStaticMaterial>& MeshMaterials = StaticMesh->GetStaticMaterials();
|
|
for (const FStaticMaterial& Material : MeshMaterials)
|
|
{
|
|
NewStaticMesh->GetStaticMaterials().Add(Material);
|
|
}
|
|
|
|
for (UStaticMeshComponent* Component : InReferencingComponentsToUpdate[OrigMeshIdx])
|
|
{
|
|
Component->SetStaticMesh(NewStaticMesh);
|
|
}
|
|
|
|
StaticMesh = NewStaticMesh;
|
|
}
|
|
|
|
FDynamicMeshToMeshDescription DynamicMeshToMeshDescription;
|
|
|
|
// Full conversion if normal topology changed or faces were inverted
|
|
DynamicMeshToMeshDescription.Convert(UseMesh, *MeshDescription);
|
|
|
|
UStaticMesh::FCommitMeshDescriptionParams Params;
|
|
Params.bMarkPackageDirty = false;
|
|
Params.bUseHashAsGuid = true;
|
|
|
|
StaticMesh->CommitMeshDescription(0, Params);
|
|
|
|
for (UStaticMeshComponent* Component : InReferencingComponentsToUpdate[OrigMeshIdx])
|
|
{
|
|
Component->MarkRenderStateDirty();
|
|
}
|
|
|
|
if (!bKeepOriginalMesh)
|
|
{
|
|
OutModifiedStaticMeshes.Add(StaticMesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
TUniquePtr<UE::Geometry::FDynamicMeshOperator> UDataprepPlaneCutOperation::MakeNewOperator(
|
|
const FTransform& InMeshLocalToWorld,
|
|
TSharedPtr<UE::Geometry::FDynamicMesh3, ESPMode::ThreadSafe> InOriginalMesh,
|
|
float InMeshUVScaleFactor)
|
|
{
|
|
TUniquePtr<UE::Geometry::FPlaneCutOp> CutOp = MakeUnique<UE::Geometry::FPlaneCutOp>();
|
|
CutOp->bFillCutHole = bFillCutHole;
|
|
CutOp->bFillSpans = false;
|
|
|
|
FTransform LocalToWorld = InMeshLocalToWorld;
|
|
CutOp->SetTransform(LocalToWorld);
|
|
// for all plane computation, change LocalToWorld to not have any zero scale dims
|
|
FVector LocalToWorldScale = LocalToWorld.GetScale3D();
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
float DimScale = FMathf::Abs(LocalToWorldScale[i]);
|
|
float Tolerance = KINDA_SMALL_NUMBER;
|
|
if (DimScale < Tolerance)
|
|
{
|
|
LocalToWorldScale[i] = Tolerance * FMathf::SignNonZero(LocalToWorldScale[i]);
|
|
}
|
|
}
|
|
|
|
// Default plane normal is Z axis (XY plane)
|
|
const FTransform OrientPlane(FRotator::MakeFromEuler(CutPlaneNormalAngles));
|
|
FVector WorldNormal = OrientPlane.TransformVector(FVector::ZAxisVector);
|
|
|
|
if (CutPlaneKeepSide == EPlaneCutKeepSide::Positive)
|
|
{
|
|
WorldNormal *= -1;
|
|
}
|
|
|
|
LocalToWorld.SetScale3D(LocalToWorldScale);
|
|
FTransform WorldToLocal = LocalToWorld.Inverse();
|
|
FVector LocalOrigin = WorldToLocal.TransformPosition(CutPlaneOrigin);
|
|
UE::Geometry::FTransformSRT3d W2LForNormal(WorldToLocal);
|
|
FVector LocalNormal = (FVector)W2LForNormal.TransformNormal((FVector3d)WorldNormal);
|
|
FVector BackTransformed = LocalToWorld.TransformVector(LocalNormal);
|
|
float NormalScaleFactor = FVector::DotProduct(BackTransformed, WorldNormal);
|
|
if (NormalScaleFactor >= FLT_MIN)
|
|
{
|
|
NormalScaleFactor = 1.0 / NormalScaleFactor;
|
|
}
|
|
CutOp->LocalPlaneOrigin = (FVector3d)LocalOrigin;
|
|
CutOp->LocalPlaneNormal = (FVector3d)LocalNormal;
|
|
CutOp->OriginalMesh = InOriginalMesh;
|
|
CutOp->bKeepBothHalves = (CutPlaneKeepSide == EPlaneCutKeepSide::Both);
|
|
CutOp->CutPlaneLocalThickness = SpacingBetweenHalves * NormalScaleFactor;
|
|
CutOp->UVScaleFactor = InMeshUVScaleFactor;
|
|
|
|
return CutOp;
|
|
}
|
|
|
|
UStaticMeshComponent* UDataprepPlaneCutOperation::FinalizeStaticMeshActor(
|
|
AStaticMeshActor* InActor,
|
|
const FString& InMeshName,
|
|
const FMeshDescription* InMeshDescription,
|
|
int InNumMaterialSlots,
|
|
const UStaticMesh* InOriginalMesh)
|
|
{
|
|
check(InMeshDescription != nullptr);
|
|
|
|
// create new UStaticMesh object
|
|
EObjectFlags flags = EObjectFlags::RF_Public | EObjectFlags::RF_Standalone;
|
|
UStaticMesh* NewStaticMesh = Cast<UStaticMesh>(CreateAsset(UStaticMesh::StaticClass(), InMeshName));
|
|
|
|
// initialize the LOD 0 MeshDescription
|
|
NewStaticMesh->SetNumSourceModels(1);
|
|
NewStaticMesh->GetSourceModel(0).BuildSettings.bRecomputeNormals = false;
|
|
NewStaticMesh->GetSourceModel(0).BuildSettings.bRecomputeTangents = true;
|
|
|
|
NewStaticMesh->CreateMeshDescription(0, *InMeshDescription);
|
|
|
|
if (InOriginalMesh->GetBodySetup())
|
|
{
|
|
if (NewStaticMesh->GetBodySetup() == nullptr)
|
|
{
|
|
NewStaticMesh->CreateBodySetup();
|
|
}
|
|
|
|
NewStaticMesh->GetBodySetup()->CollisionTraceFlag = InOriginalMesh->GetBodySetup()->CollisionTraceFlag.GetValue();
|
|
}
|
|
|
|
// add a material slot. Must always have one material slot.
|
|
int AddMaterialCount = FMath::Max(1, InNumMaterialSlots);
|
|
for (int MatIdx = 0; MatIdx < AddMaterialCount; MatIdx++)
|
|
{
|
|
NewStaticMesh->GetStaticMaterials().Add(FStaticMaterial());
|
|
}
|
|
|
|
// assuming we have updated the LOD 0 MeshDescription, tell UStaticMesh about this
|
|
NewStaticMesh->CommitMeshDescription(0);
|
|
|
|
UStaticMeshComponent* NewMeshComponent = nullptr;
|
|
|
|
// if we have a StaticMeshActor we already have a StaticMeshComponent, otherwise we
|
|
// need to make a new one. Note that if we make a new one it will not be editable in the
|
|
// Editor because it is not a UPROPERTY...
|
|
AStaticMeshActor* StaticMeshActor = Cast<AStaticMeshActor>(InActor);
|
|
if (StaticMeshActor != nullptr)
|
|
{
|
|
NewMeshComponent = StaticMeshActor->GetStaticMeshComponent();
|
|
}
|
|
else
|
|
{
|
|
// create component
|
|
NewMeshComponent = NewObject<UStaticMeshComponent>(InActor);
|
|
InActor->SetRootComponent(NewMeshComponent);
|
|
}
|
|
|
|
// this disconnects the component from various events
|
|
NewMeshComponent->UnregisterComponent();
|
|
|
|
// Configure flags of the component. Is this necessary?
|
|
NewMeshComponent->SetMobility(EComponentMobility::Movable);
|
|
NewMeshComponent->bSelectable = true;
|
|
|
|
// replace the UStaticMesh in the component
|
|
NewMeshComponent->SetStaticMesh(NewStaticMesh);
|
|
|
|
// re-connect the component (?)
|
|
NewMeshComponent->RegisterComponent();
|
|
|
|
// if we don't do this, world traces don't hit the mesh
|
|
NewMeshComponent->MarkRenderStateDirty();
|
|
|
|
return NewMeshComponent;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|