Files
UnrealEngine/Engine/Plugins/Experimental/Enterprise/DataprepGeometryOperations/Source/Private/JacketingProcess.cpp
2025-05-18 13:04:45 +08:00

845 lines
30 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "JacketingProcess.h"
#include "CoreMinimal.h"
#include "AssetRegistry/AssetData.h"
#include "Async/ParallelFor.h"
#include "Editor.h"
#include "Editor/TransBuffer.h"
#include "Editor/UnrealEdEngine.h"
#include "Engine/StaticMesh.h"
#include "Layers/LayersSubsystem.h"
#include "Logging/LogMacros.h"
#include "Math/Box.h"
#include "MeshMergeData.h"
#include "StaticMeshAttributes.h"
#include "Misc/ScopedSlowTask.h"
#include "StaticMeshOperations.h"
#include "Templates/Tuple.h"
#include "UObject/SoftObjectPath.h"
#include "UObject/StrongObjectPtr.h"
#include "Components/StaticMeshComponent.h"
#if WITH_PROXYLOD
#include "ProxyLODVolume.h"
#endif
#include "Subsystems/AssetEditorSubsystem.h"
DEFINE_LOG_CATEGORY_STATIC(LogJacketingProcess, Display, All);
#define LOCTEXT_NAMESPACE "JacketingProcess"
//#define DEBUG_EXPORT_ENVELOP
namespace JacketingProcessUtils
{
/**
* Roughly (gu)estimate if value of Options->Accuracy could generate huge number of voxels
* If so, warn user, choose a more adequate value and proceed
*/
float ValidateVoxelSize(float InAccuracy, const TArray<UStaticMeshComponent*>& StaticMeshComponents)
{
const uint64 EmpiricalReasonableVoxelCount = 1073741824;
float OutAccuracy = InAccuracy;
double TotalVolume = 0;
for (UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
{
const FVector Scale = StaticMeshComponent->GetComponentTransform().GetScale3D();
FVector ComponentExtent = StaticMeshComponent->GetStaticMesh()->GetBoundingBox().GetExtent() * Scale;
// Add volume of component's bounding box
ComponentExtent = ComponentExtent.ComponentMax(FVector(InAccuracy));
TotalVolume += (double)ComponentExtent[0] * (double)ComponentExtent[1] * (double)ComponentExtent[2];
}
const uint64 MaxGuestimatedVoxelCount = (uint64) (TotalVolume / (InAccuracy*InAccuracy*InAccuracy));
if (MaxGuestimatedVoxelCount > EmpiricalReasonableVoxelCount)
{
// Best guest on accuracy is to be equal to the value to use in order to potentially
// subdivide the total volume of the selected actors in 'EmpiricalReasonableVoxelCount' cells.
OutAccuracy = FMath::Pow(TotalVolume / (double)EmpiricalReasonableVoxelCount, 1.0/3.0);
// Inform user accuracy has been modified
UE_LOG(LogJacketingProcess, Warning, TEXT("AssemblyJacketing: Voxel precision of %.5f too small. Using %.5f instead."), InAccuracy, OutAccuracy);
}
return OutAccuracy;
}
}
#if WITH_PROXYLOD
void FJacketingProcess::FindOverlappingActors(const TArray<AActor*>& InActorsToTest, const TArray<AActor*>& InActorsToTestAgainst, const FJacketingOptions* Options, TArray<AActor*>& OutOverlappingActors, bool bSilent)
{
if (InActorsToTestAgainst.Num() == 0 || InActorsToTest.Num() == 0)
{
UE_LOG(LogJacketingProcess, Warning, TEXT("FindOverlappingActors: No actors to process. Aborting..."));
return;
}
// Collect start time to log amount of time spent to import incoming file
uint64 StartTime = FPlatformTime::Cycles64();
uint64 LastTime = FPlatformTime::Cycles64();
TFunction<void(const TArray<AActor*>&, TArray<UStaticMeshComponent*>&)> GetActorsComponents = [](const TArray<AActor*>& InActors, TArray<UStaticMeshComponent*>& OutComponents)
{
for (AActor* Actor : InActors)
{
if (Actor == nullptr)
{
continue;
}
for (UActorComponent* Component : Actor->GetComponents())
{
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(Component))
{
if (StaticMeshComponent->GetStaticMesh() == nullptr)
{
continue;
}
OutComponents.Add(StaticMeshComponent);
}
}
}
};
TArray<UStaticMeshComponent*> BuildVolumeComponents;
GetActorsComponents(InActorsToTestAgainst, BuildVolumeComponents);
if (BuildVolumeComponents.Num() == 0)
{
UE_LOG(LogJacketingProcess, Warning, TEXT("FindOverlappingActors: No meshes to process. Aborting..."));
return;
}
// Geometry input data for voxelizing methods
TArray<FMeshMergeData> Geometry;
for (UStaticMeshComponent* StaticMeshComponent : BuildVolumeComponents)
{
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
if (StaticMesh == nullptr)
{
continue;
}
// FMeshMergeData will release the newly created MeshDescription
FMeshDescription* MeshDescriptionOriginal = StaticMesh->GetMeshDescription(0);
FMeshDescription* MeshDescription = new FMeshDescription();
*MeshDescription = *MeshDescriptionOriginal;
//Make sure all ID are from 0 to N
FElementIDRemappings OutRemappings;
MeshDescription->Compact(OutRemappings);
const FTransform& ComponentToWorldTransform = StaticMeshComponent->GetComponentTransform();
// Transform raw mesh vertex data by the Static Mesh Component's component to world transformation
FStaticMeshOperations::ApplyTransform(*MeshDescription, ComponentToWorldTransform);
// Geometry input data for voxelizing methods
FMeshMergeData MergeData;
MergeData.bIsClippingMesh = false;
MergeData.SourceStaticMesh = StaticMesh;
MergeData.RawMesh = MeshDescription;
Geometry.Add(MergeData);
}
if (Geometry.Num() == 0)
{
UE_LOG(LogJacketingProcess, Warning, TEXT("FindOverlappingActors: No geometry to process. Aborting..."));
return;
}
const float Accuracy = JacketingProcessUtils::ValidateVoxelSize(Options->Accuracy, BuildVolumeComponents);
TSharedPtr<FScopedSlowTask> Progress;
const float Voxelization = 40.f;
const float GapFilling = 20.f;
const float MeshTesting = 38.f;
if (!bSilent)
{
Progress = TSharedPtr<FScopedSlowTask>(new FScopedSlowTask(100.0f, LOCTEXT("StartWork", "Occlusion culling ..."), true, *GWarn));
Progress->MakeDialog(true);
Progress->EnterProgressFrame(1.f, FText::FromString(TEXT("Analyzing meshes ...")));
}
if (Progress.IsValid())
{
Progress->EnterProgressFrame(Voxelization, FText::FromString(TEXT("Creating Voxelization ...")));
}
TUniquePtr<IProxyLODVolume> Volume(IProxyLODVolume::CreateSDFVolumeFromMeshArray(Geometry, Accuracy));
if (!Volume.IsValid())
{
UE_LOG(LogJacketingProcess, Error, TEXT("FindOverlappingActors: Voxelization of geometry failed. Aborting process..."));
return;
}
// Release the memory used by geometry (deletes the allocated mesh descriptions)
Geometry.Empty();
if (Progress.IsValid())
{
Progress->EnterProgressFrame(GapFilling, FText::FromString(TEXT("Closing gaps ...")));
}
double IntermediateTime = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - LastTime);
UE_LOG(LogJacketingProcess, Log, TEXT("FindOverlappingActors: Creation of volume took %.3f s."), IntermediateTime);
LastTime = FPlatformTime::Cycles64();
double HoleRadius = 0.5 * Options->MergeDistance; // cm
IProxyLODVolume::FVector3i VolumeBBoxSize = Volume->GetBBoxSize();
// Clamp the hole radius.
const double VoxelSize = Volume->GetVoxelSize();
int32 MinIndex = VolumeBBoxSize.MinIndex();
double BBoxMinorAxis = VolumeBBoxSize[MinIndex] * VoxelSize;
if (HoleRadius > 0.5 * BBoxMinorAxis)
{
HoleRadius = 0.5 * BBoxMinorAxis;
UE_LOG(LogJacketingProcess, Warning, TEXT("OverlappingActors: Merge distance %f too large, clamped to %f."), Options->MergeDistance, float(2. * HoleRadius));
}
// Used in gap-closing. This max is to bound a potentially expensive computation.
// If the gap size requires more dilation steps at the current voxel size,
// then the dilation (and erosion) will be done with larger voxels.
const int32 MaxDilationSteps = 7;
LastTime = FPlatformTime::Cycles64();
if (HoleRadius > 0.25 * VoxelSize && MaxDilationSteps > 0)
{
// performance tuning number. if more dilations are required for this hole radius, a coarser grid is used.
Volume->CloseGaps(HoleRadius, MaxDilationSteps);
}
IntermediateTime = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - LastTime);
UE_LOG(LogJacketingProcess, Log, TEXT("FindOverlappingActors: Closure of gaps took %.3f s."), IntermediateTime);
LastTime = FPlatformTime::Cycles64();
TSet< AActor* > OverlappingActorSet;
OverlappingActorSet.Reserve(Geometry.Num());
// Set the maximum distance over which a point is considered outside the volume
const float MaxDistance = -1.99f * Accuracy;
TArray<UStaticMeshComponent*> StaticMeshComponentsToTest;
GetActorsComponents(InActorsToTest, StaticMeshComponentsToTest);
float ProcessingStep = MeshTesting / (float)StaticMeshComponentsToTest.Num();
// Compute volume bounding box for quick early testing
FBox VolumeBoundingBox( EForceInit::ForceInit );
for (AActor* VolumeActor : InActorsToTestAgainst)
{
VolumeBoundingBox += VolumeActor->GetComponentsBoundingBox();
}
// Check each actor agains volume
for (UStaticMeshComponent* StaticMeshComponent : StaticMeshComponentsToTest)
{
if (Progress.IsValid())
{
Progress->EnterProgressFrame(ProcessingStep, FText::FromString(TEXT("Checking inclusion of meshes ...")));
}
const FBox OverlapBox = VolumeBoundingBox.Overlap( StaticMeshComponent->Bounds.GetBox() );
bool bComponentInside = (OverlapBox.GetVolume() > 0);
// Component's bounding box intersects with volume, check on vertices
// (we need at least one vertex inside of volume)
if (bComponentInside)
{
bComponentInside = false;
const FMeshDescription* MeshDescription = StaticMeshComponent->GetStaticMesh()->GetMeshDescription(0);
const FTransform& ComponentTransform = StaticMeshComponent->GetComponentTransform();
TVertexAttributesConstRef<FVector3f> VertexPositions = MeshDescription->VertexAttributes().GetAttributesRef<FVector3f>(MeshAttribute::Vertex::Position);
for (FVertexID VertexID : MeshDescription->Vertices().GetElementIDs())
{
const FVector WorldPosition = ComponentTransform.TransformPosition((FVector)VertexPositions[VertexID]);
if (Volume->QueryDistance(WorldPosition) <= MaxDistance)
{
bComponentInside = true;
break;
}
}
}
if (bComponentInside)
{
AActor* Actor = StaticMeshComponent->GetOwner();
OverlappingActorSet.Add(Actor);
}
}
double ElapsedSeconds = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - StartTime);
int32 ElapsedMin = int32(ElapsedSeconds / 60.0);
ElapsedSeconds -= 60.0 * (double)ElapsedMin;
UE_LOG(LogJacketingProcess, Log, TEXT("FindOverlappingActors: took %d min %.3f s. %d occluded actors out of %d"), ElapsedMin, ElapsedSeconds, OverlappingActorSet.Num(), BuildVolumeComponents.Num());
OutOverlappingActors = OverlappingActorSet.Array();
}
// See FVoxelizeMeshMerging::ProxyLOD
void FJacketingProcess::ApplyJacketingOnMeshActors(const TArray<AActor*>& Actors, const FJacketingOptions* Options, TArray<AActor*>& OccludedActorArray, bool bSilent)
{
if (Actors.Num() == 0)
{
UE_LOG(LogJacketingProcess, Warning, TEXT("AssemblyJacketing: No actors to process. Aborting..."));
return;
}
//JacketingProcessUtils::UpdateUndoBufferSize();
// Collect start time to log amount of time spent to import incoming file
uint64 StartTime = FPlatformTime::Cycles64();
uint64 LastTime = FPlatformTime::Cycles64();
TMap<AActor*, int32> ActorOccurences;
ActorOccurences.Reserve(Actors.Num());
for (AActor* Actor : Actors)
{
ActorOccurences.Add(Actor, 0);
}
// Collect all StaticMeshComponent objects
TArray<UStaticMeshComponent*> StaticMeshComponents;
for (AActor* Actor : Actors)
{
if (Actor == nullptr)
{
continue;
}
int32 ComponentCount = 0;
for (UActorComponent* Component : Actor->GetComponents())
{
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(Component))
{
if (StaticMeshComponent->GetStaticMesh() == nullptr)
{
continue;
}
ComponentCount++;
StaticMeshComponents.Add(StaticMeshComponent);
}
}
ActorOccurences[Actor] = ComponentCount;
}
if (StaticMeshComponents.Num() == 0)
{
UE_LOG(LogJacketingProcess, Warning, TEXT("AssemblyJacketing: No meshes to process. Aborting..."));
return;
}
float Accuracy = JacketingProcessUtils::ValidateVoxelSize(Options->Accuracy, StaticMeshComponents);
TSharedPtr<FScopedSlowTask> Progress;
const float Voxelization = Options->Target == EJacketingTarget::Mesh ? 10.f : 40.f;
const float GapFilling = Options->Target == EJacketingTarget::Mesh ? 5.f : 20.f;
const float MeshTesting = Options->Target == EJacketingTarget::Mesh ? 10.f : 38.f;
const float VertexTesting = Options->Target == EJacketingTarget::Mesh ? 20.f : 0.f;
const float TriangleDeletion = Options->Target == EJacketingTarget::Mesh ? 20.f : 0.f;
const float MeshBuilding = Options->Target == EJacketingTarget::Mesh ? 33.f : 0.f;
if (!bSilent)
{
Progress = TSharedPtr<FScopedSlowTask>(new FScopedSlowTask(100.0f, LOCTEXT("StartWork", "Occlusion culling ..."), true, *GWarn));
Progress->MakeDialog(true);
Progress->EnterProgressFrame(1.f, FText::FromString(TEXT("Analyzing meshes ...")));
}
// Geometry input data for voxelizing methods
TArray<FMeshMergeData> Geometry;
// Store world space mesh for each static mesh component
TMap<UStaticMeshComponent*, FMeshDescription*> MeshDescriptions;
int32 VertexCount = 0;
int32 PolygonCount = 0;
int32 DeletedPolygonCount = 0;
for (UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
{
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
if (StaticMesh == nullptr)
{
continue;
}
// FMeshMergeData will release the allocated MeshDescription...
FMeshDescription* MeshDescriptionOriginal = StaticMesh->GetMeshDescription(0);
FMeshDescription* MeshDescription = new FMeshDescription();
*MeshDescription = *MeshDescriptionOriginal;
//Make sure all ID are from 0 to N
FElementIDRemappings OutRemappings;
MeshDescription->Compact(OutRemappings);
const FTransform& ComponentToWorldTransform = StaticMeshComponent->GetComponentTransform();
// Transform raw mesh vertex data by the Static Mesh Component's component to world transformation
FStaticMeshOperations::ApplyTransform(*MeshDescription, ComponentToWorldTransform);
VertexCount += MeshDescription->Vertices().Num();
PolygonCount += MeshDescription->Polygons().Num();
// Stores transformed MeshDescription for later use
MeshDescriptions.Add(StaticMeshComponent, MeshDescription);
FMeshMergeData MergeData;
MergeData.bIsClippingMesh = false;
MergeData.SourceStaticMesh = StaticMesh;
MergeData.RawMesh = MeshDescription;
Geometry.Add(MergeData);
}
if (Geometry.Num() == 0)
{
UE_LOG(LogJacketingProcess, Warning, TEXT("AssemblyJacketing: No geometry to process. Aborting..."));
return;
}
if (Progress.IsValid())
{
Progress->EnterProgressFrame(Voxelization, FText::FromString(TEXT("Creating Voxelization ...")) );
}
UE_LOG(LogJacketingProcess, Log, TEXT("AssemblyJacketing: Processing %i components, %i polygons"), StaticMeshComponents.Num(), PolygonCount);
TUniquePtr<IProxyLODVolume> Volume(IProxyLODVolume::CreateSDFVolumeFromMeshArray(Geometry, Accuracy));
if (!Volume.IsValid())
{
UE_LOG(LogJacketingProcess, Error, TEXT("AssemblyJacketing: Voxelization of geometry failed. Aborting process..."));
return;
}
if (Progress.IsValid())
{
Progress->EnterProgressFrame(GapFilling, FText::FromString(TEXT("Closing gaps ...")));
}
double IntermediateTime = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - LastTime);
UE_LOG(LogJacketingProcess, Log, TEXT("AssemblyJacketing: Creation of volume took %.3f s."), IntermediateTime);
LastTime = FPlatformTime::Cycles64();
double HoleRadius = 0.5 * Options->MergeDistance;
IProxyLODVolume::FVector3i VolumeBBoxSize = Volume->GetBBoxSize();
// Clamp the hole radius.
const double VoxelSize = Volume->GetVoxelSize();
int32 MinIndex = VolumeBBoxSize.MinIndex();
double BBoxMinorAxis = VolumeBBoxSize[MinIndex] * VoxelSize;
if (HoleRadius > .5 * BBoxMinorAxis)
{
HoleRadius = .5 * BBoxMinorAxis;
UE_LOG(LogJacketingProcess, Warning, TEXT("AssemblyJacketing: Merge distance %f too large, clamped to %f."), Options->MergeDistance, float(2. * HoleRadius));
}
// Used in gap-closing. This max is to bound a potentially expensive computation.
// If the gap size requires more dilation steps at the current voxel size,
// then the dilation (and erosion) will be done with larger voxels.
const int32 MaxDilationSteps = 7;
LastTime = FPlatformTime::Cycles64();
if (HoleRadius > 0.25 * VoxelSize && MaxDilationSteps > 0)
{
// performance tuning number. if more dilations are required for this hole radius, a coarser grid is used.
Volume->CloseGaps(HoleRadius, MaxDilationSteps);
}
TSet< AActor* > OccludedActorSet;
OccludedActorSet.Reserve(Geometry.Num());
IntermediateTime = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - LastTime);
UE_LOG(LogJacketingProcess, Log, TEXT("AssemblyJacketing: Closure of gaps took %.3f s."), IntermediateTime);
LastTime = FPlatformTime::Cycles64();
int32 OccludedComponentCount = 0;
// Set the maximum distance over which a point is considered outside the volume
// Set to twice the precision requested
float MaxDistance = -2.0f * Accuracy;
float ProcessingStep = MeshTesting / (float)StaticMeshComponents.Num();
for (UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
{
if (Progress.IsValid())
{
Progress->EnterProgressFrame(ProcessingStep, FText::FromString(TEXT("Checking inclusion of meshes ...")));
}
FVector Min, Max;
StaticMeshComponent->GetLocalBounds(Min, Max);
bool bComponentInside = true;
// Check the corners of the component's bounding box
for (int32 i = 0; i < 8; i++)
{
FVector Corner = Min;
if (i % 2)
{
Corner.X += Max.X - Min.X;
}
if ((i / 2) % 2)
{
Corner.Y += Max.Y - Min.Y;
}
if (i / 4)
{
Corner.Z += Max.Z - Min.Z;
}
const FTransform& ComponentTransform = StaticMeshComponent->GetComponentTransform();
const FVector WorldCorner = ComponentTransform.TransformPosition(Corner);
const float Value = Volume->QueryDistance(WorldCorner);
if (Value > MaxDistance)
{
bComponentInside = false;
break;
}
}
// Component's bounding box intersect with volume, check on vertices
if (!bComponentInside)
{
bComponentInside = true;
FMeshDescription* MeshDescription = MeshDescriptions[StaticMeshComponent];
TVertexAttributesConstRef<FVector3f> VertexPositions = MeshDescription->VertexAttributes().GetAttributesRef<FVector3f>(MeshAttribute::Vertex::Position);
for (FVertexID VertexID : MeshDescription->Vertices().GetElementIDs())
{
if (Volume->QueryDistance((FVector)VertexPositions[VertexID]) > MaxDistance)
{
bComponentInside = false;
break;
}
}
}
if (bComponentInside)
{
DeletedPolygonCount += MeshDescriptions[StaticMeshComponent]->Polygons().Num();
OccludedComponentCount++;
AActor* Actor = StaticMeshComponent->GetOwner();
// Decrement number of
int32& ComponentCount = ActorOccurences[Actor];
--ComponentCount;
// All static mesh components of an actor are occluded, take action
if (ComponentCount == 0)
{
OccludedActorSet.Add(Actor);
}
}
}
// Fill up input array and return if target is only Level
if (Options->Target == EJacketingTarget::Level)
{
double ElapsedSeconds = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - StartTime);
int32 ElapsedMin = int32(ElapsedSeconds / 60.0);
ElapsedSeconds -= 60.0 * (double)ElapsedMin;
UE_LOG(LogJacketingProcess, Log, TEXT("AssemblyJacketing: took %d min %.3f s. %d occluded actors out of %d"), ElapsedMin, ElapsedSeconds, OccludedActorSet.Num(), StaticMeshComponents.Num());
OccludedActorArray = OccludedActorSet.Array();
return;
}
IntermediateTime = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - LastTime);
UE_LOG(LogJacketingProcess, Log, TEXT("AssemblyJacketing: Processing of meshes took %.3f s."), IntermediateTime);
LastTime = FPlatformTime::Cycles64();
// Proceed with triangle deletion
if (Options->MergeDistance > VoxelSize)
{
// Expand interior narrow band to reach visible vertices discarded because of gap filling
Volume->ExpandNarrowBand(VoxelSize, Options->MergeDistance);
// Update MaxDistance to reflect expansion of narrow band
if (Options->MergeDistance > 2.0f * VoxelSize)
{
MaxDistance = -Options->MergeDistance;
}
}
if (Progress.IsValid())
{
Progress->EnterProgressFrame(VertexTesting, FText::FromString(TEXT("Checking occlusion of vertices ...")));
}
typedef TTuple<UStaticMeshComponent*, const FVector3f*, bool> FVertexData;
TArray<FVertexData> VertexDataArray;
VertexDataArray.Reserve(VertexCount);
LastTime = FPlatformTime::Cycles64();
// Mark any vertices of visible meshes which are outside the volume
for (UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
{
if (OccludedActorSet.Find(StaticMeshComponent->GetOwner()) != nullptr)
{
continue;
}
TVertexAttributesConstRef<FVector3f> VertexPositions = MeshDescriptions[StaticMeshComponent]->VertexAttributes().GetAttributesRef<FVector3f>(MeshAttribute::Vertex::Position);
for (FVertexID VertexID : MeshDescriptions[StaticMeshComponent]->Vertices().GetElementIDs())
{
VertexDataArray.Emplace(FVertexData(StaticMeshComponent, &VertexPositions[VertexID], false));
}
}
VertexCount = VertexDataArray.Num();
ParallelFor(VertexDataArray.Num(), [&](int32 Index) {
FVertexData& VertexData = VertexDataArray[Index];
if (!VertexData.Get<2>() && Volume->QueryDistance((FVector)*VertexData.Get<1>()) > MaxDistance)
{
VertexData.Get<2>() = true;
}
});
IntermediateTime = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - LastTime);
UE_LOG(LogJacketingProcess, Log, TEXT("AssemblyJacketing: Processing of vertices took %.3f s."), IntermediateTime);
LastTime = FPlatformTime::Cycles64();
if (Progress.IsValid())
{
Progress->EnterProgressFrame(TriangleDeletion, FText::FromString(TEXT("Deleting triangles ...")));
}
// Combining vertex visibility for meshes used in more than one static mesh component
typedef TPair<UStaticMeshComponent*, TArray<bool>> TVisibilityPair;
TMap<UStaticMesh*, TVisibilityPair> MeshesToRebuild;
MeshesToRebuild.Reserve(StaticMeshComponents.Num());
for (int32 Index = 0; Index < VertexCount; )
{
UStaticMeshComponent* StaticMeshComponent = VertexDataArray[Index].Get<0>();
if (MeshesToRebuild.Find(StaticMeshComponent->GetStaticMesh()) == nullptr)
{
TVisibilityPair& VisibilityPair = MeshesToRebuild.Add(StaticMeshComponent->GetStaticMesh(), TVisibilityPair(StaticMeshComponent, TArray<bool>()));
VisibilityPair.Value.AddZeroed(MeshDescriptions[StaticMeshComponent]->Vertices().Num());
}
TArray<bool>& VertexVisibility = MeshesToRebuild[StaticMeshComponent->GetStaticMesh()].Value;
for (int32 VertexIndex = 0; Index < VertexCount && StaticMeshComponent == VertexDataArray[Index].Get<0>(); ++VertexIndex, ++Index)
{
VertexVisibility[VertexIndex] |= VertexDataArray[Index].Get<2>();
}
}
// Removing occluded triangles from meshes
TArray<UStaticMesh*> StaticMeshArray;
MeshesToRebuild.GetKeys(StaticMeshArray);
ParallelFor(MeshesToRebuild.Num(), [&](int32 Index)
{
UStaticMesh* StaticMesh = StaticMeshArray[Index];
TVisibilityPair& VisibilityPair = MeshesToRebuild[StaticMesh];
UStaticMeshComponent* StaticMeshComponent = VisibilityPair.Key;
TArray<bool>& VertexVisibility = VisibilityPair.Value;
FMeshDescription* MeshDescription = MeshDescriptions[StaticMeshComponent];
if (MeshDescription->Polygons().Num() == 0 || MeshDescription->Vertices().Num() == 0)
{
VisibilityPair.Key = nullptr;
return;
}
FMeshDescription NewMeshDescription = *MeshDescription;
TArray<FPolygonID> PolygonToRemove;
for (const FPolygonID& PolygonID : NewMeshDescription.Polygons().GetElementIDs())
{
TArray<FVertexID> PolygonVertices;
NewMeshDescription.GetPolygonVertices(PolygonID, PolygonVertices);
bool bPolygonVisible = false;
for (FVertexID VertexID : PolygonVertices)
{
bPolygonVisible |= VertexVisibility[VertexID.GetValue()];
}
if (!bPolygonVisible)
{
PolygonToRemove.Add(PolygonID);
}
}
// All triangles are visible. This mesh might be instantiated in several locations
if (PolygonToRemove.Num() == 0)
{
VisibilityPair.Key = nullptr;
}
// This should never happen. Such situation should have been caught earlier
else if (PolygonToRemove.Num() == NewMeshDescription.Polygons().Num())
{
check(false);
}
// Update mesh to only contain visible triangles
else
{
NewMeshDescription.DeletePolygons(PolygonToRemove);
//Compact and Remap IDs so we have clean ID from 0 to n since we just erased some polygons
FElementIDRemappings RemappingInfos;
NewMeshDescription.Compact(RemappingInfos);
FTransform Transform = StaticMeshComponent->GetComponentTransform().Inverse();
//Apply the inverse component transform
FStaticMeshOperations::ApplyTransform(NewMeshDescription, Transform);
TVertexInstanceAttributesRef<FVector3f> VertexInstanceNormals = NewMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector3f> VertexInstanceTangents = NewMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Tangent);
MeshDescription->Empty();
//Create the missing normals and tangents on polygons because FStaticMeshOperations::ComputeTangentsAndNormals requires it
if(!NewMeshDescription.TriangleAttributes().GetAttributesRef<FVector3f>(MeshAttribute::Triangle::Normal).IsValid()
|| !NewMeshDescription.TriangleAttributes().GetAttributesRef<FVector3f>(MeshAttribute::Triangle::Tangent).IsValid())
{
FStaticMeshOperations::ComputeTriangleTangentsAndNormals(NewMeshDescription);
}
const FMeshBuildSettings& BuildSettings = StaticMesh->GetSourceModel(0).BuildSettings;
EComputeNTBsFlags ComputeNTBsOptions = EComputeNTBsFlags::BlendOverlappingNormals;
ComputeNTBsOptions |= BuildSettings.bRecomputeNormals ? EComputeNTBsFlags::Normals : EComputeNTBsFlags::None;
ComputeNTBsOptions |= BuildSettings.bRecomputeTangents ? EComputeNTBsFlags::Tangents : EComputeNTBsFlags::None;
ComputeNTBsOptions |= BuildSettings.bUseMikkTSpace ? EComputeNTBsFlags::UseMikkTSpace : EComputeNTBsFlags::None;
ComputeNTBsOptions |= BuildSettings.bComputeWeightedNormals ? EComputeNTBsFlags::WeightedNTBs : EComputeNTBsFlags::None;
ComputeNTBsOptions |= BuildSettings.bRemoveDegenerates ? EComputeNTBsFlags::IgnoreDegenerateTriangles : EComputeNTBsFlags::None;
FStaticMeshOperations::ComputeTangentsAndNormals(NewMeshDescription, ComputeNTBsOptions);
// TODO: Maybe add generation of lightmap UV here.
// Update mesh description of static mesh with new geometry
FMeshDescription* OrigMeshDescription = StaticMesh->GetMeshDescription(0);
*OrigMeshDescription = MoveTemp(NewMeshDescription);
}
});
for (UStaticMesh* StaticMesh : StaticMeshArray)
{
//Commit the result so the old FMeshDescription is updated
StaticMesh->CommitMeshDescription(0);
}
IntermediateTime = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - LastTime);
UE_LOG(LogJacketingProcess, Log, TEXT("AssemblyJacketing: Deleting triangles took %.3f s."), IntermediateTime);
LastTime = FPlatformTime::Cycles64();
// Rebuilding meshes which have been truncated
ProcessingStep = MeshBuilding / (float)MeshesToRebuild.Num();
for (auto& MeshToRebuild : MeshesToRebuild)
{
UStaticMesh* StaticMesh = MeshToRebuild.Key;
if (Progress.IsValid())
{
Progress->EnterProgressFrame(ProcessingStep, FText::FromString(TEXT("Building meshes ...")));
}
if (MeshToRebuild.Value.Key != nullptr)
{
FMeshBuildSettings CachedBuildSettings = StaticMesh->GetSourceModel(0).BuildSettings;
FMeshBuildSettings& BuildSettings = StaticMesh->GetSourceModel(0).BuildSettings;
BuildSettings.bRecomputeNormals = false;
BuildSettings.bRecomputeTangents = false;
StaticMesh->PreEditChange(nullptr);
StaticMesh->CommitMeshDescription(0);
StaticMesh->PostEditChange();
BuildSettings.bRecomputeNormals = CachedBuildSettings.bRecomputeNormals;
BuildSettings.bRecomputeTangents = CachedBuildSettings.bRecomputeTangents;
}
}
IntermediateTime = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - LastTime);
UE_LOG(LogJacketingProcess, Log, TEXT("AssemblyJacketing: Rebuilding static meshes took %.3f s."), IntermediateTime);
LastTime = FPlatformTime::Cycles64();
if (Progress.IsValid())
{
Progress->EnterProgressFrame(1.f, FText::FromString(TEXT("Updating level ...")));
}
for (FMeshMergeData& MergeData : Geometry)
{
MergeData.ReleaseData();
}
#if defined(DEBUG_EXPORT_ENVELOP)
{
FSoftObjectPath SoftObjectPath(TEXT("/Game/jacketing.jacketing"));
UStaticMesh* VolumeStaticMesh = Cast<UStaticMesh>(SoftObjectPath.TryLoad());
if (VolumeStaticMesh)
{
FMeshDescription* MeshDescription = VolumeStaticMesh->GetMeshDescription(0);
Volume->ConvertToRawMesh(*MeshDescription);
// Update raw mesh with new geometry
VolumeStaticMesh->PreEditChange(nullptr);
VolumeStaticMesh->CommitMeshDescription(0);
VolumeStaticMesh->PostEditChange();
}
}
#endif
// Delete actors
if (OccludedActorSet.Num() > 0)
{
UWorld* World = GEditor->GetEditorWorldContext(false).World();
ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem<ULayersSubsystem>();
for (AActor* Actor : OccludedActorSet)
{
//To avoid dangling gizmo after actor has been destroyed
if (Actor->IsSelected())
{
GEditor->SelectActor(Actor, false, true);
}
LayersSubsystem->DisassociateActorFromLayers(Actor);
if (!World->DestroyActor(Actor, false, true))
{
UE_LOG(LogJacketingProcess, Error, TEXT("AssemblyJacketing: Cannot delete Actor %s."), *Actor->GetActorLabel());
}
}
World->BroadcastLevelsChanged();
}
// Log time spent to perform jacketing process
double ElapsedSeconds = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - StartTime);
int32 ElapsedMin = int32(ElapsedSeconds / 60.0);
ElapsedSeconds -= 60.0 * (double)ElapsedMin;
UE_LOG(LogJacketingProcess, Log, TEXT("AssemblyJacketing: took %d min %.3f s. %d occluded components out of %d, %d polygons deleted out of %d"),
ElapsedMin, ElapsedSeconds, OccludedComponentCount, StaticMeshComponents.Num(), DeletedPolygonCount, PolygonCount);
}
#else
void FJacketingProcess::ApplyJacketingOnMeshActors(const TArray<AActor*>&, const FJacketingOptions*, TArray<AActor*>&, bool) {}
void FJacketingProcess::FindOverlappingActors(const TArray<AActor*>& InActorsToTest, const TArray<AActor*>& InActorsToTestAgainst, const FJacketingOptions* Options, TArray<AActor*>& OutOverlappingActors, bool bSilent) {}
#endif // WITH_PROXYLOD
#undef LOCTEXT_NAMESPACE