// 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& 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& InActorsToTest, const TArray& InActorsToTestAgainst, const FJacketingOptions* Options, TArray& 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&, TArray&)> GetActorsComponents = [](const TArray& InActors, TArray& OutComponents) { for (AActor* Actor : InActors) { if (Actor == nullptr) { continue; } for (UActorComponent* Component : Actor->GetComponents()) { if (UStaticMeshComponent* StaticMeshComponent = Cast(Component)) { if (StaticMeshComponent->GetStaticMesh() == nullptr) { continue; } OutComponents.Add(StaticMeshComponent); } } } }; TArray 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 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 Progress; const float Voxelization = 40.f; const float GapFilling = 20.f; const float MeshTesting = 38.f; if (!bSilent) { Progress = TSharedPtr(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 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 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 VertexPositions = MeshDescription->VertexAttributes().GetAttributesRef(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& Actors, const FJacketingOptions* Options, TArray& 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 ActorOccurences; ActorOccurences.Reserve(Actors.Num()); for (AActor* Actor : Actors) { ActorOccurences.Add(Actor, 0); } // Collect all StaticMeshComponent objects TArray StaticMeshComponents; for (AActor* Actor : Actors) { if (Actor == nullptr) { continue; } int32 ComponentCount = 0; for (UActorComponent* Component : Actor->GetComponents()) { if (UStaticMeshComponent* StaticMeshComponent = Cast(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 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(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 Geometry; // Store world space mesh for each static mesh component TMap 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 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 VertexPositions = MeshDescription->VertexAttributes().GetAttributesRef(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 FVertexData; TArray 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 VertexPositions = MeshDescriptions[StaticMeshComponent]->VertexAttributes().GetAttributesRef(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> TVisibilityPair; TMap 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())); VisibilityPair.Value.AddZeroed(MeshDescriptions[StaticMeshComponent]->Vertices().Num()); } TArray& 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 StaticMeshArray; MeshesToRebuild.GetKeys(StaticMeshArray); ParallelFor(MeshesToRebuild.Num(), [&](int32 Index) { UStaticMesh* StaticMesh = StaticMeshArray[Index]; TVisibilityPair& VisibilityPair = MeshesToRebuild[StaticMesh]; UStaticMeshComponent* StaticMeshComponent = VisibilityPair.Key; TArray& VertexVisibility = VisibilityPair.Value; FMeshDescription* MeshDescription = MeshDescriptions[StaticMeshComponent]; if (MeshDescription->Polygons().Num() == 0 || MeshDescription->Vertices().Num() == 0) { VisibilityPair.Key = nullptr; return; } FMeshDescription NewMeshDescription = *MeshDescription; TArray PolygonToRemove; for (const FPolygonID& PolygonID : NewMeshDescription.Polygons().GetElementIDs()) { TArray 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 VertexInstanceNormals = NewMeshDescription.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); TVertexInstanceAttributesRef VertexInstanceTangents = NewMeshDescription.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); MeshDescription->Empty(); //Create the missing normals and tangents on polygons because FStaticMeshOperations::ComputeTangentsAndNormals requires it if(!NewMeshDescription.TriangleAttributes().GetAttributesRef(MeshAttribute::Triangle::Normal).IsValid() || !NewMeshDescription.TriangleAttributes().GetAttributesRef(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(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(); 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&, const FJacketingOptions*, TArray&, bool) {} void FJacketingProcess::FindOverlappingActors(const TArray& InActorsToTest, const TArray& InActorsToTestAgainst, const FJacketingOptions* Options, TArray& OutOverlappingActors, bool bSilent) {} #endif // WITH_PROXYLOD #undef LOCTEXT_NAMESPACE